# ------------- Machine Learning - Topic 2: Logistic Regression Multivariate

## Initialization
import numpy as np
from scipy.optimize import fmin, fmin_bfgs
import os, sys
sys.path.append(os.getcwd() + os.path.dirname('/ml/ex2/'))
from helpers import sigmoid, costFunctionReg, predict, plotData, plotDecisionBoundary, mapFeature

# Load Data
#  The first two columns contains the exam scores and the third column
#  contains the label.
data = np.loadtxt('ml/ex2/ex2data2.txt', delimiter=",")
X = data[:, :2]
y = data[:, 2]
plt, p1, p2 = plotData(X, y)

# # Labels and Legend
plt.xlabel('Microchip Test 1')
plt.ylabel('Microchip Test 2')
plt.legend((p1, p2), ('y = 1', 'y = 0'), numpoints=1, handlelength=0)
plt.show(block=False) # prevents having to close the graph to move forward
input('Program paused. Press enter to continue.\n')

## =========== Part 1: Regularized Logistic Regression ============
#  In this part, you are given a dataset with data points that are not
#  linearly separable. However, you would still like to use logistic
#  regression to classify the data points.
#
#  To do so, you introduce more features to use -- in particular, you add
#  polynomial features to our data matrix (similar to polynomial
#  regression).
#

# Add Polynomial Features

# Note that mapFeature also adds a column of ones for us, so the intercept
# term is handled
X = mapFeature(X[:,0], X[:,1])
m, n = X.shape

# Initialize fitting parameters
initial_theta = np.zeros((n, 1))

# Set regularization parameter lambda to 1
lambda_reg = 0.1

# Compute and display initial cost
# gradient is too large to display in this exercise
cost = costFunctionReg(initial_theta, X, y, lambda_reg)

print('Cost at initial theta (zeros): {:f}'.format(cost))
# print('Gradient at initial theta (zeros):')
# print(grad)

input('Program paused. Press enter to continue.\n')

## ============= Part 2: Regularization and Accuracies =============
#  Optional Exercise:
#  In this part, you will get to try different values of lambda and
#  see how regularization affects the decision coundart
#
#  Try the following values of lambda (0, 1, 10, 100).
#
#  How does the decision boundary change when you vary lambda? How does
#  the training set accuracy vary?

# Initialize fitting parameters
initial_theta = np.zeros((n, 1))

# Set regularization parameter lambda to 1 (you should vary this)
lambda_reg = 1

#  Run fmin_bfgs to obtain the optimal theta
#  This function returns theta and the cost
myargs=(X, y, lambda_reg)
theta = fmin_bfgs(costFunctionReg, x0=initial_theta, args=myargs)

# Plot Boundary
plotDecisionBoundary(theta, X, y)

# # Labels, title and Legend
plt.xlabel('Microchip Test 1')
plt.ylabel('Microchip Test 2')
plt.title('lambda = {:f}'.format(lambda_reg))

# % Compute accuracy on our training set
p = predict(theta, X)

print('Train Accuracy: {:f}'.format(np.mean(p == y) * 100))

input('Program paused. Press enter to continue.\n')


import numpy as np

def matrix_multiply(X, Y):
    """ Matrix multiplication
    Inputs:
      - X: A numpy array of shape (N, M)
      - Y: A numpy array of shape (M, K)
    Output:
      - out: A numpy array of shape (N, K)
    """
    out = np.dot(X, Y)
    return out

def matrix_rowmean(X, weights=np.empty(0)):
    """ Calculate mean of each row.
    In case of weights do weighted mean.
    For example, for matrix [[1, 2, 3]] and weights [0, 1, 2]
    weighted mean equals 2.6666 (while ordinary mean equals 2)
    Inputs:
      - X: A numpy array of shape (N, M)
      - weights: A numpy array of shape (M,) or an emty array of shape (0,)
    Output:
      - out: A numpy array of shape (N,)
    """
    if weights.size > 0:
        out = np.average(X, weights=weights, axis=1)
    else:
        out = np.average(X, axis=1)
    return out

def cosine_similarity(X, top_n=10, with_mean=True, with_std=True):
    """ Calculate cosine similarity between each pair of row.
    1. In case of with_mean: subtract mean of each row from row
    2. In case of with_std: divide each row on it's std
    3. Select top_n best elements in each row or set other to zero.
    4. Compute cosine similarity between each pair of rows.
    Inputs:
      - X: A numpy array of shape (N, M)
      - top_n: int, number of best elements in each row
      - with_mean: bool, in case of subtracting each row's mean
      - with_std: bool, in case of subtracting each row's std
    Output:
      - out: A numpy array of shape (N, N)

    Example (with top_n=1, with_mean=True, with_std=True):
        X = array([[1, 2], [4, 3]])
        after mean and std transform:
        X = array([[-1.,  1.], [ 1., -1.]])
        after top n choice
        X = array([[0.,  1.], [ 1., 0]])
        cosine similarity:
        X = array([[ 1.,  0.], [ 0.,  1.]])

    """
    if with_mean:
        X = X - np.mean(X, axis=1, keepdims=True)
    if with_std:
        X = X / np.std(X, axis=1, keepdims=True)
    if top_n is not None:
        X[np.arange(X.shape[0]).reshape(-1, 1),
          np.argsort(X)[:, :-top_n]] = 0
    out = np.dot(X, X.T) / (np.linalg.norm(X, axis=1, keepdims=True)
                            * np.linalg.norm(X, axis=1, keepdims=True).T)
    return out


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

plt.rcParams['figure.figsize'] = (20.0,10.0)

#Reading data

data = pd.read_csv('data.csv')
print(data.shape)

print(data.head())

#collecting the x and y values
X = data['experience'].values
Y = data['salary'].values

#to get the equation of the regression line y = mx + c ,
#we need to find the value of slope first
# m = Sum{ ([X-mean_x] * [Y - mean_y]) } / Sum { [X - mean_x]**2 }

#calculating the mean

mean_x =np.mean(X)
mean_y =np.mean(Y)

#collecting the total number of values so that we can minus the mean from everything

n = len(X)

# Lets consider m = numerator / denominator , we they each take the respective
#positions as mentioned in l20 equation

numerator, denominator = 0 , 0

for i in range(n):
	numerator += (X[i] - mean_x)*(Y[i] - mean_y)
	denominator += (X[i]-mean_x)**2

# lets get the value of m and c || y = mx + c
m = numerator/denominator
# from y = mx + c || c = y - mx
c = mean_y - (m*mean_x)

print(" m = {} and c = {} ".format(m,c))

#plotting Values and regression line

max_x = np.max(X) + 10
min_x = np.min(X) - 10

#calculating line values x and y

x = np.linspace(min_x, max_x,100)
y = m*x + c

#Rsquared method , did'nt understand this part

ss_t , ss_r= 0,0

for i in range(n):
	y_pred = m + X[i]*c
	ss_t += (Y[i] - mean_y)**2
	ss_r += (Y[i]- y_pred)**2

r2 = 1 -(ss_r/ss_t)
print("R2 = ",r2)

#plotting line

plt.plot(x,y, color ="#58b970", label= "Regression Line")

#plotting scatter points
plt.scatter(X, Y, c="#ef5423", label="Scatter Plots")

plt.xlabel('Experience')
plt.ylabel('Salary')
plt.legend()
plt.show()
from sklearn import model_selection,linear_model

exp_train , exp_test,salary_train , salary_test = model_selection.train_test_split(X,Y)
reg = linear_model.LinearRegression()
reg.fit(exp_train, salary_train)

print("From Calculation")
print("Slope = ", m)
print("Intercept = ", c)

print("From Linear Regression")
print("Slope = ", reg.coef_)
print("intercept  = ", reg.intercept_)


import numpy as np
import numpy.linalg as la
import matplotlib.pyplot as plt

v1 = np.array([1,1])
plt.plot([0, v1[0]], [0, v1[1]], 'b' , label='v', linewidth=2 )
plt.plot([1,0],[0,0], 'g', label='ihat')
plt.plot([0,0],[0,1], 'r', label='jhat')

plt.legend()
plt.axis('square')
plt.axis((-0.5,1.5,-0.5,1.5))
plt.grid()
plt.show()

TFM_Scale = np.array([[2,0], [0,3]])
v2 = TFM_Scale @ v1
print("Scale along diagonal transform matrix ")
print(TFM_Scale)
print(v2)

TFM_Rotate = np.array([[0,-1],[-1,0]])
v3=TFM_Rotate @ v1
print("90 degrees clockwise rotation: ")
print(TFM_Rotate)
print(v3)

TFM_Shear = np.array([[1,1],[0,1]])
v4 = TFM_Shear @ v1
print("Shear along x-axis: ")
print(TFM_Shear)
print(v4)

#Plot Transformation
plt.plot([0, v1[0]], [0, v1[1]], label='v', linewidth=2)
plt.plot([0, v2[0]], [0, v2[1]], label='Scale', linewidth=2)
plt.plot([0, v3[0]], [0, v3[1]], label="Rotate", linewidth=2)
plt.plot([0, v4[0]], [0, v4[1]], label='Shear',  linewidth= 2)

plt.legend()
plt.axis('square')
plt.axis((-3,4,-2,5))
plt.grid()
plt.show()


# Univariate Linear Regression
# Implementation using numpy, more efficient

import numpy as np
from sklearn import datasets

def partial_derivative(theta, trainX, trainY, trainSize):
    return (theta.dot(trainX) - trainY).dot(trainX.T) / trainSize

def fit(trainX, trainY, learningRate=0.1, threashold=1E-5):
    theta = np.array([0, 1], dtype=float) # Initialize theta
    trainSize = len(trainY)
    derivative = partial_derivative(theta, trainX, trainY, trainSize)
    while abs(derivative).max() > threashold: # While gradient doesn't converge
        theta -= learningRate * derivative # Perform gradient descent
        derivative = partial_derivative(theta, trainX, trainY, trainSize) # Update partial derivative
    return theta

def standard_fit(trainX, trainY):
    trainX = trainX[1, :] # Remove the extra bias term
    model = np.polyfit(trainX, trainY, deg=1) # Degree of 1 (linear model)
    return model

def accuracy(theta, testX, testY):
    # Returns the R Squared value of the model
    explained = theta.dot(testX)
    mean = np.sum(testY) / len(testY)
    totSumSquares = np.sum((testY - mean) ** 2)
    resSumSquares = np.sum((testY - explained) ** 2)
    accuracy = 1 - resSumSquares / totSumSquares
    return accuracy

dataset = datasets.load_diabetes()
data, target = dataset.data, dataset.target
dataSize = len(data)
data = data[:, 2] # Take only one feature
data = np.vstack((np.ones(dataSize), data)) # Add an extra bias term
separation = int(dataSize * 0.8) # Percentage of training data set and testing data set
trainX, trainY = data[:, :separation], target[:separation]
testX, testY = data[:, separation:], target[separation:]

hypothesis = standard_fit(trainX, trainY)
print('Fitted Model: y = {0}x {2} {1}'.format(hypothesis[0], abs(hypothesis[1]), ('-', '+')[hypothesis[1]>0]))
acc = accuracy(hypothesis[::-1], testX, testY)
print('Standard Accuracy: {:.4f}\n'.format(acc))

hypothesis = fit(trainX, trainY, 1, 1E-6)
print('Fitted Model: y = {1}x {2} {0}'.format(abs(hypothesis[0]), hypothesis[1], ('-', '+')[hypothesis[0]>0]))
acc = accuracy(hypothesis, testX, testY)
print('Fitted Accuracy: {:.4f}'.format(acc))


import numpy as np

