#' % Linear Regression model with Python
#' % Matti Pastell
#' % 19.4.2013

#' #Requirements
#' This en example of doing linear regression analysis using Python
#' and [statsmodels](http://statsmodels.sourceforge.net). We'll use the new formula API
#' which makes fitting the models very familiar for R users.
#' You'll also need [Numpy](http://www.numpy.org/), [Pandas](http://pandas.pydata.org/)
#' and [matplolib](http://matplotlib.org/).

#' The analysis can be published using  Pweave 0.22 and later.

#' Import libraries

import pandas as pd
import numpy as np
import statsmodels.formula.api as sm
import statsmodels
import matplotlib.pyplot as plt

#' Statsmodels api seems to change often, check release version:
#+ term=True

statsmodels.__version__

#' We'll use [whiteside](http://stat.ethz.ch/R-manual/R-patched/library/MASS/html/whiteside.html) dataset from R package MASS. You can read the description of the dataset from the link, but in short it contains:

#' >*The weekly gas consumption and average external temperature at a house in south-east England for two
#' heating seasons, one of 26 weeks before, and one of 30 weeks after cavity-wall insulation was installed.*

#' Load dataset using Pandas:

url = 'https://raw.githubusercontent.com/mpastell/Rdatasets/master/csv/MASS/whiteside.csv'
whiteside = pd.read_csv(url)

#' # Fitting the model
#' Let's see what the relationship between the gas consumption is before the insulation.
#' See [statsmodels documentation](http://statsmodels.sourceforge.net/devel/example_formulas.html)
#' for more information about the syntax.

model = sm.ols(formula='Gas ~ Temp', data=whiteside, subset = whiteside['Insul']=="Before")
fitted = model.fit()
print(fitted.summary())

#' # Plot the data and fit

Before = whiteside[whiteside["Insul"] == "Before"]
plt.plot(Before["Temp"], Before["Gas"], 'ro')
plt.plot(Before["Temp"], fitted.fittedvalues, 'b')
plt.legend(['Data', 'Fitted model'])
plt.ylim(0, 10)
plt.xlim(-2, 12)
plt.xlabel('Temperature')
plt.ylabel('Gas')
plt.title('Before Insulation')

#' # Fit diagnostiscs
#' Statsmodels [OLSresults](http://statsmodels.sourceforge.net/devel/generated/statsmodels.regression.linear_model.OLSResults.html) objects contain the usual diagnostic information about the model and you can use the `get_influence()` method to get more diagnostic information (such as Cook's distance).

#' ## A look at the residuals
#' Histogram of normalized residuals

plt.hist(fitted.resid_pearson)
plt.ylabel('Count')
plt.xlabel('Normalized residuals')

#' ## Cooks distance

#' [OLSInfluence](http://statsmodels.sourceforge.net/devel/generated/statsmodels.stats.outliers_influence.OLSInfluence.html)
#'  objects contain more diagnostic information

influence = fitted.get_influence()
#c is the distance and p is p-value
(c, p) = influence.cooks_distance
plt.stem(np.arange(len(c)), c, markerfmt=",")

#' # Statsmodels builtin plots

#' Statsmodels includes a some builtin function for plotting residuals against leverage:

from statsmodels.graphics.regressionplots import *
plot_leverage_resid2(fitted)
influence_plot(fitted)

import numpy,import statsmodels,from statsmodels


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

# In[2]:

import numpy as np

# In[4]:

minha_lista = [1,2,3]

# In[6]:

minha_lista

# In[8]:

np.array(minha_lista) #Vai converter minha lista em um Array de Python

# In[41]:

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

# In[42]:

minha_matriz

# In[43]:

np.array(minha_matriz)

# In[16]:

np.arange(0, 10, 2) #Cria uma sequência de números pulando de acordo com a constante dada

# In[17]:

np.zeros(5)

# In[19]:

arr = np.zeros((5,5))

# In[21]:

arr

# In[22]:

np.ones(10)

# In[24]:

arr2 = np.ones((10,10))

# In[26]:

arr2

# In[28]:

np.eye(5) # Cria uma matrix identidade

# In[30]:

np.linspace(0,10,5) # Números igualmente espaçados

# In[32]:

np.random.rand(5) # Números entre 0 e 1

# In[34]:

np.random.rand(5)*100

# In[40]:

np.random.rand(5,5) # 5 Linhas e 5 colunas

# In[46]:

np.random.rand(5,5,5) # Matriz de 3 dimensões

# In[48]:

np.random.randn(4) # Distribuição Gauciana (ver em estatística)

# In[50]:

np.random.randint(0,100,10) # Tirei 10 números dentro intervalo de 100 números.

# In[52]:

arr3 = np.random.rand(25)

# In[54]:

arr3

# In[69]:

arr3.reshape((5,5))

# In[70]:

arr3.shape

# In[72]:

arr.shape

# In[74]:

arr3.max()

# In[76]:

arr3.min()

# In[78]:

arr3.argmax() # Vai retornar o índice na qual o maior elemento se encontra.

# In[80]:

arr3.argmin() # Vai retornar o índice na qual o menor elemento se encontra.


# 100 numpy exercises

# This is a collection of exercises that have been collected in the numpy mailing list, on stack overflow
# and in the numpy documentation. The goal of this collection is to offer a quick reference for both old
# and new users but also to provide a set of exercises for those who teach.

# If you find an error or think you've a better way to solve some of them, feel
# free to open an issue at <https://github.com/rougier/numpy-100>.
# File automatically generated. See the documentation to update questions/answers/hints programmatically.

#### 1. Import the numpy package under the name `np` (★☆☆)
import numpy as np

#### 2. Print the numpy version and the configuration (★☆☆)
print(np.__version__)

#### 3. Create a null vector of size 10 (★☆☆)
null_vec_10 = np.zeros(10)

#### 4. How to find the memory size of any array (★☆☆)
print(null_vec_10.nbytes)

#### 5. How to get the documentation of the numpy add function from the command line? (★☆☆)

#help(np.add)

#### 6. Create a null vector of size 10 but the fifth value which is 1 (★☆☆)
x = np.zeros(10)
x[4] = 1

#### 7. Create a vector with values ranging from 10 to 49 (★☆☆)
x = np.array(range(10, 50))

#### 8. Reverse a vector (first element becomes last) (★☆☆)
x = np.flip(x)

#### 9. Create a 3x3 matrix with values ranging from 0 to 8 (★☆☆)
x = np.array([range(0, 3),
              range(3, 6),
              range(6, 9)])

#### 10. Find indices of non-zero elements from [1,2,0,0,4,0] (★☆☆)
x = np.array([1,2,0,0,4,0])
non0_indices = np.where(x != 0)[0]

#### 11. Create a 3x3 identity matrix (★☆☆)
x = np.identity(3)

#### 12. Create a 3x3x3 array with random values (★☆☆)
x = np.random.rand(3, 3, 3)

#### 13. Create a 10x10 array with random values and find the minimum and maximum values (★☆☆)
x = np.random.rand(10, 10)
x_min = np.min(x)
x_max = np.max(x)

#### 14. Create a random vector of size 30 and find the mean value (★☆☆)
x = np.random.rand(30)
x_mean = np.mean(x)

#### 15. Create a 2d array with 1 on the border and 0 inside (★☆☆)
ones = np.ones(3, dtype=int)
x = np.array([ones, [1, 0, 1], ones])

#### 16. How to add a border (filled with 0's) around an existing array? (★☆☆)
x = np.pad(x, pad_width=1, mode='constant', constant_values=0)

#### 17. What is the result of the following expression? (★☆☆)

# 0 * np.nan                   # nan
# np.nan == np.nan             # False
# np.inf > np.nan              # False
# np.nan - np.nan              # nan
# np.nan in set([np.nan])      # True
# 0.3 == 3 * 0.1               # False

#### 18. Create a 5x5 matrix with values 1,2,3,4 just below the diagonal (★☆☆)
x = np.diag([1, 2, 3, 4], -1)

#### 19. Create a 8x8 matrix and fill it with a checkerboard pattern (★☆☆)
x = np.indices([8, 8]).sum(axis=0) % 2

#### 20. Consider a (6,7,8) shape array, what is the index (x,y,z) of the 100th element? (★☆☆)
print (np.unravel_index(100, (6,7,8)))

#### 21. Create a checkerboard 8x8 matrix using the tile function (★☆☆)

#### 22. Normalize a 5x5 random matrix (★☆☆)

#### 23. Create a custom dtype that describes a color as four unsigned bytes (RGBA) (★☆☆)

#### 24. Multiply a 5x3 matrix by a 3x2 matrix (real matrix product) (★☆☆)

#### 25. Given a 1D array, negate all elements which are between 3 and 8, in place. (★☆☆)

#### 26. What is the output of the following script? (★☆☆)
#```python
# Author: Jake VanderPlas

#print(sum(range(5),-1))
#from numpy import *
#print(sum(range(5),-1))
#```

#### 27. Consider an integer vector Z, which of these expressions are legal? (★☆☆)
# ```python
# Z**Z
# 2 << Z >> 2
# Z <- Z
# 1j*Z
# Z/1/1
# Z<Z>Z
#```

#### 28. What are the result of the following expressions? (★☆☆)
#```python
# np.array(0) / np.array(0)
# np.array(0) // np.array(0)
# np.array([np.nan]).astype(int).astype(float)
#```

#### 29. How to round away from zero a float array ? (★☆☆)

#### 30. How to find common values between two arrays? (★☆☆)

#### 31. How to ignore all numpy warnings (not recommended)? (★☆☆)

#### 32. Is the following expressions true? (★☆☆)
#```python
#np.sqrt(-1) == np.emath.sqrt(-1)
#```

#### 33. How to get the dates of yesterday, today and tomorrow? (★☆☆)

#### 34. How to get all the dates corresponding to the month of July 2016? (★★☆)

#### 35. How to compute ((A+B)*(-A/2)) in place (without copy)? (★★☆)

#### 36. Extract the integer part of a random array of positive numbers using 4 different methods (★★☆)

#### 37. Create a 5x5 matrix with row values ranging from 0 to 4 (★★☆)

#### 38. Consider a generator function that generates 10 integers and use it to build an array (★☆☆)

#### 39. Create a vector of size 10 with values ranging from 0 to 1, both excluded (★★☆)

#### 40. Create a random vector of size 10 and sort it (★★☆)

#### 41. How to sum a small array faster than np.sum? (★★☆)

#### 42. Consider two random array A and B, check if they are equal (★★☆)

#### 43. Make an array immutable (read-only) (★★☆)

#### 44. Consider a random 10x2 matrix representing cartesian coordinates, convert them to polar coordinates (★★☆)

#### 45. Create random vector of size 10 and replace the maximum value by 0 (★★☆)

#### 46. Create a structured array with `x` and `y` coordinates covering the [0,1]x[0,1] area (★★☆)

#### 47. Given two arrays, X and Y, construct the Cauchy matrix C (Cij =1/(xi - yj)) (★★☆)

#### 48. Print the minimum and maximum representable value for each numpy scalar type (★★☆)

#### 49. How to print all the values of an array? (★★☆)

#### 50. How to find the closest value (to a given scalar) in a vector? (★★☆)

#### 51. Create a structured array representing a position (x,y) and a color (r,g,b) (★★☆)

#### 52. Consider a random vector with shape (100,2) representing coordinates, find point by point distances (★★☆)

#### 53. How to convert a float (32 bits) array into an integer (32 bits) in place?

#### 54. How to read the following file? (★★☆)
#```
#1, 2, 3, 4, 5
#6,  ,  , 7, 8
# ,  , 9,10,11
#```

#### 55. What is the equivalent of enumerate for numpy arrays? (★★☆)

#### 56. Generate a generic 2D Gaussian-like array (★★☆)

#### 57. How to randomly place p elements in a 2D array? (★★☆)

#### 58. Subtract the mean of each row of a matrix (★★☆)

#### 59. How to sort an array by the nth column? (★★☆)

#### 60. How to tell if a given 2D array has null columns? (★★☆)

#### 61. Find the nearest value from a given value in an array (★★☆)

#### 62. Considering two arrays with shape (1,3) and (3,1), how to compute their sum using an iterator? (★★☆)

#### 63. Create an array class that has a name attribute (★★☆)

#### 64. Consider a given vector, how to add 1 to each element indexed by a second vector (be careful with repeated indices)? (★★★)

#### 65. How to accumulate elements of a vector (X) to an array (F) based on an index list (I)? (★★★)

#### 66. Considering a (w,h,3) image of (dtype=ubyte), compute the number of unique colors (★★☆)

#### 67. Considering a four dimensions array, how to get sum over the last two axis at once? (★★★)

#### 68. Considering a one-dimensional vector D, how to compute means of subsets of D using a vector S of same size describing subset  indices? (★★★)

#### 69. How to get the diagonal of a dot product? (★★★)

#### 70. Consider the vector [1, 2, 3, 4, 5], how to build a new vector with 3 consecutive zeros interleaved between each value? (★★★)

#### 71. Consider an array of dimension (5,5,3), how to mulitply it by an array with dimensions (5,5)? (★★★)

#### 72. How to swap two rows of an array? (★★★)

#### 73. Consider a set of 10 triplets describing 10 triangles (with shared vertices), find the set of unique line segments composing all the  triangles (★★★)

#### 74. Given a sorted array C that corresponds to a bincount, how to produce an array A such that np.bincount(A) == C? (★★★)

#### 75. How to compute averages using a sliding window over an array? (★★★)

#### 76. Consider a one-dimensional array Z, build a two-dimensional array whose first row is (Z[0],Z[1],Z[2]) and each subsequent row is  shifted by 1 (last row should be (Z[-3],Z[-2],Z[-1]) (★★★)

#### 77. How to negate a boolean, or to change the sign of a float inplace? (★★★)

