#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Dec  6 04:54:06 2018

Problem was stated as follows:
    A company must reduce prices by 10%. Profit will be modelled as the charge
    y minus a penalty P(t,dotY) (where dotY is the first derivative of y wr to t
P is given by:
        P(t,dotY) = alpha*dotY**2 + beta*(t**2 - 1)*dotY**3
    We find y that maximizes profif by minimizing:
        S(y) = integrate(P-y,0,1) w.r. to t

    The lagrangian for this problem is:
        L(t,y,dotY) = P(t,dotY) - y                         ...(1)

    with boundary conditions:
        y(0) = 1 and y(1) = 0.9                              ...(2)

for S to be minimal, we calculate Euler-Lagrange equation (e/l-eq) and equalize
it to 0:

    2*alpha*doubleDotY + 6*beta*(t*dotY**2 + (t**2-1)*dotY*doubleDotY) = 0

        where doubleDotY refers to y's second derivative w.r. to t

We explain Differential Transform Method as follows:
    objetive is to approximate y(t) using a it's taylor polynomials.

    for a function w(t), we define for every k>=0, W(k) as the kth derivative of
    w(t) w.r. to t evaluated in t0=0 and devided by k!

    We'll call W(k) the k-th differential transform of w(t)

    it follows that:
        w(t) is approximated by W(0) + W(1)*t + W(2)*t**2 + ... + W(k)*t**k

    using (e/l)-eq, we can derive a recursive method to obtain Y(k) in terms
    of Y(1) (which we'll call gamma)
    This is implemented within DTM file. Lagrangian and euler-lagrange methods
    for this particular set of problems are implemented in PoliticalPricing file.

For this model to approximate an optimal solution. Function y is assumed to be analytic within 0 and 1

@author: Amir
"""

import matplotlib.pyplot as plt
import numpy as np
import DTM
import PoliticalPricing as pp

domain = np.linspace(0,1,30)
k = 10       #Degree of polynomial

#case1 alpha = beta = 0
alpha = 5.0
beta = 5.0

def line(t):
    """
    A straight line connecting (0,1) and (1,0.9)
    """
    return 1.0 - 0.1*t

#fits the paramters of the taylor polynomial
gamma = DTM.findGammaNumerical(k,alpha,beta)
Ys = DTM.diffTrans(k,alpha,beta,False,gamma)

def myModel(t,k=k, Ys=Ys):
    return DTM.model(t,k=k,Ys=Ys)

plt.figure(1)
plt.plot(domain,myModel(domain))
plt.plot(domain,line(domain))
plt.title("Fitted Model vs Straight Line")

plt.figure(2)
plt.plot(domain, pp.eulerEquation(domain,myModel,alpha,beta), label ="My Model")
plt.plot(domain, pp.eulerEquation(domain,line,alpha,beta), label = "Straight line")
plt.plot(domain,[0 for i in domain])
plt.title("Euler-Lagrange equation when alpha=beta=5")
plt.legend(loc = "best")
plt.legend()

sModel = pp.integLag(myModel,alpha,beta)[0]
sLine = pp.integLag(line,alpha,beta)[0]

print("When alpha and beta are both equal to 5.")
print("The integral of the lagrangian in our model is: ")
print(sModel)
print("the integral of the lagrangian using the straight line is: " )
print(sLine)
print("\n")

#case2: alpha != beta

alpha = 7.0/4.0
beta = 5.0

#fits the paramters of the taylor polynomial
#for k in [5,10,15,20,50]:
for k in [5,7]:
    gamma = DTM.findGammaNumerical(k,alpha,beta)
    #gamma = -0.061787462699583774
#    print(gamma)
    Ys = DTM.diffTrans(k,alpha,beta,False,gamma)
#    print(Ys)

    def myModel(t,Ys=Ys):
        """
        The model fitted
        """
        return DTM.model(t, Ys=Ys)

#    print(myModel(1))

    plt.figure(k+2)
    plt.plot(domain,myModel(domain))
    plt.plot(domain,line(domain))
    plt.title("Fitted Model vs Straight Line when alpha != beta in k=" + str(k))

    plt.figure(k+3)
    plt.plot(domain, pp.eulerEquation(domain,myModel,alpha,beta), label = "My Model")
    plt.plot(domain, pp.eulerEquation(domain,line,alpha,beta), label = "Straight line")
    plt.plot(domain,[0 for i in domain])
    plt.title("Euler-Lagrange equation when alpha=7/4 and beta=5")
    plt.legend()

    sModel = pp.integLag(myModel,alpha,beta)[0]
    sLine = pp.integLag(line,alpha,beta)[0]

    print("When alpha equals 7/4 and beta equals 5.")
    print("The integral of the lagrangian in our model is: ")
    print(sModel)
    print("the integral of the lagrangian using the straight line is: " )
    print(sLine)
    print("\n")


"""Simple linear regression implementation.

Author: Karol Oleszek 2019

Model symbols:
x     - explanatory variable
y     - response variable
y^    - predicted response
b0,b1 - estimated regression coefficients
e     - estimation error
y_    - mean y
x_    - mean x
n     - observations count
s     - residual standard error
mse   - mean square error

Model:
y^ = b0 + b1 * x
e = y - y^

Regression:
b0 = y_ - b1 * x_

     nE xy - ExEy
b1 = ---------------
     nEx^2 - (Ex)^2

"""

import math
import numpy as np

class SimpleLinearRegressionModel:
    """Simple linear regression model.
       Features model fitting and predictor.
    """
    def __init__(self, x, y):
        """Initialize model.
        Args:
            x - explanatory variable vector.
            y - response variable vector.
        """
        self.x = np.array(x)
        self.y = np.array(y)
        self.n = len(x)
        self.b0 = 0
        self.b1 = 0
        self.s = 0
        self.mse = 0
        if len(x) != len(y):
            print("Length of response vector doesn't match length of explanatory variable vector")

    def fit(self):
        """Fit model."""
        print("Fitting model...")
        self.b1 = (self.n * sum(self.x * self.y) - sum(self.x) * sum(self.y)) / \
                  (self.n * sum(self.x * self.x) - sum(self.x) ** 2)
        self.b0 = self.y.mean() - self.b1 * self.x.mean()
        print("Model fitted with coefficients: b0 = {0}, b1 = {1}".format(self.b0, self.b1))
        print("Calculating model errors...")
        pred = np.vectorize(self.predict)
        self.mse = (sum(self.y - pred(self.x)) ** 2) / (self.n - 2)
        self.s = math.sqrt(self.mse)
        print("Mean square error: {0}".format(self.mse))
        print("Residual standard error: {0}".format(self.s))

    def predict(self, x_var):
        return self.b0 + x_var * self.b1

if __name__ == "__main__":
    print("Simple linear regression demo.")
    reg_mod = SimpleLinearRegressionModel([1, 2, 332], [3.12, 2.324, 1.2134])
    reg_mod.fit()
    print("Trying predictions:")
    print(1, reg_mod.predict(1))
    print(2, reg_mod.predict(2))
    print(332, reg_mod.predict(332))


# pprint()用于在控制台上漂亮地打印输出。 LaTeX 可以达到最佳效果，例如 在 Jupyter 笔记本中。

from sympy import pprint, Symbol, exp, sqrt
from sympy import init_printing

init_printing(use_unicode=True)

x = Symbol('x')

a = sqrt(2)
pprint(a)
print(a)

print("------------------------")

c = (exp(x) ** 2) / 2
pprint(c)
print(c)


import numpy as np

def mean_normalize(X):
    return X - np.mean(X, axis=0)

def standardize(X, with_mean=True, with_std=True):
    if with_mean:
        X = mean_normalize(X)
    if with_std:
        X = X / np.std(X, axis=0)
    return X

def minmax_normailze(X):
    min_ = np.min(X, axis=0)
    max_ = np.max(X, axis=0)
    return (X - min_) / (max_ - min_)

def maxabs_normalize(X):
    min_ = np.min(X, axis=0)
    max_ = np.max(X, axis=0)
    maxabs = np.maximum(min_, max_)
    return X / maxabs


# Machine Learning Online Class
#  Exercise 7 | Principle Component Analysis and K-Means Clustering
#
#  Instructions
#  ------------
#
#  This file contains code that helps you get started on the
#  exercise. You will need to complete the following functions:
#
#     pca.m
#     projectData.m
#     recoverData.m
#     computeCentroids.m
#     findClosestCentroids.m
#     kMeansInitCentroids.m
#
#  For this exercise, you will not need to change any code in this file,
#  or any other files other than those mentioned above.
#

import numpy
import matplotlib.pyplot as plt

from skimage.io import imread
from scipy.io import loadmat

import kmeans_pca_funcs

# ================== Part 1: Load Example Dataset  ===================
print("Visualizing example dataset for PCA.\n\n")

#  The following command loads the dataset. You should now have the variable X in your environment
data = loadmat("./ex7data1.mat")
x_data = data['X']
num_examples, num_features = x_data.shape
print("Ex.7.2 PCA #training examples:", num_examples, "#features:", num_features, "\n")

#  Visualize the example dataset
plt.figure(dpi=120)
plt.title("PCA dataset 1")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.scatter(x_data[:, 0], x_data[:, 1], marker='o', s=16, linewidths=1.0, color='blue')
plt.xlim(0.5, 6.5)
plt.ylim(2, 8)
plt.gca().set_aspect('equal')
plt.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
plt.tight_layout()
plt.show()

# =============== Part 2: Principal Component Analysis ===============
#  Before running PCA, it is important to first normalize X
x_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(x_data)
mat_U, mat_S = kmeans_pca_funcs.pca(x_norm)  # Run PCA

#  Draw the eigenvectors centered at mean of data. These lines show the directions of maximum variations in the dataset.
fig, ax = plt.subplots()
plt.title("Eigenvectors")
ax.scatter(x_data[:, 0], x_data[:, 1], marker='o', s=16, linewidths=1.0, color='blue')

for i in range(num_features):
    ax.arrow(mu[0], mu[1], 1.5*mat_S[i]*mat_U[0, i], 1.5*mat_S[i]*mat_U[1, i],
             head_width=0.25, head_length=0.2, fc='k', ec='k', lw=2, zorder=1000)

ax.axis([0.5, 6.5, 2, 8])
ax.set_aspect('equal')
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
fig.tight_layout()
fig.show()

print("Computed eignevector = [{:.6f} {:.6f}]".format(mat_U[0, 0], mat_U[1, 0]))
print("Expected eigenvector = [-0.707107 -0.707107]")

# =================== Part 3: Dimension Reduction ===================
#  Project the data onto K = 1 dimension
ndims_K = 1
projection_Z = kmeans_pca_funcs.project_data(x_norm, mat_U, ndims_K)
print("\nComputed projection value: {:.6f}".format(projection_Z[0, 0]))
print("Expected projection value: 1.481274")

recovered_X = kmeans_pca_funcs.recover_data(projection_Z, mat_U, ndims_K)
print("\nComputed recovered value of the first example: [{:.6f} {:.6f}]".format(recovered_X[0, 0], recovered_X[0, 1]))
print("Expected recovered value of the first example: [-1.047419 -1.047419]")

#  Plot the normalized dataset (returned from featureNormalize)
fig, ax = plt.subplots(figsize=(5, 5), dpi=120)
plt.title("Recovered data")
ax.scatter(x_norm[:, 0], x_norm[:, 1], marker='o', s=16, linewidths=1.0, color='blue')
ax.set_aspect('equal')
plt.axis([-3, 2.75, -3, 2.75])
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)

# Draw lines connecting the projected points to the original points
ax.scatter(recovered_X[:, 0], recovered_X[:, 1], marker='o', s=16, linewidths=1.0, color='red')
for xnorm, xrec in zip(x_norm, recovered_X):
    ax.plot([xnorm[0], xrec[0]], [xnorm[1], xrec[1]], '--k', lw=1)

fig.tight_layout()
fig.show()

# =============== Part 4: Loading and Visualizing Face Data =============
#  Load Face dataset
data = loadmat('./ex7faces.mat')
x_faces = data['X']
num_examples, num_features = x_faces.shape
print("\nEx.7.3 Faces PCA #training examples:", num_examples, "#features:", num_features, "\n")

#  Display the first 100 faces in the dataset
kmeans_pca_funcs.display_data(x_faces[:100, :], figsize=(8, 8))

# =========== Part 5: PCA on Face Data: Eigenfaces  ===================
# normalize X by subtracting the mean value from each feature
x_faces_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(x_faces)

faces_U, faces_S = kmeans_pca_funcs.pca(x_faces_norm)  # Run PCA

# Visualize the top 36 eigenvectors found
kmeans_pca_funcs.display_data(numpy.transpose(faces_U[:, :36]), figsize=(8, 8))

# ============= Part 6: Dimension Reduction for Faces =================
#  Project images to the eigen space using the top k eigenvectors if you are applying a machine learning algorithm
ndims_K = 100

faces_Z = kmeans_pca_funcs.project_data(x_faces_norm, faces_U, ndims_K)

print("The projected data Z has a shape of:", faces_Z.shape)

# # ==== Part 7: Visualization of Faces after PCA Dimension Reduction ====
#  Project images to the eigen space using the top K eigen vectors and visualize only using those K dimensions
#  Compare to the original input, which is also displayed
ndims_K = 100
recovered_faces_X = kmeans_pca_funcs.recover_data(faces_Z, faces_U, ndims_K)

# Display normalized data
kmeans_pca_funcs.display_data(x_faces_norm[:100, :], figsize=(6, 6))
plt.gcf().suptitle('Original faces')

# Display reconstructed data from only k eigenfaces
kmeans_pca_funcs.display_data(recovered_faces_X[:100, :], figsize=(6, 6))
plt.gcf().suptitle('Recovered faces')

# === Part 8(a): Optional (ungraded) Exercise: PCA for Visualization ===

# # If imread does not work for you, you can try instead
img = imread("./bird_small.png")
img = img/255
img_2D = img.reshape(-1, 3)

# perform the K-means clustering again here
num_centroids_K = 16
max_iters = 10
initial_centroids = kmeans_pca_funcs.kmeans_init_centroids(img_2D, num_centroids_K)
centroids, idx = kmeans_pca_funcs.run_kmeans(
    kmeans_pca_funcs.find_closest_centroids, kmeans_pca_funcs.compute_centroids, img_2D, initial_centroids, max_iters, False)

#  Sample 1000 random indexes (since working with all the data is too expensive. If you have a fast computer, you may increase this.
sel = numpy.random.choice(img_2D.shape[0], size=1000)

fig = plt.figure(figsize=(6, 6), dpi=120)
ax = fig.add_subplot(111, projection='3d')

ax.scatter(img_2D[sel, 0], img_2D[sel, 1], img_2D[sel, 2], cmap='rainbow', c=idx[sel], s=8**2)
ax.set_title('Pixel dataset plotted in 3D.\nColor shows centroid memberships')
fig.show()

# === Part 8(b): Optional (ungraded) Exercise: PCA for Visualization ===
# Subtract the mean to use PCA
img_2D_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(img_2D)

# PCA and project the data to 2D
U, S = kmeans_pca_funcs.pca(img_2D_norm)
Z = kmeans_pca_funcs.project_data(img_2D_norm, U, 2)

fig = plt.figure(figsize=(6, 6), dpi=120)
ax = fig.add_subplot(111)

ax.scatter(Z[sel, 0], Z[sel, 1], cmap='rainbow', c=idx[sel], s=64)
ax.set_title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction')
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
fig.show()

print("\n\n\nDone\n\n\n")


# %%
import math
import operator

import numpy as np
from IPython.display import display
from toolz import reduce

# %% [markdown]
# # Computation in NumPy
#
# Computations can be performed of tensors with compatible dimensions:

# %%
v1 = np.array([2, 4, 5])
v2 = np.arange(3.0)

# %%
v1 + v2

# %%
v1 * v2

# %%
v1 @ v2

# %%
v1.dot(v2)

# %%
m1 = np.arange(12).reshape(3, 4)
m2 = np.ones((3, 4))
m3 = np.array([[1, 3], [5, 7], [2, 4], [6, 8]])
print("m1.shape:", m1.shape, "m2.shape:", m2.shape, "m3.shape:", m3.shape)

# %%
m1 + m2

# %%
# m1 + m3

# %%
m1 @ m3

# %% [markdown]
# ## Boolean Operations

# %%
v1 = np.arange(4)
v2 = np.ones(4)

# %%
v1 == v2  # noqa B015

# %%
v1 <= v2  # noqa B015

# %%
# if v1 == v2: print("Done")

# %%
equals = v1 == v2
equals

# %%
equals.all()

# %%
equals.any()

# %%
if equals.any():
    print("Done")

# %% [markdown]
# ## Broadcasting (Part 1)
#
# Most operations in NumPy can be used with scalars as well:

# %%
v1 = np.arange(8)
v1

# %%
v1 + 5

# %%
3 + v1

# %%
v1 * 2

# %%
v1 ** 2

# %%
2 ** v1

# %%
v1 > 5  # noqa B015

# %% [markdown]
# ## Minimum, Maximum, Sum, ...

# %%
np.set_printoptions(precision=2)

# %%
rng = np.random.default_rng(42)
vec = rng.random(10)
vec

# %%
vec.max()

# %%
vec.argmax()

# %%
vec[vec.argmax()]

# %%
vec.min()

# %%
vec.argmin()

# %%
rng = np.random.default_rng(42)
arr = rng.random((3, 5))

# %%
arr.max()

# %%
arr.argmax()

# %%
arr.min()

# %%
arr.argmin()

# %%
arr.reshape(-1)[arr.argmin()]

# %%
# arr[np.unravel_index(arr.argmin(), arr.shape)]

# %%
arr

# %%
arr.sum()

# %%
arr.sum(axis=0)

# %%
arr.sum(axis=1)

# %%
arr.mean()

# %%
arr.mean(axis=0)

# %%
arr.mean(axis=1)

# %%
v1 = np.arange(2)
v2 = np.linspace(1, 3, 3)

# %%
np.concatenate([v1, v2])

# %%
m1 = np.arange(12).reshape(3, 4)
m2 = np.arange(12, 24).reshape(3, 4)

# %%
m1

# %%
m2

# %%
np.concatenate([m1, m2])

# %%
np.concatenate([m1, m2], axis=1)

# %%
m3 = np.arange(12, 15).reshape(3, -1)
m3

# %%
# np.concatenate([m1, m3])

# %%
np.concatenate([m1, m3], axis=1)

# %% [markdown]
# ## Indices for NumPy Arrays

# %%
vec = np.arange(10)

# %%
vec

# %%
vec[3]

# %%
vec[3:8]

# %%
vec[-1]

# %%
arr = np.arange(24).reshape(4, 6)

# %%
arr

# %%
arr[1]

# %%
arr[1][2]

# %%
arr[1, 2]

# %%
arr

# %%
arr[1:3]

# %%
arr[1:3][2:4]

# %%
arr[1:3, 2:4]

# %%
arr[:, 2:4]

# %%
# Danger!
arr[:2:4]

# %%
arr[:, 1:6:2]

# %% [markdown]
# ## Slices and Modifications
#
# It's possible to apply operations to slices. Modification of slices *changes
# the underlying array.*

# %%
arr = np.ones((3, 3))
arr

# %%
arr[1:, 1:] = 2.0

# %%
arr

# %%
lst = [1, 2, 3]
vec = np.array([1, 2, 3])

# %%
lst[:] = [99]

# %%
lst

# %%
vec[:] = [99]

# %%
vec

# %%
vec[:] = 11
vec

# %% [markdown]
# ## Danger!
# Don't use the `lst[:]` Idiom!

# %%
lst1 = list(range(10))
lst2 = lst1[:]
lst1[:] = [22] * 10
print(lst1)
print(lst2)

# %%
vec1 = np.arange(10)
vec2 = vec1[:]
vec1[:] = 22
print(vec1)
print(vec2)

# %%
vec1 = np.arange(10)
vec2 = vec1.copy()
vec1[:] = 22
print(vec1)
print(vec2)

# %% [markdown]
#
# Similar considerations hold for reshaped arrays:

# %%
vec = np.arange(4)
arr = vec.reshape(2, 2)
arr

# %%
arr[1, 1] = 10
vec[0] = 20
arr

# %%
vec

# %% [markdown]
# ### Boolean Operations on NumPy Arrays

# %%
bool_vec = np.array([True, False, True, False, True])

# %%
neg_vec = np.logical_not(bool_vec)
neg_vec

# %%
np.logical_and(bool_vec, neg_vec)

# %%
~bool_vec

# %%
bool_vec & neg_vec

# %%
bool_vec | neg_vec

# %% [markdown]
# ## Conditional Selection
#
# You can use a NumPy array with Boolean values as index value, if it has the
# same shape as the "value array". This will select all elements of the value
# array for which the index evaluates to true.

# %%
vec = np.arange(9)
bool_vec = vec % 3 == 0
print(vec)
print(bool_vec)

# %%
vec[bool_vec]

# %%
arr = np.arange(8).reshape(2, 4)
bool_arr = arr % 2 == 0
bool_arr

# %%
arr[bool_arr]

# %%
# Error!
# arr[bool_arr.reshape(-1)]

# %%
vec[vec % 2 > 0]

# %%
arr[arr < 5]

# %%
arr = np.arange(30).reshape(6, 5)
arr

# %%
arr[:, 1]

# %%
arr[:, 1] % 2 == 0  # noqa B015

# %%
arr[arr[:, 1] % 2 == 0]

# %%
arr[arr[:, 1] % 2 == 1]

# %% [markdown]
# ## Universal NumPy Functions
#
# NumPy offers a wealth of universal functions that work on NumPy arrays, lists,
# and often numbers

# %%
vec1 = rng.random(5)
vec2 = rng.random(5)
display(vec1)

list1 = list(vec1)
list2 = list(vec2)
display(list1)

matrix = np.arange(6).reshape(2, 3)
list_matrix = [[0, 1, 2], [3, 4, 5]]
display(matrix)
display(list_matrix)

# %%
vec1.sum()

# %%
# list1.sum()

# %%
reduce(operator.add, list1, 0)

# %%
reduce(operator.add, vec1, 0)

# %%
np.sum(vec1)

# %%
np.sum(list1)

# %%
np.sum(matrix)

# %%
np.sum(list_matrix)

# %%
np.sum(123)

# %%
np.sum(list_matrix, axis=0)

# %%
np.sin(vec1)

# %%
np.sin(list1)

# %%
np.sin(matrix)

# %%
np.sin(list_matrix)

# %%
np.sin(math.pi)

# %%
np.mean(vec1)

# %%
np.median(vec1)

# %%
np.std(vec1)

# %%
np.greater(vec1, vec2)

# %%
np.greater(list1, list2)

# %%
np.greater(vec1, list2)

# %%
display(vec1)
display(vec2)

# %%
np.maximum(vec1, vec2)

# %%
np.maximum(list1, list2)

# %%
np.maximum(list1, vec2)

# %% [markdown]
#
# A complete list of universal functions is
# [here](https://numpy.org/doc/stable/reference/ufuncs.html).

# %% [markdown]
#
# ## Broadcasting (Part 2)

# %%
arr = np.arange(16).reshape(2, 2, 4)
print(f"arr.shape: {arr.shape}")
arr

# %%
arr * arr

# %%
3 * arr

# %%
vec1 = np.arange(3)
display(vec1)
print(f"vec1.shape: {vec1.shape}")
print(f"arr.shape:  {arr.shape}")
# arr * vec1

# %%
vec2 = np.arange(4)
display(arr)
display(vec2)
print(f"vec2.shape: {vec2.shape}")
print(f"arr.shape:  {arr.shape}")
arr * vec2

# %% [markdown]
#
# ### Rules for broadcasting:
#
# When performing an operation on `a` and `b`:
#
# - Axes (shapes) of `a` and `b` are compared from right to left
#
# - If `a` and `b` have the same length for an axis, they are compatible
#
# - If either `a` or `b` has length 1 for an axis, it is conceputally repeated
#   along this axis to fit the other array
#
# - If `a` and `b` have different lengths along an axis and neither has length 1
#   they are incompatible
#
# - The array with lower rank is treated as if it has rank 1 for the missing
#   axes, the missing axes are appended on the left

# %%
def ones(shape):
    return np.ones(shape, dtype=np.int32)

# %%
def tensor(shape):
    from functools import reduce
    from operator import mul

    size = reduce(mul, shape, 1)
    return np.arange(1, size + 1).reshape(*shape)

# %%
tensor((2, 3))

# %%
tensor((1, 3))

# %%
tensor((2, 1))

# %%
ones((1, 3)) + tensor((2, 1))

# %%
np.concatenate([tensor((2, 1))] * 3, axis=1)

# %%
ones((1, 3))

# %%
np.concatenate([ones((1, 3))] * 2, axis=0)

# %%
ones((1, 3)) + tensor((2, 1))

# %%
tensor((1, 3)) + ones((2, 1))

# %%
tensor((1, 3)) + tensor((2, 1))

# %%
tensor((2, 3, 4))

# %%
tensor((2, 3, 1))

# %%
tensor((2, 1, 4))

# %%
ones((2, 3, 1)) + tensor((2, 1, 4))

# %%
ones((2, 3, 1))

# %%
np.concatenate([ones((2, 3, 1))] * 4, axis=2)

# %%
tensor((2, 1, 4))

# %%
np.concatenate([tensor((2, 1, 4))] * 3, axis=1)

# %%
ones((2, 3, 1)) + tensor((2, 1, 4))

# %%
tensor((2, 3, 1)) + ones((2, 1, 4))

# %%
tensor((2, 3, 1)) + tensor((2, 1, 4))

# %%
tensor((3, 1)) + tensor((2, 1, 4))

# %%
tensor((3, 1))

# %%
tmp1 = np.concatenate([tensor((3, 1))] * 4, axis=1)
print("Shape:", tmp1.shape)
tmp1

# %%
tmp2 = tmp1.reshape(1, 3, 4)
print("Shape:", tmp2.shape)
tmp2

# %%
tmp3 = np.concatenate([tmp2] * 2, axis=0)
print("Shape:", tmp3.shape)
tmp3

# %%
tensor((2, 1, 4))

# %%
tmp4 = np.concatenate([tensor((2, 1, 4))] * 3, axis=1)
print("Shape:", tmp4.shape)
tmp4

# %%
display(tmp3)
display(tmp4)

# %%
tmp3 + tmp4

# %%


import numpy as np
import matplotlib.pyplot as plt

#=============================================================================
# Basic linear algebra operations
#=============================================================================

# Defining matrices

A = np.random.rand(3, 2) # 3x2 random matrix
print("A =\n" + str(A))

B = np.array([[4, -3, 1], [9, 3, 2], [-1, 0, 6]]) # 3x3 mat def elementwise
print("\nB =\n" + str(B))

C = np.eye(2) # 2x2 identity matrix
print("\nC =\n" + str(C))

D = np.zeros((3, 3)) # 3x3 matrix filled using a loop
for i in range(3):
    for j in range(3):
        D[i][j] = 3*i + j + 1 # Note that Python indices begin at 0
print("\nD =\n" + str(D))

E = np.block([[A, B], [C, A.T]]) # 5x5 matrix defined as a block matrix
print("\nE =\n" + str(E))

# Basic matrix operations

print("\ndet(B) =")
print(np.linalg.det(B)) # Matrix determinant

print("\ninv(B) =")
print(np.linalg.inv(B)) # Matrix inverse

print("\nB' =")
print(B.T) # Matrix transpose

print("\n3B =")
print(3*B) # Scalar multiplication

print("\nBD =")
print(B@D) # Matrix-matrix product

print("\nB.D =")
print(B*D) # Elementwise matrix-matrix product

# Defining vectors

# Note that NumPy does not distinguish between column and row vectors, so
# anything that specifically requires a column or row vector requires
# first converting the vector into a 2D array.

u = np.random.rand(3) # Length 3 random vector
print("\nu =\n" + str(u))

v = np.array([3, -4, 8, 0, -1, 2]) # Length 6 vector defined elementwise
print("\nv =\n" + str(v))

w = np.zeros(5) # Redefining a range of vector values
w[1:4] = 1
print("\nw =\n" + str(w))

x = np.linspace(0, 2*np.pi, 101) # Vector of equally-spaced values on [0,2pi]
print("\nx =\n" + str(x[:11]) + "\n...\n" + str(x[-10:]))

y = np.array([i for i in range(10, -2, -2)]) # Values defined by a range
print("\ny =\n" + str(y))

# Basic vector operations

print("\n2v =")
print(2*v) # Scalar multiplication

print("\nv'y =")
print(np.inner(v, y)) # Inner product

print("\nvy' =")
print(np.outer(v, y)) # Outer product

print("\nv.y =")
print(v*y) # Elementwise vector-vector product

print("\nmin(v) =")
print(min(v)) # Minimum value

print("\nmax(v) =")
print(max(v)) # Maximum value

print("\nmean(v) =")
print(np.mean(v)) # Average value

print("\n||v||_2 =")
print(np.linalg.norm(v)) # Euclidean norm

print("\n||v||_1 =")
print(np.linalg.norm(v, 1)) # Taxicab norm

print("\n||v||_inf =")
print(np.linalg.norm(v, np.inf)) # Chebyshev norm

# Eigenvalues

# Note that NumPy's eig() function always returns two arrays: A 2D array of
# eigenvectors, and a 1D array of eigenvalues, respectively. The separate
# eigvals() function returns only the eigenvalues.

ew = np.linalg.eigvals(B) # Vector of eigenvalues
print("\nEigenvalues of B:\n" + str(ew))

ew, ev = np.linalg.eig(B) # Matrices of eigenvalues and eigenvectors
print("\nEigenvectors of B:\n" + str(ev))

print("\nX Lambda X^(-1) =")
print(ev @ np.diag(ew) @ np.linalg.inv(ev)) # Eigendecomposition of B

# Linear systems

v = v[:3]
z = np.linalg.solve(B, v) # Solve the linear system Bz = v
print("\nz =\n" + str(z))

print("\nBz =")
print(B@z) # Matrix-vector product

#=============================================================================
# Basic programming structures
#=============================================================================

# Conditionals

if np.linalg.det(B) > 0:
    print("\ndet(B) is positive")
elif np.linalg.det(B) < 0:
    print("\ndet(B) is negative")
else:
    print("\ndet(B) is zero")

# For loops

s = np.zeros(10)
for i in range(len(s)):
    s[i] = np.sqrt(i+1) # Square root
print("\ns =\n" + str(s))

# While loops

p = 0
while 2**p < 1000:
    p += 1 # Combined assignment
print("\np =\n" + str(p))

# Functions

def square(x):
    return x**2

print("\nsquare(y) =\n" + str(square(y)))

#=============================================================================
# Basic plotting
#=============================================================================

# Single 2D plot

# In Pyplot, multiple series can be plotted on the same axis either by passing
# all of them to the plot() function, or plotting them separately before using
# the show() function.
# Series labels in the legend are most easily added by defining them in the
# 'label' keyword argument of the respective plot() function before calling the
# legend() function.

y1 = np.cos(x) # Generate function values on [0,2pi]
y2 = np.sin(x)

plt.figure() # Create a new figure window

plt.plot(x, y1, 'r', label="cos(x)") # Plot a red solid line
plt.plot(x, y2, 'b.', label="sin(x)") # Plot a blue dotted line
plt.title("Example 1") # Figure title
plt.xlabel("x") # x-axis label
plt.ylabel("y") # y-axis label
plt.legend() # Plot legend (automatically adopts legend keywords from plots)

# Stacked 2D plots

plt.figure()
plt.subplot(2, 1, 1) # Target first tile in a 2x1 layout
plt.plot(x, y1, 'r') # Plot first function
plt.title("Example 2") # Add title above first subplot
plt.subplot(2, 1, 2) # Target second tile in a 2x1 layout
plt.plot(x, y2, 'b.') # Plot second function

# 3D surface plots

# Surface plots are usually generated with the help of NumPy's meshgrid()
# function, which returns a pair of matrices: One whose rows are identical
# copies of the x-values, and one whose columns are identical copies of the
# y-values. The mesh of z-coordinates can then be computed by evaluating an
# elementwise function with these two functions as the x- and y-values.

[X, Y] = np.meshgrid(np.linspace(-4,4,41), np.linspace(-4,4,41)) # Grids

def f(x, y): # Define a function for the surface
	return 100*np.cos(x) + 1 - 10*y**2 + y**4

Z = f(X, Y) # Generate z-coordinates

plt.figure()
ax = plt.axes(projection='3d') # Get handle for 3D axis in current figure
ax.plot_surface(X, Y, Z) # Surface plot
plt.title("Example 3")

plt.show() # Show all generated figures


import numpy as np
import equidistantPoints as p
import gaussianNoise as gn
import linearRegression as lr
import leastSquaresEstimate as ls
import meanSquareError as m
from random import random
from random import seed
import matplotlib.pyplot as plt
import math

'''
-------------------------------------------------------------------------------------------
Exercise 1:
-------------------------------------------------------------------------------------------
generalised regression model
y = theta0 + theta1 * x + theta2 * x^2 + theta3 * x^3 + theta5 * x^5 + noise

where noise corresponds to white Gaussian noise

and the components of the weight vector theta assume the values:
theta0 = 0.2
theta1 = -1
theta2 = 0.9
theta3 = 0.7
theta4 =  0
theta5 = -0.2

so:
theta = transposed [0.2 -1 0.9 0.7 -0.2]

In all cases of exercise 1 we consider N equidistant points x1,x2, ... xn n=1, 2, ... , N
all the points are in the interval [0,2]

our training set consists of
yn = theta0 + theta1 * xn + theta2 * xn^2 + theta3 * xn^3 + theta5 * xn^5 + noise_n n=1, 2, ... ,N

noise_n are noise samples coming from a Gaussian distribution with
mean=0, variance= sigma_n^2

--------------------------------------------------------------------------------------------
'''
'''
--------------------------------------------------------------------------------------------
For question 1.1:

Consider N=20, sigma_n^2=0.1
and
y = theta0 + theta1 * x + theta2 * x^2 + theta3 * x^3 + theta5 * x^5 + noise

1)Use Least Squares method to estimate the parameter vector
2)Calculate MSE of y over
a)the training set
b)a test set of 1000 points in the interval [0,2]
--------------------------------------------------------------------------------------------
'''

#initializing weight vector theta
theta = np.zeros((6,1))
thetaValues = [0.2, -1, 0.9, 0.7,  0, -0.2]

for i in range(0, 6):
    theta[i] = thetaValues[i]

thetaTransposed = np.transpose(theta)

#N=20 equidistant points in the interval [0,2]
N=20
start=0
end=2

X, Fx = p.getPoints(N,start,end,6)

#noise
variance=0.1
mean=0
noise = gn.getNoise(N,mean,variance)

#get the yn's
Y = lr.getY(N, X, noise, thetaTransposed).T
Y1 = lr.getYNonoise(N, X, thetaTransposed)

#there are more data points than there are parameters to be determined
thetaPredicted, Fx = ls.getY(N,Fx,Y,6)

#predicted y values
Y_pred = np.dot(thetaPredicted.T,Fx.T)

#mean square error over training set
MSE = m.MSE(Y_pred,Y,N)

print("theta LS estimation is: ",thetaPredicted.T)
print("the MSE for the train set is:", MSE)
#blue for actual values red for predicted
plt.figure(num=1)
plt.title("training set")
plt.plot(X,Y1[0],'bo',X,Y_pred[0],'ro')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

'''
----------------------------------------
#create the test set
----------------------------------------
'''
N=1000
# seed random number generator
seed(1)
# generate 1000 random numbers between 0-2
upper_bound = 2
lower_bound = 0
test_X=[]
for _ in range(1000):

    if lower_bound != 0:
        value = random()*(upper_bound-lower_bound)*lower_bound
    else:
        value = random()*upper_bound

    test_X.append(value)

test_X = np.array(test_X, dtype=float)
Fx = np.ones((N,6))
for i in range(6):
    for j in range(N):
        Fx[j,i] = math.pow(test_X[j], i)

#noise
noise = gn.getNoise(N,mean,variance)

#get the yn's
Y_test = lr.getY(N, test_X, noise, thetaTransposed).T
Y1 = lr.getYNonoise(N, test_X, thetaTransposed)

#predicted y values over the testing set
Y_pred_test = np.dot(thetaPredicted.T,Fx.T)

#mean square error over test set
MSE_test = m.MSE(Y_pred_test,Y_test,N)

print("the MSE for the test set is:", MSE_test)

#blue for actual values red for predicted
plt.figure(num=3)
plt.title("test set 1 ")
plt.plot(test_X,Y1[0],'bo',test_X,Y_pred_test[0],'ro')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()


import numpy as np
from scipy.stats import wasserstein_distance
from sklearn.metrics import pairwise_distances
import ctypes
from functools import partial

def l2_dist(x, y):

    xx = np.sum(x**2, axis = 1)
    yy = np.sum(y**2, axis = 1)
    xy = np.dot(x, y.transpose((1,0)))

    d = xx[:,None] - 2*xy + yy[None,:]
    d[d<0] = 0
    d = (d)**(1/2)

    return d

def l1_dist(x, y):
    return np.sum(np.absolute(x[:,None,:]-y[None,:,:]), axis = 2)

def earth_mover_dist(x, y):
    return pairwise_distances(x, y, metric=wasserstein_distance)

def smart_dist(x, y, method="L2"):
    if method == "L1":
        return l1_dist(x, y)
    elif method == "L2":
        return l2_dist(x, y)
    elif method == "EM":
        return earth_mover_dist(x, y)
    else:
        return x


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

def get_normal_to_plane(points):
    imh, imw = points.shape
    mean = np.average(points, axis=0)
    R = points - mean
    w,v = np.linalg.eig(np.matmul(R.T,R))
    idx = np.argmin(w)
    normal = v[:,idx]
    return mean, normal

def plot_plane(points, normal, mean):
    fig = plt.figure()
    ax = plt.axes(projection="3d")
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.scatter(points[:,0], points[:,1], points[:,2], color='b')
    x = np.linspace(min(points[:,0]),max(points[:,0]),3)
    y = np.linspace(min(points[:,1]),max(points[:,1]),3)
    X,Y = np.meshgrid(x,y)
    Z = -(normal[0]/normal[2])*X -(normal[1]/normal[2])*Y + (np.dot(normal, mean)/normal[2])
    ax.plot_wireframe(X,Y,Z,color='r')
    plt.show()

def get_average_distanace(points, normal, mean):
    normal = normal/np.linalg.norm(normal)
    return np.mean(abs(np.dot(points-mean, normal)))

if __name__ == "__main__":
    filename = sys.argv[1]
    points = np.loadtxt(filename)
    mean, normal = get_normal_to_plane(points)
    plot_plane(points, normal, mean)
    avg_distance = get_average_distanace(points, normal, mean)
    print (normal, avg_distance, np.dot(normal,mean))


'''
Eduard Larrañaga
Computational Astrophysics
2020

Hermite Interpolation Method
'''

import numpy as np

#Hermite Coefficients
def psi0(z):
	'''
	------------------------------------------
	psi0(z)
	------------------------------------------
	Returns the Hermite coefficients Psi_0
	for the interpolation
	Receives as arguments: z
	------------------------------------------
	'''
	psi_0 = 2*z**3 - 3*z**2 + 1
	return psi_0

def psi1(z):
	'''
	------------------------------------------
	psi1(z)
	------------------------------------------
	Returns the Hermite coefficients Psi_1 for
	the interpolation
	Receives as arguments: z
	------------------------------------------
	'''
	psi_1 = z**3 - 2*z**2 + z
	return psi_1

# Interpolated Polynomial
def H3(x, xi, fi, dfidx):
	'''
	------------------------------------------
    H3(x, xi, fi, dfidx)
    ------------------------------------------
    Returns the values of the Cubic Hermite
    interpolated polynomial in a set of points
    defined by x
    x : array of points where the interpolated
    polynomial will be evaluated
    xi : array of 2 data points
    fi : array of values of the function at xi
    dfidx : array of values of the derivative
    of the function at xi
    ------------------------------------------
	'''
	# variable z in the interpolation
	z = (x - xi[0])/(xi[1] - x[0])

	h1 = psi0(z) * fi[0]
	h2 = psi0(1-z)*fi[1]
	h3 = psi1(z)*(xi[1] - xi[0])*dfidx[0]
	h4 = psi1(1-z)*(xi[1] - xi[0])*dfidx[1]
	H =  h1 + h2 + h3 - h4
	return H


import numpy as np

class GaussianNB:
    classes = None
    priors = None
    means = None
    variances = None

    def __init__(self):
        pass

    def fit(self, features, labels, priors_equal=True):
        self.classes = np.unique(labels)

        self.priors = np.ones(self.classes.shape) / self.classes.size

        self.means = {}
        self.variances = {}
        for i, clazz in enumerate(self.classes):
            positions = (labels == clazz).ravel()
            if not priors_equal:
                self.priors[i] = positions.sum() / labels.size
            self.means[clazz] = features[positions, :].mean(axis=0)
            self.variances[clazz] = features[positions, :].var(axis=0)

    def predict(self, features):
        likelihoods = np.zeros((features.shape[0], self.classes.size))
        for i, clazz in enumerate(self.classes):
            gaussians = np.exp(-np.square(features - self.means[clazz]) / (2 * self.variances[clazz])) \
                        / np.sqrt(2 * np.pi * self.variances[clazz])
            likelihoods[:, i] = np.product(gaussians, axis=1)

        posteriors = likelihoods * self.priors
        decisions = np.argmax(posteriors, axis=1)
        return self.classes[decisions]


# Fundamentos de Matemática Discreta com Python

## Matemática Discreta

- Área da Matemática que lida com objetos discretos, a saber, conjuntos, sequencias, listas, coleções ou quaisquer entidades *contáveis*.
- Exemplo, $\mathbb{R}$ é incontável, ou não enumerável
- Vários exemplos de contáveis:
    - O conjunto das vogais da língua portuguesa;
    - O conjunto dos times de futebol brasileiros da série A em 2020;
    - O conjunto de nomes das estações do ano;
    - O conjunto das personagens do quarteto do filme *Os Pinguins de Madagascar* e;
    - O conjunto dos números pares positivos menores ou iguais a dez.

- Conjuntos denotados por *extensão*: quando listamos seus elementos

- $\{ a, e, i, o, u \}$
- $\{ \text{Atlético-PR}, \ldots, \text{Bahia}, \text{Botafogo}, \ldots, \text{Coritiba}, \ldots, \text{Fortaleza}, \ldots, \text{Internacional}, \ldots, \text{São Paulo}, \text{Sport}, \text{Vasco} \}$
- $\{ \text{Primavera}, \text{Verão}, \text{Outono}, \text{Inverno}\}$
- $\{ \text{Capitão}, \text{Kowalski}, \text{Recruta}, \text{Rico}\}$
- $\{ 2, 4, 6, 8,10\}$

- Denotados por *compreensão*: quando usamos uma propriedade que distingue seus elementos.

- $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}$
- $\{ t \in \mathbb{T} \, ; \, t \text{ é da Série A} \}$
- $\{ x  \, ; \, x \text{ é uma estação do ano} \}$
- $\{ p  \, ; \, p \text{ é personagem do quarteto principal do filme Os Pinguins de Madagascar} \}$
- $\{ e  \, ; \, e \text{ é estação do ano} \}$
- $\{ n \in \mathbb{Z} \, | \, n = 2k \wedge 2 \leq n \leq 10 \wedge k \in \mathbb{Z} \}$

Por livre conveniência:

- $\mathbb{A}$ é o conjunto de todas as letras de nosso alfabeto
- $\mathbb{T}$ é o conjunto de todos os times de futebol do Brasil.

## Estruturas de dados para objetos discretos

As principais que aprenderemos:

- `list`: estrutura cujo conteúdo é modificável e o tamanho variável. Listas são caracterizadas por *mutabilidade* e *variabilidade*. Objetos `list` são definidos por um par de colchetes e vírgulas que separam seus elementos: `[., ., ... ,.]`.

- `tuple`: estrutura cujo conteúdo não é modificável e o tamanho fixado. Tuplas são caracterizadas por *imutabilidade* e *invariabilidade*. Objetos `tuple` são definidos por um par de colchetes e vírgulas que separam seus elementos: `(., ., ... ,.)`.

- `dict`: estruturas contendo uma coleção de pares do tipo *chave-valor*. Dicionários são caracterizados por *arrays associativos* (*tabelas hash*). Objetos `dict` são definidos por um par de chaves e agrupamentos do tipo  `'chave':valor` (*key:value*), separados por vírgula: `{'chave1':valor1, 'chave2':valor2, ... ,'chaven':valorn}`. As chaves (*keys*) podem ser do tipo `int`, `float`, `str`, ou `tuple` ao passo que os valores podem ser de tipos arbitrários.

- `set`: estruturas similares a `dict`, porém não possuem chaves e contêm objetos únicos. Conjuntos são caracterizadas por *unicidade* de elementos. Objetos `set` são definidos por um par de chaves e vírgulas que separam seus elementos: `{., ., ... ,.}`.

## Listas

Estruturas `list` formam uma coleção de objetos arbitrários e podem ser criadas de modo sequenciado com operadores de pertencimento ou por expressões geradoras, visto que são estruturas iteráveis.

vogais = ['a','e','i','o','u'] # elementos são 'str'
vogais

times = ['Bahia', 'Sport', 'Fortaleza', 'Flamengo']
times

pares10 = [2,4,6,8,10]
pares10

mix = ['Bahia',24,6.54,[1,2]] # vários objetos na lista
mix

### Listas por geração

**Exemplo**: crie uma lista dos primeiros 100 inteiros não-negativos.

os_100 = range(100) # range é uma função geradora
print(list(os_100)) # casting com 'list'

**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x < 10 \}$

print(list(range(-20,10))) # print é usado para imprimir column-wise

**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x \leq 10 \}$

print(list(range(-20,11))) # para incluir 10, 11 deve ser o limite. Por quê?

## Adicionando e removendo elementos

Há vários métodos aplicáveis para adicionar e remover elementos em listas.

### Adição por apensamento

Adiciona elementos por concatenação no final da lista.

times.append('Botafogo')
times

times.append('Fluminense')
times

### Adição por extensão

Para incluir elementos através de um objeto iterável, sequenciável, usamos `extend`.

falta = ['Vasco', 'Atlético-MG']
times.extend(falta) # usa outra lista pra estender a lista
times

#### Iteração e indexação

- *Iterar* sobre uma lista é "passear" por seus elementos

- Em Python, a indexação de listas vai de `0` a `n - 1`, onde `n` é o tamanho da lista.

Por exemplo:

$\text{posição} : \{p=0, p=1, \ldots, p={n-1}\}$

$\text{elementos na lista} : [x_1, x_2, \ldots, x_{n}]$

- Mesma idéia aplicável a qualquer coleção, sequencia ou objeto iterável.

### Remoção por índice

Suponha que tivéssemos criado a lista:

pares = [0,2,5,6] # 5 não é par
pares

Como 5 não é par, não deveria estar na lista. Para excluírmos um elemento em uma posição específica, usamos `pop` passando o *índice* onde o elemento está.

pares.pop(2) # o ímpar 5 está na posição 2 e NÃO 3!
pares

### Adição por índice

Nesta lista, podemos pensar em incluir 4 entre 2 e 6. Para isto, usamos `insert(posicao,valor)`, para `valor` na `posicao` desejada.

pares.insert(2,4) # 4 é inserido na posição de 6, que é deslocado
pares

### Apagar conteúdo da lista

Podemos apagar o conteúdo inteiro da lista com `clear`.

times.clear()
times # lista está vazia

Podemos contar o número de elementos da lista com `len`.

len(times) # verifica que a lista está vazia

type([]) # a lista é vazia, mas continua sendo lista

### Outros métodos de lista

Conte repetições de elementos na lista com `count`.

numeros = [1,1,2,3,1,2,4,5,6,3,4,4,5,5]
print( numeros.count(1), numeros.count(3), numeros.count(7) )

Localize a posição de um elemento com `index`.

numeros.index(5) # retorna a posição da primeira aparição

Remova a primeira aparição do elemento com `remove`.

numeros.remove(1) # perde apenas o primeiro
numeros

Faça uma reflexão ("flip") *in-place* (sem criar nova lista) da lista com `reverse`.

numeros.reverse()
numeros

Ordene a lista de maneira *in-place* (sem criar nova lista) com `sort`.

numeros.sort()
numeros

## Concatenação de listas

Listas são concatenadas ("somadas") com `+`. Caso já possua listas definidas, use `extend`.

['Flamengo', 'Botafogo'] + ['Fluminense']

['Flamengo', 'Botafogo'] + 'Fluminense' # erro: 'Fluminense' não é list

times_nordeste = ['Fortaleza','Sport']
times_sul = ['Coritiba','Atlético-PR']
times_nordeste + times_sul

times_nordeste.extend(times_sul) # mesma coisa
times_nordeste

## Fatiamento de listas

O fatiamento ("slicing") permite que selecionemos partes da lista através do modelo `start:stop`, em que `start` é um índice incluído na iteração, e `stop` não.

letras = ['a','b','c','d','e','f','g']
letras[0:2]

letras[1:4]

letras[5:6]

letras[0:7] # toda a lista

### Omissão de `start` e `stop`

letras[:3] # até 3, exclusive

letras[:5] # até 5, exclusive

letras[4:] # de 4 em diante

letras[6:] # de 6 em diante

### Modo reverso

letras[-1] # último índice

letras[-2:-1] # do penúltimo ao último, exclusive

letras[-3:-1]

letras[-4:-2]

letras[-7:-1] # toda a lista

letras[-5:]

letras[:-3]

## Elementos alternados com `step`

Podemos usar um dois pontos duplo (`::`) para dar um "passo" de alternância.

letras[::2] # salta 2-1 intermediários

letras[::3] # salta 3-1 intermediários

letras[::7] # salto de igual tamanho

letras[::8] # salto além do tamanho

## Mutabilidade de listas

Podemos alterar o conteúdo de elementos diretamente por indexação.

from sympy.abc import x,y

ops = [x+y,x-y,x*y,x/y]
ops2 = ops.copy() # cópia de ops
ops

ops[0] = x-y
ops

ops[2] = x/y
ops

ops[1], ops[3] = x + y, x*y # mutação por desempacotamento
ops

ops[1:3] = [False, False, True] # mutação por fatiamento
ops

ops = ops2 # recuperando ops
ops

ops2 is ops

ops3 = [] # lista vazia
ops3

ops2 = ops + ops3 # concatenação cria uma lista nova
ops2

ops2 is ops # agora, ops2 não é ops

print(id(ops), id(ops2)) # imprime local na memória de ambas

ops2 == ops # todos os elementos são iguais

O teste de identidade é `False`, mas o teste de igualdade é `True`.

**Exemplo:** Escreva uma função que calcule a área, perímetro, comprimento da diagonal, raio, perímetro e área do círculo inscrito, e armazene os resultados em uma lista.

# usaremos matemática simbólica
from sympy import symbols
from math import pi

# símbolos
B, H = symbols('B H',positive=True)

def propriedades_retangulo(B,H):
    '''
        A função assume que a base B
        é maior do que a altura H. Senão,
        as propriedades do círculo inscrito
        não serão determinadas.
    '''
    d = (B**2 + H**2)**(1/2) # comprimento da diagonal
    r = H/2 # raio do círculo inscrito
    return [B*H, 2*(B+H), d, r, 2*pi*r, pi*(r)**2]

# lista de objetos símbolos
propriedades_retangulo(B,H)

# substituindo valores
B, H = 4.0, 2.5

propriedades_retangulo(B,H)

### Formatação de strings

O *template* a seguir usa a função `format` para substituição de valores indexados.

```python
templ = '{0} {1} ... {n}'.format(arg0,arg1,...,argn)
```

**Nota:** Para ajuda plena sobre formatação, consultar:

```python
help('FORMATTING')
```

# considere R: retângulo; C: círculo inscrito

res = propriedades_retangulo(B,H) # resultado

props = ['Área de R',
         'Perímetro de R',
         'Diagonal de R',
         'Raio de C',
         'Perímetro de C',
         'Área de C'
        ] # propriedades

# template
templ = '{0:s} = {1:.2f}\n\
{2:s} = {3:.3f}\n\
{4:s} = {5:.4f}\n\
{6:s} = {7:.5f}\n\
{8:s} = {9:.6f}\n\
{10:s} = {11:.7f}'.format(props[0],res[0],\
                          props[1],res[1],\
                          props[2],res[2],\
                          props[3],res[3],\
                          props[4],res[4],\
                          props[5],res[5])

# impressão formatada
print(templ)

### Como interpretar o que fizemos?

- `{0:s}` formata o primeiro argumento de `format`, o qual é `props[0]`, como `str` (`s`).
- `{1:.2f}` formata o segundo argumento de `format`, o qual é `res[0]`, como `float` (`f`) com duas casas decimais (`.2`).
- `{3:.3f}` formata o quarto argumento de `format`, o qual é `res[1]`, como `float` (`f`) com três casas decimais (`.3`).

A partir daí, percebe-se que um template `{X:.Yf}` diz para formatar o argumento `X` como `float` com `Y` casas decimais, ao passo que o template `{X:s}` diz para formatar o argumento `X` como `str`.

Além disso, temos:

- `\n`, que significa "newline", isto é, uma quebra da linha.
- `\`, que é um *caracter de escape* para continuidade da instrução na linha seguinte. No exemplo em tela, o *template* criado é do tipo *multi-line*.

**Nota:** a contrabarra em `\n` também é um caracter de escape e não um caracter *literal*. Isto é, para imprimir uma contrabarra literalmente, é necessário fazer `\\`. Vejamos exemplos de literais a seguir.

#### Exemplos de impressão de caracteres literais

print('\\') # imprime contrabarra literal
print('\\\\') # imprime duas contrabarras literais
print('\'') # imprime plica
print('\"') # imprime aspas

#### f-strings

Temos uma maneira bastante interessante de criar templates usando f-strings, que foi introduzida a partir da versão Python 3.6. Com f-strings a substituição é imediata.

print(f'{props[0]} = {res[0]}') # estilo f-string

#### Estilos de formatação

Veja um comparativo de estilos:

print('%s = %f ' % (props[0], res[0])) # Python 2
print('{} = {}'.format(props[0], res[0])) # Python 3
print('{0:s} = {1:.4f}'.format(props[0], res[0])) # Python 3 formatado

**Exemplo:** Considere o conjunto: V = $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}.$ Crie a concatenação de todos os elementos com f-string.

V = ['a','e','i','o','u']
V

f'{V[0]}{V[1]}{V[2]}{V[3]}{V[4]}' # pouco Pythônico

Veremos à frente meios mais elegantes de fazer coisas similares.

## Controle de fluxo: laço `for`

Em Python, podemos realizar iterar por uma coleção ou iterador usando *laços*. Introduziremos aqui o laço `for`. Em Python, o bloco padrão para este laço é dado por:

```python
for valor in sequencia:
    # faça algo com valor
```

Acima, `valor` é um iterador.

for v in vogais: # itera sobre lista inteira
    print(v)

for v in vogais[0:3]: # itera parcialmente
    print(v + 'a')

for v in vogais[-2:]:
    print(f'{v*10}')

## Compreensão de lista

Usando `for`, a criação de listas torna-se bastante facilitada.

**Exemplo:** crie a lista dos primeiros 10 quadrados perfeitos.

Q = [q*q for q in range(1,11)]
Q

A operação acima equivale a:

Q2 = []
for q in range(1,11):
    Q2.append(q*q)
Q2

**Exemplo:** crie a PA: $a_n = 3 + 6(n-1), \, 1 \leq n \leq 10$

PA = [3 + 6*(n-1) for n in range(1,11) ]
PA

**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, crie a "soma" $X + Y$ elemento a elemento.

X = [1,2,3]
Y = [4,5,6]

XsY = [ X[i] + Y[i] for i in range(len(X)) ]
XsY

**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, cria o "produto" $X * Y$ elemento a elemento.

XpY = [ X[i]*Y[i] for i in range(len(X)) ]
XpY

from sympy import lambdify
from sympy.abc import x

for i,v in enumerate(XpY)
    lambdify(x,'x**2')

## Tuplas

Tuplas são são sequencias imutáveis de tamanho fixo. Em Matemática, uma tupla é uma sequência ordenada de elementos. Em geral, o termo $n-$upla ("ênupla") é usado para se referir a uma tupla com $n$ elementos.

par = 1,2; par

type(par)

trio = (1,2,3); trio

quad = (1,2,3,4); quad

nome = 'Nome'; tuple(nome) # casting

Tuplas são acessíveis por indexação.

quad[2]

quad[1:4]

quad[3] = 5 # tuplas não são mutáveis

Se na tupla houver uma lista, a lista é modificável.

super_trio = tuple([1,[2,3],4]) # casting
super_trio

super_trio[1].extend([4,5])
super_trio

Tuplas também são concatenáveis com `+`.

(2,3) + (4,3)

('a',[1,2],(1,1)) # repetição

### Desempacotamento de tuplas

a,b,c,d = (1,2,3,4)

for i in [a,b,c,d]:
    print(i) # valor das variáveis

a,b = (1,2)
a,b = b,a # troca de valores
a,b

### `enumerate`

Podemos controlar índice e valor ao iterar em uma sequencia.

X = [1,2,3] # lista / sequencia

for i,x in enumerate(X): # (i,x) é uma tupla (índice,valor)
    print(f'{i} : {x}')

**Exemplo:** Construa o produto cartesiano

$$A \times B = \{(a,b) \in \mathbb{Z} \times \mathbb{Z} \, ; \, -4 \leq a \leq 4 \wedge 3 \leq b \leq 7\}$$

AB = [(a,b) for a in range(-4,5) for b in range(3,8)]
print(AB)

## Dicionários

Dicionários, ou especificamente, objetos `dict`, possuem extrema versatilidade e são muito poderosos. Criamos um `dict` por diversas formas. A mais simples é usar chaves e pares explícitos.

d = {} # dict vazio
d

type(d)

Os pares chave-valor incorporam quaisquer tipos de dados.

d = {'par': [0,2,4,6,8], 'ímpar': [1,3,5,7,9], 'nome':'Meu dict', 'teste': True}
d

### Acesso a conteúdo

Para acessar o conteúdo de uma chave, indexamos pelo seu nome.

d['par']

d['nome']

**Exemplo:** construindo soma e multiplicação especial.

# dict
op = {'X' :[1,2,3], 'delta' : 0.1}

# função
def sp(op):
    s = [x + op['delta'] for x in op['X']]
    p = [x * op['delta'] for x in op['X']]

    return (s,p) # retorna tupla

soma, prod = sp(op) # desempacota

for i,s in enumerate(soma):
    print(f'pos({i}) | Soma = {s} | Prod = {prod[i]}')

### Inserção de conteúdo

# apensa variáveis
op[1] = 3
op['novo'] = (3,4,1)
op

### Alteração de conteúdo

op['novo'] = [2,1,4] # sobrescreve
op

### Deleção de conteúdo com `del` e `pop`

del op[1] # deleta chave
op

novo = op.pop('novo') # retorna e simultaneamente deleta
novo

op

### Listagem de chaves e valores

Usamos os métodos `keys()` e `values()` para listar chaves e valores.

arit = {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/'} # dict
k = list(arit.keys())
print(k)
val = list(arit.values())
print(val)
for v in range(len(arit)):
    print(f'A operação \'{k[v]}\' de "arit" usa o símbolo \'{val[v]}\'.')

### Combinando dicionários

Usamos `update` para combinar dicionários. Este método possui um resultado similar a `extend`, usado em listas.

pot = {'pot': '**'}
arit.update(pot)
arit

### Dicionários a partir de sequencias

Podemos criar dicionários a partir de sequencias existentes usando `zip`.

arit = ['soma', 'subtr', 'mult', 'div', 'pot']
ops = ['+', '-', '*', '/', '**']

dict_novo = {}

for chave,valor in zip(arit,ops):
    dict_novo[chave] = valor

dict_novo

Visto que um `dict` é composto de várias tuplas de 2, podemos criar um  de maneira ainda mais simples.

dict_novo = dict(zip(arit,ops))
dict_novo

### *Hashability*

Dissemos acima que os valores de um `dict` podem ser qualquer objeto Python. Porém, as chaves estão limitadas por uma propriedade chamada *hashability*. Um objeto *hashable* em geral é imutável. Para saber se um objeto pode ser usado como chave de um `dict`, use a função `hash`. Caso retorne erro, a possibilidade de *hashing* é descartada.

# todos aqui são imutáveis, portanto hashable'
hash('s'), hash(2), hash(2.1), hash((1,2))

# não hashable
hash([1,2]), hash((1,2),[3,4])

Para usar `list` como chave, podemos convertê-las em `tuple`.

d = {}; d[tuple([1,2])] = 'hasheando lista em tupla'; d

## Compreensão de dicionário

Podemos usar `for` para criar dicionários de maneira esperta do mesmo modo que as compreensões de lista com a distinção de incluir pares chaves/valor.

{chave:valor for chave,valor in enumerate(arit)} # chave:valor

{valor:chave for chave,valor in enumerate(arit)} # valor:chave


# importing modules
#-----------------------------------------
# this is okay
import math
d = 90
a = math.sin(math.radians(d))
print(f'sin of {d:2.0f} degrees = {a}')

#-----------------------------------------
# this is okay
from math import cos, radians
d = 0
a = cos(radians(d))
print(f'cos of {d:2.0f} degrees = {a}')

#-----------------------------------------
# very rarely should you do this
from math import *
a = tan(tanh(cos(acos(sin(pi)))))
print('a =', a)

#-----------------------------------------
# this is common practice
# import numpy as np
# import pandas as pd

# a = np.array([1, 2, 3])
# df = pd.DataFrame()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jul 17 16:17:25 2017

@author: jorgemauricio
"""

# librerias
import numpy as np

# Funciones universales
arr = np.arange(11)

# desplegar
arr

# raiz cuadrada
np.sqrt(arr)

# calcular el exponencial (e^)
np.exp(arr)

# Funciones binarias requieren dos arreglos

# arreglo aleatorio (distribucion normal)
A = np.random.randn(10)

# desplegar
A

# arreglo aleatorio (distribucion normal)
B = np.random.randn(10)

# desplegar
B

# anadir
np.add(A,B)

# Maximo y minimo de dos arreglos
np.maximum(A,B)

#


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

# In this script, we will explore the dynamics of phase transitions in a
# two component mixture. We discussed two models in class -- the Regular
# Solution theory and the Flory-Huggins Theory.

# The regulular solution describes a mixture of componnents in which each
# individual molecule occupies only one lattice site in our closed system.
# We can define the probability of observing like-bonded molecules as phi.
# We can also include the Flory parameter which describes the energy of mixing
# the two species. The full description of the regular solution theory
# description of the free energy is
#
# free-energy / kBT = (2 * X * phi) * (1 - phi) + phi * log(phi) * (1-phi) * log(phi)#
#
# where X is the flory parameter. Let's see how varying this parameter
# influences the phases of the system.

# We'll set up our parameters.
phi = np.linspace(0, 1, 500)
chi = [0.01, 0.1, 0.5, 0.8, 1, 1.25, 1.5, 2, 4]

# We'll compute the value at each chi and plot it at once.
plt.figure()
for x in chi:
    # Calculate the free energy
    free_energy = 2 * x * phi * (1 - phi) + phi * np.log(phi) + (1 - phi) * np.log(1 - phi)
    plt.plot(phi, free_energy, label='$\chi$ = ' + str(x))

# Of course we should add labels.
plt.xlabel('$\phi$')
plt.ylabel(r'$\frac{F(\phi)}{k_BT}$')
plt.title('Regular Solution Theory')
plt.legend()
plt.show()

# We see that at the intermediate values of X , there are now multiple local
# minima where phases will separate.

# Now we can consider a case in which we are mixing together polymers. In this
# case, the polymers now occupy several different lattice sites. The result for
# the free energy of the system is given by the Flory-Huggins theory,
#
# free-energy / k_BT = 2 * X * phi * (1-phi) + (phi / L) * log(phi) + (1-phi) * log(1-phi)
#
# where L is the degree of polymerization, essentially just the length of the
# polymer.
L = 100 # Degree of polymerization

# Now we'll do the same iteration of x and  plot the free energy.
plt.figure()
for x in chi:
    # Calculate the free energy.
    free_energy = 2 * x * phi * (1 - phi) + (phi / L) * np.log(phi) +\
            (1 - phi) * np.log(1 - phi)
    # Plot it!
    plt.plot(phi, free_energy, label='$\chi$ = ' + str(x))

plt.xlabel('$\phi$')
plt.ylabel(r'$\frac{F(\phi)}{k_BT}$')
plt.legend()
plt.title('Flory-Huggins Theory')
plt.show()

# We now see that there is a degree of nonsymmetry when compared to the
# regular solution theory. This significantly changes the way that
# phases separate.


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

Automatically generated by Colaboratory.

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

# illustrate automatic differentiation using jax
# https://github.com/google/jax
import numpy as np # original numpy
import jax.numpy as jnp
from jax import grad, hessian

np.random.seed(42)
D = 5
w = np.random.randn(D) # jax handles RNG differently

x = np.random.randn(D)
y = 0 # should be 0 or 1

def sigmoid(x): return 0.5 * (jnp.tanh(x / 2.) + 1)

#d/da sigmoid(a) = s(a) * (1-s(a))
deriv_sigmoid = lambda a: sigmoid(a) * (1-sigmoid(a))
deriv_sigmoid_jax = grad(sigmoid)
a0 = 1.5
assert jnp.isclose(deriv_sigmoid(a0), deriv_sigmoid_jax(a0))

# mu(w)=s(w'x), d/dw mu(w) = mu * (1-mu) .* x
def mu(w): return sigmoid(jnp.dot(w,x))
def deriv_mu(w): return mu(w) * (1-mu(w)) * x
deriv_mu_jax =  grad(mu)
assert jnp.allclose(deriv_mu(w), deriv_mu_jax(w))

# NLL(w) = -[y*log(mu) + (1-y)*log(1-mu)]
# d/dw NLL(w) = (mu-y)*x
def nll(w): return -(y*jnp.log(mu(w)) + (1-y)*jnp.log(1-mu(w)))
#def deriv_nll(w): return -(y*(1-mu(w))*x - (1-y)*mu(w)*x)
def deriv_nll(w): return (mu(w)-y)*x
deriv_nll_jax = grad(nll)
assert jnp.allclose(deriv_nll(w), deriv_nll_jax(w))

# Now do it for a batch of data

def predict(weights, inputs):
    return sigmoid(jnp.dot(inputs, weights))

def loss(weights, inputs, targets):
    preds = predict(weights, inputs)
    logprobs = jnp.log(preds) * targets + jnp.log(1 - preds) * (1 - targets)
    return -np.sum(logprobs)

N = 3
X = np.random.randn(N, D)
y = np.random.randint(0, 2, N)

from jax import vmap
from functools import partial

preds = vmap(partial(predict, w))(X)
preds2 = vmap(predict, in_axes=(None, 0))(w, X)
preds3 = [predict(w, x) for x in X]
preds4 = predict(w, X)
assert jnp.allclose(preds, preds2)
assert jnp.allclose(preds, preds3)
assert jnp.allclose(preds, preds4)

grad_fun = grad(loss)
grads = vmap(partial(grad_fun, w))(X,y)
assert grads.shape == (N,D)
grads2 = jnp.dot(np.diag(preds-y), X)
assert jnp.allclose(grads, grads2)

grad_sum = jnp.sum(grads, axis=0)
grad_sum2 = jnp.dot(np.ones((1,N)), grads)
assert jnp.allclose(grad_sum, grad_sum2)

# Now make things go fast
from jax import jit

grad_fun = jit(grad(loss))
grads = vmap(partial(grad_fun, w))(X,y)
assert jnp.allclose(grads, grads2)

# Logistic regression
H1 = hessian(loss)(w, X, y)
mu = predict(w, X)
S = jnp.diag(mu * (1-mu))
H2 = jnp.dot(np.dot(X.T, S), X)
assert jnp.allclose(H1, H2)


# -*- coding: utf-8 -*-
# @Time    : 7/29/2018 9:57 PM
# @Author  : DonaldY

# 1. 定义数据集
x = [4, 8, 12, 25, 32, 43, 58, 63, 69, 79]
y = [20, 33, 50, 56, 42, 31, 33, 46, 65, 75]

# 2. 使用Matplotlib 绘制数据
from matplotlib import pyplot as plt

plt.scatter(x, y)
plt.show()

# 3. 实现 2 次多项式函数及误差函数
def func(p, x1):
    w0, w1, w2 = p
    f = w0 + w1*x1 + w2*x1*x1
    return f

def err_func(p, x1, y1):
    ret = func(p, x1) - y1
    return ret

# 4. 生成随机数
import numpy as np
# 生成 3 个随机数
p_init = np.random.randn(3)

p_init

# 5. 使用 Scipy 提供的最小二乘法哈数得到最佳拟合参数
from scipy.optimize import leastsq

parameters = leastsq(err_func, p_init, args=(np.array(x), np.array(y)))

print('Fitting Parameters: ', parameters[0])

## 6. 绘制拟合后的图像
# 绘制拟合图像时需要的临时点
x_temp = np.linspace(0, 80, 10000)

# 绘制拟合函数曲线
plt.plot(x_temp, func(parameters[0], x_temp), 'r')

# 绘制原数据点
plt.scatter(x, y)


import numpy as np

class LDA(object):

    def __init__(self, n_components):
        self.n_components = n_components

    def _within_class_scatter_matrices(self, X, y):

        shape = X.shape[1]
        matrix = np.zeros((shape, shape))

        for i in np.unique(y):
            matrix += np.cov(X[y == i].T)

        return matrix

    def _between_class_scatter_matrix(self, X, y):

        mean_vectors = []
        for i in np.unique(y):
            mean_vectors.append(np.mean(X[y == i], axis=0))

        overall_mean = np.mean(X, axis=0)

        shape = X.shape[1]
        matrix = np.zeros((shape, shape))
        for i, mean_vec in enumerate(mean_vectors):
            n = X[y == i + 1,:].shape[0]
            mean_vec = mean_vec.reshape(shape, 1)
            overall_mean = overall_mean.reshape(shape, 1)
            matrix += n * (mean_vec - overall_mean).dot((mean_vec - overall_mean).T)

        return matrix

    def _create_eig_values_and_vectors(self, X, y):

        wc = self._within_class_scatter_matrices(X, y)
        bc = self._between_class_scatter_matrix(X, y)

        A = np.linalg.pinv(wc).dot(bc)

        eig_vals, eig_vecs = np.linalg.eig(A)

        return eig_vals, eig_vecs

    def fit(self, X, y):

        eig_vals, eig_vecs = self._create_eig_values_and_vectors(X, y)

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

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

        shape = X.shape[1]
        self.W = np.hstack([eig_pairs[comp][1].reshape(shape, 1) for comp in range(self.n_components)])

        return self

    def transform(self, X, y):
        return X.dot(self.W).real

    def fit_transform(self, X, y):
        self = self.fit(X, y)
        return self.transform(X, y)


# -*- coding: utf-8 -*-
"""
-----------------------------------------------------------------------------
Ejercicio 1: Integración - Fórmulas de cuadratura simples.
-----------------------------------------------------------------------------
"""
import numpy as np
import sympy as sym

# Cálculo de la integral exacta de la función f(x) = exp(x) en [0,3]
#----------------------------------------------------------------------------
x = sym.Symbol('x', real=True)            # definimos la variable x simbólica
f = sym.exp(x)            # definimos la función f simbólica
I_exacta = sym.integrate(f,(x,0,3))
I_exacta = float(I_exacta)
print ('El valor exacto es: ',I_exacta)

"""
Ejercicio 1a: Integración - Fórmula del punto medio simple.
-----------------------------------------------------------------------------
Función punto_medio: Halla la integral aproximada utlizando la fórmula del
punto medio simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula del punto medio simple de la función
        f en [a,b] (número real).

Ejemplos:
    f = lambda x : x**2
    I = punto_medio(f,0,2)
    print('Ejemplo de prueba con punto medio simple =', I)
    Salida:
        Ejemplo de prueba con punto medio simple = 2.0

"""

# Función punto_medio
#--------------------------------------
def punto_medio(f,a,b):
    I = (b-a)*f((a+b)/2.0)      # fórmula del punto medio simple
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con punto medio simple =', punto_medio(lambda x : x**2,0,2))

#--------------------------------------
# Ejercicio 1a
#--------------------------------------

# Cálculo de la integral aproximada
#---------------------------------------
f = lambda x :np.exp(x)   # función
a = 0
b = 3
Ipm = punto_medio(f,a,b)  # cálculo de la integral aproximada con

print ('El valor aproximado con punto medio simple es:', Ipm)

"""
Ejercicio 1b: Integración - Fórmula del trapecio simple.
-----------------------------------------------------------------------------
Función trapecio: Halla la integral aproximada utlizando la fórmula del
trapecio simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula del trapecio de la función
        f en [a,b].

Ejemplos:
    f = lambda x : x**2
    I = trapecio(f,0,2)
    print('Ejemplo de prueba con trapecio simple =', I)
    Salida:
       Ejemplo de prueba con trapecio simple = 4.0
"""

# Función trapecio
#--------------------------------------
def trapecio(f,a,b):
    I = ((b-a)/2.0)*(f(a)+f(b))
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con trapecio simple =', trapecio(lambda x : x**2,0,2))

#--------------------------------------
# Ejercicio 1b
#--------------------------------------

# Cálculo de la integral aproximada
#---------------------------------------
It = trapecio(f,a,b)
print ('El valor aproximado con trapecio simple es:', It)

"""
Ejercicio 1c: Integración - Fórmula de Simpson simple.
-----------------------------------------------------------------------------
Función simpson: Halla la integral aproximada utlizando la fórmula de
Simpson simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula de Simpson de la función
        f en [a,b].

Ejemplos:
    f = lambda x : x**2
    I = simpson(f,0,2)
    print('Ejemplo de prueba con simpson simple =', I)
    Salida:
        Ejemplo de prueba con simpson simple = 2.6666666666666665
"""

# Función simpson
#--------------------------------------
def simpson(f,a,b):
    I = ((b-a)/6.0)*(f(a)+4*f((a+b)/2.0)+f(b))
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con simpson simple =', simpson(lambda x : x**2,0,2))

#--------------------------------------
# Ejercicio 1c
#--------------------------------------

# Cálculo de la integral aproximada
#---------------------------------------
Is = simpson(f,a,b)
print ('El valor aproximado con simpson simple es:', Is)


import os
import numpy as np
import matplotlib.pyplot as plt
import pylab
#%matplotlib inline

path = os.getcwd() + "/data/ex1data1.txt"
data = np.loadtxt(path, delimiter=",")
temp = np.ones((97,1), dtype=np.float64)
data = np.append(temp, data, axis=1)
'''
print(data.itemsize)
print(data.ndim)
print(data.shape)
print(data.size)
'''

def partial(data, theta0, theta1, i):
	temp = 0.0
	for row in data:
		temp += (theta0 * row[0] + theta1 * row[1] - row[2]) * row[i]
	temp /= (data.shape)[0]
	return temp

def J(theta0, theta1, data):
	j = 0.0;
	for row in data:
		j += (theta0 * row[0] + theta1 * row[1] - row[2]) ** 2
	j /= (2 * (data.shape)[0])
	return j

def regression(data):
	#for plotting error vs training epoch
	#count = 0
	alpha = 0.01
	m, n = (data.shape)[0], (data.shape)[1]
	theta0, theta1 = 0.0, 0.0
	j = J(theta0, theta1, data)
	j_prev = j + 1
	epsilon = 0.0001
	while (abs(j - j_prev) >= epsilon):
		temp0 = theta0 - alpha * partial(data, theta0, theta1, 0)
		temp1 = theta1 - alpha * partial(data, theta0, theta1, 1)
		theta0 = temp0
		theta1 = temp1
		j_prev = j
		j = J(theta0, theta1, data)
		#plt.plot(count, j, "b.")
		#count += 1
	return theta0, theta1

def abline(slope, intercept):
    #Plot a line from slope and intercept
    axes = plt.gca()
    x_vals = np.array(axes.get_xlim())
    y_vals = intercept + slope * x_vals
    plt.plot(x_vals, y_vals, '-')

theta0, theta1 = regression(data)
temp1 = [theta0, theta0 + theta1]

plt.plot(data[:,1], data[:,2], "r.")
abline(theta1, theta0)
plt.ylabel('some numbers')
plt.show()


import matplotlib.pyplot as plt

def model():
    """Solve u'' = -1, u(0)=0, u'(1)=0."""
    import sympy as sym
    x, c_0, c_1, = sym.symbols('x c_0 c_1')
    u_x = sym.integrate(1, (x, 0, x)) + c_0
    u = sym.integrate(u_x, (x, 0, x)) + c_1
    r = sym.solve([u.subs(x,0) - 0,
                   sym.diff(u,x).subs(x, 1) - 0],
                  [c_0, c_1])
    u = u.subs(c_0, r[c_0]).subs(c_1, r[c_1])
    u = sym.simplify(sym.expand(u))
    return u

def midpoint_rule(f, M=100000):
    """Integrate f(x) over [0,1] using M intervals."""
    from numpy import sum, linspace
    dx = 1.0/M                       # interval length
    x = linspace(dx/2, 1-dx/2, M)    # integration points
    return dx*sum(f(x))

def check_integral_b():
    from numpy import pi, sin
    for i in range(12):
        exact = 2/(pi*(2*i+1))
        numerical = midpoint_rule(
            f=lambda x: sin((2*i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def sine_sum(x, N):
    s = 0
    from numpy import pi, sin, zeros
    u = [] # u[k] is the sum i=0,...,k
    k = 0
    for i in range(N+1):
        s += - 16.0/((2*i+1)**3*pi**3)*sin((2*i+1)*pi*x/2)
        u.append(s.copy())  # important with copy!
    return u

def plot_sine_sum():
    from numpy import linspace
    x = linspace(0, 1, 501)  # coordinates for plot
    u = sine_sum(x, N=10)
    u_e = 0.5*x*(x-2)
    N_values = 0, 1, 10
    for k in N_values:
        plt.plot(x, u[k])
    plt.plot(x, u_e)
    plt.legend(['N=%d' % k for k in N_values] + ['exact'],
               loc='upper right')
    plt.xlabel('$x$');  plt.ylabel('$u$')
    plt.savefig('tmpc.png'); plt.savefig('tmpc.pdf')

def check_integral_d():
    from numpy import pi, sin
    for i in range(24):
        if i % 2 == 0:
            exact = 2/(pi*(i+1))
        elif (i-1) % 4 == 0:
            exact = 2*2/(pi*(i+1))
        else:
            exact = 0
        numerical = midpoint_rule(
            f=lambda x: sin((i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def check_integral_d_sympy_answer():
    from numpy import pi, sin
    for i in range(12):
        exact = 2/(pi*(i+1))
        numerical = midpoint_rule(
            f=lambda x: sin((i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def sine_sum_d(x, N):
    s = 0
    from numpy import pi, sin, zeros
    u = []  # u[k] is the sum i=0,...,k
    k = 0
    for i in range(N+1):
        if i % 2 == 0:       # even i
            s +=   - 16.0/((i+1)**3*pi**3)*sin((i+1)*pi*x/2)
        elif (i-1) % 4 == 0:   # 1, 5, 9, 13, 17
            s += - 2*16.0/((i+1)**3*pi**3)*sin((i+1)*pi*x/2)
        else:
            s += 0
        u.append(s.copy())
    return u

def plot_sine_sum_d():
    from numpy import linspace
    x = linspace(0, 1, 501)  # coordinates for plot
    u = sine_sum_d(x, N=20)
    u_e = 0.5*x*(x-2)
    N_values = 0, 1, 2, 3, 20
    for k in N_values:
        plt.plot(x, u[k])
    plt.plot(x, u_e)
    plt.legend(['N=%d' % k for k in N_values] + ['exact'],
               loc='upper right')
    plt.xlabel('$x$');  plt.ylabel('$u$')
    #plt.axis([0.9, 1, -0.52, -0.49])
    plt.savefig('tmpd.png'); plt.savefig('tmpd.pdf')

if __name__ == '__main__':
    import sys
    print(model())
    print('sine 2*i+1 integral:')
    check_integral_b()
    print('sine i+1 integral, sympy answer:')
    check_integral_d_sympy_answer()
    print('sine i+1 integral:')
    check_integral_d()
    #sys.exit(0)
    plot_sine_sum()
    plt.figure()
    plot_sine_sum_d()
    plt.show()


import numpy as np

from source.week2.ex2_1 import xy, construct_element_table
from source.week2.ex2_2a import basfun

from scipy.sparse import dia_matrix

def compute_k_rsn(abc, delta, r, s, lam1, lam2):
    br = abc[r, 1]
    bs = abc[s, 1]
    cr = abc[r, 2]
    cs = abc[s, 2]
    k_rsn = (lam1 * br * bs + lam2 * cr * cs) / np.abs(delta * 4)
    return k_rsn

def assembly(VX, VY, EToV, lam1, lam2, qt, M):
    N = len(EToV)
    A = np.zeros((M, M))
    b = np.zeros(M)
    for n in range(1, N + 1):
        abc, delta = basfun(n, VX, VY, EToV)
        for r in range(3):
            i = EToV[n][r] - 1
            qr = qt[n] * np.abs(delta) / 3  # (2.28)
            b[i] += qr
            for s in range(3):
                j = EToV[n][s] - 1
                k_rsn = compute_k_rsn(abc, delta, r, s, lam1, lam2)  # (2.27)
                A[i, j] += k_rsn
    return A, b

def construct_qt(etov_dict, VX, VY, test_case=None):
    """
    Computes points as means of each elements' vertices.
    """
    qt_dict = dict()
    for e, (v1, v2, v3) in etov_dict.items():
        if test_case == 1:
            qt_dict[e] = 0
        elif test_case == 2:
            x1, x2, x3 = VX[v1 - 1], VX[v2 - 1], VX[v3 - 1]
            y1, y2, y3 = VY[v1 - 1], VY[v2 - 1], VY[v3 - 1]

            def f(x, y):
                return -6 * x + 2 * y - 2

            q1 = f(x1, y1)
            q2 = f(x2, y2)
            q3 = f(x3, y3)

            q_avg = np.mean([q1, q2, q3])
            qt_dict[e] = q_avg
        else:
            raise Exception("Unknown test case")
    return qt_dict

def test_case_data_ex_2_3(nr_of_test_case):
    if nr_of_test_case == 1:
        x0 = 0
        y0 = 0
        L1 = 1
        L2 = 1
        X, Y = xy(x0=x0, y0=y0, L1=L1, L2=L2, noelms1=4, noelms2=3)
        etov, M = construct_element_table(4, 3)
        # TODO: get lambda value
        lam1 = 1
        lam2 = 1
        qt = construct_qt(etov, X, Y, test_case=1)
        return nr_of_test_case, X, Y, etov, M, lam1, lam2, qt, x0, y0, L1, L2
    elif nr_of_test_case == 2:
        x0 = -2.5
        y0 = -4.8
        L1 = 7.6
        L2 = 5.9
        X, Y = xy(x0=x0, y0=y0, L1=L1, L2=L2, noelms1=4, noelms2=3)
        etov, M = construct_element_table(4, 3)
        lam1 = 1
        lam2 = 1
        qt = construct_qt(etov, X, Y, test_case=2)
        return nr_of_test_case, X, Y, etov, M, lam1, lam2, qt, x0, y0, L1, L2
    else:
        raise Exception("Unknown test case")

def sparse_diags(A):
    # x = dia_matrix(A)
    max_diag_size = A.diagonal(0).shape[0]
    d = []
    data = []
    for diag in range(-A.shape[0], A.shape[1]):
        diag_value = A.diagonal(diag)
        if np.any(diag_value):
            d.append(diag)

            if diag > 0:
                x = np.zeros(max_diag_size)
                x[diag:] = diag_value
                diag_value = x
            data.append(diag_value)

    d = np.array(d)
    B = np.zeros((len(d), max_diag_size))
    return B, d

def main():
    nr_of_test_case, X, Y, etov_dict, M, lam1, lam2, qt, x0, y0, L1, L2 = test_case_data_ex_2_3(2)
    A, b = assembly(X, Y, etov_dict, lam1=lam1, lam2=lam2, qt=qt, M=M)
    B, d = sparse_diags(A)

    print()

if __name__ == '__main__':
    main()


"""ESS (Effective Sample Size) for MCMC sampling efficiency measurements."""
import typing as t

import numpy as np

def ess(samples: np.ndarray,
        mean: t.Union[float, int],
        var: t.Union[float, int],
        verbose: bool = False) -> float:
    r"""ESS (Effective sample size) of a MCMC chain.

    This statistics evaluate how many independent samples a given
    ``samples`` of size n is worth. If all samples are uncorrelated,
    then $ESS = n$, while highly correlated samples have very small
    $ESS \ll n$.

    Arguments
    ---------
    samples : :obj:`np.ndarray`, shape: (n,)
        Samples chain from a MCMC process.

    mean : :obj:`float` or :obj:`int`
        Estimate of the process mean.

    var : :obj:`float` or :obj:`int`
        Estimate of the process variance.

    verbose : :obj:`bool`, optional
        If True, enable message printing related to this process.

    Returns
    -------
    :obj:`float`
        ESS statistic for ``samples.``
    """
    lag_size = 1
    stop_condition = False
    autocorr = []  # type: t.List[float]

    if not isinstance(samples, np.ndarray):
        samples = np.array(samples)

    shifted_samples = samples - mean

    while not stop_condition:
        autocorr.append(
            np.mean([
                shifted_samples[i] * shifted_samples[i + lag_size]
                for i in np.arange(samples.size - lag_size)
            ]))

        if lag_size >= 3 and lag_size % 2 == 1:
            stop_condition = (
                autocorr[lag_size - 1] + autocorr[lag_size - 2]) < 0

        lag_size += 1

    ess_stat = samples.size / (1 + 2 / var * np.sum(autocorr[:-2]))

    if verbose:
        print("Chain size: {} - ESS: {}".format(samples.size, ess_stat))
        print("ESS / (chain size):", ess_stat / samples.size)
        print("Last lag size effectively used:", lag_size - 2)

    return ess_stat


from pathlib import Path
import numpy as np
from dolfin import *

def poisson_error(N, order=1, plot=False):
    # Create mesh and define function space
    mesh = UnitSquareMesh(N, N)
    V = FunctionSpace(mesh, "P", order)

    # Define boundary condition
    u_D = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", degree=6)

    def boundary(x, on_boundary):
        return on_boundary

    bc = DirichletBC(V, u_D, boundary)

    # Define variational problem
    u = TrialFunction(V)
    v = TestFunction(V)
    f = Constant(-6.0)
    a = dot(grad(u), grad(v)) * dx
    L = f * v * dx

    # Compute solution
    u = Function(V)
    solve(a == L, u, bc)

    if plot:
        # Save solution to file in VTK format
        f = XDMFFile(str(Path(__file__).parent / "poisson.xdmf"))
        f.write(u,0.)

    # Compute error in L2 norm
    error_L2 = errornorm(u_D, u, "L2")
    return error_L2

if __name__ == "__main__":
    Ns = [1, 2, 4, 8, 16, 32, 64, 128]
    errors = []
    for N in Ns:
        errors.append(poisson_error(N, plot=N == 128))

    np.save(Path(__file__).parent / "poisson_convergence_Ns", Ns)
    np.save(Path(__file__).parent / "poisson_convergence_errors", errors)


# %% codecell
import cvxpy as cp
# %% markdown
# 2. Convert the annualized average returns and the covariance matrix to `numpy` arrays:
# %% codecell
avg_returns = avg_returns.values
cov_mat = cov_mat.values
# %% markdown
# 3. Set up the optimization problem:
# %% codecell
weights = cp.Variable(n_assets)
gamma = cp.Parameter(nonneg=True)
portf_rtn_cvx = avg_returns * weights
portf_vol_cvx = cp.quad_form(weights, cov_mat)
objective_function = cp.Maximize(portf_rtn_cvx - gamma * portf_vol_cvx)
problem = cp.Problem(objective_function,
                     [cp.sum(weights) == 1, weights >= 0])
# %% markdown
# 4. Calculate the Efficient Frontier:
# %% codecell
N_POINTS = 25
portf_rtn_cvx_ef = np.zeros(N_POINTS)
portf_vol_cvx_ef = np.zeros(N_POINTS)
weights_ef = []
gamma_range = np.logspace(-3, 3, num=N_POINTS)

for i in range(N_POINTS):
    gamma.value = gamma_range[i]
    problem.solve()
    portf_vol_cvx_ef[i] = cp.sqrt(portf_vol_cvx).value
    portf_rtn_cvx_ef[i] = portf_rtn_cvx.value
    weights_ef.append(weights.value)
# %% markdown
# 5. Plot the allocation for different values of the risk-aversion parameter:
# %% codecell
weights_df = pd.DataFrame(weights_ef,
                          columns=RISKY_ASSETS,
                          index=np.round(gamma_range, 3))
ax = weights_df.plot(kind='bar', stacked=True)
ax.set(title='Weights allocation per risk-aversion level',
       xlabel=r'$\gamma$',
       ylabel='weight')
ax.legend(bbox_to_anchor=(1,1))

plt.tight_layout()
plt.savefig('images/ch7_im15.png')
plt.show()

# %% codecell
#MARKS = ['o', 'X', 'd', '*']

fig, ax = plt.subplots()
ax.plot(portf_vol_cvx_ef, portf_rtn_cvx_ef, 'g-')
for asset_index in range(n_assets):
     plt.scatter(x=np.sqrt(cov_mat[asset_index, asset_index]),
                 y=avg_returns[asset_index],
                 marker=MARKS[asset_index],
                 label=RISKY_ASSETS[asset_index],
                 s=150)
ax.set(title='Efficient Frontier',
       xlabel='Volatility',
       ylabel='Expected Returns', )
ax.legend()

plt.tight_layout()
plt.savefig('images/ch7_im16.png')
plt.show()

# # %% markdown
# # ### There's more
# # %% codecell
# x_lim = [0.25, 0.6]
# y_lim = [0.125, 0.325]
#
# fig, ax = plt.subplots(1, 2)
# ax[0].plot(vols_range, rtns_range, 'g-', linewidth=3)
# ax[0].set(title='Efficient Frontier - Minimized Volatility',
#           xlabel='Volatility',
#           ylabel='Expected Returns',
#           xlim=x_lim,
#           ylim=y_lim)
#
# ax[1].plot(portf_vol_cvx_ef, portf_rtn_cvx_ef, 'g-', linewidth=3)
# ax[1].set(title='Efficient Frontier - Maximized Risk-Adjusted Return',
#           xlabel='Volatility',
#           ylabel='Expected Returns',
#           xlim=x_lim,
#           ylim=y_lim)
#
# plt.tight_layout()
# plt.savefig('images/ch7_im17.png')
# plt.show()
# # %% codecell
# max_leverage = cp.Parameter()
# problem_with_leverage = cp.Problem(objective_function,
#                                    [cp.sum(weights) == 1,
#                                     cp.norm(weights, 1) <= max_leverage])
# # %% codecell
# LEVERAGE_RANGE = [1, 2, 5]
# len_leverage = len(LEVERAGE_RANGE)
# N_POINTS = 25
#
# portf_vol_l_ef = np.zeros((N_POINTS, len_leverage))
# portf_rtn_l_ef = np.zeros(( N_POINTS, len_leverage))
# weights_ef = np.zeros((len_leverage, N_POINTS, n_assets))
#
# for lev_ind, leverage in enumerate(LEVERAGE_RANGE):
#     for gamma_ind in range(N_POINTS):
#         max_leverage.value = leverage
#         gamma.value = gamma_range[gamma_ind]
#         problem_with_leverage.solve()
#         portf_vol_l_ef[gamma_ind, lev_ind] = cp.sqrt(portf_vol_cvx).value
#         portf_rtn_l_ef[gamma_ind, lev_ind] = portf_rtn_cvx.value
#         weights_ef[lev_ind, gamma_ind, :] = weights.value
#
# # %% codecell
# fig, ax = plt.subplots()
#
# for leverage_index, leverage in enumerate(LEVERAGE_RANGE):
#     plt.plot(portf_vol_l_ef[:, leverage_index],
#              portf_rtn_l_ef[:, leverage_index],
#              label=f'{leverage}')
#
# ax.set(title='Efficient Frontier for different max leverage',
#        xlabel='Volatility',
#        ylabel='Expected Returns')
# ax.legend(title='Max leverage')
#
# plt.tight_layout()
# plt.savefig('images/ch7_im18.png')
# plt.show()
# # %% codecell
# fig, ax = plt.subplots(len_leverage, 1, sharex=True)
#
# for ax_index in range(len_leverage):
#     weights_df = pd.DataFrame(weights_ef[ax_index],
#                               columns=RISKY_ASSETS,
#                               index=np.round(gamma_range, 3))
#     weights_df.plot(kind='bar',
#                     stacked=True,
#                     ax=ax[ax_index],
#                     legend=None)
#     ax[ax_index].set(ylabel=(f'max_leverage = {LEVERAGE_RANGE[ax_index]}'
#                              '\n weight'))
#
#
# ax[len_leverage - 1].set(xlabel=r'$\gamma$')
# ax[0].legend(bbox_to_anchor=(1,1))
# ax[0].set_title('Weights allocation per risk-aversion level',
#                 fontsize=16)
#
# plt.tight_layout()
# plt.savefig('images/ch7_im19.png')
# plt.show()
# # %% codecell


from numpy import zeros, linspace, array
from numpy.linalg import norm

def gaussJacobi(A, B, Ap, e):
    C = zeros((len(A), len(A)))
    g = zeros((len(B),1))

    # Construir as matrizes C e g
    for i in range(len(A)):
        g[i] = B[i]/A[i][i]
        for j in range(len(A)):
            if i == j :
                C[i][j] = -A[i][j]/A[i][i]

    # Testando a condição de parada
    if norm(C, 1) < 1:
        erro = 1
        # Se quisermos saber quantas iterações foram feitas
        #n = 0
        while erro > e:
            An = C*Ap + g
            erro = norm(An-Ap)/norm(An)
            Ap = An
            #n = n + 1

        return Ap

    else:
        return None

if __name__ == '__main__':

    '''
    t  = int(input("Tamanho M da matriz A[MxM]:"))

    A  = [(map(float,input("linha "+str(x+1)+":").split())) for i in range(t)]
    A  = array(A)

    B  = [[x] for x in list(map(float,input('Digite os valores do vetor B [Mx1]: ').split()))]
    B  = array(B)

    Ap = array([x for x in list(map(float,input('Digite os valores do vetor X[Mx1]: ').split()))])

    e  = input("Entre com o erro máximo tolerado: ")
    '''

    t = 8
    A  = array([[-10,1,-2,0], [-1,11,-1,3], [2,-1,10,-1], [0,3,-1,8]])
    B  = array([-6,25,-11,15])
    Ap = array([0,0,0,0])
    e  = 0.001

    valores = gaussJacobi(A, B, Ap, e)

    if valores is not None:

        print('Os valores convergem no ponto ', end='')
        str_append = ''
        soma = 0

        for i in range (len(valores)):
            str_append = str_append + "x%i: %10.8f" %(i, A[0][i]*valores[i])
            soma = soma + A[0][i]*valores[i]

        print(' y(x)=%10.8f onde:' %soma)
        print(str_append)

    else:
        print('Não há convergência!!!')


from __future__ import annotations
import numpy as np

def sigmoid(x: np.ndarray) -> np.ndarray:
    return 1/(1+np.exp(-x))

class LogisticRegression:
    """Logistic Regression
    Parameters:
    -----------
    learning_rate: float
        The step length used when following the negative gradient during training.
    num_features: int
        The number of feature in the data
    penalty: str, default='l2'
        The type of penalty used.
    C: float, default=1
       Regularization strength
    """
    def __init__(self, learning_rate: float, num_features: int, penalty: str = 'l2', C: float = 0.1) -> None:
        self.learning_rate = learning_rate
        self.penalty = penalty
        self.C = C
        self.b = 0
        self.w = np.zeros((1, num_features))
        assert penalty in ['l2', 'l1', None]

    def cost_function(self, y: np.ndarray, y_pred: np.ndarray) -> float:
        y_T = y.T
        if self.penalty == 'l1':
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))) + self.C * np.sum(np.absolute(self.w)))
        elif self.penalty == 'l2':
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))) + self.C * np.sum(np.square(self.w)))
        else:
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))))

    def fit(self, X: np.ndarray, y: np.ndarray, num_iterations) -> LogisticRegression:
        for i in range(num_iterations):
            pred = sigmoid(np.dot(self.w, X.T) + self.b)
            cost = self.cost_function(y, pred)

            # Calculate Gradients/Derivatives
            dw = (1 / X.shape[0]) * (np.dot(X.T, (pred - y.T).T))
            db = (1 / X.shape[0]) * (np.sum(pred - y.T))

            self.w = self.w - (self.learning_rate * dw.T)
            self.b = self.b - (self.learning_rate * db)
        return self

    def predict(self, X: np.ndarray) -> list:
        predictions = sigmoid(np.dot(self.w, X.T) + self.b)[0]
        return [1 if pred >= 0.5 else 0 for pred in predictions]

    def predict_proba(self, X: np.ndarray) -> np.ndarray:
        return sigmoid(np.dot(self.w, X.T) + self.b)[0]