def stmul(A,B) :
    # 에러 처리 부분은 생략하겠습니다. (e.g len(A)!=2^k)

    n=len(A)

    if 2<n :
        A11=A[0:n//2,0:n//2]
        A12=A[0:n//2,n//2:n]
        A21=A[n//2:n,0:n//2]
        A22=A[n//2:n,n//2:n]

        B11=B[0:n//2,0:n//2]
        B12=B[0:n//2,n//2:n]
        B21=B[n//2:n,0:n//2]
        B22=B[n//2:n,n//2:n]

        M1=stmul(A11+A22,B11+B22)
        M2=stmul(A21+A22,B11)
        M3=stmul(A11,B12-B22)
        M4=stmul(A22,B21-B11)
        M5=stmul(A11+A12,B22)
        M6=stmul(A21-A11,B11+B12)
        M7=stmul(A12-A22,B21+B22)

        C11=M1+M4-M5+M7
        C12=M3+M5
        C21=M2+M4
        C22=M1-M2+M3+M6

        C=np.zeros((n,n))

        C[0:n//2,0:n//2]=C11
        C[0:n//2,n//2:n]=C12
        C[n//2:n,0:n//2]=C21
        C[n//2:n,n//2:n]=C22

    else :

        A11=A[0,0]
        A12=A[0,1]
        A21=A[1,0]
        A22=A[1,1]

        B11=B[0,0]
        B12=B[0,1]
        B21=B[1,0]
        B22=B[1,1]

        M1=(A11+A22)*(B11+B22)
        M2=(A21+A22)*(B11)
        M3=(A11)*(B12-B22)
        M4=(A22)*(B21-B11)
        M5=(A11+A12)*(B22)
        M6=(A21-A11)*(B11+B12)
        M7=(A12-A22)*(B21+B22)

        C11=M1+M4-M5+M7
        C12=M3+M5
        C21=M2+M4
        C22=M1-M2+M3+M6

        C=np.zeros((n,n))

        C[0,0]=C11
        C[0,1]=C12
        C[1,0]=C21
        C[1,1]=C22

    return C

if __name__ == '__main__':
    A=np.array([[1,0,2,1],[4,1,1,0],[0,1,3,0],[5,0,2,1]])
    B=np.array([[0,1,0,1],[2,1,1,4],[2,0,1,1],[1,3,5,0]])

    C=stmul(A,B)
    print(C)

    D=np.matmul(A,B)
    print(D) #numpy 내장함수를 통해 행렬 A,B를 곱한 것이 strassen formula를 통해 구한 것과 동일함을 알 수 있다.


# 0452-qp-ls-lr-poly3.pg
# Impelement QP for LS in the case of LR for POLY3
# Sparisoma Viridi | https://github.com/dudung/bug
# 20220215 Create this program.

# abbreviations
# QP quaratic programming
# LS least square
# LR linear regression
# POLY3 polynomial function of 3rd order

# import numpy
import os, sys
import numpy as np

# open file, read lines and store them in a list, close file
fn = '0452-a.txt'
print('file:', fn)
with open(os.path.join(sys.path[0], fn), 'r') as f:
    lines = f.readlines()

# create empty list
x = [[]]
y = [[]]
A = []

# iterate through lines but ommit the first element
for l in lines[1:]:
    s = l.rstrip('\n')
    s = s.split('\t')
    xi = float(s[0])
    yi = float(s[1])

    x[0].append(xi)
    y[0].append(yi)

    p0 = 1
    p1 = xi
    p2 = xi**2
    p3 = xi**3
    Ai = [p0, p1, p2, p3]
    A.append(Ai)

# create numpy array
x = np.transpose(np.array(x))
y = np.transpose(np.array(y))
A = np.array(A)

# find c via some temporary variables
AT = np.transpose(A)
ATA = np.matmul(AT, A)
ATA_1 = np.linalg.inv(ATA)
ATA_1AT = np.matmul(ATA_1, AT)
c = np.matmul(ATA_1AT, y)

# print dimension of matrices
print()
print('y:', y.shape)
print('A:', A.shape)
print('AT:', AT.shape)
print('ATA:', ATA.shape)
print('ATA_1:', ATA_1.shape)
print('ATA_1AT:', ATA_1AT.shape)
print('c: ', c.shape)

# print results
print()
print('c = ')
print(c)


import numpy as np

def rmse(predictions, targets):
    return np.sqrt(((predictions - targets) ** 2).mean())

def polar_to_cartesian(z,theta):
    return z*np.exp(1j*np.deg2rad(theta))

def cartesian_to_polar(Z):
    x = Z.real
    y = Z.imag
    z = np.sqrt(x**2 + y**2)
    theta = np.arctan2(y, x)
    return z,theta

def MAPE(predictions, targets):
    targets, predictions = np.array(targets), np.array(predictions)
    return np.mean(np.abs((targets - predictions) / targets)) * 100


#encoding=utf-8

"""
1. Take the whole dataset consisting of dd-dimensional samples ignoring the class labels
2. Compute the dd-dimensional mean vector (i.e., the means for every dimension of the whole dataset)
3. Compute the scatter matrix (alternatively, the covariance matrix) of the whole data set
4. Compute eigenvectors (ee1,ee2,...,eedee1,ee2,...,eed) and corresponding eigenvalues (λλ1,λλ2,...,λλdλλ1,λλ2,...,λλd)
5. Sort the eigenvectors by decreasing eigenvalues and choose kk eigenvectors with the largest eigenvalues to form a d×kd×k dimensional
matrix WWWW(where every column represents an eigenvector)
6. Use this d×kd×k eigenvector matrix to transform the samples onto the new subspace. This can be summarized by the mathematical
equation: yy=WWT×xxyy=WWT×xx (where xxxx is a d×1d×1-dimensional vector representing one sample, and yyyy is the transformed
k×1k×1-dimensional sample in the new subspace.)
"""

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

np.random.seed(12)

mu_vec1 = np.array([0,0,0])
cov_mat1 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class1_sample = np.random.multivariate_normal(mu_vec1, cov_mat1, 20).T
assert class1_sample.shape == (3,20), "The matrix has not the dimensions 3x20"

mu_vec2 = np.array([1,1,1])
cov_mat2 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class2_sample = np.random.multivariate_normal(mu_vec2, cov_mat2, 20).T
assert class2_sample.shape == (3,20), "The matrix has not the dimensions 3x20"

#
# fig = plt.figure(figsize=(8,8))
# ax = fig.add_subplot(111, projection='3d')
# plt.rcParams['legend.fontsize'] = 10
# ax.plot(class1_sample[0,:], class1_sample[1,:], class1_sample[2,:], 'o', markersize=8, color='blue', alpha=0.5, label='class1')
# ax.plot(class2_sample[0,:], class2_sample[1,:], class2_sample[2,:], '^', markersize=8, alpha=0.5, color='red', label='class2')
#
# plt.title('Samples for class 1 and class 2')
# ax.legend(loc='upper right')
#
# plt.show()

all_samples = np.concatenate((class1_sample, class2_sample), axis=1)
assert all_samples.shape == (3,40), "The matrix has not the dimensions 3x40"

mean_x = np.mean(all_samples[0,:])
mean_y = np.mean(all_samples[1,:])
mean_z = np.mean(all_samples[2,:])

mean_vector = np.array([[mean_x],[mean_y],[mean_z]])

print('Mean Vector:\n', mean_vector)

"""
The scatter matrix is computed by the following equation:
S=∑k=1n(xxk−mm)(xxk−mm)T
where mmmm is the mean vector
mm=1n∑k=1nxxkmm=1n∑k=1nxxk
"""

scatter_matrix = np.zeros((3,3))
for i in range(all_samples.shape[1]):
    scatter_matrix += (all_samples[:,i].reshape(3,1) - mean_vector).dot((all_samples[:,i].reshape(3,1) - mean_vector).T)
print('Scatter Matrix:\n', scatter_matrix)

cov_mat = np.cov([all_samples[0,:],all_samples[1,:],all_samples[2,:]])
print('Covariance Matrix:\n', cov_mat)

"""
4 --
"""
# eigenvectors and eigenvalues for the from the scatter matrix
eig_val_sc, eig_vec_sc = np.linalg.eig(scatter_matrix)

# eigenvectors and eigenvalues for the from the covariance matrix
eig_val_cov, eig_vec_cov = np.linalg.eig(cov_mat)

for i in range(len(eig_val_sc)):
    eigvec_sc = eig_vec_sc[:,i].reshape(1,3).T
    eigvec_cov = eig_vec_cov[:,i].reshape(1,3).T
    assert eigvec_sc.all() == eigvec_cov.all(), 'Eigenvectors are not identical'

    print('Eigenvector {}: \n{}'.format(i+1, eigvec_sc))
    print('Eigenvalue {} from scatter matrix: {}'.format(i+1, eig_val_sc[i]))
    print('Eigenvalue {} from covariance matrix: {}'.format(i+1, eig_val_cov[i]))
    print('Scaling factor: ', eig_val_sc[i]/eig_val_cov[i])
    print(40 * '-')

#
# from matplotlib import pyplot as plt
# from mpl_toolkits.mplot3d import Axes3D
# from mpl_toolkits.mplot3d import proj3d
# from matplotlib.patches import FancyArrowPatch
#
# class Arrow3D(FancyArrowPatch):
#     def __init__(self, xs, ys, zs, *args, **kwargs):
#         FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)
#         self._verts3d = xs, ys, zs
#
#     def draw(self, renderer):
#         xs3d, ys3d, zs3d = self._verts3d
#         xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
#         self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
#         FancyArrowPatch.draw(self, renderer)
#
# fig = plt.figure(figsize=(7,7))
# ax = fig.add_subplot(111, projection='3d')
#
# ax.plot(all_samples[0,:], all_samples[1,:], all_samples[2,:], 'o', markersize=8, color='green', alpha=0.2)
# ax.plot([mean_x], [mean_y], [mean_z], 'o', markersize=10, color='red', alpha=0.5)
# for v in eig_vec_sc.T:
#     a = Arrow3D([mean_x, v[0]], [mean_y, v[1]], [mean_z, v[2]], mutation_scale=20, lw=3, arrowstyle="-|>", color="r")
#     ax.add_artist(a)
# ax.set_xlabel('x_values')
# ax.set_ylabel('y_values')
# ax.set_zlabel('z_values')
#
# plt.title('Eigenvectors')
#
# plt.show()

# Make a list of (eigenvalue, eigenvector) tuples
eig_pairs = [(np.abs(eig_val_sc[i]), eig_vec_sc[:,i]) for i in range(len(eig_val_sc))]

# Sort the (eigenvalue, eigenvector) tuples from high to low
eig_pairs.sort(key=lambda x: x[0], reverse=True)

# Visually confirm that the list is correctly sorted by decreasing eigenvalues
for i in eig_pairs:
    print(i[0])

matrix_w = np.hstack((eig_pairs[0][1].reshape(3,1), eig_pairs[1][1].reshape(3,1)))
print('Matrix W:\n', matrix_w)

"""
6
"""
transformed = matrix_w.T.dot(all_samples)
assert transformed.shape == (2,40), "The matrix is not 2x40 dimensional."

plt.plot(transformed[0,0:20], transformed[1,0:20], 'o', markersize=7, color='blue', alpha=0.5, label='class1')
plt.plot(transformed[0,20:40], transformed[1,20:40], '^', markersize=7, color='red', alpha=0.5, label='class2')
plt.xlim([-4,4])
plt.ylim([-4,4])
plt.xlabel('x_values')
plt.ylabel('y_values')
plt.legend()
plt.title('Transformed samples with class labels')

plt.show()

from matplotlib.mlab import PCA as mlabPCA

mlab_pca = mlabPCA(all_samples.T)

print('PC axes in terms of the measurement axes scaled by the standard deviations:\n', mlab_pca.Wt)

plt.plot(mlab_pca.Y[0:20,0],mlab_pca.Y[0:20,1], 'o', markersize=7, color='blue', alpha=0.5, label='class1')
plt.plot(mlab_pca.Y[20:40,0], mlab_pca.Y[20:40,1], '^', markersize=7, color='red', alpha=0.5, label='class2')

plt.xlabel('x_values')
plt.ylabel('y_values')
plt.xlim([-4,4])
plt.ylim([-4,4])
plt.legend()
plt.title('Transformed samples with class labels from matplotlib.mlab.PCA()')

plt.show()

from sklearn.decomposition import PCA as sklearnPCA

sklearn_pca = sklearnPCA(n_components=2)
sklearn_transf = sklearn_pca.fit_transform(all_samples.T)

plt.plot(sklearn_transf[0:20,0],sklearn_transf[0:20,1], 'o', markersize=7, color='blue', alpha=0.5, label='class1')
plt.plot(sklearn_transf[20:40,0], sklearn_transf[20:40,1], '^', markersize=7, color='red', alpha=0.5, label='class2')

plt.xlabel('x_values')
plt.ylabel('y_values')
plt.xlim([-4,4])
plt.ylim([-4,4])
plt.legend()
plt.title('Transformed samples with class labels from matplotlib.mlab.PCA()')

plt.show()


__author__ = 'Randall'

"""
    DEMO: basisChebyshev
    This script provides a few examples on using the basis classes.
    Last updated: March 15, 2015.
    Copyright (C) 2014 Randall Romero-Aguilar
    Licensed under the MIT license, see LICENSE.txt
"""

from compecon import BasisChebyshev
import numpy as np
import matplotlib.pyplot as plt

"""
    EXAMPLE 1:
        Using BasisChebyshev to interpolate a 1-D function with a Chebyshev basis
        PROBLEM: Interpolate the function y = f(x) = 1 + sin(2*x) on the domain [0,pi], using 5 Gaussian nodes.
"""

# First, create the function f
f = lambda x: 1 + np.sin(2 * x)

# and the Chebyshev basis B. If the type of nodes is unspecified, Gaussian is computed by default
F = BasisChebyshev(5, 0, np.pi, f=f)

# Interpolation matrix and nodes: Obtain the interpolation matrix Phi, evaluated at the basis nodes.

# The basis nodes are:
xnodes = F.nodes

# and the basis coefficients are
c = F.c

# Next plot the function f and its approximation. To evaluate the function defined by the basis B and coefficients c
# at values xx we use the interpolation method:

# We also plot the residuals, showing the residuals at the interpolating nodes (zero by construction)
xx = np.linspace(F.a, F.b, 121)
f_approx = F(xx)
fxx = f(xx)

plt.figure()
plt.subplot(2, 1, 1)
plt.plot(xx, fxx)
plt.plot(xx, f_approx)
plt.legend(['f = 1 + sin(2x)','approx.'])
plt.title('Chebyshev approximation with 5 nodes')

plt.subplot(2, 1, 2)
plt.plot(xx, f_approx - fxx)
plt.title('Residuals using 5 nodes')

# Adjusting the number of nodes: to increase accuracy, we increase the number of nodes in B to 25.
F = BasisChebyshev(25, 0, np.pi, f=f)
plt.figure()
plt.subplot(2,1,1)
plt.plot(xx, f(xx))
plt.plot(xx, F(xx))
plt.legend(['f = 1 + sin(2x)','approx.'])
plt.title('Chebyshev approximation with 25 nodes')

plt.subplot(2,1,2)
plt.plot(xx, F(xx) - f(xx), 'r')
plt.title('Residuals using 25 nodes')

# With previous basis, now compute derivative.
df = lambda x: 2 * np.cos(2 * x)
plt.figure()
plt.subplot(2, 1, 1)
plt.plot(xx, df(xx))
plt.plot(xx, F(xx, 1))  # notice the 1 in Phi(xx,1) to compute first derivative
plt.legend(['df/dx = 2cos(2x)', 'approx.'])
plt.title('Chebyshev approximation with 25 nodes')

plt.subplot(2, 1, 2)
plt.plot(xx, F(xx, 1) - df(xx), 'r')
plt.title('Residuals using 25 nodes')

plt.show()


# THIS...
# IS.....
# TRIGGGGGGGGG!
print('''

CONSIDER THE FOLLOWING...

Imagine coordinates, x, and y

            |y
            |
            |
-----------------------------x
            |
            |
            |

What is the most glorious shape in the world?
Why, a circle of course!

I can't draw a circle with my keyboard (O, is that right??)
So let's plot it

OH, btw, we're making a circle with radius = 1

There's an EXTREMELY FANCY WAY to explain to your fellow mathematicians
that your circle has radius 1. You ready? The term is "UNIT".

It is a UNIT circle if it has radius 1.

''')

import matplotlib.pyplot as plt
import numpy as np

# Create the x axis, from -1 to 1, including 1 (linspace does this auto)

xs = np.linspace(-1,1,100)  # from -1 to 1, make 100 equally spaced points

ys = {'top':[], 'bot':[]}  # create a python dictionary holding the circle's ys

# top half

for x in xs:

    y_top = + np.sqrt( 1**2 - x**2)
    y_bot = - np.sqrt( 1**2 - x**2)

    ys['top'].append( y_top )
    ys['bot'].append( y_bot )

plt.plot(xs, ys['top'])
plt.plot(xs, ys['bot'])
plt.show()

print(
'''
Now, all that was a bit tedious wasn't it?

What if...

What if we could just say "I WANT A UNIT CIRCLE THAT GOES AROUND ONCE",
and it would just... WORK?

Hmmm...

In 'polar' coordinates, instead of using x and y as our "coordinates",
we can just use "r" and "theta", or a "radius" and an "angle"........

So maybe we can just "sweep" over angles, and plot the radii?

Something like...
'''

''' What is the circumference of a circle?

    ... I hope you remember...

    It's 2 * pi * radius.... Since our radius is 1, what's the circumference?

    2*pi. SO!

AHA!

 What if we just define our angles as fractions of the circumference?
 (instead of using degrees)

    So 0 degrees = 0 * 2*pi

    So 45 degrees = (2*pi / 8) =     (pi / 4) <--> 45 deg

    So 90 degrees = (2*pi / 4) =     (pi / 2) <--> 90 deg

    So 180 deg = (2*pi / 2)    =      (pi)    <--> 180 deg

    So 360 deg = (2*pi / 1)    =    (2*pi)    <--> 360 deg

    ... FYI these new fancy fractional angles are called 'radians'
'''
)

from numpy import pi

angles = np.linspace(0, 2*pi, 100)

print('''

Okay, now how do to translate our new polar coordinates into xs and ys?
(We need to do this so we can plot...)

''')

# Here's how........

xs = np.cos(angles)  # cosine maps an angle to an X value on the unit circle
ys = np.sin(angles)  # sine maps an angle to a Y value on the unit circle

# tan = sin / cos ........ slope = y / x

plt.plot(xs, ys)
plt.show()

print(
''' Well... That was easy. But what the heck is cos and sine?

    The answer is best seen by drawing triangles inside of circles.

    I shall explain on the white board...!

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

    SOH : sin = opposite/hypotenuse
    CAH : cos = adjacent/hypotenuse
    TOA : tan = opposite/adjacent

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

    Basically,

    x = cos(angle) works because cosine = adjacent / hypotenuse = x / 1
    y = sin(angle) works because cosine = opposite / hypotenuse = y / 1

    if we draw the triangle like this:

          /|
    hyp  / |
        /  | opposite side
        ----
        adj

    where (angle) is measured between the adj and hypotenuse sides

    Notice also that:

        x**2 + y**2 = 1

                which means that

        cos(angle)**2 + sin(angle)**2 = 1    (for any angle)

'''
)

print(
'''

Congratulations!
That's basically all of the really important stuff about trigonometry!

If you ever want to get the ANGLE, and you know the LENGTH,

use angle = arccos(length) = acos(length), or
    angle = arcsin(length) = asin(length), or
    angle = arctan(length) = atan(length),

    which will all work for a certain range of lengths (not all lengths...)

Some extra tidbits:

    - There are these things called "trig identities"...
    - They are used like crazy in calculus and physics...
    - Basically, they boil down to equivalent ways to say exactly the same thing
    - If you want to see most of them, go here:
        https://en.wikipedia.org/wiki/List_of_trigonometric_identities

'''
)

Math You Will Actually Use/Week8_HowToTrig.py


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

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1EBz2EcAFR7wNNt5BtgNLWoT9kxSkqfy3

# HackerRank Problems Continued

Task 11: Mean, Var, and STD
"""

import numpy as np

a = np.array([[1, 2], [3, 4]])
print(np.mean(a, axis = 1))
print(np.var(a, axis=0))
print(np.std(a, axis=None))

"""Task 12 Dot and Cross"""

## The dot tool returns the dot product of two arrays
## The cross tool returns the cross product of two arrays.
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 2], [3, 4]])
print(np.dot(A, B))
#dot is just the matrix

"""Task 13 Inner and Outer"""

## The inner tool returns the inner product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print(numpy.inner(A, B))   #Output : 4
## The outer tool returns the outer product of two arrays.
import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print(numpy.outer(A, B))    #Output : [[0 0]
                            #          [3 4]]

import numpy as np
A = np.array([0,1])
B = np.array([2, 3])

print(np.inner(A, B))
print(np.outer(A, B))

"""Task 14 Polynomials"""

import numpy as np
poly = np.array([1.1, 2, 3])
np.polyval((poly), 0)

"""The poly tool returns the coefficients of a polynomial with the given sequence of roots.
 - The polyint tool returns an antiderivative (indefinite integral) of a polynomial.
 - The roots tool returns the roots of a polynomial with the given coefficients.
 - The polyder tool returns the derivative of the specified order of a polynomial.
 - The polyval tool evaluates the polynomial at specific value. (used above)
"""
#print numpy.poly([-1, 1, 1, 10])
#Output : [  1 -11   9  11 -10]

#why are the outputs that way? are they different points of intersection

"""Task 15 Linear Algebra"""

import numpy as np

A = np.array([[1.1, 1.1], [1.1, 1.1]])
print(np.linalg.det(A))

"""# MatPlotLib

Video One: Creating + Customizing Plots
"""

import matplotlib

from matplotlib import pyplot as plt

# print(plt.style.available)
plt.style.use('fivethirtyeight')
# Median Developer Salaries by Age
ages_x = [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

dev_y = [38496, 42000, 46752, 49320, 53200,
         56000, 62316, 64928, 67317, 68748, 73752]

plt.plot(ages_x, dev_y, color='k', linestyle = '--' ,label = 'All Devs')

# Median Python Developer Salaries by Age
## can remobe to keep axis py_dev_x = [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
py_dev_y = [45372, 48876, 53850, 57287, 63016,
            65998, 70003, 70000, 71496, 75370, 83640]

plt.plot(ages_x, py_dev_y, color = '#009000', marker = '*', linewidth=3, label = 'Python')

# Median JavaScript Developer Salaries by Age
js_dev_y = [37810, 43515, 46823, 49293, 53437,
            56373, 62375, 66674, 68745, 68746, 74583]

plt.plot(ages_x, js_dev_y, color = '#adad3b', marker = '.', label = 'JavaScript')

plt.xlabel('Ages')
plt.ylabel('Salary')
plt.title('Median Salary (USD) by Age')

# If you know the order of which color is which, or just add as new parameter in plt.plot
# plt.legend(['All Devs','Python']), just legend better bc updates automatically
plt.legend()
# format string fmt = '[marker][line][color]'

#plt.grid(True)
plt.tight_layout()

plt.savefig('plot.png')

plt.show(ages_x, dev_y)

"""Video 2: Bar Charts and Analyzing Data from CSV"""

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

ages_x = [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

x_indexes = np.arange(len(ages_x))
width = 0.25

dev_y = [38496, 42000, 46752, 49320, 53200,
         56000, 62316, 64928, 67317, 68748, 73752]

plt.bar(x_indexes - width, dev_y, width=width, color = "#444444", label = "All Devs")

# Median Python Developer Salaries by Age
py_dev_y = [45372, 48876, 53850, 57287, 63016,
            65998, 70003, 70000, 71496, 75370, 83640]

plt.bar(x_indexes, py_dev_y, width=width, color = "#004400", label = "Python")

# Median JavaScript Developer Salaries by Age
js_dev_y = [37810, 43515, 46823, 49293, 53437,
            56373, 62375, 66674, 68745, 68746, 74583]

plt.bar(x_indexes + width, js_dev_y, width=width, color = "#440000", label = "JavaScript")

plt.xticks(ticks=x_indexes, labels = ages_x)

plt.legend()

import matplotlib
import csv
import pandas as pd #open files neater
import numpy as np
from collections import Counter
from matplotlib import pyplot as plt

'''with open('data.txt') as csv_file:
  csv_reader = csv.DictReader(csv_file)'''

data = pd.read_csv('data.txt')
ids = data['Responder_id']
lang_responses = data['LanguagesWorkedWith']

language_counter = Counter()

      #replace row with response after use of pandas and cvs_reader with lang_responses
for response in lang_responses:   #row also replaced
  language_counter.update(response.split(';'))
                      #and remove 'LanguagesWorkedWith idk why
languages =[]
popularity = []

for item in language_counter.most_common(15):
  languages.append(item[0])
  popularity.append(item[1])

#print(language_counter.most_common(15))
languages.reverse()
popularity.reverse()
plt.barh(languages, popularity)

"""Video 3 Pie Charts"""

import matplotlib
from matplotlib import pyplot as plt

# Language Popularity
slices = [59219, 55466, 47544, 36443, 35917]
labels = ['JavaScript', 'HTML/CSS', 'SQL', 'Python', 'Java']
explode = [0, 0, 0, 0.1, 0]
                                                                                #ask to define percentages
plt.pie(slices, labels = labels, explode = explode, shadow = True, startangle = 90, autopct = '%1.1f%%',colors = colors, wedgeprops = {'edgecolor' : 'black'})

plt.title("My Awesome Pie Chart")
plt.show()

"""Video 4 Stack Plots"""

import matplotlib
from matplotlib import pyplot as plt

plt.style.use("fivethirtyeight")

minutes = [1, 2, 3, 4, 5, 6, 7, 8, 9]

player1 = [8, 6, 5, 5, 4, 2, 1, 1, 0]
player2 = [0, 1, 2, 2, 2, 4, 4, 4, 4]
player3 = [0, 1, 1, 1, 2, 2, 3, 3, 4]

labels = ('Player 1', 'Player 2', 'Player 3')
plt.stackplot(minutes, player1, player2, player3, labels= labels)

plt.legend(loc = (0.07, 0.05))  #google matplotlib legend to adjust coordinates

plt.title("My Awesome Stack Plot")
plt.tight_layout()
plt.show()

"""Filling Area on Line Plots"""

import pandas as pd
import matplotlib
from matplotlib import pyplot as plt

data = pd.read_csv('data.csv')
ages = data['Age']
dev_salaries = data['All_Devs']
py_salaries = data['Python']
js_salaries = data['JavaScript']

plt.plot(ages, dev_salaries, color='#444444',
         linestyle='--', label='All Devs')

plt.plot(ages, py_salaries, label='Python')

overall_median = 57287
                                    #to change intensity of fill
plt.fill_between(ages, py_salaries, overall_median,
                 where = (py_salaries <= overall_median),
                 interpolate = True, alpha = 0.25, )
plt.fill_between(ages, py_salaries, overall_median,
                 where = (py_salaries > overall_median),
                 color = "green",alpha = 0.25)

plt.legend()

plt.title('Median Salary (USD) by Age')
plt.xlabel('Ages')
plt.ylabel('Median Salary (USD)')

plt.tight_layout()

plt.show()

## TIMESTAMP: 11:30


import sympy

x = sympy.symbols('x')

f = (x**2 - 2*x)*sympy.exp(3 - x)

fp = sympy.simplify(sympy.diff(f)) # (x*(2 - x) + 2*x - 2)*exp(3 - x)

fp2 = -(x**2 - 4*x + 2)*sympy.exp(3 - x)

sympy.simplify(fp2 - fp) == 0  # True

F = sympy.integrate(f, x)  # -x**2*exp(3 - x)


# -*- coding: utf-8 -*-
"""
Simulazione 8 Settembre 2020

Funzione e radici
"""
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
from scipy import optimize
import math

# L'unico metodo a convergenza quadratica è Newton
def newton(fname, fpname, x0, m,  tol, nMaxIt):
    xk = []
    it = 0
    if abs(fpname(x0)) <= np.spacing(1):
        print("Derivata nulla in x0")
        return [], 0, []
    else:
        d = (fname(x0)/ fpname(x0))
        x1 = x0 - d*m
        xk.append(x1)
        it += 1
        while it < nMaxIt and abs(fname(x1)) >= tol and abs(d) >= tol * abs(x1):
            x0 = x1
            if abs(fpname(x0)) <= np.spacing(1):
                print("Derivata nulla in x0")
                return x1, it, xk
            d = (fname(x0)/ fpname(x0))
            x1 = x0 - d*m
            xk.append(x1)
            it += 1

        if it == nMaxIt:
            print("Numero massimo di iterazioni raggiunto")
        return x1, it, xk

# Definisco Funzione e derivata
x = sym.Symbol("x")
xx = np.linspace(1, 3, 100)
fx = x - (2 * sym.sqrt(x - 1))
dfx = sym.diff(fx, x, 1)

f = sym.utilities.lambdify(x, fx, np)
d = sym.utilities.lambdify(x, dfx, np)

# Plotto la funzione per cercare le radici
plt.plot(xx, f(xx))
plt.title("Funzione")
plt.show()

# Noto che la funzione vicino al 2 vale 0
zero = optimize.fsolve(f, 2.0)
print(zero)

x1, it, xks = newton(f, d, 3.0, 2.0, 1.e-12, 500)
print("La derivata nel punto è ", d(x1))
# Stimo l'ordine di convergenza del metodo
numO = math.log(abs(xks[-2] - xks[-1]) / abs(xks[-3] - xks[-2]))
denO = math.log(abs(xks[-3] - xks[-2]) / abs(xks[-4] - xks[-3]))
ordine = numO / denO
print(f"L'ordine di convergenza del metodo è {ordine}")

plt.semilogy(np.arange(it), xks)
plt.title("Iterazioni")
plt.show()

"""
    Osservazioni: il metodo di Newton inizialmente con la radice "2" aveva un ordine
    di convergenza pari a 1, quindi la radice ha molteplicità m, provando poi ad
    implementare la versione modificata del metodo ho ottenuto un ordine di convergenza
    pari a 2, utilizzando m = 2 dunque la radice ha molteplicità pari a 2
    Inoltre il metodo non converge usando '1' come valore di innesco in quanto quest'ultimo
    ha una derivata prossima allo 0
"""


# Necessary import(s)
import numpy as np

# The helper methods used by the learning methods above are implemented below:

def compute_error_vector(y, tx, w):
    """
    Computes the error vector that is defined as y - tx . w
    Args:
        y: labels
        tx: features
        w: weight vector
    Returns:
        error_vector: the error vector defined as y - tx.dot(w)
    """
    return y - tx.dot(w)

def compute_mse(error_vector):
    """
    Computes the mean squared error for a given error vector.
    Args:
        error_vector: error vector computed for a specific dataset and model
    Returns:
        mse: numeric value of the mean squared error
    """
    return np.mean(error_vector ** 2) / 2

def compute_gradient(tx, error_vector):
    """
    Computes the gradient for the mean squared error loss function.
    Args:
        y: labels
        error_vector: error vector computed for a specific data set and model
    Returns:
        gradient: the gradient vector computed according to its definition
    """
    return - tx.T.dot(error_vector) / error_vector.size

def build_polynomial(x, degree):
    """
    Extends the feature matrix, x, by adding a polynomial basis of the given degree.
    Args:
        x: features
        degree: degree of the polynomial basis
    Returns:
        augmented_x: expanded features based on a polynomial basis
    """
    num_cols = x.shape[1] if len(x.shape) > 1 else 1
    augmented_x = np.ones((len(x), 1))
    for col in range(num_cols):
        for degree in range(1, degree + 1):
            if num_cols > 1:
                augmented_x = np.c_[augmented_x, np.power(x[ :, col], degree)]
            else:
                augmented_x = np.c_[augmented_x, np.power(x, degree)]
        if num_cols > 1 and col != num_cols - 1:
            augmented_x = np.c_[augmented_x, np.ones((len(x), 1))]
    return augmented_x

def compute_rmse(loss_mse):
    """
    Computes the root mean squared error.
    Args:
        loss_mse: numeric value of the mean squared error loss
    Returns:
        loss_rmse: numeric value of the root mean squared error loss
    """
    return np.sqrt(2 * loss_mse)

def sigmoid(t):
    """
    Applies the sigmoid function to a given input t.
    Args:
        t: the given input to which the sigmoid function will be applied.
    Returns:
        sigmoid_t: the value of sigmoid function applied to t
    """
    return 1. / (1. + np.exp(-t))

def compute_logistic_loss(y, tx, w):
    """
    Computes the loss as the negative log likelihood of picking the correct label.
    Args:
        y: labels
        tx: features
        w: weight vector
    Returns:
        loss: the negative log likelihood of picking the correct label
    """
    tx_dot_w = tx.dot(w)
    return np.sum(np.log(1. + np.exp(tx_dot_w)) - y * tx_dot_w)

def compute_logistic_gradient(y, tx, w):
    """
    Computes the gradient of the loss function used in logistic regression.
    Args:
        y: labels
        tx: features
        w: weight vector
    Returns:
        logistic_gradient: the gradient of the loss function used in
            logistic regression.
    """
    return tx.T.dot(sigmoid(tx.dot(w)) - y)

def penalized_logistic_regression(y, tx, w, lambda_):
    """
    Adds the penalization term (2-norm of w vector) on top of the normal
    logistic loss. Computes the modified loss and gradient.
    Args:
        y: labels
        tx: features
        w: weight vector
    Returns:
        loss: the modified version of the normal logistic loss
        logistic_gradient: the gradient of modified loss function used in
            penalized logistic regression.
    """
    loss = compute_logistic_loss(y, tx, w) + (lambda_ / 2) * w.T.dot(w)
    gradient = compute_logistic_gradient(y, tx, w) + lambda_ * w
    return loss, gradient

def cross_terms(x, x_initial):
    """
    Adds the multiplication of different features as new features.
    Args:
        x: the given feature matrix
        x_initial: the features whose multiplications will be added
    Returns:
        x_cross_terms: feature matrix with cross terms
    """
    for col1 in range(x_initial.shape[1]):
        for col2 in np.arange(col1 + 1, x_initial.shape[1]):
            if col1 != col2:
                x = np.c_[x, x_initial[:, col1] * x_initial[:, col2]]
    return x

def log_terms(x, x_initial):
    """
    Adds the logarithms of features as new features.
    Args:
        x: the given feature matrix
        x_initial: the features whose logarithms will be added
    Returns:
        x_log_terms: feature matrix with logarithms
    """
    for col in range(x_initial.shape[1]):
        current_col = x_initial[:, col]
        current_col[current_col <= 0] = 1
        x = np.c_[x, np.log(current_col)]
    return x

def sqrt_terms(x, x_initial):
    """
    Adds the square roots of features as new features.
    Args:
        x: the given feature matrix
        x_initial: the features whose square roots will be added
    Returns:
        x_sqrt_terms: feature matrix with square roots
    """
    for col in range(x_initial.shape[1]):
        current_col = np.abs(x_initial[:, col])
        x = np.c_[x, np.sqrt(current_col)]
    return x

def apply_trigonometry(x, x_initial):
    """
    Adds the sin and cos of features as new features.
    Args:
        x: the given feature matrix
        x_initial: the features whose sin and cos will be added
    Returns:
        x_sqrt_terms: feature matrix with sine values
    """
    for col in range(x_initial.shape[1]):
        x = np.c_[x, np.sin(x_initial[:, col])]
        x = np.c_[x, np.cos(x_initial[:, col])]
    return x

def feature_engineering(x, degree, has_angles = False):
    """
    Builds a polynomial with the given degree from the initial features,
    add the cross terms, logarithms and square roots of the initial features
    as new features. Also includes the sine of features as an option.
    Args:
        x: features
        degree: degree of the polynomial basis
        has_angles: Boolean value to determine including sin and cos of features
    Returns:
        x_engineered: engineered features
    """
    x_initial = x
    x = build_polynomial(x, degree)
    x = cross_terms(x, x_initial)
    x = log_terms(x, x_initial)
    x = sqrt_terms(x, x_initial)
    if has_angles:
        x = apply_trigonometry(x, x_initial)
    return x


import numpy as np
import plotly.graph_objects as go

def best_fit_linear(data, x_var, y_var, color):
    """
    Determines the linear line of best fit between x_var and y_var for the inputted data.
    :param data: The data of which to find the line of best fit
    :param x_var: The dependent variable to use in the regression
    :param y_var: The response variable to use in the regression
    :param color: The desired color of the line of best fit
    :return: An object of the go.Figure type, containing the line of best fit
    """
    # y = a * x + b
    x = data[x_var]
    y = data[y_var]

    beta_1, beta_0 = np.polyfit(x, y, 1)

    x_plot = np.arange(min(x), max(x))
    y_plot = beta_1 * x_plot + beta_0

    trendline = go.Scatter(x=x_plot, y=y_plot, line=dict(color=color), showlegend=False, visible=False)
    return trendline, y, y_plot

def best_fit_exp(data, x_var, y_var, color):
    """
    Determines the exponential line of best fit between x_var and y_var for the inputted data.
    :param data: The data of which to find the line of best fit
    :param x_var: The dependent variable to use in the regression
    :param y_var: The response variable to use in the regression
    :param color: The desired color of the line of best fit
    :return: An object of the go.Figure type, containing the line of best fit
    """
    # y = a * e^(bx)
    x = data[x_var]
    y = data[y_var]

    beta_0, beta_1 = np.polyfit(x, np.log(y), 1, w=np.sqrt(y))

    x_plot = np.arange(min(x), max(x))
    y_plot = np.exp(beta_1) * np.exp(beta_0*x_plot)

    trendline = go.Scatter(x=x_plot, y=y_plot, line=dict(color=color), showlegend=False, visible=False)
    return trendline, y, y_plot

def best_fit_log(data, x_var, y_var, color):
    """
    Determines the logarithmic line of best fit between x_var and y_var for the inputted data.
    :param data: The data of which to find the line of best fit
    :param x_var: The dependent variable to use in the regression
    :param y_var: The response variable to use in the regression
    :param color: The desired color of the line of best fit
    :return: An object of the go.Figure type, containing the line of best fit
    """
    # y = a + b * log(x)
    x = data[x_var]
    y = data[y_var]

    beta_0, beta_1 = np.polyfit(np.log(x), y, 1)

    x_plot = np.arange(min(x), max(x))
    y_plot = beta_1 + beta_0 * np.log(x_plot)

    trendline = go.Scatter(x=x_plot, y=y_plot, line=dict(color=color), showlegend=False, visible=False)
    return trendline, y, y_plot

def get_r_squared(y_obs, y_pred):
    """
    Determines the correlation coefficient for a line of best fit.
    :param y_obs: The observed values for the response variable.
    :param y_pred: The predicted values for the response variable.
    :return: The correlation coefficient (R squared)
    """
    y_mean = y_obs.mean()

    ss_total = 0
    ss_residual = 0

    for i in range(0, len(y_obs)):
        ss_total = ss_total + (y_obs[i] - y_mean)**2

    for i in range(0, len(y_obs)):
        ss_residual = ss_residual + (y_obs[i] - y_pred[i])**2

    r = 1 - ss_residual / ss_total
    r_squared = r**2

    return r_squared


import math
import numpy as np

def Rastrigin(n):
    #f(x) = A*n + [sum (x_i² - A*cos(2*pi*x_i)) i = 1 .. n]
    A = 10
    def f(x):
        return A*n + sum(map(lambda x: x**2-A*math.cos(2*math.pi*x), x))
    def grad_f(x):
        grad = []
        for i in range(len(x)):
            grad.append(2*x[i] + 2*math.pi*A*math.sin(2*math.pi*x[i]))
        return np.array(grad)
    def hess_f(x):
        hess = [[2+(A*2*math.pi)**2*math.cos(2*math.pi*x[i]) if i==j else 0 for j in range(n)] for i in range(n)]
        return np.array(hess)
    return {"function": f, "gradient":grad_f, "hessian":hess_f, "search_region": None, "global_minimum":[0 for i in range(n)], "name":"Rastrigin"}

def Rosenbrock(n):
    # f(x, y) = 100(y-x²)² + (1-x)²
    if n != 2:
        return None
    def f(entry):
        x, y = entry[0], entry[1]
        return 100*(y-x**2)**2 + (1-x)**2
    def grad_f(entry):
        #return aprox_grad(f, entry)
        x, y = entry[0], entry[1]
        return np.array([2*(200*x**3-200*x*y+x-1), 200*(y-x**2)])
    def hess_f(entry):
        x, y = entry[0], entry[1]
        return np.array([[-400*(y-x**2)+800*x**2+2, -400*x],[-400*x, 200]])
    return {"function": f, "gradient": grad_f, "hessian": hess_f, "search_region": None, "global_minimum":[1 for i in range(n)], "name":"Rosenbrock"}

def Sphere(n):
    # f(x) = sum x_i² i = 1 .. n
    def f(x):
        return sum(map(lambda it: it**2, x))
    def grad_f(x):
        grad = [2*x[i] for i in range(n)]
        return np.array(grad)
    def hess_f(x):
        hess = [[2 if i==j else 0 for j in range(n)] for i in range(n)]
        return np.array(hess)
    return {"function": f, "gradient":grad_f, "hessian":hess_f, "search_region":None, "global_minimum":[0,0], "name":"Sphere"}

def Goldstein_price(n):
    if n != 2:
        return None
    def f(entry):
        x, y = entry[0], entry[1]
        return (1+(x+y+1)**2*(19-14*x+3*x**2-14*y+6*x*y+3*y**2))*(30+(2*x-3*y)**2*(18-32*x+12*x**2+48*y-36*x*y+27*y**2))
    def grad_f(entry):
        return aprox_grad(f, entry)
    return {"function": f, "gradient":grad_f, "hessian":None, "search_region": [-2,2], "global_minimum":[0,-1], "name":"Goldstein price"}

def aprox_deriv(f, x, h=10**(-4)):  # symetric difference quotient
    dy = f(x+h) - f(x-h)
    return dy/(2*h)

def aprox_grad(f, x):
    grad = []
    for i in range(len(x)):
        fs = lambda inp: f([*x[0:i], inp, *x[(i+1):len(x)]])
        grad.append(aprox_deriv(fs, x[i]))
    #fs = [lambda inp: f([*x[0:i], inp, *x[(i+1):len(x)]]) for i in range(len(x))]
    return np.array(grad)

def sph(x):
    return x[0]**2+x[1]**2

fns = [Rastrigin, Rosenbrock, Sphere, Goldstein_price]


from statistics import mean
import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib import style
style.use('ggplot')

def create_dataset(hm,variance,step=2,correlation=False):
    val = 1
    ys = []
    for i in range(hm):
        y = val + random.randrange(-variance,variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val+=step
        elif correlation and correlation == 'neg':
            val-=step

    xs = [i for i in range(len(ys))]

    return np.array(xs, dtype=np.float64),np.array(ys,dtype=np.float64)

def best_fit_slope_and_intercept(xs,ys):
    m = (((mean(xs)*mean(ys)) - mean(xs*ys)) /
         ((mean(xs)*mean(xs)) - mean(xs*xs)))

    b = mean(ys) - m*mean(xs)

    return m, b

def coefficient_of_determination(ys_orig,ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]

    squared_error_regr = sum((ys_line - ys_orig) * (ys_line - ys_orig))
    squared_error_y_mean = sum((y_mean_line - ys_orig) * (y_mean_line - ys_orig))

    print(squared_error_regr)
    print(squared_error_y_mean)

    r_squared = 1 - (squared_error_regr/squared_error_y_mean)

    return r_squared

xs, ys = create_dataset(40,40,2,correlation='pos')
m, b = best_fit_slope_and_intercept(xs,ys)
regression_line = [(m*x)+b for x in xs]
r_squared = coefficient_of_determination(ys,regression_line)
print(r_squared)

plt.scatter(xs,ys,color='#003F72', label = 'data')
plt.plot(xs, regression_line, label = 'regression line')
plt.legend(loc=4)
plt.show()


import numpy as np

def compute_sides(coord):

    ax = coord[0, 0]
    ay = coord[1, 0]  # Coordinates of the point A
    bx = coord[0, 1]
    by = coord[1, 1]  # Coordinates of the point B
    cx = coord[0, 2]
    cy = coord[1, 2]  # Coordinates of the point C

    ab = compute_length(ax, ay, bx, by)
    bc = compute_length(bx, by, cx, cy)
    ca = compute_length(cx, cy, ax, ay)
    return ab, bc, ca

def compute_length(p1x, p1y, p2x, p2y):
    return np.sqrt(np.power(p1x-p2x, 2)+np.power(p1y-p2y, 2))

def compute_area(coord):
    """ make sure the triangle exists"""
    [a, b, c] = sorted(compute_sides(coord))
    if a + b - c < np.finfo(float).eps:
        return 0
    s = (a + b + c) / 2
    return (s*(s-a)*(s-b)*(s-c)) ** 0.5

def circum_inside(coord):
    ax = coord[0, 0]
    ay = coord[1, 0]  # Coordinates of the point A
    bx = coord[0, 1]
    by = coord[1, 1]  # Coordinates of the point B
    cx = coord[0, 2]
    cy = coord[1, 2]  # Coordinates of the point C

    d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))

    # Coordinates of the circumcenter
    ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by)
          * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
    uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by)
          * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d

    # Radius of the circle
    r = np.sqrt((ax - ux)**2 + (ay - uy)**2)

    # Determine whether or not the circumcircle is covered by the unit circle
    area = compute_area(coord)
    if np.sqrt(ux**2+uy**2) + r <= 1 and area > np.finfo(float).eps:
        return True

def is_inside(a):
    return a[0]**2 + a[1]**2 <= 1

N = 10**6  # Number of Monte Carlo samples in both methods

# Generate samples by rejecting/accepting

count = 0  # Initialize the number of desired groups
k = 0
data_inside = np.zeros((2, 3))

while k <= 3*N:
    pt = np.random.uniform(-1, 1, 2)  # draw one point
    if is_inside(pt):  # Determine if the point lies inside the unit circle
        s = k % 3
        data_inside[0, s] = pt[0]
        data_inside[1, s] = pt[1]
        if k % 3 == 2 and circum_inside(data_inside):
            count += 1
        k += 1

per_rej = count / N

# Generate samples by polar coordinates
ct = 0  # Initialize the number of desired groups
for i in range(N):
    data_polar = np.array([np.sqrt(np.random.random(3)),
                           np.random.uniform(-np.pi, np.pi, 3)])
    # Tranform polar coornates to cartesian coordinates
    sp = data_polar.copy()
    sc = np.array([sp[0, :]*np.cos(sp[1, ]), sp[0, :]*np.sin(sp[1, ])])
    if circum_inside(sc):
        ct += 1

per_polar = ct / N

print("The Monte Carlo method (N={}) tell the answer is:".format(N))
print("polar     coordinates: {:.6f}".format(per_polar))
print("Cartesian coordinates: {:.6}".format(per_rej))
print("But formula 2*pi/15  : {:.6}".format(2*np.pi/15))


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model

def polynomial_sframe(feature, degree):
    # assume that degree >= 1
    # initialize the SFrame:
    poly_sframe = pd.DataFrame()
    # and set poly_sframe['power_1'] equal to the passed feature
    poly_sframe['power_1'] = feature
    # first check if degree > 1
    if degree > 1:
        # then loop over the remaining degrees:
        # range usually starts at 0 and stops at the endpoint-1. We want it to start at 2 and stop at degree
        for power in range(2, degree + 1):
            # first we'll give the column a name:
            name = 'power_' + str(power)
            # then assign poly_sframe[name] to the appropriate power of feature
            poly_sframe[name] = feature ** power

    # poly_sframe['constant'] = 1
    return poly_sframe

def fit_poly(data_set, degree):
    poly_data_ = polynomial_sframe(data_set['sqft_living'], degree)
    features_ = poly_data_.columns.values[:degree].tolist()
    # features_ = ['constant'] + poly_data_.columns.values[:degree].tolist()
    model_ = linear_model.LinearRegression()
    # model_ = linear_model.LinearRegression(fit_intercept=False)
    model_.fit(poly_data_[features_], data_set['price'])
    return model_, poly_data_, features_

dtype_dict = {'bathrooms': float, 'waterfront': int, 'sqft_above': int, 'sqft_living15': float, 'grade': int,
              'yr_renovated': int, 'price': float, 'bedrooms': float, 'zipcode': str, 'long': float,
              'sqft_lot15': float, 'sqft_living': float, 'floors': str, 'condition': int, 'lat': float, 'date': str,
              'sqft_basement': int, 'yr_built': int, 'id': str, 'sqft_lot': int, 'view': int}
training_data = pd.read_csv('wk3_kc_house_train_data.csv', dtype=dtype_dict)
validation_data = pd.read_csv('wk3_kc_house_valid_data.csv', dtype=dtype_dict)
test_data = pd.read_csv('wk3_kc_house_test_data.csv', dtype=dtype_dict)

validation_RSS = []
models = []
features_list = []
for degree_value in range(1, 15 + 1):
    print('Learning degree %s ' % degree_value)
    model, poly_data, features = fit_poly(training_data, degree_value)
    print(features)
    validation_poly = polynomial_sframe(validation_data['sqft_living'], degree_value)
    validation_error = validation_data['price'] - model.predict(validation_poly[features])
    validation_RSS.append(validation_error.T.dot(validation_error))
    models.append(model)
    features_list.append(features)
print(validation_RSS)
best_degree = validation_RSS.index(min(validation_RSS))
print('Min validation error degree: %s' % (best_degree + 1))


import math
from datetime import date, timedelta
from scipy import special
from sys import maxsize
import itertools

def sphere_volume(r):
    """ a function that computes the volume of a sphere, given its radius r. """
    return 4/3*math.pi*r**3

def quadratic_equation(a, b, c):
    """ a function that computes the real roots of a given quadratic equation a*X^2+b*X+c=0. """
    d = math.sqrt(b**2-4*a*c)

    r1 = (-b+d)/(2*a)
    r2 = (-b-d)/(2*a)

    return (r1, r2)

def number_of_zeros(lst):
    """  a function that returns the number of zeros in a given simple list of numbers lst. """
    return len(list(filter(lambda x: x == 0, lst)))

def draw_pascal(n):
    """ a function that takes an integer n as a parameter and prints the first n rows of the Pascal's triangle. """
    # Iterate through every line
    # and print entries in it
    for line in range(0, n):

        # Every line has number of
        # integers equal to line
        # number
        for i in range(0, line + 1):
            print(bc(line, i), " ", "")
        # '/n'
        print()

def euler():
    """ returns the unique positive integer whose square has the form 1_2_3_4_5_6_7_8_9_0, where each "_" is a single digit."""

def days(date1, date2):
    """a function that takes two dates, date1 and date2, in some format, and returns the number of days from date1 to date2, inclusive."""

    # this will give you a list containing all of the dates
    numDays = [str(date1 + timedelta(days=x))
               for x in range((date2-date1).days + 1)]

    return "\n".join(numDays)

def remove_consecutive_dups(lst):
    """ return a copy of lst with consecutive duplicates of elements eliminated. For example, for lst = [a, a, a, a, b, c, c, a, a, d, e, e, e, e], the returned list is [a, b, c, a, d, e]. """
    return [v for i, v in enumerate(lst) if i == 0 or v != lst[i-1]]

def remove_dups(lst):
    """ return a copy of lst with duplicates of elements eliminated. For example, for lst = [a, a, a, a, b, c, c, a, a, d, e, e, e, e], the returned list is [a, b, c, d, e]. """
    s = sorted(lst)
    return [v for i, v in enumerate(s) if i == 0 or v != s[i-1]]

def replicate(lst, n):
    """ Replicate each of the elements of lst a given number of times. For example, for lst = [a, b, c] and n = 3, the returned list is [a, a, a, b, b, b, c, c, c]. """
    def rep_helper(val, n):
        i = 0
        v = ""
        while i < n:
            v = v + val
            i += 1
        return v
    list_of_lists = [list(rep_helper(a, n)) for a in lst]
    return [val for sublist in list_of_lists for val in sublist]

def split_list(lst, n):
    """ split lst into two parts with the first part having n elements, and return a list that contains these two parts. """
    return [ lst[:n+1], lst[n+2:] ]

def min_max_median(lst):
    """ a function that takes a simple list of numbers lst as a parameter and returns a list with the min, max, and the median of lst. """
    s = sorted(lst)
    n = len(s)
    return [ s[0], s[-1], s[n//2] if n % 2 == 1  else (s[n//2 - 1] + s[n//2]) / 2]

def bc(n, k):
    """ return the binomial coefficient "n choose k". Can you figure out a method that is less likely to cause an overflow than using the formula(n*(n-1)*...*(n-k+1))/(k*(k-1)*...*2)? """

    return special.binom(n, k)

def subsets(s, n):
    """ return the set of n-element subsets of s. """
    return list(itertools.combinations(s, n))

def max_subarray(arr):
    """ return a contiguous subarray within arr which has the largest sum. """
    max_so_far = -maxsize - 1
    max_ending_here = 0
    max_itr = 0
    min_itr = 0

    for i in range(0, len(arr)):
        max_ending_here = max_ending_here + arr[i]
        if (max_so_far < max_ending_here):
            max_so_far = max_ending_here
            temp = i
        if (temp > max_itr):
            min_itr = max_itr
            max_itr = temp

        if max_ending_here < 0:
            max_ending_here = 0
    return arr[min_itr:max_itr + 1]


# ------------- Machine Learning - Topic 1: Linear Regression Multivariate

# depends on
#   featureNormalize.py
#   gradientDescentMulti.py
#   normalEqn.py
#
import os, sys
sys.path.append(os.getcwd() + os.path.dirname('/ml/ex1/'))
from helpers import featureNormalize, gradientDescentMulti, normalEqn
import numpy as np
import matplotlib.pyplot as plt

## ================ Part 1: Feature Normalization ================

print('Loading data ...\n')

## Load Data
data = np.loadtxt('ml/ex1/ex1data2.txt', delimiter=",")
X = data[:,:2]
y = data[:,2]
m = len(y) # number of training examples

# Print out some data points
print('First 10 examples from the dataset: \n')
for i in range(10):
    print ("x = [{:.0f} {:.0f}], y = {:.0f}".format(X[i,0], X[i,1], y[i]))

input('Program paused. Press enter to continue.\n')

# Scale features and set them to zero mean
print('Normalizing Features...')

X_norm, mu, sigma = featureNormalize(X)

# Add intercept term to X
X_padded = np.column_stack((np.ones((m,1)), X_norm)) # Add a column of ones to x

## ================ Part 2: Gradient Descent ================

print('Running gradient descent...')

# Choose some alpha value
alpha = 0.01
num_iters = 400

# Init Theta and Run Gradient Descent
theta = np.zeros((3, 1))

theta, J_history = gradientDescentMulti(X_padded, y, theta, alpha, num_iters)

# Plot the convergence graph
plt.plot(range(J_history.size), J_history, "-b", linewidth=2)
plt.xlabel('Number of iterations')
plt.ylabel('Cost J')
plt.show(block=False)

# Display gradient descent's result
print('Theta computed from gradient descent: ')
print("{:f}, {:f}, {:f}".format(theta[0,0], theta[1,0], theta[2,0]))
print("")

# Estimate the price of a 1650 sq-ft, 3 br house
# ====================== YOUR CODE HERE ======================
# Recall that the first column of X is all-ones. Thus, it does
# not need to be normalized.
area_norm = (1650 - float(mu[:,0])) / float(sigma[:,0])
br_norm = (3 - float(mu[:,1]))/float(sigma[:,1])
house_norm_padded = np.array([1, area_norm, br_norm])

price = np.array(house_norm_padded).dot(theta)

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

print("Predicted price of a 1650 sq-ft, 3 br house (using gradient descent):\n ${:,.2f}".format(price[0]))

input('Program paused. Press enter to continue.\n')

## ================ Part 3: Normal Equations ================

print('Solving with normal equations...')

# ====================== YOUR CODE HERE ======================
# Instructions: The following code computes the closed form
#               solution for linear regression using the normal
#               equations. You should complete the code in
#               normalEqn.m
#
#               After doing so, you should complete this code
#               to predict the price of a 1650 sq-ft, 3 br house.
#

## Load Data
data = np.loadtxt('ml/ex1/ex1data2.txt', delimiter=",")
X = data[:,:2]
y = data[:,2]
m = len(y) # number of training examples

# Add intercept term to X
X_padded = np.column_stack((np.ones((m,1)), X))

# Calculate the parameters from the normal equation
theta = normalEqn(X_padded, y)

# Display normal equation's result
print('Theta computed from the normal equations:')
print("{:f}, {:f}, {:f}".format(theta[0], theta[1], theta[2]))
print('')

# Estimate the price of a 1650 sq-ft, 3 br house
# ====================== YOUR CODE HERE ======================
house_norm_padded = np.array([1, 1650, 3])
price = np.array(house_norm_padded).dot(theta)

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

print("Predicted price of a 1650 sq-ft, 3 br house (using gradient descent):\n ${:,.2f}".format(price))


#-------------------------------------------------------------------------
# Note: please don't use any additional package except the following packages
import numpy as np
import math
#-------------------------------------------------------------------------
'''
    Problem 1: PCA
    In this problem, you will implement a version of the principal component analysis method to reduce the dimensionality of data.
    You could test the correctness of your code by typing `nosetests -v test1.py` in the terminal.

    Notations:
            ---------- input data ------------------------
            n: the number of data instances (for example, # of images), an integer scalar.
            p: the number of dimensions (for example, # of pixels in each image), an integer scalar.
            X: the feature matrix, a float numpy matrix of shape n by p.
            ---------- computed data ----------------------
            mu: the average vector of matrix X, a numpy float matrix of shape 1 by p.
                Each element mu[0,i] represents the average value in the i-th column of matrix X.
            Xc: the centered matrix X, a numpy float matrix of shape n by p.
                Each column has a zero mean value.
            C:  the covariance matrix of matrix X, a numpy float matrix of shape p by p.
            k:  the number of dimensions to reduce to (k should be smaller than p), an integer scalar
            E:  the eigen vectors of matrix X, a numpy float matrix of shape p by p.
                Each column of E corresponds to an eigen vector of matrix X.
            v:  the eigen values of matrix X, a numpy float array of length p.
                Each element corresponds to an eigen value of matrix X. E and v are paired.
            Xp: the projected feature matrix with reduced dimensions, a numpy float matrix of shape n by k.
             P: the projection matrix, a numpy float matrix of shape p by k.
            -----------------------------------------------
'''

#--------------------------
def centering_X(X):
    '''
        Centering matrix X, so that each column has zero mean.
        Input:
            X:  the feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
        Output:
            Xc:  the centered matrix X, a numpy float matrix of shape n by p.
            mu:  the average row vector of matrix X, a numpy float matrix of shape 1 by p.
        Note: please don't use the np.cov() function. There seems to be a bug in their code which will result in an error in later test cases.
              Please implement this function only using basic numpy functions, such as np.mean().
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    n, p = X.shape

    _1 = np.ones(n)

    mu = 1/n * np.dot(_1, X)
    Xc = X - mu

    #########################################
    return Xc, mu

#--------------------------
def compute_C(Xc):
    '''
        Compute the covariance matrix C.
        Input:
            Xc:  the centered feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
        Output:
            C:  the covariance matrix, a numpy float matrix of shape p by p.
        Note: please don't use the np.cov() function here. Implement the function using matrix multiplication.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    n, p = Xc.shape

    C = 1 / (n-1) * np.dot(Xc.T, Xc)

    #########################################
    return C

#--------------------------
def compute_eigen_pairs(C):
    '''
        Compute the eigen vectors and eigen values of C.
        Input:
            C:  the covariance matrix, a numpy float matrix of shape p by p.
        Output:
            E:  the eigen vectors of matrix C, a numpy float matrix of shape p by p. Each column of E corresponds to an eigen vector of matrix C.
            v:  the eigen values of matrix C, a numpy float array of length p. Each element corresponds to an eigen value of matrix C. E and v are paired.
        Hint: you could use np.linalg.eig() to compute the eigen vectors of a matrix.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    v, E = np.linalg.eig(C)

    #########################################
    return E,v

#--------------------------
def compute_P(E,v,k):
    '''
        Compute the projection matrix P by combining the eigen vectors with the top k largest eigen values.
        Input:
            E:  the eigen vectors of matrix X, a numpy float matrix of shape p by p. Each column of E corresponds to an eigen vector of matrix X.
            v:  the eigen values of matrix X, a numpy float array of length p. Each element corresponds to an eigen value of matrix X. E and v are paired.
            k:  the number of dimensions to reduce to (k should be smaller than p), an integer scalar
        Output:
            P: the projection matrix, a numpy float matrix of shape p by k.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    select_indices = np.argsort(v)[::-1][:k]

    top_k = v[select_indices]
    P = E[:, select_indices]

    #########################################
    return P

#--------------------------
def compute_Xp(Xc,P):
    '''
        Compute the projected feature matrix Xp by projecting data Xc using matrix P.
        Input:
            Xc:  the feature matrix after centering, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
             P: the projection matrix, a numpy float matrix of shape p by k.
        Output:
            Xp: the feature matrix after projection (dimension reduced), a numpy float matrix of shape p by k.
    '''

    #########################################
    ## INSERT YOUR CODE HERE
    Xp = np.matmul(Xc, P)
    #########################################
    return Xp

#--------------------------
def PCA(X, k=1):
    '''
        Compute PCA of matrix X.
        Input:
            X:  the feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
            k:  the number of dimensions to output (k should be smaller than p)
        Output:
            Xp: the feature matrix with reduced dimensions, a numpy float matrix of shape n by k.
             P: the projection matrix, a numpy float matrix of shape p by k.
        Note: in this problem, you cannot use existing package for PCA, such as scikit-learn
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    # centering matrix X

    Xc, _ = centering_X(X)

    # compute covariance matrix C

    C = compute_C(Xc)

    # compute eigen pairs of L

    E, v = compute_eigen_pairs(C)
    # compute the projection matrix

    # project the data into lower dimension using projection matrix P and centered data matrix X

    P = compute_P(E, v, k)

    Xp = compute_Xp(Xc, P)

    #########################################
    return Xp, P


from mpmath import mp
import numpy

def bisection_search(f, low:float, high:float):
    """
    A root finding method that does not rely on derivatives

    :param f: a function f: X -> R
    :param low: the lower bracket
    :param high: the upper limit bracket
    :return: the location of the root, e.g. f(mid) ~ 0
    """
    # flip high and low if out of order
    if f(high) < f(low):
        low, high = high, low

    # find mid point
    mid = .5 * (low + high)

    while True:

        # bracket up
        if f(mid) < 0:
            low = mid
        # braket down
        else:
            high = mid

        # update mid point
        mid = .5 * (high + low)

        # break if condition met
        if abs(high - low) < 10 ** (-(mp.dps / 2)):
            break

    return mid

def concave_max(f, low:float, high:float):
    """
    Forms a lambda for the approximate derivative and finds the root

    :param f: a function f: X -> R
    :param low: the lower bracket
    :param high: the upper limit bracket
    :return: the location of the root f'(mid) ~ 0
    """
    # create an approximate derivative expression
    scale = high - low

    h = mp.mpf('0.' + ''.join(['0' for i in range(int(mp.dps / 1.5))]) + '1') * scale
    df = lambda x: (f(x + h) - f(x - h)) / (2.0 * h)

    return bisection_search(df, low, high)

def chev_points(n:int, lower:float = -1, upper:float = 1):
    """
    Generates a set of chebychev points spaced in the range [lower, upper]
    :param n: number of points
    :param lower: lower limit
    :param upper: upper limit
    :return: a list of multipressison chebychev points that are in the range [lower, upper]
    """
    #generate chebeshev points on a range [-1, 1]
    index = numpy.arange(1, n+1)
    range_ = abs(upper - lower)
    return [(.5*(mp.cos((2*i-1)/(2*n)*mp.pi)+1))*range_ + lower for i in index]

def remez(func, n_degree:int, lower:float=-1, upper:float=1, max_iter:int = 10):
    """
    :param func: a function (or lambda) f: X -> R
    :param n_degree: the degree of the polynomial to approximate the function f
    :param lower: lower range of the approximation
    :param upper: upper range of the approximation
    :return: the polynomial coefficients, and an approximate maximum error associated with this approximation
    """
    # initialize the node points

    x_points = chev_points(n_degree + 2, lower, upper)

    A = mp.matrix(n_degree + 2)
    coeffs = numpy.zeros(n_degree + 2)

    # place in the E column
    mean_error = float('inf')

    for i in range(n_degree + 2):
        A[i, n_degree + 1] = (-1) ** (i + 1)

    for i in range(max_iter):

        # build the system
        vander = numpy.polynomial.chebyshev.chebvander(x_points, n_degree)

        for i in range(n_degree + 2):
            for j in range(n_degree + 1):
                A[i, j] = vander[i, j]

        b = mp.matrix([func(x) for x in x_points])
        l = mp.lu_solve(A, b)

        coeffs = l[:-1]

        # build the residual expression
        r_i = lambda x: (func(x) - numpy.polynomial.chebyshev.chebval(x, coeffs))

        interval_list = list(zip(x_points, x_points[1:]))
        #         interval_list = [[x_points[i], x_points[i+1]] for i in range(len(x_points)-1)]

        intervals = [upper]
        intervals.extend([bisection_search(r_i, *i) for i in interval_list])
        intervals.append(lower)

        extermum_interval = [[intervals[i], intervals[i + 1]] for i in range(len(intervals) - 1)]

        extremums = [concave_max(r_i, *i) for i in extermum_interval]

        extremums[0] = mp.mpf(upper)
        extremums[-1] = mp.mpf(lower)

        errors = [abs(r_i(i)) for i in extremums]
        mean_error = numpy.mean(errors)

        if numpy.max([abs(error - mean_error) for error in errors]) < 0.000001 * mean_error:
            break

        x_points = extremums

    return [float(i) for i in numpy.polynomial.chebyshev.cheb2poly(coeffs)], float(mean_error)

def c_code_gen(data_type, name, poly_coeffs, comments = None):
    method_string = f'{data_type} {name} ({data_type} x)' + '{\n'

    if comments is not None:
        method_string += '\t// ' + str(comments) + ' \n\n'

    data_type_converter = '' if data_type == 'double' else 'f'

    method_string += '\n'.join([f'\tconst {data_type} a_{i} = {str(val) + data_type_converter};' for i, val in enumerate(poly_coeffs)])

    horner = 'return a_0+'
    for i in range(len(poly_coeffs)-2):
        horner += f'x*(a_{i+1} +'
    horner += f'x*a_{len(poly_coeffs)-1}' + ')'*(len(poly_coeffs)-2) + ';\n}'

    return method_string + '\n \t' + horner


import numpy as np
import math

def true(x, t, pro=1):

    if pro == 1:
        return np.cos(math.pi*t) * np.sin(math.pi*x)

    elif pro == 2:
        return np.sin(t) * np.sin(4*math.pi*x)

def ex3(h, tao, pro=1):
    xmin = 0
    xmax = 1
    tmin = 0
    tmax = 1
    xs = np.arange(xmin, xmax+h, h)
    ts = np.arange(tmin, tmax+tao, tao)
    if pro == 1:
        a = 1
        def u0(_i):
            _x =xs[_i]
            return np.sin(math.pi*_x)
        def ut0(_i):
            return 0
    elif pro == 2:
        a = 1 / (16*math.pi**2)
        def u0(_i):
            return 0
        def ut0(_i):
            _x = xs[_i]
            return np.sin(4*math.pi*_x)

    r = a*tao / h
    print("r={}" .format(r), end=',')

    U = np.zeros((len(ts), len(xs)))
    print("网格节点数（时间层，空间层）:",U.shape)

    for x in xs:
        i = list(xs).index(x)
        U[0][i] = u0(i)
    for x in xs:
        i = list(xs).index(x)
        U[1][i] = 0.5 * r**2 * (u0(i-1) + u0(i+1)) + (1 - r**2) * u0(i) + tao * ut0(i)
    for k in ts[1:-1]:
        ki = list(ts).index(k)
        for j in xs[1:-1]:
            ji = list(xs).index(j)
            U[ki+1][ji] = r**2 * (U[ki][ji-1] + U[ki][ji+1]) + 2*(1-r**2)*U[ki][ji] - U[ki-1][ji]

    #print(U)

    UTrue = np.zeros((len(ts), len(xs)))
    for x in xs:
        for t in ts:
            UTrue[list(ts).index(t)][list(xs).index(x)] = true(x, t, pro=pro)
    #print(UTrue)

    print("误差：", np.linalg.norm(U - UTrue))

'''
print("r > 1, 不稳定：")
ex3(0.1, 0.2, pro=1)
ex3(0.05, 0.1, pro=1)
ex3(0.005, 0.01, pro=1)

print("r = 1")
ex3(0.2, 0.2, pro=1)
ex3(0.1, 0.1, pro=1)
ex3(0.05, 0.05, pro=1)
ex3(0.01, 0.01, pro=1)

print("r < 1, 稳定：")
ex3(0.2, 0.1, pro=1)
ex3(0.1, 0.05, pro=1)
ex3(0.01, 0.005, pro=1)
'''
a2 = 16*math.pi**2
print("r > 1, 不稳定：")
ex3(0.0012, 0.2, pro=2)
ex3(0.00063, 0.1, pro=2)
#ex3(0.005, 0.01, pro=2)

print("r < 1, 稳定：")
ex3(0.5, 0.5, pro=2)
ex3(0.25, 0.25, pro=2)
#ex3(2/a2, 1, pro=2)


#1.解一元二次方程
import math
a = float(input('a='))
b = float(input('b='))
c = float(input('c='))
root = pow(b , 2) - 4 * a * c
if root > 0:
    r1 = (-b + math.sqrt(root)) / (2 * a)
    r2 = (-b - math.sqrt(root)) / (2 *a)
    print(r1 ,r2)
elif root == 0:
    r1 = -b / 2 * a
    print(r1)
else:
    print('The equation has no real roots')

#2.学习加法
import random

num1 = random.randint(0 , 100)
num2 = random.randint(0 , 100)
print(num1 , num2)
sum_ = int(input('亲，请输入这两个整数的和：'))
if num1 + num2 == sum_:
    print('答案正确，你真棒！')
else:
    print('答案错误，再试一次吧！')

#3.找未来数据
def week(day,fut):
    days = ['Sunday','Monday','Tuesday','Wendnesday','Thursday','Friday','Saturday']      #创建一个列表
    a = days[day]
    b = days[(day + fut) % 7]
    print("Today is %s and the future day is %s"%(a,b) )

def Start():
    day = int(input("Enter today's day:"))
    fut = int(input("Enter the number of days elapsed since today:"))
    week(day,fut)
Start()

#4.对三个整数排序
a = int(input("请输入第一个整数："))
b = int(input("请输入第二个整数："))
c = int(input("请输入第三个整数："))
d = [a,b,c]
d.sort()     #升序sort()
print(d)

#5.比较价钱
money1 = float(input("请输入第一种大米的价钱："))
weight1 = float(input("请输入第一种大米的重量："))
money2 = float(input("请输入第二种大米的价钱："))
weight2 = float(input("请输入第二种大米的重量："))
per1 = money1 / weight1
per2 = money2 / weight2
if per1 > per2:
    print('第二种包装价格更好')
elif per1 < per2:
    print('第一种包装价格更好')
else:
    print('两种大米价格相同')

#6.找出一个月中的天数
year = int(input('请输入年份：'))
month = int(input('请输入月份：'))
list1 = [1,3,5,7,8,10,12]
list2 = [4,6,9,11]
if month in list1:
    print('%d年%d月份有31天'%(year,month))
elif month in list2:
    print('%d年%d月份有30天'%(year,month))
else:
    if year % 400 == 0 and year % 4 ==0:
        print('%d年%d月份有29天'%(year,month))
    else:
        print('%d年%d月份有28天'%(year,month))

#7.头或尾
import numpy as np
res = np.random.choice(['正面','反面'])
print(res)
guess = input("请输入'正面','反面':")
if res == guess:
    print('恭喜你，答对啦！')
else:
    print('很遗憾，答错了!')

#8.剪刀石头布
import random
computer = random.randint(1,3)
print(computer)
user = int(input("请输入[1.石头 2.剪刀 3.布]"))
if computer != user:
    if  computer == 1  and user == 2:
        print('你输了，再来一局')
    elif computer == 2 and user == 3:
        print('你输了，再来一局')
    elif computer == 3 and user == 1:
        print('你输了，再来一局')
    else:
        print('你赢了，太棒了')
else :
    print('平局，再来一局')

#9.一周的星期几
year = int(input('请输入年份(eg.2008):'))
month = int(input('请输入月份(1-12):'))
days = int(input('请输入月份的第几天(1-31):'))
week = ['Saturday','Sunday','Monday','Tuesday','Wendnesday','Thursday','Friday']
if month == 1:
    month = 13
    year = year - 1
if month ==2:
    month = 14
    year = year - 1
h = int(days+((26*(month+1))//10)+(year%100)+((year%100)/4)+((year//100)/4)+5*year//100)%7
day = week[h]
print('Day of the week is %s'%day)

#10.选出一张牌
import numpy as np
card = np.random.choice(['Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King'])
color = np.random.choice(['梅花','红桃','方块','黑桃'])
print('The card you picked is the %s of %s'%(card,color))

#11.回文数
num = int(input('亲，请输入一个三位数：'))
a = num % 10 *100 + num // 10 % 10 *10 + num // 100
if num == a:
    print('%d是回文数'%num)
else:
    print('%d不是回文数'%num)

#或者
num = input('输入一个三位数：')
a = num[2]
b = num[0]
if a == b :
    print('%r是回文数'%num)
else:
    print('%r不是回文数'%num)

#12.计算三角形的周长
a = int(input("请输入三角形的边长:"))
b = int(input("请输入三角形的边长:"))
c = int(input("请输入三角形的边长:"))
if a < b + c and b < a + c and c < b + a:
    sum = a + b + c
    print("三角形的周长是：%d"%sum)
else:
    print('亲，您输入的边长是不合法的哦！')

#13.统计正数和负数的个数然后计算这些数的平均值
positive_number = 0
negative_number = 0
sum_number = 0
for i in range(100):
    n = int(input('请输入整数:'))
    if n != 0:
        if n > 0:
            positive_number += 1
        else:
            negative_number += 1
    else:
        break
    sum_number += n
average_number = sum_number / (positive_number + negative_number)
print('您输入的正数有%d个，负数有%d个，平均值是%.2f'%(positive_number,negative_number,average_number))

#14.计算未来学费
money = 10000
ten_money = 0
for i in range(13):
    money = money * (1 + 0.05)
    if i == 9:
        print("十年后的学费是:%d"%money)
    if i >= 9:
        ten_money += money
print("十年后大学四年的总学费:%d"%ten_money)

#15.找出可以被5和6同时整除的数
count = 0
for i in range(100,700):
    if i % 5 == 0 and i % 6 == 0:
        print(i,end = " ")
        count += 1
        if count % 10 == 0:
            print()

#16.找出最小的n满足n²>12000；找出最大的n满足n³<12000；
n = 1
while n ** 2 < 12000:
    n += 1
print(n)

n_ = 1
while n_ ** 3 < 12000:
    n_ += 1
print(n_-1)

#17.演示消除错误（1+1/2+1/3+.....+1/n）
a = 0
for i in range(1,5001):
    a += 1 / i
print('从左到右的和为：%r'%a)

b = 0
for i in range(5000,0,-1):
    b += 1 / i
print('从右到左的和为：%r'%b)

#18.数列求和(1/3+3/5+5/7+9/11+11/13+....95/97+97/99)

sum = 0
for i in range(1,99,2):
    j = i + 2
    sum += i / j
print("数列的和是:%f"%sum)

#19.计算∏=4（1-1/3+1/5-1/7+1/9-1/11+.....+(-1)**(i+1)/(2i-1)）

pi = 0
for i in range(1,100000):
    pi += 4 * ((-1) ** (i + 1) / (2 * i - 1))
print('pi = %r '%pi)

#20.完全数
for i in range(1,10000):     #i是在1~10000里的数
    a = 0    #除了本身的和
    for j in range(1,i):
        if i % j == 0:    #j是i的本身
            a += j
    if a == i:
        print(a,end = (" "))

#21.组合(7*6/2=21,)
count = 0
for i in range(1,8,2):
    for j in range(2,8):
        if i != j :
            count += 1
            print(i,j,end = "\n")
print("组合共有%d个数"%count)


from ._integrand import Integrand
from ..discrete_distribution import DigitalNetB2
from ..true_measure import Uniform
from numpy import *

class BoxIntegral(Integrand):
    """
    $B_s(x) = \\left(\\sum_{j=1}^d x_j^2 \\right)^{s/2}$

    >>> l1 = BoxIntegral(DigitalNetB2(2,seed=7), s=[7])
    >>> x1 = l1.discrete_distrib.gen_samples(2**10)
    >>> y1 = l1.f(x1)
    >>> y1.shape
    (1024, 1)
    >>> y1.mean(0)
    array([0.75156724])
    >>> l2 = BoxIntegral(DigitalNetB2(5,seed=7), s=[-7,7])
    >>> x2 = l2.discrete_distrib.gen_samples(2**10)
    >>> y2 = l2.f(x2,compute_flags=[1,1])
    >>> y2.shape
    (1024, 2)
    >>> y2.mean(0)
    array([ 6.67548708, 10.52267786])

    References:

    [1] D.H. Bailey, J.M. Borwein, R.E. Crandall,Box integrals,
    Journal of Computational and Applied Mathematics, Volume 206, Issue 1, 2007, Pages 196-208, ISSN 0377-0427,
    https://doi.org/10.1016/j.cam.2006.06.010. (https://www.sciencedirect.com/science/article/pii/S0377042706004250)

    [2] https://www.davidhbailey.com/dhbpapers/boxintegrals.pdf
    """

    def __init__(self, sampler, s=array([1,2])):
        """
        Args:
            sampler (DiscreteDistribution/TrueMeasure): A
                discrete distribution from which to transform samples or a
                true measure by which to compose a transform
            s (list or ndarray): vectorized s parameter, len(s) is the number of vectorized integrals to evalute.
        """
        self.parameters = ['s']
        self.s = array([s]) if isscalar(s) else array(s)
        self.dprime = len(self.s)
        self.sampler = sampler
        self.true_measure = Uniform(self.sampler, lower_bound=0., upper_bound=1.)
        super(BoxIntegral,self).__init__() # output dimensions per sample

    def g(self, t, **kwargs):
        compute_flags = kwargs['compute_flags'] if 'compute_flags' in kwargs else ones(self.dprime,dtype=int)
        n,d = t.shape
        Y = zeros((n,self.dprime),dtype=float)
        for j in range(self.dprime):
            if compute_flags[j] == 1:
                Y[:,j] = (t**2).sum(1)**(self.s[j]/2)
        return Y

    def _spawn(self, level, sampler):
        return BoxIntegral(sampler=sampler,s=self.s)


"""
Inferring a binomial proportion via grid approximation.
"""
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')
import numpy as np
from hpd import hpd

def bern_grid(theta, p_theta, data, credib=.95):
    """
    Bayesian updating for Bernoulli likelihood and prior specified on a grid.
    Input arguments:
     theta is a vector of theta values, all between 0 and 1.
     p_theta is a vector of corresponding probability _masses_.
     data is a vector of 1's and 0's, where 1 corresponds to a and 0 to b.
     credib is the probability mass of the credible interval, default is 0.95.
    Output:
     p_theta_given_data is a vector of posterior probability masses over theta.
     Also creates a three-panel graph of prior, likelihood, and posterior
     probability masses with credible interval.
    Example of use:
     Create vector of theta values.
     bin_width = 1/1000
     theta_grid = np.arange(0, 1+bin_width, bin_width)
     Specify probability mass at each theta value.
     > rel_prob = np.minimum(theta_grid, 1-theta_grid) relative prob at each theta
     > prior = rel_prob / sum(rel_prob) probability mass at each theta
     Specify the data vector.
     data_vec = np.repeat([1, 0], [11, 3])  # 3 heads, 1 tail
     Call the function.
     > posterior = bern_grid( theta=theta_grid , p_theta=prior , data=data_vec )
    """

# Create summary values of data
    z = sum(data[data == 1])  # number of 1's in data
    N = len(data)  # number of flips in data
# Compute the likelihood of the data for each value of theta.
    p_data_given_theta = theta**z * (1 - theta)**(N - z)
# Compute the evidence and the posterior.
    p_data = sum(p_data_given_theta * p_theta)
    p_theta_given_data = p_data_given_theta * p_theta / p_data
    # Determine the limits of the highest density interval
    x = np.random.choice(theta, size=5000, replace=True, p=p_theta_given_data)
    intervals = hpd(x, alpha=1-credib)

# Plot the results.
    plt.figure(figsize=(12, 12))
    plt.subplots_adjust(hspace=0.7)

#    # Plot the prior.
    locx = 0.05
    mean_theta = sum(theta * p_theta)  # mean of prior, for plotting
    plt.subplot(3, 1, 1)
    plt.plot(theta, p_theta)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_theta)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(\theta)$')
    plt.title('Prior')
    plt.text(locx, np.max(p_theta)/2, r'mean($\theta$;%5.2f)' % mean_theta)
    # Plot the likelihood:
    plt.subplot(3, 1, 2)
    plt.plot(theta, p_data_given_theta)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_data_given_theta)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(D|\theta)$')
    plt.title('Likelihood')
    plt.text(locx, np.max(p_data_given_theta)/2, 'data: z=%s, N=%s' % (z, N))
    # Plot the posterior:
    mean_theta_given_data = sum(theta * p_theta_given_data)
    plt.subplot(3, 1, 3)
    plt.plot(theta, p_theta_given_data)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_theta_given_data)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(\theta|D)$')
    plt.title('Posterior')
    loc = np.linspace(0, np.max(p_theta_given_data), 5)
    plt.text(locx, loc[1], r'mean($\theta$;%5.2f)' % mean_theta_given_data)
    plt.text(locx, loc[2], 'P(D) = %g' % p_data)
    # Plot the HDI
    plt.text(locx, loc[3],
             'Intervals =%s' % ', '.join('%.3f' % x for x in intervals))
    for i in range(0, len(intervals), 2):
        plt.fill_between(theta, 0, p_theta_given_data,
                         where=np.logical_and(theta > intervals[i],
                                              theta < intervals[i+1]),
                         color='blue', alpha=0.3)
    plt.savefig('Figure_6.1.png')
    plt.show()
    return p_theta_given_data

###Create vector of theta values.
bin_width = 1/1000.
theta_grid = np.arange(0, 1+bin_width, bin_width)
##Specify probability mass at each theta value.
rel_prob = np.array([0.1] * len(theta_grid))  # uniform prior
rel_prob = np.array([0.1] * len(theta_grid))  # uniform prior
prior = rel_prob / sum(rel_prob)  # probability mass at each theta

#### figure 6.2 ###
#np.random.seed(123)
#a = [0.1] * 50
#b = np.linspace(0.1, 1, 50)
#c = np.linspace(1, 0.1, 50)
#d = [0.1] * 50
#p_theta = np.concatenate((a, b, c, d))
#prior = np.where(p_theta != 0 , p_theta / sum(p_theta), 0.)
#width = 1. / len(p_theta)
#theta_grid = np.arange(width/2 , (1-width/2)+width, width)

### figure 6.3 ###
#np.random.seed(123)
#a = np.repeat([0], [50])
#b = np.linspace(0, 1, 50)
#c = (np.linspace(1, 0, 20))**2
#d = np.random.uniform(size=3)
#e = np.repeat([1], [20])
#p_theta = np.concatenate((a, b, c, d, e))
#prior = np.where(p_theta != 0 , p_theta / sum(p_theta), 0.)
#width = 1. / len(p_theta)
#theta_grid = np.arange(width/2 , (1-width/2)+width, width)

###Specify the data vector.
data_vec = np.repeat([1, 0], [11, 3])  # 3 heads, 1 tail
###Call the function.
posterior = bern_grid(theta=theta_grid, p_theta=prior, data=data_vec)


# test_10.py
''' FUNCIONES
Ejemplo para el curso de métodos numéricos
por Ing. Giancarlo Ortiz '''
# Instalar módulos
''' Las funciones en Python se definen con la palabra clave def, seguida del nombre de la función.
Sus parámetros se escriben entre parentesis y pueden incluir valores por defecto.
Otra forma de escribir funciones, aunque menos utilizada, es con la palabra clave lambda.
El valor devuelto en las funciones con def será el dado con la instrucción return. '''

# DECLARACIÓN:
# Importar módulos que requieren instalación
import numpy as np
from numpy import ndarray
from numpy.core.records import array

# Definiciones de nuevas funciones con la palabra clave Def
def _menor(A: ndarray, i: int, j: int) -> ndarray:
    ''' Define el menor de la matriz A como Ă(i, j).

        ## Parámetros:
            A (array): una matriz.
            i (int): el primer indice.
            j (int): el segundo indice.

        ## Devoluciones:
            B (array): retorna el menor.
    '''
    B = np.delete(A, i-1, axis=0)
    C = np.delete(B, j-1, axis=1)
    return C

def _cofactor(A: ndarray, i: int, j: int) -> float:
    ''' Define el cofactor A(i, j) de una matriz A dada.

        ## Parámetros:
            A (array): una matriz.
            i (int): el primer indice.
            j (int): el segundo indice.

        ## Devoluciones:
            B (float): retorna el cofactor.
    '''
    B = _menor(A, i, j)
    C = pow(-1, i+j) * round(np.linalg.det(B), 3)
    return C

def _matriz_de_cofactores(A: ndarray):
    ''' Define la matriz de cofactores, que se obtiene de sustituir cada termino de A(i,j) por el C(i,j).

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (array): la matriz de cofactores.
    '''
    filas = A.shape[0]
    columnas = A.shape[1]
    B = np.zeros_like(A)
    for i in range(filas):
        for j in range(columnas):
            B[i, j] = _cofactor(A, i+1, j+1)
    return B

def _matriz_adjunta(A: ndarray):
    ''' Define la matriz de adjunta, que se obtiene de la transpuesta de la matriz de cofactores.

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (array): la matriz adjunta.
    '''
    B = np.transpose(_matriz_de_cofactores(A))
    return B

def _determinante(A: list) -> float:
    ''' Define el determinante de una matriz A dada.

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (float): el determinante de la matriz.
    '''
    filas = A.shape[0]
    columnas = A.shape[1]
    if (filas != columnas):
        return f"ERROR: Determinante no esta definido para matrices {filas}x{columnas}"
    if (filas == 1):
        return A[0][0]
    sum = 0
    for i in range(filas):
        C = pow(-1, i+2) * A[i][0]
        M = _menor(A, i+1, 1)
        D = C * _determinante(M)
        sum = D + sum
    return sum

# Definiciones de nuevas funciones con la palabra clave Lambda
cof = lambda A: _matriz_de_cofactores(A)
adj = lambda A: _matriz_adjunta(A)
det = lambda A: _determinante(A)
inv = lambda A: (1/det(A))*adj(A)

# Asignación y llamado a funciones declaradas en el script
def _calcular_matrices(A):
    ''' Imprimir en pantalla una demostración de la funcionalidad.
    '''
    # funciones definidas en el script
    Matriz_Cofactores = cof(A)
    Matriz_Adjunta = adj(A)
    Matriz_Inversa = inv(A)
    return Matriz_Cofactores, Matriz_Adjunta, Matriz_Inversa

# Función que no retorna ningún valor
def demo(Array):
    ''' Imprimir en pantalla una demostración de la funcionalidad.
    '''
    M13 = _menor(Array, 1, 3)
    C13 = _cofactor(Array, 1, 3)

    # Asignación multiple usando una función que retorna múltiples valores
    Cof, Adj, Inv = _calcular_matrices(Array)

    # función incorporada en NumPy para comparar
    Inv_np = np.linalg.inv(Array)

    # Salida Estándar
    print(f"--------------------------------------------------------")
    print(f"Dada una matriz de entrada A en R³:")
    print(f">>>")
    print(f"\n{Array}\n")
    print(f"--------------------------------------------------------")
    print(f"Se tiene que cada matriz  Ă(i,j) se define como el menor")
    print(f"que resulta  de eliminar  la i-ésima fila  y  la j-ésima")
    print(f"columna, por ejemplo A₁₃ es:")
    print(f">>>")
    print(f"\n{M13}\n")
    print(f"--------------------------------------------------------")
    print(f"El cofactor se define como C(i,j) =(-1)²det(Ă(i,j)), por")
    print(f"ejemplo C₁₃ es igual a {C13};  finalmente se tiene que la")
    print(f"matriz de  cofactores de A en R³ definida como Cof(A) es")
    print(f"aquella  que resulta de  reemplazar cada elemento por su")
    print(f"cofactor:")
    print(f">>>")
    print(f"\n{Cof}\n")
    print(f"--------------------------------------------------------")
    print(f"Y la matriz adjunta  definida  como la transpuesta de la")
    print(f"matriz de cofactores es Adj(A) =trs(Cof(A)) que resulta:")
    print(f">>>")
    print(f"\n{Adj}\n")
    print(f"--------------------------------------------------------")
    print(f"Finalmente dado que inv(A) = (1/det(A)).adj(A) se  tiene")
    print(f"que la inversa definida en el script y la de NumPy son:")
    print(f">>>")
    print(f"\n{Inv}\n")
    print(f">>>")
    print(f"\n{Inv_np}\n")

# EJECUCIÓN:
# Definición de un vector de prueba en R³ usando un tipo de dato de NumPy
Array_de_prueba = np.array([[1, 1, 1], [-1, 2, -3], [3, 0, 2]])
demo(Array_de_prueba)


import numpy as np

def spherical_to_cartesian(inclination_radians: np.ndarray, azimuth_radians: np.ndarray, radius: np.ndarray = None):
    """convert spherical to 3D cartesian coordinates

    spherical coordinates have to be specified as separate ndarrays of inclination angle (0 radians in zenith, pi
    radians in nadir) and optionally a radial coordinate (which is otherwise implicitly assumed to be 1 for unit
    vectors);

    input array dimensions are arbitrary but must match between the different arrays;
    returns tuple of cartesian coordinates in separate x, y and z arrays"""

    if radius is None:
        radius = 1

    sin_theta = np.sin(inclination_radians)
    x = radius * sin_theta * np.cos(azimuth_radians)
    y = radius * sin_theta * np.sin(azimuth_radians)
    z = radius * np.cos(inclination_radians)
    return x, y, z

def cartesian_to_spherical(x: np.ndarray, y: np.ndarray, z: np.ndarray, normalized: bool = False, with_radius: bool = False):
    """convert (optionally normalized) 3D cartesian to spherical coordinates

    spherical coordinates follow the inclination-azimuth convention: inclination angle theta goes from 0 radians in the
    zenith (north pole) to pi radians at nadir (south pole), azimuth is circular from 0 to 2 pi radians, with 0 in x
    direction

    inputs are numpy ndarrays with arbitrary but matching dimensions; if normalized == True, inputs are assumed to be of
    unit length; with_radius determines whether only inclination and azimuth, or if also the radial coordinate should be
    returned in case the inputs are not normalized
    returns spherical coordinates in separate arrays: inclination, azimuth [and radius, when with_radius == True]
    """

    if not normalized:
        radius = np.sqrt(x**2 + y**2 + z**2)
        inv_radius = 1 / radius
        x *= inv_radius
        y *= inv_radius
        z *= inv_radius
    else:
        radius = np.ones_like(x)

    inclination = np.arccos(z)
    azimuth = np.arctan2(y, x)

    if with_radius:
        return inclination, azimuth, radius
    else:
        return inclination, azimuth

def cartesian_to_plane_stereographic(x: np.ndarray, y: np.ndarray, z: np.ndarray):
    """convert 3D cartesian vectors on the unit sphere to 2D coordinates by transforming as:

    x_2d = x / (1 + z)
    y_2dr = y / (1 + z)

    this corresponds to a stereographic projection from the lower pole (nadir), which maps the upper hemisphere (z > 0)
    inside the unit circle in the x-y-plane

    inputs numpy ndarrays with arbitrary but matching dimensions;
    returns tuple x_par, y_par with same dimensions as inputs"""

    inv_denom = 1. / (1 + z)
    return x * inv_denom, y * inv_denom

def plane_to_cartesian_stereographic(x_2d: np.ndarray, y_2d: np.ndarray):
    """transform 2D coordinates from the x-y-plane to 3D cartesian coordinates on the unit sphere by means of a
    stereographic projection from the lower pole, which maps 2D points from within the x-y unit circle to the upper
    hemisphere

    inputs: x_2d, y_2d: np.ndarrays with arbitrary but matching dimensions
    returns 3-tuple of np.ndarrays x, y, z, where each array has the same shape as the inputs"""

    len_sqr = x_2d ** 2 + y_2d ** 2
    x = 2 * x_2d / (len_sqr + 1)
    y = 2 * y_2d / (len_sqr + 1)
    z = (1 - len_sqr) / (1 + len_sqr)

    return x, y, z