#### 78. Consider 2 sets of points P0,P1 describing lines (2d) and a point p, how to compute distance from p to each line i (P0[i],P1[i])? (★★★)

#### 79. Consider 2 sets of points P0,P1 describing lines (2d) and a set of points P, how to compute distance from each point j (P[j]) to each line i (P0[i],P1[i])? (★★★)

#### 80. Consider an arbitrary array, write a function that extract a subpart with a fixed shape and centered on a given element (pad with a `fill` value when necessary) (★★★)

#### 81. Consider an array Z = [1,2,3,4,5,6,7,8,9,10,11,12,13,14], how to generate an array R = [[1,2,3,4], [2,3,4,5], [3,4,5,6], ..., [11,12,13,14]]? (★★★)

#### 82. Compute a matrix rank (★★★)

#### 83. How to find the most frequent value in an array?

#### 84. Extract all the contiguous 3x3 blocks from a random 10x10 matrix (★★★)

#### 85. Create a 2D array subclass such that Z[i,j] == Z[j,i] (★★★)

#### 86. Consider a set of p matrices wich shape (n,n) and a set of p vectors with shape (n,1). How to compute the sum of of the p matrix products at once? (result has shape (n,1)) (★★★)

#### 87. Consider a 16x16 array, how to get the block-sum (block size is 4x4)? (★★★)

#### 88. How to implement the Game of Life using numpy arrays? (★★★)

#### 89. How to get the n largest values of an array (★★★)

#### 90. Given an arbitrary number of vectors, build the cartesian product (every combinations of every item) (★★★)

#### 91. How to create a record array from a regular array? (★★★)

#### 92. Consider a large vector Z, compute Z to the power of 3 using 3 different methods (★★★)

#### 93. Consider two arrays A and B of shape (8,3) and (2,2). How to find rows of A that contain elements of each row of B regardless of the order of the elements in B? (★★★)

#### 94. Considering a 10x3 matrix, extract rows with unequal values (e.g. [2,2,3]) (★★★)

#### 95. Convert a vector of ints into a matrix binary representation (★★★)

#### 96. Given a two dimensional array, how to extract unique rows? (★★★)

#### 97. Considering 2 vectors A & B, write the einsum equivalent of inner, outer, sum, and mul function (★★★)

#### 98. Considering a path described by two vectors (X,Y), how to sample it using equidistant samples (★★★)?

#### 99. Given an integer n and a 2D array X, select from X the rows which can be interpreted as draws from a multinomial distribution with n degrees, i.e., the rows which only contain integers and which sum to n. (★★★)

#### 100. Compute bootstrapped 95% confidence intervals for the mean of a 1D array X (i.e., resample the elements of an array with replacement N times, compute the mean of each sample, and then compute percentiles over the means). (★★★)


import numpy as np

def generate_brownian(
    n_steps: int,
    n_paths: int,
    volatility: float = 0.2,
    dt: float = 1 / 250,
    drift: float = 0.0,
) -> np.ndarray:
    """Generate Brownian motion.

    Args:
        n_steps (int): Number of time steps.
        n_paths (int): Number of simulated paths.
        volatility (float, default=0.2): Volatility of the motion.
        dt (float, default=1/250): Intervals of time steps.
        drift (float, default=0.0): Drift of the motion.

    Examples:

        >>> np.random.seed(42)
        >>> generate_brownian(5, 2)
        array([[0.        , 0.        ],
               [0.00819268, 0.01926497],
               [0.00523085, 0.01630335],
               [0.02520649, 0.02601072],
               [0.01926806, 0.03287362]])
    """
    randn = np.random.randn(n_steps, n_paths)
    randn[0] = 0.0

    w = randn.cumsum(0)
    drift = (drift * np.arange(n_steps)).reshape(-1, 1)

    return drift + volatility * np.sqrt(dt) * w

def generate_geometric_brownian(
    n_steps: int,
    n_paths: int,
    volatility: float = 0.2,
    dt: float = 1 / 250,
    drift: float = 0.0,
) -> np.ndarray:
    """Generate geometric Brownian motion.

    Args:
        n_steps (int): Number of time steps.
        n_paths (int): Number of simulated paths.
        volatility (float, default=0.2): Volatility of the motion.
        dt (float, default=1/250): Intervals of time steps.
        drift (float, default=0.0): Drift of the motion.

    Examples:

        >>> np.random.seed(42)
        >>> generate_geometric_brownian(5, 2)
        array([[1.        , 1.        ],
               [0.98826212, 0.99926524],
               [0.96582835, 0.97658191],
               [0.96580482, 0.96658186],
               [0.94107547, 0.95396683]])
    """
    w = generate_brownian(
        n_steps=n_steps,
        n_paths=n_paths,
        volatility=volatility,
        dt=dt,
        drift=drift - (volatility ** 2 / 2),
    )

    return np.exp(w)


import numpy as np
from copy import copy

# Example backpropagation code for binary classification with 2-layer
# neural network (single hidden layer)

sigmoid = lambda x: 1 / (1 + np.exp(-x))