# -*- coding: utf-8 -*-
"""
Created on Sat Jun 29 21:17:53 2019

@author: Parikshith.H
"""

import numpy as np

# create an array of sine values
a = np.array([0, np.pi/2, np.pi])
print ("Sine values of array elements:", np.sin(a))
# =============================================================================
# #output:
# Sine values of array elements: [0.0000000e+00 1.0000000e+00 1.2246468e-16]
# =============================================================================

# exponential values
a = np.array([0, 1, 2, 3])
print ("Exponent of array elements:", np.exp(a))
# =============================================================================
# #output:
# Exponent of array elements: [ 1.          2.71828183  7.3890561  20.08553692]
# =============================================================================

# square root of array values
print ("Square root of array elements:", np.sqrt(a))
# =============================================================================
# #output:
# Square root of array elements: [0.         1.         1.41421356 1.73205081]
# =============================================================================


import numpy as np
from scipy import special

def t_fit(X, dof=4, iter=200, tol=1e-7):
    '''t
    Estimates the mean and covariance of the dataset
    X (rows are datapoints) assuming they come from a
    student t likelihood with no priors and dof degrees
    of freedom using the EM algorithm.
    :param X: np.array[n,d]
    :param dof: float > 2
    :param iter: int, max iterations of algorithm
    :param tol: float, tolerance
    :return: estimated covariance, estimated mean, list of
             objectives at each iteration.
    :rtype: np.array[d,d], np.array[d], list[float]
    '''
    # initialize parameters
    D = X.shape[1]
    N = X.shape[0]
    cov = np.cov(X,rowvar=False)
    mean = X.mean(axis=0)
    mu = X - mean[None,:]
    delta = np.einsum('ij,ij->i', mu, np.linalg.solve(cov,mu.T).T)
    z = (dof + D) / (dof + delta)
    obj = [
        -N*np.linalg.slogdet(cov)[1]/2 - (z*delta).sum()/2 \
        -N*special.gammaln(dof/2) + N*dof*np.log(dof/2)/2 + dof*(np.log(z)-z).sum()/2
    ]

    # iterate
    for i in range(iter):
        # M step
        mean = (X * z[:,None]).sum(axis=0).reshape(-1,1) / z.sum()
        mu = X - mean.squeeze()[None,:]
        cov = np.einsum('ij,ik->jk', mu, mu * z[:,None])/N

        # E step
        delta = (mu * np.linalg.solve(cov,mu.T).T).sum(axis=1)
        delta = np.einsum('ij,ij->i', mu, np.linalg.solve(cov,mu.T).T)
        z = (dof + D) / (dof + delta)

        # store objective
        obj.append(
            -N*np.linalg.slogdet(cov)[1]/2 - (z*delta).sum()/2 \
            -N*special.gammaln(dof/2) + N*dof*np.log(dof/2)/2 + dof*(np.log(z)-z).sum()/2
        )

        if np.abs(obj[-1] - obj[-2]) < tol:
            break

    return mean.squeeze(), cov