def fprop(x, y, params):
  # Follows procedure given in notes
  W1, b1, W2, b2 = [params[key] for key in ('W1', 'b1', 'W2', 'b2')]
  z1 = np.dot(W1, x) + b1
  a1 = sigmoid(z1)
  z2 = np.dot(W2, a1) + b2
  a2 = sigmoid(z2)
  loss = -(y * np.log(a2) + (1-y) * np.log(1-a2))
  ret = {'x': x, 'y': y, 'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2, 'loss': loss}
  for key in params:
    ret[key] = params[key]
  return ret

def bprop(fprop_cache):
  # Follows procedure given in notes
  x, y, z1, a1, z2, a2, loss = [fprop_cache[key] for key in ('x', 'y', 'z1', 'a1', 'z2', 'a2', 'loss')]
  dz2 = (a2 - y)
  dW2 = np.dot(dz2, a1.T)
  db2 = dz2
  dz1 = np.dot(fprop_cache['W2'].T, dz2) * sigmoid(z1) * (1-sigmoid(z1))
  dW1 = np.dot(dz1, x.T)
  db1 = dz1
  return {'b1': db1, 'W1': dW1, 'b2': db2, 'W2': dW2}

# Gradient checking

if __name__ == '__main__':
  # Initialize random parameters and inputs
  W1 = np.random.rand(2,2)
  b1 = np.random.rand(2, 1)
  W2 = np.random.rand(1, 2)
  b2 = np.random.rand(1, 1)
  params = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
  x = np.random.rand(2, 1)
  y = np.random.randint(0, 2)  # Returns 0/1

  fprop_cache = fprop(x, y, params)
  bprop_cache = bprop(fprop_cache)

  # Numerical gradient checking
  # Note how slow this is! Thus we want to use the backpropagation algorithm instead.
  eps = 1e-6
  ng_cache = {}
  # For every single parameter (W, b)
  for key in params:
    param = params[key]
    # This will be our numerical gradient
    ng = np.zeros(param.shape)
    for j in range(ng.shape[0]):
      for k in xrange(ng.shape[1]):
        # For every element of parameter matrix, compute gradient of loss wrt
        # that element numerically using finite differences
        add_eps = np.copy(param)
        min_eps = np.copy(param)
        add_eps[j, k] += eps
        min_eps[j, k] -= eps
        add_params = copy(params)
        min_params = copy(params)
        add_params[key] = add_eps
        min_params[key] = min_eps
        ng[j, k] = (fprop(x, y, add_params)['loss'] - fprop(x, y, min_params)['loss']) / (2 * eps)
    ng_cache[key] = ng

  # Compare numerical gradients to those computed using backpropagation algorithm
  for key in params:
    print key
    # These should be the same
    print(bprop_cache[key])
    print(ng_cache[key])


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 22 07:39:12 2020

Illustrating various approaches to computing ordinary derivatives

@author: zettergm
"""

# Imports
import numpy as np
import matplotlib.pyplot as plt

# Test function and exact derivative
lx=20       #number of points in numerical grid
x=np.linspace(-10,10,lx)
dx=x[1]-x[0]     # grid step size or resolution
y=np.sin(0.5*x)
yprime=0.5*np.cos(0.5*x)
ydprime=-0.25*np.sin(0.5*x)

# Plots
plt.figure(1)
plt.plot(x,y)
plt.plot(x,yprime)
plt.xlabel("x")
plt.ylabel("y and dy/dx")

# Centered difference approximation to the first derivative (recommended approach for most problems)
dydx=np.zeros(lx)
dydx[0]=(y[1]-y[0])/dx                  #cannot do centered difference on the edges; foward difference at the beginning
for ix in range(1,lx-1):
    dydx[ix]=(y[ix+1]-y[ix-1])/2/dx     #centered difference in the interior points
dydx[lx-1]=(y[lx-1]-y[lx-2])/dx         #backward difference on the end

plt.plot(x,dydx,"--")

# Foward difference approximation
dydx_fwd=np.zeros(lx)
dydx_fwd[0]=(y[1]-y[0])/dx                  #foward difference at the beginning
for ix in range(1,lx-1):
    dydx_fwd[ix]=(y[ix+1]-y[ix])/dx     #centered difference in the interior points
dydx_fwd[lx-1]=(y[lx-1]-y[lx-2])/dx         #backward difference on the end

plt.plot(x,dydx_fwd,'.')

# Demo of second derivative, centered formula
d2ydx2=np.zeros(lx)     # note that we are not computing edges values for 2nd deriv.
for ix in range(1,lx-1):
    d2ydx2[ix]=(y[ix+1]-2*y[ix]+y[ix-1])/dx**2

# Plot second derivative and legend
plt.plot(x,ydprime,'-')
plt.plot(x,d2ydx2,'^')
plt.legend(("y","dy/dx","centered","forward","2nd deriv. by hand","2nd deriv."))
plt.show()


"""
@Author=Monotera
"""

import numpy as np

"""
 2x + y   =  7
-5x + 11y = -1
"""

"""
Fucnion de python:

Pre-condiciones
la matriz de coeficientes debe ser cuadrada y de rango completo, es decir,
todas las filas (o, de manera equivalente, las columnas) deben ser linealmente .
independientes; si no es cierto, use lstsq para la mejor “solución” de mínimos
cuadrados del sistema / ecuación.
"""
def python_solution():
    a = np.array([[2,1],[-5,11]])
    b = np.array([7,-1])

    x = np.linalg.solve(a,b);

    #np.dot = producto de dos array
    print(x)
    print(np.allclose(np.dot(a,x),b));

"""
Permite resolver para una sola incognita
"""
def cramer_solution():
    A = np.array([[2,1],[-5,11]])
    b = np.array([7,-1])
    D = np.linalg.det(A)
    N = len(b)
    x = np.zeros(N)

    for i in range(0,N):
        Ai = A.copy()
        Ai.T[i] = b
        Di = np.linalg.det(Ai)
        x[i] = Di/D

    print("Solucion: ", x)
    print("Numero de iteraciones: ", N)

def gauss_jordan_solution():
# Método de Gauss-Jordan
# Solución a Sistemas de Ecuaciones
# de la forma A.X=B

# INGRESO
    A = np.array([[2,1],[-5,11]])

    B = np.array([[7.0],[-1.0]])

# Evitar truncamiento en operaciones
    A = np.array(A,dtype=float)

# Matriz aumentada
    AB = np.concatenate((A,B),axis=1)
    AB0 = np.copy(AB)

# Pivoteo parcial por filas
    tamano = np.shape(AB)
    n = tamano[0]
    m = tamano[1]

    # Para cada fila en AB
    for i in range(0,n-1,1):
        # columna desde diagonal i en adelante
        columna = abs(AB[i:,i])
        dondemax = np.argmax(columna)

    # dondemax no está en diagonal
        if (dondemax !=0):
            # intercambia filas
            temporal = np.copy(AB[i,:])
            AB[i,:] = AB[dondemax+i,:]
            AB[dondemax+i,:] = temporal

    AB1 = np.copy(AB)

# eliminacion hacia adelante
    for i in range(0,n-1,1):
        pivote = AB[i,i]
        adelante = i + 1
        for k in range(adelante,n,1):
            factor = AB[k,i]/pivote
            AB[k,:] = AB[k,:] - AB[i,:]*factor
    AB2 = np.copy(AB)

# elimina hacia atras
    ultfila = n-1
    ultcolumna = m-1
    for i in range(ultfila,0-1,-1):
        pivote = AB[i,i]
        atras = i-1
        for k in range(atras,0-1,-1):
            factor = AB[k,i]/pivote
            AB[k,:] = AB[k,:] - AB[i,:]*factor
            # diagonal a unos
        AB[i,:] = AB[i,:]/AB[i,i]
    X = np.copy(AB[:,ultcolumna])
    X = np.transpose([X])

# SALIDA
    print('Matriz aumentada:')
    print(AB0)
    print('Pivoteo parcial por filas')
    print(AB1)
    print('eliminacion hacia adelante')
    print(AB2)
    print('eliminación hacia atrás')
    print(AB)
    print('solución de X: ')
    print(X)

python_solution()
cramer_solution()
gauss_jordan_solution()


import numpy as np

class Layer:
    def __init__(self, name):
        self.name = name
        self.params = {}
        self.grads = {}

    def __call__(self, inputs):
        raise NotImplementedError

    def backward(self, grad):
        raise NotImplementedError

def xavier_init(input_dims, output_dims):

    return np.random.normal(
            scale=np.sqrt(2 / (input_dims + output_dims)),
            size=(input_dims, output_dims),
    )

def bias_init(output_dims):
    return np.zeros((output_dims,))

class Affine(Layer):
    def __init__(self, name, input_dims, output_dims):
        super().__init__(name)
        self.params['w'] = xavier_init(input_dims, output_dims)
        self.params['b'] = bias_init(output_dims) #np.random.randn(output_dims,)
        self.grads['w'] = np.zeros((input_dims, output_dims))
        self.grads['b'] = np.zeros(output_dims)

    def __call__(self, inputs):
        self.inputs = inputs
        return inputs@self.params['w'] + self.params['b']

    def backward(self, grad):
        # (m,n) @ (n,  1) = (m, 1)
        #print("gradshape", grad.shape)
        self.grads['w'] = self.inputs.T @ grad
        # (n, 1) @ (n, 1) = (1,)
        self.grads['b'] = np.sum(grad, axis=0)
        # return (n, 1) @ (1 , m) = (n, m)
        return grad@self.params['w'].T

class Activation(Layer):
    """
    An activation layer just applies a function
    elementwise to its inputs
    """
    def __init__(self, name, f, f_prime):
        super().__init__(name)
        self.f = f
        self.f_prime = f_prime

    def __call__(self, inputs):
        self.inputs = inputs
        return self.f(inputs)

    def backward(self, grad):
        # (N,1) * (N,1) = (N,1)
        return self.f_prime(self.inputs) * grad

sigmoid = lambda f: 1/(1+np.exp(-f))

def sigmoid_prime(f):
    h = sigmoid(f)
    return h*(1-h)

class Sigmoid(Activation):
    def __init__(self, name):
        super().__init__(name, sigmoid, sigmoid_prime)

def tanh(x):
    return np.tanh(x)

def tanh_prime(x):
    y = tanh(x)
    return 1 - y ** 2

class Tanh(Activation):
    def __init__(self, name):
        super().__init__(name, tanh, tanh_prime)

relu = lambda x: np.maximum(0, x)

def relu_prime(x):
    y = relu(x)
    dreludx = np.zeros(y.shape)
    dreludx[np.where(y > 0)] = 1
    return dreludx

class Relu(Activation):
    def __init__(self, name):
        super().__init__(name, relu, relu_prime)


import numpy as np

def euclidean(x, y):
    """
    Compute the Euclidean (`L2`) distance between two real vectors

    Notes
    -----
    The Euclidean distance between two vectors **x** and **y** is

    .. math::

        d(\mathbf{x}, \mathbf{y}) = \sqrt{ \sum_i (x_i - y_i)^2  }

    Parameters
    ----------
    x,y : :py:class:`ndarray <numpy.ndarray>` s of shape `(N,)`
        The two vectors to compute the distance between

    Returns
    -------
    d : float
        The L2 distance between **x** and **y**.
    """
    return np.sqrt(np.sum((x - y) ** 2))

def manhattan(x, y):
    """
    Compute the Manhattan (`L1`) distance between two real vectors

    Notes
    -----
    The Manhattan distance between two vectors **x** and **y** is

    .. math::

        d(\mathbf{x}, \mathbf{y}) = \sum_i |x_i - y_i|

    Parameters
    ----------
    x,y : :py:class:`ndarray <numpy.ndarray>` s of shape `(N,)`
        The two vectors to compute the distance between

    Returns
    -------
    d : float
        The L1 distance between **x** and **y**.
    """
    return np.sum(np.abs(x - y))

def chebyshev(x, y):
    """
    Compute the Chebyshev (:math:`L_\infty`) distance between two real vectors

    Notes
    -----
    The Chebyshev distance between two vectors **x** and **y** is

    .. math::

        d(\mathbf{x}, \mathbf{y}) = \max_i |x_i - y_i|

    Parameters
    ----------
    x,y : :py:class:`ndarray <numpy.ndarray>` s of shape `(N,)`
        The two vectors to compute the distance between

    Returns
    -------
    d : float
        The Chebyshev distance between **x** and **y**.
    """
    return np.max(np.abs(x - y))

def minkowski(x, y, p):
    """
    Compute the Minkowski-`p` distance between two real vectors.

    Notes
    -----
    The Minkowski-`p` distance between two vectors **x** and **y** is

    .. math::

        d(\mathbf{x}, \mathbf{y}) = \left( \sum_i |x_i - y_i|^p \\right)^{1/p}

    Parameters
    ----------
    x,y : :py:class:`ndarray <numpy.ndarray>` s of shape `(N,)`
        The two vectors to compute the distance between
    p : float > 1
        The parameter of the distance function. When `p = 1`, this is the `L1`
        distance, and when `p=2`, this is the `L2` distance. For `p < 1`,
        Minkowski-`p` does not satisfy the triangle inequality and hence is not
        a valid distance metric.

    Returns
    -------
    d : float
        The Minkowski-`p` distance between **x** and **y**.
    """
    return np.sum(np.abs(x - y) ** p) ** (1 / p)

def hamming(x, y):
    """
    Compute the Hamming distance between two integer-valued vectors.

    Notes
    -----
    The Hamming distance between two vectors **x** and **y** is

    .. math::

        d(\mathbf{x}, \mathbf{y}) = \\frac{1}{N} \sum_i \mathbb{1}_{x_i \\neq y_i}

    Parameters
    ----------
    x,y : :py:class:`ndarray <numpy.ndarray>` s of shape `(N,)`
        The two vectors to compute the distance between. Both vectors should be
        integer-valued.

    Returns
    -------
    d : float
        The Hamming distance between **x** and **y**.
    """
    return np.sum(x != y) / len(x)


'''
    Bisection Method
    Purpose:
        Computes approximate solution of f(x) = 0
    Input:
        f: function handle
        a, b: interval [a, b], such that f(a)*f(b)<0 and f(x) = 0 has a unique
        solution over [a, b]
        tol: tolerance error
    Output:
        Approximate solution

Last edited by Xiaozhou Li, September 9, 2018
'''

import numpy as np
import matplotlib.pyplot as plt

def bisect(f, a, b, tol):
    if (np.sign(f(a)*f(b)) >= 0):      # wrong input
        print ('$f(a)f(b) <0$ not satisfied')
        return
    fa = f(a)
    fb = f(b)
    while ((b - a)/2) > tol:
        c = (a + b)/2;
        print (c)
        fc = f(c);
        if fc == 0:                 # c is a solution, done
            return c
        if (np.sign(fc*fa) < 0):       # new interval [a, c]
            b = c;
            fb = fc
        else:                       # new interval [c, b]
            a = c
            fa = fc
    return (a + b)/2                #new midpoint is best estimate

#########################################################
# Example
def fun(x):
    return np.exp(-x) - np.sin(np.pi/2*x)

print (bisect(fun,0,1,1.e-12))


import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)

def run_ch04():
    X = 2 * np.random.rand(100, 1)
    y = 4 + 3 * X + np.random.rand(100, 1)

    X_b = np.c_[np.ones((100,1)), X]
    theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
    print(theta_best)

    X_new = np.array([[0], [2]])
    X_new_b = np.c_[np.ones((2,1)), X_new]
    y_predict = X_new_b.dot(theta_best)
    print(y_predict)

    plt.plot(X_new, y_predict, 'r-')
    plt.plot(X, y, 'b.')
    plt.axis([0, 2, 0, 15])
    plt.show()

    from sklearn.linear_model import LinearRegression
    lin_reg = LinearRegression()
    lin_reg.fit(X,y)
    print(lin_reg.intercept_, lin_reg.coef_)
    print(lin_reg.predict(X_new))

    theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
    print(theta_best_svd)

    print(np.linalg.pinv(X_b).dot(y))

    eta = 0.1  # learning rate
    n_iterations = 1000
    m = 100

    theta = np.random.randn(2, 1)  # random initialization

    for iteration in range(n_iterations):
        gradients = 2 / m * X_b.T.dot(X_b.dot(theta) - y)
        theta = theta - eta * gradients

    print(theta)

    m = len(X_b)

    n_epochs = 50
    t0, t1 = 5, 50  # learning schedule hyperparameters

    def learning_schedule(t):
        return t0 / (t + t1)

    theta = np.random.randn(2, 1)  # random initialization

    for epoch in range(n_epochs):
        for i in range(m):
            random_index = np.random.randint(m)
            xi = X_b[random_index:random_index + 1]
            yi = y[random_index:random_index + 1]
            gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
            eta = learning_schedule(epoch * m + i)
            theta = theta - eta * gradients

    from sklearn.linear_model import SGDRegressor

    sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1, random_state=42)
    sgd_reg.fit(X, y.ravel())

    print(sgd_reg.intercept_, sgd_reg.coef_)

    import numpy.random as rnd

    m = 100
    X = 6 * np.random.rand(m, 1) - 3
    y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1)

    plt.plot(X, y, "b.")
    plt.xlabel("$x_1$", fontsize=18)
    plt.ylabel("$y$", rotation=0, fontsize=18)
    plt.axis([-3, 3, 0, 10])
    # save_fig("quadratic_data_plot")
    plt.show()

    from sklearn.preprocessing import PolynomialFeatures
    poly_features = PolynomialFeatures(degree=2, include_bias=False)
    X_poly = poly_features.fit_transform(X)
    print(X[0])

    print(X_poly[0])

    from sklearn.metrics import mean_squared_error
    from sklearn.model_selection import train_test_split

    def plot_learning_curves(model, X, y):
        X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=10)
        train_errors, val_errors = [], []
        for m in range(1, len(X_train)):
            model.fit(X_train[:m], y_train[:m])
            y_train_predict = model.predict(X_train[:m])
            y_val_predict = model.predict(X_val)
            train_errors.append(mean_squared_error(y_train[:m], y_train_predict))
            val_errors.append(mean_squared_error(y_val, y_val_predict))

        plt.plot(np.sqrt(train_errors), "r-+", linewidth=2, label="train")
        plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="val")
        plt.legend(loc="upper right", fontsize=14)  # not shown in the book
        plt.xlabel("Training set size", fontsize=14)  # not shown
        plt.ylabel("RMSE", fontsize=14)  # not shown

    lin_reg = LinearRegression()
    plot_learning_curves(lin_reg, X, y)
    plt.axis([0, 80, 0, 3])  # not shown in the book
    # save_fig("underfitting_learning_curves_plot")  # not shown
    plt.show()  # not shown

    from sklearn.pipeline import Pipeline

    polynomial_regression = Pipeline([
        ("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
        ("lin_reg", LinearRegression()),
    ])

    plot_learning_curves(polynomial_regression, X, y)
    plt.axis([0, 80, 0, 3])  # not shown
    # save_fig("learning_curves_plot")  # not shown
    plt.show()  # not shown

    m = 20
    X = 3 * np.random.rand(m, 1)
    y = 1 + 0.5 * X + np.random.randn(m, 1) / 1.5
    X_new = np.linspace(0, 3, 100).reshape(100, 1)

    from sklearn.linear_model import Ridge
    ridge_reg = Ridge(alpha=1, solver="cholesky", random_state=42)
    ridge_reg.fit(X, y)
    ridge_reg.predict([[1.5]])

    ridge_reg = Ridge(alpha=1, solver="sag", random_state=42)
    ridge_reg.fit(X, y)
    ridge_reg.predict([[1.5]])

    from sklearn.linear_model import Ridge
    from sklearn.preprocessing import StandardScaler

    def plot_model(model_class, polynomial, alphas, **model_kargs):
        for alpha, style in zip(alphas, ("b-", "g--", "r:")):
            model = model_class(alpha, **model_kargs) if alpha > 0 else LinearRegression()
            if polynomial:
                model = Pipeline([
                    ("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
                    ("std_scaler", StandardScaler()),
                    ("regul_reg", model),
                ])
            model.fit(X, y)
            y_new_regul = model.predict(X_new)
            lw = 2 if alpha > 0 else 1
            plt.plot(X_new, y_new_regul, style, linewidth=lw, label=r"$\alpha = {}$".format(alpha))
        plt.plot(X, y, "b.", linewidth=3)
        plt.legend(loc="upper left", fontsize=15)
        plt.xlabel("$x_1$", fontsize=18)
        plt.axis([0, 3, 0, 4])

    plt.figure(figsize=(8, 4))
    plt.subplot(121)
    plot_model(Ridge, polynomial=False, alphas=(0, 10, 100), random_state=42)
    plt.ylabel("$y$", rotation=0, fontsize=18)
    plt.subplot(122)
    plot_model(Ridge, polynomial=True, alphas=(0, 10 ** -5, 1), random_state=42)

    # save_fig("ridge_regression_plot")
    plt.show()

    sgd_reg = SGDRegressor(penalty="l2", max_iter=1000, tol=1e-3, random_state=42)
    sgd_reg.fit(X, y.ravel())
    sgd_reg.predict([[1.5]])

    from sklearn.linear_model import Lasso
    lasso_reg = Lasso(alpha=0.1)
    lasso_reg.fit(X, y)
    lasso_reg.predict([[1.5]])

    from sklearn.linear_model import ElasticNet
    elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
    elastic_net.fit(X, y)
    elastic_net.predict([[1.5]])

    m = 100
    X = 6 * np.random.rand(m, 1) - 3
    y = 2 + X + 0.5 * X ** 2 + np.random.randn(m, 1)

    X_train, X_val, y_train, y_val = train_test_split(X[:50], y[:50].ravel(), test_size=0.5, random_state=10)

    from sklearn.base import clone

    poly_scaler = Pipeline([
        ("poly_features", PolynomialFeatures(degree=90, include_bias=False)),
        ("std_scaler", StandardScaler())
    ])

    X_train_poly_scaled = poly_scaler.fit_transform(X_train)
    X_val_poly_scaled = poly_scaler.transform(X_val)

    sgd_reg = SGDRegressor(max_iter=1, tol=-np.infty, warm_start=True,
                           penalty=None, learning_rate="constant", eta0=0.0005, random_state=42)

    minimum_val_error = float("inf")
    best_epoch = None
    best_model = None
    for epoch in range(1000):
        sgd_reg.fit(X_train_poly_scaled, y_train)  # continues where it left off
        y_val_predict = sgd_reg.predict(X_val_poly_scaled)
        val_error = mean_squared_error(y_val, y_val_predict)
        if val_error < minimum_val_error:
            minimum_val_error = val_error
            best_epoch = epoch
            best_model = clone(sgd_reg)

    from sklearn import datasets
    iris = datasets.load_iris()
    list(iris.keys())

    print(iris.DESCR)

    X = iris["data"][:, 3:]  # petal width
    y = (iris["target"] == 2).astype(np.int)  # 1 if Iris virginica, else 0

    from sklearn.linear_model import LogisticRegression
    log_reg = LogisticRegression(solver="lbfgs", random_state=42)
    log_reg.fit(X, y)

    X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
    y_proba = log_reg.predict_proba(X_new)

    plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica")
    plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2, label="Not Iris virginica")

    X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
    y_proba = log_reg.predict_proba(X_new)
    decision_boundary = X_new[y_proba[:, 1] >= 0.5][0]

    plt.figure(figsize=(8, 3))
    plt.plot(X[y == 0], y[y == 0], "bs")
    plt.plot(X[y == 1], y[y == 1], "g^")
    plt.plot([decision_boundary, decision_boundary], [-1, 2], "k:", linewidth=2)
    plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica")
    plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2, label="Not Iris virginica")
    plt.text(decision_boundary + 0.02, 0.15, "Decision  boundary", fontsize=14, color="k", ha="center")
    plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
    plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g')
    plt.xlabel("Petal width (cm)", fontsize=14)
    plt.ylabel("Probability", fontsize=14)
    plt.legend(loc="center left", fontsize=14)
    plt.axis([0, 3, -0.02, 1.02])
    # save_fig("logistic_regression_plot")
    plt.show()

    print(decision_boundary)
    print(log_reg.predict([[1.7], [1.5]]))

    X = iris["data"][:, (2, 3)]  # petal length, petal width
    y = iris["target"]

    softmax_reg = LogisticRegression(multi_class="multinomial", solver="lbfgs", C=10, random_state=42)
    softmax_reg.fit(X, y)

    print(softmax_reg.predict([[5, 2]]))

    print(softmax_reg.predict_proba([[5, 2]]))


import matplotlib.pyplot as plt
import numpy as np
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split

size = 1000

x = 5.0 * np.random.randn(size)

y = 1.051 * x + 2.05 * np.random.random(size)

x = x[:, np.newaxis]
y = y[:, np.newaxis]

#Criando o objeto que realizará a regressão linear
regr = linear_model.LinearRegression()

#Quebrando o arranjo em dados de treino e teste
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.33, random_state=42)

#Treinando o modelo usando os dados de treino definidos acima
regr.fit(X_train,y_train)

#Realizar previsões utilizando o arranjo de testes
y_pred = regr.predict(X_test)

#Exibindo os coeficientes
print('Coeficiente angular: \n', regr.coef_)
print('Coeficiente linear: \n', regr.intercept_)

#Determinando erro quadrático médio
mse = mean_squared_error(y_test, y_pred)
print("Erro quadrático médio= %.2f" %mse)

#Determinando a variância
variancia = r2_score(y_test, y_pred)
print("Variância= %.2f" %variancia)

#Graficando os dados
plt.figure(1)
plt.plot(x,y,'*',color='black',label="Dado original")
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Regressão Linear')
plt.show()

plt.figure(2)
plt.scatter(X_test,y_test,color='black',label="Dados de teste")
plt.plot(X_test,y_pred,color='blue',label="Dados ajustado")
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Regressão Linear')
plt.show()


import numpy  as np
import matplotlib.pyplot as plt

"""
x_list = [1,3,5,7,8]
y_list = [5,2,4,1,2]
"""
x_list = [0.0,0.6,0.9]
y_list = [np.log(1+0.0),np.log(1+0.6),np.log(1+0.9)]

def PolyLagrange(x_list,y_list,x):
    sum = 0

    for i in  range(0, len(x_list)):
        prod = 1
        for j in range(0,len(x_list)):
            if(j != i):
                prod = prod*(x - x_list[j])/(x_list[i] - x_list[j])
        sum += y_list[i]*prod

    return sum

print(PolyLagrange(x_list,y_list,0.45))
print(np.log(1+0.45))

"""
x_array = np.linspace(min(x_list), max(x_list),100)
y_array = []

for i in range(0,len(x_array)):
    y_array.append(PolyLagrange(x_list,y_list,x_array[i]))

fig = plt.figure()
ax = plt.axes()
ax.plot(x_list,y_list,'*')
ax.plot(x_array, y_array)
plt.show()
"""


# 0.5*dy/dt + y(t) = 2*u(t),	y(t=0) = 0
# tau = 0.5 sec & k = 2

import numpy as np
import matplotlib.pyplot as plt

from scipy.integrate import odeint
from scipy.signal import step
from scipy.signal import TransferFunction as tf
from scipy.signal import StateSpace as ss

#########################
# SIMULATION'S SETTINGS #
#########################
k, tau = 2.0, 0.5

############################
# ODE: NUMERIC INTEGRATION #
############################
# 0.5*dy/dt + y(t) = 2*u(t)	<===>	dy/dt = 1/0.5*(-y + 2*u)
def mySys(x, t):
	u = 1
	return 1/0.5*(-x + 2*u)

tspan = np.linspace(0.0, 7.0, 100)
y1 = odeint(mySys, 0, tspan)

######
# TF #
######
hTF = tf([k], [tau, 1])
_, y2 = step(hTF, T=tspan)

######
# SS #
######
A, B, C, D = -1/tau, k/tau, 1, 0
hSS = ss(A, B, C, D)
_, y3 = step(hSS, T=tspan)

plt.plot(tspan, y1, 'r--', linewidth=2, label='ODE')
plt.plot(tspan, y2, 'g--', linewidth=2, label='TF')
plt.plot(tspan, y3, 'b--', linewidth=2, label='SS')

plt.title('Step response of 1st order LTI plant')
plt.xlabel('t (sec)')
plt.ylabel('y(t)')
plt.grid()
plt.legend(loc='best')
plt.show()


"""Utilities and atomic functions
"""
import math
import numpy as np

def spherical2cartesian(a):
    """Transform the spherical coordinate system `a` to cartesian
    """
    ndim = len(a)
    x = np.empty(ndim+1)

    asin = np.sin(a)
    acos = np.cos(a)

    for i in range(ndim):
        x[i] = acos[i]
        for j in range(i):
            x[i] *= asin[j]

    x[-1] = 1.0
    for j in range(ndim):
        x[-1] *= asin[j]

    return x

def cartesian2spherical(v):
    """Transform the cartesian coordinate system `v` to spherical
    """
    ndim = len(v)
    a = np.empty(ndim-1)

    v2 = np.power(v, 2)

    for i in range(ndim-2):
        a[i] = math.acos(v[i]/np.sqrt(np.sum(v2[i:])))

    d = math.acos(v[-2]/np.sqrt(v2[-1] + v2[-2]))
    if v[-1] >= 0.0:
        a[-1] = d
    else:
        a[-1] = 2*np.pi - d

    return a

def orthonormal_basis(a):
    """Determine an orthonormal basis with the first vector `a`
    """
    # a is the first direction
    d = len(a)

    # orthonormal basis of a
    U = np.eye(d)
    U[:, 0] = a  # the first column is the first

    # Q is the orthonormal component having
    # the first column the direction a (normalised)
    Q, R = np.linalg.qr(U)

    return Q


# -*- coding: utf-8 -*-
"""Student Activity: Financial Analysis using NPV.

This script will choose the optimal project scenario to
undertake based on max NPV values.
"""

# Import the NumPy library
import numpy_financial as npf

# Discount Rate
discount_rate = .1

# Initial Investment, Cash Flow 1, Cash Flow 2, Cash Flow 3, Cash Flow 4
cash_flows_conservative = [-1000, 400, 400, 400, 400]
cash_flows_neutral = [-1500, 600, 600, 600, 600]
cash_flows_aggressive = [-2250, 800, 800, 800, 800]

# Initialize dictionary to hold NPV return values
npv_dict = {}

# Calculate the NPV for each scenario
npv_dict['conservative'] = npf.npv(discount_rate, cash_flows_conservative)
npv_dict['neutral'] = npf.npv(discount_rate, cash_flows_neutral)
npv_dict['aggressive'] = npf.npv(discount_rate, cash_flows_aggressive)

# Initialize variables
max_value = 0
max_key = 0

# Iterate over the dictionary and find the max key-value pair
for key, value in npv_dict.items():
    print(f"{key} {value}")

    if max_value == 0:
        max_value = value
        max_key = key
    elif value > max_value:
        max_value = value
        max_key = key

# Print out the optimal project scenario with the highest NPV value
print("---------------------------------------------------------")
print(f"The project scenario with the max NPV value is '{max_key}' with a NPV of {max_value}")


# -*- coding: utf-8 -*-
"""Day 76 - Computation with NumPy and N Dimensional Arrays.ipynb

Automatically generated by Colaboratory.

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

# Introduction

In this notebook we'll learn how to use NumPy to work with numerical data.

# Import Statements
"""

import numpy as np

import matplotlib.pyplot as plt
from scipy import misc # contains an image of a racoon!
from PIL import Image # for reading image files

"""# Understanding NumPy's ndarray

NumPy's most amazing feature is the **powerful** ndarray.

#### 1-Dimensional Arrays (Vectors)
"""

my_array = np.array([1.1, 9.2, 8.1, 4.7])

my_array.shape

my_array[2]

my_array.ndim

"""#### 2-Dimensional Arrays (Matrices)"""

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

print(f'array_2d has {array_2d.ndim} dimensions')
print(f'Its shape is {array_2d.shape}')
print(f'It has {array_2d.shape[0]} rows and {array_2d.shape[1]} columns')
print(array_2d)

array_2d[1,2]

array_2d[0, :]

"""#### N-Dimensional Arrays (Tensors)

**Challenge**:
* How many dimensions does the array below have?
* What is its shape (i.e., how many elements are along each axis)?
* Try to access the value `18` in the last line of code.
* Try to retrieve a 1 dimensional vector with the values `[97, 0, 27, 18]`
* Try to retrieve a (3,2) matrix with the values `[[ 0,  4], [ 7,  5], [ 5, 97]]`

*Hint*: You can use the `:` operator just as with Python Lists.
"""

mystery_array = np.array(
    [
      [
        [0, 1, 2, 3],
        [4, 5, 6, 7]
      ],
      [
        [7, 86, 6, 98],
        [5, 1, 0, 4]
      ],
      [
        [5, 36, 32, 48],
        [97, 0, 27, 18]
      ]
     ]
   )

# Note all the square brackets!

print(f'mystery_array has {mystery_array.ndim} dimensions')
print(f'Its shape is {mystery_array.shape}')
print(f'Access the value 18 in the last line of code: {mystery_array[2, 1, 3]}')
print(f'Retrieve a 1 dimensional vector with the values [97, 0, 27, 18]:\n {mystery_array[2, 1]}')
print(f'Retrieve a (3,2) matrix with the values [[ 0, 4], [ 7, 5], [ 5, 97]]:\n {mystery_array[:, :, 0]}')

"""# NumPy Mini-Challenges

#### **Challenge 1**: Use [`.arange()`](https://numpy.org/devdocs/reference/generated/numpy.arange.html)to createa a vector `a` with values ranging from 10 to 29. You should get this:

`print(a)`

`[10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]`
"""

a = np.arange(start=10, stop=30, step=1)
print(a)

"""#### **Challenge 2**: Use Python slicing techniques on `a` to:
* Create an array containing only the last 3 values of `a`
* Create a subset with only the 4th, 5th, and 6th values
* Create a subset of `a` containing all the values except for the first 12 (i.e., `[22, 23, 24, 25, 26, 27, 28, 29]`)
* Create a subset that only contains the even numbers (i.e, every second number)
"""

a[-3:]

a[3:6]

a[12:]

a[::2]

"""#### **Challenge 3**:Reverse the order of the values in `a`, so that the first element comes last:

`[29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13,
       12, 11, 10]`

If you need a hint, you can check out this part of the [NumPy beginner's guide](
https://numpy.org/devdocs/user/absolute_beginners.html#how-to-reverse-an-array)
"""

np.flip(a)

"""#### **Challenge 4**: Print out all the indices of the non-zero elements in this array: [6,0,9,0,0,5,0]"""

arr = np.array([6, 0, 9, 0, 0, 5, 0])
print(arr)

print(np.where(arr != 0))

"""#### **Challenge 5**: Use NumPy to generate a 3x3x3 array with random numbers

Hint: Use the [`.random()` function](https://numpy.org/doc/stable/reference/random/index.html?highlight=random#module-numpy.random)
"""

array_3d = np.random.randint(0, 100, size=(3, 3, 3))
print(array_3d)

"""#### **Challenge 6**: Use [`.linspace()`](https://numpy.org/doc/stable/reference/generated/numpy.linspace.html) to create a vector `x` of size 9 with values spaced out evenly between 0 to 100 (both included)."""

array_even = np.linspace(0, 100, num=9, dtype=np.int16)
print(array_even)

"""#### **Challenge 7**: Use [`.linspace()`](https://numpy.org/doc/stable/reference/generated/numpy.linspace.html) to create another vector `y` of size 9 with values between -3 to 3 (both included). Then plot `x` and `y` on a line chart using Matplotlib."""

array_even = np.linspace(-3, 3, num=9, dtype=np.int16)
plt.plot(array_even)
plt.xlabel('Size')
plt.ylabel('Value')
plt.show()

"""#### **Challenge 8**: Use NumPy to generate an array called `noise` with shape 128x128x3 that has random values. Then use Matplotlib's [`.imshow()`](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html) to display the array as an image. """

noise = np.random.randint(0, 255, size=(128, 128, 3))
plt.imshow(noise)
plt.show()

"""# Linear Algebra with Vectors"""

v1 = np.array([4, 5, 2, 7])
v2 = np.array([2, 1, 3, 3])

# Python Lists vs ndarrays
list1 = [4, 5, 2, 7]
list2 = [2, 1, 3, 3]

v1 + v2

v1 * v2

"""# Broadcasting and Scalars

"""

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

array_2d + 10

array_2d * 5

"""# Matrix Multiplication with @ and .matmul()

"""

a1 = np.array([[1, 3],
               [0, 1],
               [6, 2],
               [9, 7]])

b1 = np.array([[4, 1, 3],
               [5, 8, 5]])

print(f'{a1.shape}: a has {a1.shape[0]} rows and {a1.shape[1]} columns.')
print(f'{b1.shape}: b has {b1.shape[0]} rows and {b1.shape[1]} columns.')
print('Dimensions of result: (4x2)*(2x3)=(4x3)')

"""**Challenge**: Let's multiply `a1` with `b1`. Looking at the wikipedia example above, work out the values for c12 and c33 on paper. Then use the [`.matmul()`](https://numpy.org/doc/stable/reference/generated/numpy.matmul.html) function or the `@` operator to check your work. """

np.matmul(a1, b1)

a1 @ b1

"""# Manipulating Images as ndarrays

"""

img = misc.face()

plt.imshow(img)
plt.show()

"""**Challenge**: What is the data type of `img`? Also, what is the shape of `img` and how many dimensions does it have? What is the resolution of the image?"""

type(img)

img.shape

img.ndim

print(f'Resolution of the img: {img.shape[0]} x {img.shape[1]}')

"""**Challenge**: Convert the image to black and white. The values in our `img` range from 0 to 255.
* Divide all the values by 255 to convert them to sRGB, where all the values are between 0 and 1.
* Next, multiply the sRGB array by the `grey_vals` to convert the image to grey scale.
* Finally use Matplotlib's [`.imshow()`](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html) together with the colormap parameter set to gray `cmap=gray` to look at the results.
"""

grey_vals = np.array([0.2126, 0.7152, 0.0722])

img_array = img / 255
print(img_array)

img_grey_scale = img_array @ grey_vals
plt.imshow(img_grey_scale, cmap='gray')
plt.show()

"""**Challenge**: Can you manipulate the images by doing some operations on the underlying ndarrays? See if you can change the values in the ndarray so that:

1) You flip the grayscale image upside down

2) Rotate the colour image

3) Invert (i.e., solarize) the colour image. To do this you need to converting all the pixels to their "opposite" value, so black (0) becomes white (255).

#### Challenge Solutions
"""

plt.imshow(np.flip(img_grey_scale), cmap='gray')
plt.show()

plt.imshow(np.rot90(img))
plt.show()

img_solar = 255 - img
plt.imshow(img_solar)
plt.show()

"""# Use your Own Image!"""

file_name = 'yummy_macarons.jpg'

my_img = Image.open(file_name)
img_array = np.array(my_img)

img_array.ndim

img_array.shape

"""#### Use PIL to open """

plt.imshow(img_array)
plt.show()

plt.imshow(255 - img_array)
plt.show()

plt.imshow((img_array / 255) @ grey_vals, cmap='gray')
plt.show()

plt.imshow(np.flip(img_array / 255 @ grey_vals), cmap='gray')
plt.show()


import numpy as np

np.random.seed(0) # seed for reproducibility

x1 = np.random.randint(10, size=6) #1d array
x2 = np.random.randint(10, size=(3,4)) #2d array
x3 = np.random.randint(10, size=(3,4,5)) #3d array

print("x3 ndim:", x3.ndim)
print("x3 shape:", x3.shape)
print("x3 size:", x3.size)

print("dytpe:", x3.dtype)

print("itemsize:", x3.itemsize,  "bytes")
print("nbytes:", x3.nbytes, "bytes")

print(x2[:,0]) # first column of x2
print(x2[0,:]) #first row of x2

print(x2[0]) # equivelent to x2[0, :]
print("x2:")
print(x2)

#views

x2_sub = x2[:2, :2]
print("x2 sub array:")
print(x2_sub)

x2_sub[0, 0] = 99 #this will change the parent array as it is a view, not a copy
print(x2_sub)
print(x2)

#copying arrays

x2_sub_copy = x2[:2,:2].copy()
print(x2_sub_copy)

x2_sub_copy[0,0] = 42
print(x2_sub_copy)
print(x2)

#reshaping arrays

grid = np.arange(1,10).reshape((3,3))
print(grid)

x = np.array([1,2,3])

print(" ")
#row vector via reshape
print(x.reshape((1,3)))

#row vector via newaxis
print(x[np.newaxis, :])

#column vector via reshape
print(x.reshape((3,1)))

#column vector from newaxis
print(x[:, np.newaxis])

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

z = [7,8,9]

print(np.concatenate([x,y,z]))

grid = np.array([[1,2,3],[4,5,6]])
print()
print(grid)
print(np.concatenate([grid,grid]))

print(np.concatenate([grid, grid], axis=1))

grid = np.array([[9,8,7],[6,5,4]])
#vertically stack arrays
print(np.vstack([x,grid]))
print("horizontally stack arrays")
y = np.array([[99],[99]])
print(np.hstack([grid,y]))

#np.dstack goes along third axis

print("splitting arrays")
x = [1,2,3,4,5,6,7,8]
print(x)
x1, x2, x3 = np.split(x, [3,5])
print("split [3,5]")
print(x1)
print(x2)
print(x3)

#hsplit, vsplit simmilar

grid = np.arange(16).reshape((4,4))
print(grid)
upper, lower = np.vsplit(grid, [2])
print("vsplit [2]")
print(upper)
print(lower)

left, right = np.hsplit(grid, [2])
print("hsplit [2]")
print(left)
print(right)


## 2. The mean as the center ##

# Make a list of values
values = [2, 4, 5, -1, 0, 10, 8, 9]
# Compute the mean of the values
values_mean = sum(values) / len(values)
# Find the difference between each of the values and the mean by subtracting the mean from each value.
differences = [i - values_mean for i in values]
# This equals 0.  Try changing the values around and verifying that it equals 0 if you want.
print(sum(differences))

# We can use the median function from numpy to find the median
# The median is the "middle" value in a set of values -- if you sort the values in order, it's the one in the center (or the average of the two in the center if there are an even number of items in the set)
# You'll see that the differences from the median don't always add to 0.  You might want to play around with this and think about why that is.
from numpy import median
values_median = median(values)
median_difference_sum = 0
for i in range(len(values)):
    val = values[i] - values_median
    median_difference_sum += val

## 3. Finding variance ##

import matplotlib.pyplot as plt
import pandas as pd
# The nba data is loaded into the nba_stats variable.
# Find the mean value of the column
pf_mean = nba_stats["pf"].mean()
# Initialize variance at zero
variance = 0
# Loop through each item in the "pf" column
for p in nba_stats["pf"]:
    # Calculate the difference between the mean and the value
    difference = p - pf_mean
    # Square the difference -- this ensures that the result isn't negative
    # If we didn't square the difference, the total variance would be zero
    # ** in python means "raise whatever comes before this to the power of whatever number is after this"
    square_difference = difference ** 2
    # Add the difference to the total
    variance += square_difference
# Average the total to find the final variance.
variance = variance / len(nba_stats["pf"])
pt_mean = nba_stats['pts'].mean()
va = 0
for i in nba_stats['pts']:
    va += (i - pt_mean)**2
point_variance =  va / len(nba_stats['pts'])

## 4. Order of operations ##

# You might be wondering why multiplication and division are on the same level.
# It doesn't matter whether we do the multiplication first, or the division first -- the answer here will always be the same.
# In this case, we need to think of division as multiplication by a fraction -- otherwise, we'll be dividing more than we want to.
# Create a formula
a = 5 * 5 / 2
# Multiply by 1/2 instead of dividing by 2 -- the result is the same (2/2 == 2 * 1/2)
a_subbed = 5 * 5 * 1/2
a_mul_first = 25 * 1/2
a_div_first = 5 * 2.5
print(a_mul_first == a_div_first)

# The same thing is true for subtraction and addition
# In this case, we need to convert subtraction into adding a negative number -- if we don't we'll end up subtracting more than we expect
b = 10 - 8 + 5
# Add -8 instead of subtracting 8
b_subbed = 10 + -8 + 5
b_sub_first = 2 + 5
b_add_first = 10 + -3
print(b_sub_first == b_add_first)

c = 10 / 2 + 5
d = 3 - 1 / 2 * 2
c = 10 / 2 * 5
d = 3 - 1 / 2 - 2

## 5. Using parentheses ##

a = 50 * 50 - 10 / 5
a_paren = 50 * (50 - 10) / 5
# If we put multiple operations inside parentheses, the order of operations is used inside to determine the order.
a_paren = 50 * (50 - 10 / 5)

b = 10 * (10 + 100)
c = (8 - 6) * 100

## 6. Fractional powers ##

a = 5 ** 2
# Raise to the fourth power
b = 10 ** 4

# Take the square root ( 3 * 3 == 9, so the answer is 3)
c = 9 ** (1/2)

# Take the cube root (4 * 4 * 4 == 64, so 4 is the cube root)
d = 64 ** (1/3)
e = 11**5
f = 10000 ** (1/4)

## 7. Calculating standard deviation ##

# The nba stats are loaded into the nba_stats variable.
def std_dev(data):
    d_mean = data.mean()
    v = 0
    for i in data:
        v += (i - d_mean)**2
    v = v/len(data)
    return(v**(1/2))

mp_dev = std_dev(nba_stats['mp'])
ast_dev = std_dev(nba_stats['ast'])

## 8. Find standard deviation distance ##

import matplotlib.pyplot as plt

plt.hist(nba_stats["pf"])
mean = nba_stats["pf"].mean()
plt.axvline(mean, color="r")
# We can calculate standard deviation by using the std() method on a pandas series.
std_dev = nba_stats["pf"].std()
# Plot a line one standard deviation below the mean
plt.axvline(mean - std_dev, color="g")
# Plot a line one standard deviation above the mean
plt.axvline(mean + std_dev, color="g")

# We can see how much of the data points fall within 1 standard deviation of the mean
# The more that falls into this range, the less spread out the data is
plt.show()

# We can calculate how many standard deviations a data point is from the mean by doing some subtraction and division
# First, we find the total distance by subtracting the mean
total_distance = nba_stats["pf"][0] - mean
# Then we divide by standard deviation to find how many standard deviations away the point is.
standard_deviation_distance = total_distance / std_dev

point_10 = nba_stats["pf"][9]
point_100 = nba_stats["pf"][99]
point_10_std = (point_10 - mean) / std_dev
point_100_std = (point_100 - mean) / std_dev

## 9. Working with the normal distribution ##

import numpy as np
import matplotlib.pyplot as plt
# The norm module has a pdf function (pdf stands for probability density function)
from scipy.stats import norm

# The arange function generates a numpy vector
# The vector below will start at -1, and go up to, but not including 1
# It will proceed in "steps" of .01.  So the first element will be -1, the second -.99, the third -.98, all the way up to .99.
points = np.arange(-1, 1, 0.01)

# The norm.pdf function will take points vector and turn it into a probability vector
# Each element in the vector will correspond to the normal distribution (earlier elements and later element smaller, peak in the center)
# The distribution will be centered on 0, and will have a standard devation of .3
probabilities = norm.pdf(points, 0, .3)

# Plot the points values on the x axis and the corresponding probabilities on the y axis
# See the bell curve?
plt.plot(points, probabilities)
plt.show()

pt = np.arange(-1,1,0.1)
prob = norm.pdf(pt,0,2)
plt.plot(pt,prob)
plt.show()

## 10. Normal distribution deviation ##

# Housefly wing lengths in millimeters
wing_lengths = [36, 37, 38, 38, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 53, 53, 54, 55]
mean = sum(wing_lengths)/len(wing_lengths)
variances = [(i - mean)**2 for i in wing_lengths]
variance = sum(variances)/len(variances)
std = variance ** (1/2)
std= [(i - mean) /std for i in wing_lengths]
def within_percentage(deviations, count):
    within = [i for i in deviations if i <= count and i >= -count]
    count = len(within)
    return count / len(deviations)
within_one_percentage = within_percentage(std, 1)
within_two_percentage = within_percentage(std, 2)
within_three_percentage = within_percentage(std, 3)

## 11. Plotting correlations ##

import matplotlib.pyplot as plt

# This is plotting field goals attempted (number of shots someone takes in a season) vs point scored in a season
# Field goals attempted is on the x-axis, and points is on the y-axis
# As you can tell, they are very strongly correlated -- the plot is close to a straight line.
# The plot also slopes upward, which means that as field goal attempts go up, so do points.
# That means that the plot is positively correlated.
plt.scatter(nba_stats["fga"], nba_stats["pts"])
plt.show()

# If we make points negative (so the people who scored the most points now score the least, because 3000 becomes -3000), we can change the direction of the correlation
# Field goals are negatively correlated with our new "negative" points column -- the more free throws you attempt, the less negative points you score.
# We can see this because the correlation line slopes downward.
plt.scatter(nba_stats["fga"], -nba_stats["pts"])
plt.show()

# Now, we can plot total rebounds (number of times someone got the ball back for their team after someone shot) vs total assists (number of times someone helped another person score)
# These are uncorrelated, so you don't see the same nice line as you see with the plot above.
plt.scatter(nba_stats["trb"], nba_stats["ast"])
plt.show()
plt.scatter(nba_stats["fta"], nba_stats["pts"])
plt.show()
plt.scatter(nba_stats["stl"], nba_stats["pf"])
plt.show()

## 12. Measuring correlation ##

from scipy.stats.stats import pearsonr

# The pearsonr function will find the correlation between two columns of data.
# It returns the r value and the p value.  We'll learn more about p values later on.
r, p_value = pearsonr(nba_stats["fga"], nba_stats["pts"])
# As we can see, this is a very high positive r value -- close to 1
print(r)

# These two columns are much less correlated
r, p_value = pearsonr(nba_stats["trb"], nba_stats["ast"])
# We get a much lower, but still positive, r value
print(r)
r_fta_pts, p_value = pearsonr(nba_stats["fta"], nba_stats["pts"])
r_stl_pf, p_value = pearsonr(nba_stats["stl"], nba_stats["pf"])

## 13. Calculate covariance ##

# The nba_stats variable has been loaded.
def covariance(x, y):
    x_mean = sum(x) / len(x)
    y_mean = sum(y) / len(y)
    x_diffs = [i - x_mean for i in x]
    y_diffs = [i - y_mean for i in y]
    codeviates = [x_diffs[i] * y_diffs[i] for i in range(len(x))]
    return sum(codeviates) / len(codeviates)

cov_stl_pf = covariance(nba_stats["stl"], nba_stats["pf"])
cov_fta_pts = covariance(nba_stats["fta"], nba_stats["pts"])

## 14. Calculate correlation ##

from numpy import cov
# The nba_stats variable has already been loaded.
r_fta_blk = cov(nba_stats["fta"], nba_stats["blk"])[0,1] / ((nba_stats["fta"].var() * nba_stats["blk"].var())** (1/2))
r_ast_stl = cov(nba_stats["ast"], nba_stats["stl"])[0,1] / ((nba_stats["ast"].var() * nba_stats["stl"].var())** (1/2))

Probability Statistics Beginner/Standard deviation and correlation-14.py


from autograd.blocks.block import SimpleBlock
import numpy as np

class exp(SimpleBlock):
    """
    exponential function on vectors
    """
    def data_fn(self, args):
        new_data = np.exp(args.data)
        return (new_data)

    def gradient_fn(self, args):
        grad = np.exp(args.data)
        return (grad)

class log(SimpleBlock):
    """
    log function on vectors
    """
    def __init__(self, base=np.e):
        super().__init__()
        self.base=base

    def data_fn(self, args):
        if self.base == np.e:
            new_data = np.log(args.data)
        elif self.base == 10:
            new_data = np.log10(args.data)
        elif self.base == 2:
            new_data = np.log2(args.data)
        else:
            if self.base <0 or self.base==0:
                raise ValueError('wrong base, negative or base=1')
            else:
                new_data = np.log(args.data)/np.log(self.base)

        return (new_data)

    def gradient_fn(self, args):
        grad = 1/(args.data*np.log(self.base))
        return (grad)

class sqrt(SimpleBlock):
    """
    square root function on vectors
    """
    def data_fn(self, args):
        new_data = np.sqrt(args.data)
        return (new_data)

    def gradient_fn(self, args):
        grad = 1/(2*np.sqrt(args.data))
        return (grad)

class logistic(SimpleBlock):
    """
    exponential function on vectors
    """
    def data_fn(self, args):
        new_data = 1/(1+np.exp(-args.data))
        return (new_data)

    def gradient_fn(self, args):
        expo = np.exp(args.data)
        grad = expo/((1+expo)**2)

        return (grad)


"""
  Question 1: Partial Harmonic Series
"""
import numpy as np
import matplotlib.pyplot as plt
import pathlib
#%% Functions
# Naive (using loop)
def f(N):
  A = 0
  for n in range(1, N + 1):
    A += 1 / n
  return A

# Vectorized (to get faster results)
fv = lambda n: np.sum(1 / n)

#%% Experiments. Compute some values of series
EXP = 25
Ns = np.array([2 ** i for i in range(EXP+1)]) # N = {2^0, 2^1, ..., 2^EXP}

# Using loop (slow but for N too large may be stored in memory...)
#As = np.array([f(n) for n in Ns], dtype=np.float32)
#Ad = np.array([f(n) for n in Ns], dtype=np.float64)

# Using vectorized implementation (faster but dangerous with memory usage...)
As = np.array([fv(np.arange(1, n+1)) for n in Ns], dtype=np.float32) # Compute summation with single precision
Ad = np.array([fv(np.arange(1, n+1)) for n in Ns], dtype=np.float64) # Compute summation with double precision
diff = np.abs(As-Ad) # Absolute value of difference between single and double precision (a simple measure)

#%% Plot both summations
plt.plot(Ns, As, label="Single precision")
plt.plot(Ns, Ad, label="Double precision")
plt.grid(True)
plt.legend()
plt.show()
#%% Plot difference
plt.plot(Ns, diff, label=r'$|A_d - A_s$|')
plt.grid(True)
plt.yscale('log')
plt.xscale('log', basex=2)
plt.legend()
plt.show()

#%% Data to save
series = np.zeros((len(Ns), 4))
series[:,0] = Ns
series[:,1] = As
series[:,2] = Ad
series[:,3] = diff
#%% Save data for plots
DIR = 'data/1/' # Directory name
pathlib.Path(DIR).mkdir(parents=True, exist_ok=True) # Create Folder
np.savetxt(DIR + 'series.csv', series, fmt='%.16f', delimiter=',', header='N,As,Ad,dif', comments="") # Save data


import numpy as np

def central_difference_z(grid, Nz, Nx, dz, order=1):
    """
    Take the differentiation in z of a "2D z-x grid" via central difference.
    """
    if (order==1):
        grid_temp = np.vstack((np.zeros(Nx), grid, np.zeros(Nx)))

        prime_grid = (grid_temp[2:Nz+2] - grid_temp[0:Nz]) / (2*dz)

        # Endpoints with 2nd order accuracy
        prime_grid[0]    = ((-3/2)*grid[0]   + 2*grid[1]    - (1/2)*grid[2]) / dz
        prime_grid[Nz-1] = ((1/2)*grid[Nz-3] - 2*grid[Nz-2] + (3/2)*grid[Nz-1]) / dz

    elif (order==2):
        grid_temp = np.vstack((np.zeros(Nx), np.zeros(Nx), grid, np.zeros(Nx), np.zeros(Nx)))

        prime_grid = (-   grid_temp[4:Nz+4] \
                      + 8*grid_temp[3:Nz+3] \
                      - 8*grid_temp[1:Nz+1] \
                      +   grid_temp[0:Nz]) / (12*dz)

    elif (order==3):
        grid_temp = np.vstack((np.zeros(Nx),np.zeros(Nx),np.zeros(Nx), grid, np.zeros(Nx),np.zeros(Nx),np.zeros(Nx)))

        prime_grid = (    grid_temp[6:Nz+6] \
                     -  9*grid_temp[5:Nz+5] \
                     + 45*grid_temp[4:Nz+4] \
                     - 45*grid_temp[2:Nz+2] \
                     +  9*grid_temp[1:Nz+1] \
                     -    grid_temp[0:Nz] ) / (60*dz)
    else:
        raise ValueError(' order value has to be 1 or 2 or 3!! ')

    return prime_grid


import math
import random
import numpy as np
import matplotlib.pyplot as plt

xdim = 1024  # x dimension of image
ydim = 1024  # y dimension of image

# smooths the noise using interpolation
def fade(t):
    return 3*(t**2) -2*(t**3)

# finds closes edge to a pixel
def findEdge(x, y, size_box):
    if x % size_box == 0 and y % size_box == 0:
        return (math.ceil(x/size_box)+1,math.ceil(y/size_box)+1)
    elif x % size_box == 0 :
        return (math.ceil(x/size_box)+1,math.ceil(y/size_box))
    elif y % size_box == 0 :
        return (math.ceil(x/size_box),math.ceil(y/size_box)+1)
    else:
        return math.ceil(x/size_box), math.ceil(y/size_box) # returns outer 2 edges contain x and y

# creates the creates the perlin noise array
def makeNoise(num_boxes):
    global xdim, ydim
    pixels = np.zeros((xdim,ydim))
    size_box = int(xdim/num_boxes)
    boxes =[]

    # array of sqaures in the grid
    for j in range(0, xdim+size_box, size_box):
            for k in range(0, ydim+size_box, size_box):
                boxes.append([k,j])

    boxes = np.asarray(boxes)
    corners = np.reshape(boxes, (num_boxes+1,num_boxes+1,2))   # squares converted to corners

    # generates a vector for each corner in the grid using the unit circle
    vectors =[]
    for i in range(len(boxes)):
        radians = (2*math.pi) * random.uniform(0, 1)
        vectors.append([round(math.cos(radians),3), round(math.sin(radians),3)])

    vectors = np.asarray(vectors)
    vectors = np.reshape(vectors,(num_boxes+1,num_boxes+1,2))

    # creates noise using perlin noise algorithim
    for x in range(xdim):
        for y in range(ydim):
            edge4 = findEdge(x, y, size_box)

            # finds nearest edges to the specified pixel
            edge1 = (edge4[0]-1,edge4[1]-1) #[0,0]
            edge2 = (edge4[0]-1,edge4[1]) #[0,1]
            edge3 = (edge4[0],edge4[1]-1) #[1,0]
            edge4 = edge4 #[1,1]

            # finds the nearest corners based on the calculated edges
            corner1 = corners[edge1[::-1]]
            corner2 = corners[edge2[::-1]]
            corner3 = corners[edge3[::-1]]
            corner4 = corners[edge4[::-1]]

            # calculates the distance between each corner and a pixel and normalizes them based on the size of each box
            dist1 = np.array([x-corner1[0], y-corner1[1]])/size_box
            dist2 = np.array([x-corner2[0], y-corner2[1]])/size_box
            dist3 = np.array([x-corner3[0], y-corner3[1]])/size_box
            dist4 = np.array([x-corner4[0], y-corner4[1]])/size_box

            # stores the vector for the specifed corners using the calculated edges
            vect1 = vectors[edge1]
            vect2 = vectors[edge2]
            vect3 = vectors[edge3]
            vect4 = vectors[edge4]

            # calculates the dot product between all 4 vectors and uses the fading function to smooth it
            dp1 = (vect1@dist1)*fade(1-dist1[0])*fade(1-dist1[1])
            dp2 = (vect2@dist2)*fade(1-dist1[0])*fade(dist1[1])
            dp3 = (vect3@dist3)*fade(dist1[0])*fade(1-dist1[1])
            dp4 = (vect4@dist4)*fade(dist1[0])*fade(dist1[1])

            sums = dp1+dp2+dp3+dp4 # sums all the dot products
            pixels[x][y] = sums # sets the pixel to the sum of all dot products

    return pixels

def add_contrast(img,amt): #change
    im2 = amt*(img-128)/128
    return 256 / (1 + np.exp(-1*im2))

def make_map():
    # map 1
    raw = makeNoise(4)  # makes an array with noise using 4 boxes

    raw -= np.amin(raw)  # finds the minimum value in the array and ensures non negative pixels
    raw /= np.amax(raw)  # finds the maximum value in the array and normalizes the pixels
    final1 = 256*raw     # creates the rgb value for each pixel

    # map 2
    raw2 = makeNoise(16)

    raw2 -= np.amin(raw2)
    raw2 /= np.amax(raw2)
    final2 = 256*raw2

    # map 3
    raw3 = makeNoise(64)

    raw3 -= np.amin(raw3)
    raw3 /= np.amax(raw3)
    final3 = 256*raw3

    completeMap = (0.4*final1)+ (0.4*(final2))+ (0.2*(final3))    # adds all 3 maps on top of each other to create 1 map
    added = add_contrast(completeMap,4)
    added -= np.amin(added)
    added /= np.amax(added)
    added = 256*added

    return completeMap

# plots the generated noise
completeMap = make_map()

fig, ax = plt.subplots(figsize=(10,10))
ax.imshow(completeMap, cmap='gray', vmin=0, vmax=256, interpolation='nearest')

# uses the generated noise to make a map
def generate_map(noise, xdim, ydim):
    new_pixels = np.zeros((512,512,3))
    for x in range(xdim):
        for y in range(ydim):
            if noise[x,y] < 105:
                new_pixels[x,y] = [165,186,58] #green
            elif noise[x,y] < 120:
                new_pixels[x,y] = [245,219,136] #yellow
            else:
                new_pixels[x,y] = [0,70,170] #blue
    return new_pixels

# plots the generated map
gm = generate_map(completeMap, 512, 512)

fig, ax = plt.subplots(figsize=(10,10))
ax.imshow(gm/256, vmin=0, vmax=256, interpolation='nearest')


import numpy as np
import matplotlib.pyplot as plt
m=10
d_teta1=0.01
teta_1_min=-1
teta_1_max=1

# here we actually have a graph with h_teta=teta1*x
X = np.arange(m)
np.random.seed(42) #use to get the same results every run
rnd = np.random.rand(m)
Y = np.arange(m)*0.5 + rnd # actually we have h = teta1*x where teta1 is actually 0.5

#estimated y -> H
Teta1 = np.arange(teta_1_min,teta_1_max,d_teta1)
# J is not required for comuting here gradient descent
# J is used here just to get min(J) as a comparison for me
J=[]

# can i do it without a loop ???
for teta1 in Teta1:
    H = X * teta1
    E = H - Y
    cost = np.dot(E,E)/(2*m)
    J.append(cost)

# compute teta1 for min(J)
min_j_index = np.argmin(J)
teta1 = Teta1[min_j_index]

h_vec = X * Teta1[min_j_index] # computed linear estimation

# gradient descent
teta1_gds = 100 # arbitrary initial condition
iterations = 0
max_iterations = 10000
alfa = 0.001 # learning rate

while iterations < max_iterations:
    H = X * teta1_gds
    E = H - Y
    dj_to_dteta1 = (1/m)*np.dot(E, X)
    teta1_gds = teta1_gds - alfa * dj_to_dteta1
    iterations += 1
    j = np.dot(E,E)/(2*m)
    iterations += 1
    print(iterations,j)

print("min(J) : {:.3f} , tata1 @ min(J) : {:.3f}".format(J[min_j_index] , teta1))
print("gradient descent tata1  : {:.3f} , iterations : {:.3f}".format(teta1_gds,iterations))


'''
This python program solves the following driven damped pendulum equation:
theta''(t) = (-1/Q)theta'(t) + sin(theta(t)) + d cos(Omega t)
Where   theta(t)        : angular position as a fn of time
        Q               : damping factor
        sin(theta(t))   : nonlinear term in Simple Harmonic motion equation
        d               : Driving force amplitude
        Omega           : Driving force frequency

This second order nonlinear differential equation is first transformed to a system of
2 first order diffeqs and then solved using scipy.odeint(...)

The first order diff eqs are:
theta'(t) = omega
omega'(t) = (-1/Q)omega + sin(theta(t)) + d cos(Omega t)
Where   omega = theta'(t) for convenience.

The new initial conditions are given by theta0 and omega0.  These are fed to the y0
argument in odeint as seen below.

For greater detail, please visit
http://www.physics.nyu.edu/pine/pymanual/html/chap9/chap9_scipy.html#solving-odes

__author__ : folks at physics.nyu.edu
'''
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint

def f(y, t, params):
    #print('inside function f...')
    theta, omega = y      # unpack current values of y
    Q, d, Omega = params  # unpack parameters
    derivs = [omega,      # list of dy/dt=f functions
             -omega/Q + np.sin(theta) + d*np.cos(Omega*t)]
    return derivs

# Parameters
print('inside initialization portion...')
Q = 2.0          # quality factor (inverse damping)
d = 1.5          # forcing amplitude
Omega = 0.65     # drive frequency

# Initial values
theta0 = 0.0     # initial angular displacement
omega0 = 0.0     # initial angular velocity

# Bundle parameters for ODE solver
params = [Q, d, Omega]

# Bundle initial conditions for ODE solver
y0 = [theta0, omega0]

# Make time array for solution
tStop = 50.
tInc = 0.05
t = np.arange(0., tStop, tInc)
#print t.size #printing for debugging

# Call the ODE solver
psoln = odeint(f, y0, t, args=(params,))
#print psoln.dtype #printing for debugging
#print psoln.size #printing for debugging

# Plot results
fig = plt.figure(1, figsize=(8,8))

# Plot theta as a function of time
ax1 = fig.add_subplot(311)
ax1.plot(t, psoln[:,0])
ax1.set_xlabel('time')
ax1.set_ylabel('theta')

# Plot omega as a function of time
ax2 = fig.add_subplot(312)
ax2.plot(t, psoln[:,1])
ax2.set_xlabel('time')
ax2.set_ylabel('omega')

# Plot omega vs theta
ax3 = fig.add_subplot(313)
twopi = 2.0*np.pi
ax3.plot(psoln[:,0]%twopi, psoln[:,1], '.', ms=1)
ax3.set_xlabel('theta')
ax3.set_ylabel('omega')
ax3.set_xlim(0., twopi)

plt.tight_layout()
plt.show()


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

#%%
# read data
path = 'ex1data2.txt'
data = pd.read_csv(path, header = None, names= ['Size', 'Bedrooms', 'Price'])

#%%
# show imported data details
# print('data = \n', data.head(10))
# print('**************************************')
# print('data.describe = \n', data.describe())
# print('**************************************')

#%%
#Normalizes the features in X
def featureNormalize(data):
    data_norm = data.copy()
    data_norm.iloc[:, 0:-1] = (data.iloc[:, 0:-1] - data.iloc[:, 0:-1].mean()) / data.iloc[:, 0:-1].std()
    return data_norm, data.mean(), data.std()

data_norm, mu, sigma = featureNormalize(data)

#%%
# show normalized data details
# print('norm data = \n', data_norm.head(10))
# print('**************************************')
# print('data_norm.describe = \n', data_norm.describe())
# print('**************************************')

#%%
# adding a new column called ones before the data
data.insert(0, 'Ones', 1)
data_norm.insert(0, 'Ones', 1)
# print('new data = \n', data_norm.head(10))
# print('**************************************')

#%%
# separate X (training data) from y (target variable)
cols = data_norm.shape[1]
X = data_norm.iloc[:, 0 : cols-1]
y = data_norm.iloc[:, cols-1 : cols]

# print('X data = \n', X.head(10))
# print('**************************************')
# print('y data = \n', y.head(10))
# print('**************************************')

#%%
# draw data
#3D
fig, ax = plt.subplots(figsize= (6,6), subplot_kw={'projection' : '3d'})
ax.scatter3D(data_norm['Size'], data_norm['Bedrooms'], data_norm['Price'], s= 30)
plt.show()

#2D
fig, ax = plt.subplots(figsize= (6,6))
ax.scatter(data_norm['Size'], data_norm['Price'], s= 30, label= 'Dataset')
ax.set(xlabel= 'Size (Normalized)', ylabel= 'Price', title= 'Dataset')
plt.legend(loc= 1, shadow=True)
plt.show()

#%%
# Convert data from data frames to numpy matrices
X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.array(np.zeros((cols-1, 1))))

# print('X \n', X)
print('\nX.shape = ', X.shape)
print('**************************************')
# print('y \n', y)
print('y.shape = ', y.shape)
print('**************************************')
# print('theta \n', theta)
print('theta.shape = ', theta.shape)
print('**************************************')

#%%
# Cost function
def computeCost(X, y, theta):
    J = np.sum(np.power(((X * theta) - y), 2)) / (2 * len(y))
    return float(J)

#%%
# Running Gradient Descent
def gradientDescent(X, y, theta, alpha, num_iters):
    temp = np.matrix(np.zeros(theta.shape))
    J_history = np.zeros((num_iters, 1))

    for i in range(num_iters):
        for j in range(len(theta)):
            temp[j, 0] = theta[j, 0] - ((alpha / len(y)) * np.sum(np.multiply(((X * theta) - y), X[:, j])))

        theta = temp.copy()
        J_history[i,0] = computeCost(X, y, theta)

    return theta, J_history

iterations = 400
alpha = 0.01

theta, J_history = gradientDescent(X, y, theta, alpha, iterations)

print('Theta computed from gradient descent: ')
for i in range(len(theta)): print(theta[i,0])
print('**************************************')

test = np.array([[1.], [1650.], [3.]])
print('Predicted price of a 1650 sq-ft, 3 br house (using gradient descent):\n $', (theta.T * test)[0,0])
print('Expected Price (approx)\n $ 165489064.118993')
print('**************************************')
# print('J_history\n', J_history)
# print('**************************************')