def t_generate(mu, S, dof=4, n=1):
    '''generate random variables of multivariate t distribution
    Parameters
    ----------
    mu : array_like
        mean of random variable, length determines dimension of random variable
    S : array_like
        square array of covariance  matrix
    d0f : int or float
        degrees of freedom
    n : int
        number of observations, return random array will be (n, len(mu))
    Returns
    -------
    rvs : ndarray, (n, len(mu))
        each row is an independent draw of a multivariate t distributed
        random variable
    '''
    mu = np.asarray(mu)
    d = len(mu)
    if dof == np.inf:
        x = 1.
    else:
        x = np.random.chisquare(dof, n)/dof
    z = np.random.multivariate_normal(np.zeros(d),S,(n,))
    return mu + z/np.sqrt(x)[:,None]


'''
BasicMatrix: Matrix Algebra Package
Author: Christiaan van Eeden
Version: 0.1
'''

#import packages
import numpy as np
import random as rdm

class BasicMatrix:
    '''
	The BasicMatrix class offers basic matrix arithmetic
	'''
    def __init__(self, row, col):
        '''
    	Attributes
    		rows (int): number of rows in matrix
    		cols (int): number of columns in matrix
    		method (str): filling method (randint/id)
    	'''
        self.row = row
        self.col = col
        self.mat = np.zeros([self.row, self.col])

    def get_matrix(self):
        '''
        Returns matrix from attribute
        '''
        return self.mat

    def set_matrix(self, X):
        '''
        Sets the instance matrix and dimensions to input matrix
        '''
        self.mat = X
        self.row = len(X)
        self.col = len(X[0])

    def shape(self):
        '''
        Returns dimensions (rows, columns) of instance matrix
        '''
        return np.shape(self.mat)

    def fill_matrix(self):
        '''
        Fills the matrix with random integers
        '''
        for m in range(self.row):
            for n in range(self.col):
                self.mat[m][n] = rdm.randint(0, 10)

    def dot(self, other):
        '''
        Multiplication of matrices after checking correct  dimension requirements

        Returns new shaped answer matrix or raises error for shape
        '''
        if self.col == other.row:
            new_mat = np.zeros([self.row, other.col])
            for m in range(self.row):
                for n in range(other.col):
                    for k in range(other.row):
                        new_mat[m][n] += self.mat[m][k] * other.mat[k][n]
            return new_mat
        else:
            raise ValueError('Shapes cannot be multiplied: change A colummns to match B rows')

    def __add__(self, other):
        '''
        Overwrites '+'  method for adding matrices

        Returns answer matrix or raises error
        '''
        if (self.row == other.row) and (self.col == other.col):
            return self.mat + other.mat
        else:
            raise ValueError('Shapes cannot be added: require equal rows and columns for AB')

    def __sub__(self, other):
        '''
        Overwrites '-' method for subtracting matrices

        Returns answer matrix or raises error
        '''
        if (self.row == other.row) and (self.col == other.col):
            return self.mat - other.mat
        else:
            raise ValueError('Shapes cannot be subtracted: require equal rows and columns for AB')

# mat1 = BasicMatrix(2, 3)
# mat1.fill_matrix()
# print(mat1.get_matrix())
# mat2 = BasicMatrix(2, 2)
# mat2.fill_matrix()
# print(mat2.get_matrix())

# print(BasicMatrix.dot(mat1, mat2))

# print(np.dot(mat1.get_matrix(), mat2.get_matrix()))

# print(mat1 + mat2)

# print(mat1 - mat2)

# bruh = np.ones([2, 2])

# mat1.set_matrix(bruh)

# print(mat1.get_matrix())

# print(mat1.shape())