#%%
# Normalize the test point
print(test)
test[1,:] = (test[1,:] - np.mean(np.matrix(data['Size']), axis= 1)) / np.std(np.matrix(data['Size']), axis= 1)
test[2,:] = (test[2,:] - np.mean(np.matrix(data['Bedrooms']), axis= 1)) / np.std(np.matrix(data['Bedrooms']), axis= 1)
print(test)

# draw the best fit line from Gradient Descent
fig, ax = plt.subplots(figsize= (6,6))
ax.scatter(data_norm['Size'], data_norm['Price'], s= 30, label= 'Dataset')
ax.scatter(test[1,:], (theta.T * test)[0,0], s= 30, marker= 'x', color= 'r', linewidths=2, label= 'Test Data')
ax.plot(X[:,1], (X[:,1]*theta[1,:]) + theta[0,:], 'r-', linewidth= 2, label= 'Best Fit line')
ax.set(xlabel= 'Size (Normalized)', ylabel= 'Price', title= 'Best Fit line from Gradient Descent')
plt.legend(loc= 2, shadow=True, borderpad= 1)
plt.show()

#%%
# draw error graph
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(np.arange(1, iterations+1), J_history, 'b', linewidth=2)
ax.set(xlabel= 'No. of Iterations', ylabel= 'Cost', title= 'Error vs. Training Data')
plt.show()

#%%
# reset the data
del X,y,theta
cols = data.shape[1]
X = data.iloc[:, 0 : cols-1]
y = data.iloc[:, cols-1 : cols]
X = np.matrix(X.values)
y = np.matrix(y.values)

#%%
# Calculate the parameters from the normal equation
def normalEqn(X, y):
    theta = np.matrix(np.zeros((X.shape[1], 1)))
    theta = np.linalg.inv(X.T * X) * X.T * y
    return theta

theta = normalEqn(X, y)

#%%
# Display normal equation's result
print('\nTheta computed from the normal equations: ')
for i in range(len(theta)): print(theta[i,0])
print('**************************************')

test = np.array([[1.], [1650.], [3.]])
print('Predicted price of a 1650 sq-ft, 3 br house (using normal equations):\n $', (theta.T * test)[0,0])
print('Expected Price (approx)\n $ 293081.464335')
print('**************************************')

#%%
# draw the best fit line from normal equation
fig, ax = plt.subplots(figsize= (6,6))
ax.scatter(data['Size'], data['Price'], s= 30, label= 'Dataset')
ax.scatter(test[1,:], (theta.T * test)[0,0], s= 30, marker= 'x', color= 'r', linewidths=2, label= 'Test Data')
ax.plot(X[:,1], (X[:,1]*theta[1,:]) + theta[0,:], 'r-', linewidth= 2, label= 'Best Fit line')
ax.set(xlabel= 'Size', ylabel= 'Price', title= 'Best Fit line from Normal Equation')
plt.legend(loc= 2, shadow=True, borderpad= 1)
plt.show()


import numpy as np
import sympy as sp
from prettytable import PrettyTable
import math
from math import cos, sin , exp
from sympy import cos, sin , exp

def cambiarValores(dic_var, nuevos_valores, num_ecuaciones, var):
    for i in range(0, num_ecuaciones):
        dic_var[f'{var[i]}'] = nuevos_valores[i]
    return dic_var

def nuevosValores(ecua, Ecuaciones, variables):
    valor_ini = []
    funcs = vars(math)
    for i in range(0, Ecuaciones):
        expr = ecua[i]
        funcion = eval(expr,funcs, variables)
        valor_ini.append(funcion)
    return valor_ini

def efunciones(var, valor, Ecuaciones):
    var_valor = {}
    for i in range(0, Ecuaciones):
        variable = var[i]
        anadir = {f'{variable}': valor[i]}
        var_valor.update(anadir)
    return var_valor

def punto_fijo_multivariable(ecua, var, valor, Ecuaciones, emax, N=50):
    encabezados = []
    encabezados.append("Iteracion")
    for i in var:
        encabezados.append(i)
    encabezados.append(f"Error |{var[0]}(i)-{var[0]}(i-1)|")
    contenido = []
    tabla = PrettyTable(encabezados)
    tabla.title = "METODO DE PUNTO FIJO MULTIVARIABLE"
    evalu_func = efunciones(var, valor, Ecuaciones)
    for k in range(1, N):

        variables = cambiarValores(evalu_func, valor, Ecuaciones, var)
        corregidos = nuevosValores(ecua, Ecuaciones, variables)
        error = abs(valor[0]-corregidos[0])
        valor = corregidos

        contenido = []
        contenido.append(k)
        for i in range(0, Ecuaciones):
            contenido.append("{0:.7f}".format(valor[i]))
        contenido.append("{0:.7f}".format(error))
        # tabla.add_row([k, "{0:.7f}".format(valor[0]), "{0:.7f}".format(
        #    valor[1]), "{0:.7f}".format(error)])
        tabla.add_row(contenido)
        if error < emax or error == 0:
            break
        # print(valor)
    #funcion = eval(expr, {f'{var1}': valor[0],f'{var2}':  valor[1]})
    #w = {f'{var1}': valor[0], f'{var2}':  valor[1]}
    print(tabla)
    print(f'Solucion del sistema:  ')
    for i in range(0, Ecuaciones):
        print(f'                     {var[i]} = {"{0:.4f}".format(valor[i])}')

#ecua = ['(x**2+y**2+8)/10', '(x*y**2+x+8)/10']
#var = ['x', 'y']
#valori = [0.0, 0.0]
#Ecuaciones = 2

#ecua = ['(9-y**2-z**2)**0.5', '1/(x*z)','(x+y)**0.5']
#var = ['x', 'y','z']
#valori = [2.5, 0.2,1.6]
#Ecuaciones = 3

#ecua = ['(0.5+cos(y*z))/3', 'x/(625)**0.5','(-exp(-x*y)-(10*pi-3)/3)/20']
#var = ['x', 'y', 'z']
#valori = [1, 1, 1]
#Ecuaciones = 3

#punto_fijo_multivariable(ecua, var, valori, Ecuaciones, 1e-3, N=50)

src/3. Sistemas de ecuaciones no lineales/Metodos/pf_multivariable.py


import numpy as np

## Linear scalar Dahlquist's equation
def linear_scalar_flux(u,t=0,k_coef=10):
    ff=np.zeros(np.shape(u))
    ff[0]= -k_coef*u[0]
    return ff

def linear_scalar_exact_solution(u0,t,k_coef=10):
    return np.array([np.exp(-k_coef*u0[0]*t)])

def linear_scalar_jacobian(u,t=0,k_coef=10):
    Jf=np.zeros((len(u),len(u)))
    Jf[0,0]=-k_coef
    return Jf

#nonlinear problem y'=-ky|y| +1
def nonlinear_scalar_flux(u,t=0,k_coef=10):
    ff=np.zeros(np.shape(u))
    ff[0]=-k_coef*abs(u[0])*u[0] +1
    return ff

def nonlinear_scalar_exact_solution(u0,t,k_coef = 10):
        sqrtk = np.sqrt(k_coef)
        ustar = 1 / sqrtk
        if u0[0] >= ustar:
            uex=np.array([1./np.tanh(sqrtk * t + np.arctanh(1/sqrtk /u0[0])) / sqrtk])
        elif u0[0] < 0 and t < - np.atan(sqrtk * u0[0]) / sqrtk:
            uex=np.array([np.tan(sqrtk * t + np.arctan(sqrtk * u0[0])) / sqrtk])
        else:
            uex=np.array([np.tanh(sqrtk * t + np.arctanh(sqrtk * u0[0])) / sqrtk])
        return uex

def nonlinear_scalar_jacobian(u,t=0,k_coef=10):
    Jf=np.zeros((len(u),len(u)))
    Jf[0,0]=-k_coef*abs(u[0])
    return Jf

# SYSTEMS

# linear systems
def linear_system2_flux(u,t=0):
    d=np.zeros(len(u))
    d[0]= -5*u[0] + u[1]
    d[1]= 5*u[0] -u[1]
    return d

def linear_system2_exact_solution(u0,t):
    A=np.array([[-5,1],[5,-1]])
    u_e=u0+(1-np.exp(-6*t))/6*np.dot(A,u0)
    return u_e

def linear_system2_jacobian(u,t=0):
    Jf=np.array([[-5,1],[5,-1]])
    return Jf

linear_system2_matrix = np.array([[-5,1],[5,-1]])

def linear_system2_production_destruction(u,t=0):
    p=np.zeros((len(u),len(u)))
    d=np.zeros((len(u),len(u)))
    p[0,1]=u[1]
    d[1,0]=u[1]
    p[1,0]=5*u[0]
    d[0,1]=5*u[0]
    return p,d

#lin system 3 x3

def linear_system3_flux(u,t=0):
    d=np.zeros(len(u))
    d[0]= -u[0] + 3*u[1]
    d[1]= -3*u[1] + 5*u[2]
    d[2]= -5*u[2]
    return d

def linear_system3_exact_solution(u0,t=0):
    u_e = np.zeros(len(u0))
    u_e[0] = 15.0/8.0*u0[2]*(np.exp(-5*t) - 2*np.exp(-3*t)+np.exp(-t))
    u_e[1] = 5.0/2.0*u0[2]*(-np.exp(-5*t) + np.exp(-3*t))
    u_e[2] = u0[2]*np.exp(-5*t)
    return u_e
def linear_system3_jacobian(u,t=0):
    Jf=np.zeros((len(u),len(u)))
    Jf[0,0]=-1.
    Jf[0,1]=3
    Jf[1,1] = -3
    Jf[1,2] = 5
    Jf[2,2] = -5
    return Jf

## Nonlinear 3x3 system production destruction
def nonlinear_system3_flux(u,t=0):
    ff=np.zeros(len(u))
    ff[0]= -u[0]*u[1]/(u[0]+1)
    ff[1]= u[0]*u[1]/(u[0]+1) -0.3*u[1]
    ff[2]= 0.3*u[1]
    return ff

def nonlinear_system3_production_destruction(u,t=0):
    p=np.zeros((len(u),len(u)))
    d=np.zeros((len(u),len(u)))
    p[1,0]=u[0]*u[1]/(u[0]+1)
    d[0,1]=p[1,0]
    p[2,1]=0.3*u[1]
    d[1,2]=p[2,1]
    return p,d

# SIR Model
def SIR_flux(u,t=0,beta=3,gamma=1):
    ff=np.zeros(len(u))
    N=np.sum(u)
    ff[0]=-beta*u[0]*u[1]/N
    ff[1]=+beta*u[0]*u[1]/N - gamma*u[1]
    ff[2]= gamma*u[1]
    return ff

def SIR_jacobian(u,t=0,beta=3,gamma=1):
    Jf=np.zeros((len(u),len(u)))
    N=np.sum(u)
    Jf[0,0]=-beta*u[1]/N
    Jf[0,1]=-beta*u[0]/N
    Jf[1,0]= beta*u[1]/N
    Jf[1,1]= beta*u[0]/N - gamma
    Jf[2,1] = gamma
    return Jf

def SIR_production_destruction(u,t=0,beta=3,gamma=1):
    p=np.zeros((len(u),len(u)))
    d=np.zeros((len(u),len(u)))
    N=np.sum(u)
    p[1,0]=beta*u[0]*u[1]/N
    d[0,1]=p[1,0]
    p[2,1]=gamma*u[1]
    d[1,2]=p[2,1]
    return p,d

# Nonlinear_oscillator
def nonLinearOscillator_flux(u,t=0,alpha=0.):
    ff=np.zeros(np.shape(u))
    n=np.sqrt(np.dot(u,u))
    ff[0]=-u[1]/n-alpha*u[0]/n
    ff[1]=u[0]/n - alpha*u[1]/n
    return ff

def nonLinearOscillator_exact_solution(u0,t):
    u_ex=np.zeros(np.shape(u0))
    n=np.sqrt(np.dot(u0,u0))
    u_ex[0]=np.cos(t/n)*u0[0]-np.sin(t/n)*u0[1]
    u_ex[1]=np.sin(t/n)*u0[0]+np.cos(t/n)*u0[1]
    return u_ex

# Non linear oscillator damped
def nonLinearOscillatorDamped_flux(u,t,alpha=0.01):
    ff=np.zeros(np.shape(u))
    n=np.sqrt(np.dot(u,u))
    ff[0]=-u[1]/n-alpha*u[0]/n
    ff[1]=u[0]/n - alpha*u[1]/n
    return ff

def nonLinearOscillatorDamped_exact_solution(u0,t,alpha=0.01):
    u_ex=np.zeros(np.shape(u0))
    n0=np.sqrt(np.dot(u0,u0))
    n=n0*np.exp(-alpha*t)
    u_ex[0]=n/n0*(np.cos(t/n)*u0[0]-np.sin(t/n)*u0[1])
    u_ex[1]=n/n0*(np.sin(t/n)*u0[0]+np.cos(t/n)*u0[1])
    return u_ex

# Robertson
def Robertson_flux(u,t=0,alpha=10**4,beta=0.04, gamma=3*10**7):
    ff=np.zeros(np.shape(u))
    ff[0] = alpha*u[1]*u[2]-beta*u[0]
    ff[1] = beta*u[0]-alpha*u[1]*u[2] - gamma*u[1]**2
    ff[2] = gamma*u[1]**2
    return ff

def Robertson_jacobian(u,t=0,alpha=10**4,beta=0.04, gamma=3*10**7):
    Jf=np.zeros((3,3))
    Jf[0,0]= -beta
    Jf[0,1]= alpha*u[2]
    Jf[0,2]= alpha*u[1]
    Jf[1,0]= beta
    Jf[1,1]= -alpha*u[2]-2*gamma*u[1]
    Jf[1,2]= -alpha*u[1]
    Jf[2,1] = 2*gamma*u[1]
    return Jf

def Robertson_production_destruction(u,t=0,alpha=10**4,beta=0.04, gamma=3*10**7):
    p=np.zeros((len(u),len(u)))
    d=np.zeros((len(u),len(u)))
    p[0,1]=alpha*u[1]*u[2]
    d[1,0]=p[0,1]
    p[1,0]=beta*u[0]
    d[0,1]=p[1,0]
    p[2,1]=gamma*u[1]**2
    d[1,2]=p[2,1]
    return p,d

def Robertson_rhs(u,t=0):
    return np.zeros(3)

# Lotka:
def lotka_flux(u,t=0,alpha=1,beta=0.2,delta=0.5,gamma=0.2):
    ff=np.zeros(np.shape(u))
    ff[0]=alpha*u[0]-beta*u[0]*u[1]
    ff[1]=delta*beta*u[0]*u[1]-gamma*u[1]
    return ff

def lotka_jacobian(u,t=0,alpha=1,beta=0.2,delta=0.5,gamma=0.2):
    Jf=np.zeros((2,2))
    Jf[0,0] = alpha -beta*u[1]
    Jf[0,1] = -beta*u[0]
    Jf[1,0] = delta*beta*u[1]
    Jf[1,1] = delta*beta*u[0] -gamma
    return Jf

#3 bodies problem in 2D: U=(x_1,x_2,v_1,v_2,y_1,y_2,w_1,w_2,z_1,z_2,s_1,s_2)
# where x is the 2D position of body1 and v is speed body1 sun
# y, w are position and velocity body2 earth
# z, s are position and velocity body3 mars

def threeBodies_flux(u,t=0):
    m1=1.98892*10**30
    m2=5.9722*10**24
    m3=6.4185*10**23
    G=6.67*10**(-11)
    f=np.zeros(np.shape(u))
    x=u[0:2]
    v=u[2:4]
    y=u[4:6]
    w=u[6:8]
    z=u[8:10]
    s=u[10:12]
    dxy3=np.linalg.norm(x-y)**3
    dxz3=np.linalg.norm(x-z)**3
    dyz3=np.linalg.norm(y-z)**3
    f[0:2]=v
    f[2:4]=-m2*G/dxy3*(x-y)-m3*G/dxz3*(x-z)
    f[4:6]=w
    f[6:8]=-m1*G/dxy3*(y-x)-m3*G/dyz3*(y-z)
    f[8:10]=s
    f[10:12]=-m1*G/dxz3*(z-x)-m2*G/dyz3*(z-y)
    return f

class ODEproblem:
    def __init__(self,name):
        self.name=name
        if self.name=="linear_scalar":
            self.u0 = np.array([1.])
            self.T_fin= 2.
            self.k_coef=10
            self.matrix=np.array([-self.k_coef])
        elif self.name=="nonlinear_scalar":
            self.k_coef=10
            self.u0 = np.array([1.1/np.sqrt(self.k_coef)])
            self.T_fin= 1.
        elif self.name=="linear_system2":
            self.u0 = np.array([0.9,0.1])
            self.T_fin= 1.
            self.matrix = np.array([[-5,1],[5,-1]])
        elif self.name=="linear_system3":
            self.u0 = np.array([0,0.,10.])
            self.T_fin= 10.
        elif self.name=="nonlinear_system3":
            self.u0 = np.array([9.98,0.01,0.01])
            self.T_fin= 30.
        elif self.name=="SIR":
            self.u0 = np.array([1000.,1,10**-20])
            self.T_fin= 10.
        elif self.name=="nonLinearOscillator":
            self.u0 = np.array([1.,0.])
            self.T_fin= 50
        elif self.name=="nonLinearOscillatorDamped":
            self.u0 = np.array([1.,0.])
            self.T_fin= 50
        elif self.name=="Robertson":
            self.u0 = np.array([1.,10**-20,10**-20])
            self.T_fin= 10.**10.
        elif self.name=="lotka":
            self.u0 = np.array([1.,2.])
            self.T_fin= 100.
        elif self.name=="threeBodies":
            self.u0 = np.array([0,0,0,0,149*10**9,0,0,30*10**3,-226*10**9,0,0,-24.0*10**3])
            self.T_fin= 10.**8.
        else:
            raise ValueError("Problem not defined")

    def flux(self,u,t=0):
        if self.name=="linear_scalar":
            return linear_scalar_flux(u,t,self.k_coef)
        elif self.name=="nonlinear_scalar":
            return nonlinear_scalar_flux(u,t,self.k_coef)
        elif self.name=="linear_system2":
            return linear_system2_flux(u,t)
        elif self.name=="linear_system3":
            return linear_system3_flux(u,t)
        elif self.name=="nonlinear_system3":
            return nonlinear_system3_flux(u,t)
        elif self.name=="SIR":
            return SIR_flux(u,t)
        elif self.name=="nonLinearOscillator":
            return nonLinearOscillator_flux(u,t)
        elif self.name=="nonLinearOscillatorDamped":
            return nonLinearOscillatorDamped_flux(u,t)
        elif self.name=="Robertson":
            return Robertson_flux(u,t)
        elif self.name=="lotka":
            return lotka_flux(u,t)
        elif self.name=="threeBodies":
            return threeBodies_flux(u,t)
        else:
            raise ValueError("Flux not defined for this problem")

    def jacobian(self,u,t=0):
        if self.name=="linear_scalar":
            return linear_scalar_jacobian(u,t,self.k_coef)
        elif self.name=="nonlinear_scalar":
            return nonlinear_scalar_jacobian(u,t,self.k_coef)
        elif self.name=="linear_system2":
            return linear_system2_jacobian(u,t)
        elif self.name=="linear_system3":
            return linear_system3_jacobian(u,t)
        elif self.name=="SIR":
            return SIR_jacobian(u,t)
        elif self.name=="Robertson":
            return Robertson_jacobian(u,t)
        elif self.name=="lotka":
            return lotka_jacobian(u,t)
        else:
            raise ValueError("Jacobian not defined for this problem")

    def exact(self,u,t):
        if self.name=="linear_scalar":
            return linear_scalar_exact_solution(u,t,self.k_coef)
        elif self.name=="nonlinear_scalar":
            return nonlinear_scalar_exact_solution(u,t,self.k_coef)
        elif self.name=="linear_system2":
            return linear_system2_exact_solution(u,t)
        elif self.name=="linear_system3":
            return linear_system3_exact_solution(u,t)
        elif self.name=="nonLinearOscillator":
            return nonLinearOscillator_exact_solution(u,t)
        elif self.name=="nonLinearOscillatorDamped":
            return nonLinearOscillatorDamped_exact_solution(u,t)
        else:
            raise ValueError("Exact solution not defined for this problem")

    def exact_solution_times(self,u0,tt):
        exact_solution=np.zeros((len(u0),len(tt)))
        for it, t in enumerate(tt):
            exact_solution[:,it]=self.exact(u0,t)
        return exact_solution

    def prod_dest(self,u,t=0):
        if self.name=="linear_system2":
            return linear_system2_production_destruction(u,t)
        if self.name=="nonlinear_system3":
            return nonlinear_system3_production_destruction(u,t)
        elif self.name=="Robertson":
            return Robertson_production_destruction(u,t)
        elif self.name=="SIR":
            return SIR_production_destruction(u,t)
        else:
            raise ValueError("Prod Dest not defined for this problem")