"""
This code implements a linear regression per week 3 assignment of the machine learning module part of Columbia University Micromaster programme in AI.
Written using Python 3.X for running on Vocareum
"""

# builtin modules
import os
#import psutil
import requests
import sys
import math

# 3rd party modules
import pandas as pd
import numpy as np

def RidgeRegression(X, y, l:int = 2):

    """
    This function implements the ℓ2 -regularized least squares linear regression algorithm (aka ridge regression) which takes the form:
    wRR=argminw(∥y−Xw∥^2 ) + λ * ∥w∥^2.

    Inputs:
    y : vector 'y' from the split dataset
    X : matrix 'X' from the split dataset
    l : an arbitray value for lambda "λ"

    Outputs:
    wRR : calculated as expressed above
    """
    n, m = X.shape
    I = np.identity(m)
    cov_matrix = np.linalg.inv(np.dot(X.T, X) + l * I)
    wRR = np.dot(np.dot(cov_matrix, X.T), y)
    #_, S, _ = np.linalg.svd(X)
    #deg_f = np.sum(np.square(S) / (np.square(S) + l))

    return wRR

def update_posterior(X, y, xx_old, xy_old, l:int = 2, sigma2:int = 3):
    n, m = X.shape
    I = np.identity(m)

    xx_old = np.dot(X.T, X) + xx_old  # XoXoT + XiXiT
    xy_old = np.dot(X.T, y) + xy_old  # Xoyo + Xiyi

    new_cov = np.linalg.inv(l * I + (1 / sigma2) * xx_old) # new covariance Σ, captures uncertainty about w as Var[wRR]
    new_mean = np.dot((np.linalg.inv(l * sigma2 * I + xx_old)),xy_old) # new µ

    return new_cov, new_mean, xx_old, xy_old

def activeLearning(X, y, X_test, l:int = 2, sigma2:int = 3):

    n, m = X.shape

    I = np.identity(m)

    X_indexes = []

    # set up an xx_old and xy_old to zero to start with
    xx_old = np.zeros((m, m))
    xy_old = np.zeros(m)

    new_cov, new_mean, xx_old, xy_old = update_posterior(X, y, xx_old, xy_old, l, sigma2)

    #Select the 10 data points to measure as per the assignment requirements
    for i in range(10):
        # Pick x0 for which sigma20 is largest

        cov_matrix = np.dot(np.dot(X_test, new_cov), X_test.T)
        row_largest = np.argmax(np.diagonal(cov_matrix))

        # update x and y values
        # first reset indexes for X_test dataframe so you can call it from its row index number using 'row_largest'
        #X_test = X_test.reset_index(drop=True) #this line is not needed when running in Vocareum presumuably because the X_data.csv does not include an index column.
        # then get x0 and pass it onto the X matrix
        #x0 = X_test.iloc[[row_largest]].to_numpy()[0]
        # use the following when running on Vocareum
        x0 = X_test[row_largest] #.to_numpy()[0]

        #X.loc[row_largest] = x0

        # use the following when running on Vocareum
        X[row_largest] = x0

        # calculate y0 and update the y vector
        y0 = np.dot(X, new_mean)
        y = y0

        #row, _ = X_test.shape
        #X_index = list(range(row))[row_largest]
        X_indexes.append(row_largest)

        # Remove x0
        #X_test = X_test.drop(index = row_largest)

        # use the following when running on Vocareum
        X_test = np.delete(X_test, (row_largest), axis=0)
        # Update the posterior distribution
        new_cov, new_mean, xx_old, xy_old = update_posterior(X, y, xx_old, xy_old, l, sigma2)

    # Create 1-based indexes
    X_indexes = [i + 1 for i in X_indexes]

    return X_indexes

def main():
    """
    ## $ python3 hw1_regression.py lambda sigma2 X_train.csv y_train.csv X_test.csv
    """
    # get inputs into local variables
    lambda_input = int(sys.argv[1])
    sigma2_input = float(sys.argv[2])
    X_train = np.genfromtxt(sys.argv[3], delimiter = ",")
    y_train = np.genfromtxt(sys.argv[4])
    X_test = np.genfromtxt(sys.argv[5], delimiter = ",")

    #### Part 1
    wRR = RidgeRegression(X_train, y_train, lambda_input)
    # write the output csv file with the wRR values
    np.savetxt("wRR_" + str(lambda_input) + ".csv", wRR, fmt='%1.2f', delimiter="\n")

    #### Part 2
    X_indexes = activeLearning(X_train, y_train, X_test, lambda_input, sigma2_input)
    # write the output csv file
    np.savetxt("active_" + str(lambda_input) + "_" + str(int(sigma2_input)) + ".csv", X_indexes, fmt='%.d', delimiter="\n")

if __name__ == '__main__':
    main()


'''
AlvinXY Utility Module

All of this is based on WHOI's original implementations in C and MATLAB.

'''

from math import *
import numpy as np

def  mdeglat(lat):
    '''
    Provides meters-per-degree latitude at a given latitude

    Args:
      lat (float): latitude

    Returns:
      float: meters-per-degree value
    '''
    latrad = lat*2.0*pi/360.0 ;

    dy = 111132.09 - 566.05 * cos(2.0*latrad) \
         + 1.20 * cos(4.0*latrad) \
         - 0.002 * cos(6.0*latrad)
    return dy

def mdeglon(lat):
    '''
    Provides meters-per-degree longitude at a given latitude

    Args:
      lat (float): latitude in decimal degrees

    Returns:
      float: meters per degree longitude
    '''
    latrad = lat*2.0*pi/360.0
    dx = 111415.13 * cos(latrad) \
         - 94.55 * cos(3.0*latrad) \
	+ 0.12 * cos(5.0*latrad)
    return dx

def ll2xy(lat, lon, orglat, orglon):
    '''
    AlvinXY: Lat/Long to X/Y
    Converts Lat/Lon (WGS84) to Alvin XYs using a Mercator projection.

    Args:
      lat (float): Latitude of location
      lon (float): Longitude of location
      orglat (float): Latitude of origin location
      orglon (float): Longitude of origin location

    Returns:
      tuple: (x,y) where...
        x is Easting in m (Alvin local grid)
        y is Northing in m (Alvin local grid)
    '''
    x = (lon - orglon) * mdeglon(orglat);
    y = (lat - orglat) * mdeglat(orglat);
    return (x,y)

def xy2ll(x, y, orglat, orglon):

    '''
    X/Y to Lat/Lon
    Converts Alvin XYs to Lat/Lon (WGS84) using a Mercator projection.

    Args:
      x (float): Easting in m (Alvin local grid)
      x (float): Northing in m (Alvin local grid)
      orglat (float): Latitude of origin location
      orglon (float): Longitude of origin location

    Returns:
      tuple: (lat,lon)
    '''
    lon = x/mdeglon(orglat) + orglon
    lat = y/mdeglat(orglat) + orglat

    return (lat, lon)

# Vectorize
vxy2ll = np.vectorize(xy2ll)
vll2xy = np.vectorize(ll2xy)


import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si
plt.style.use('dark_background')

def f(x) :

    s = 1/(1+25*np.power(x,2))

    return s

n=20
x_nodes=[]
for e in range(0,n) :
    s = np.cos(((2*e+1)/(n+1))*(np.pi/2))
    x_nodes.append(s)

y_val=f(x_nodes)
polynomial = si.lagrange(x_nodes, y_val)

#x=np.linspace(-1,1,1000)
#y=polynomial(x)

x=[]
for e in range(0,n) :
    s = np.cos(((2*e+1)/(n+1))*(np.pi/2))
    x.append(s)

y=polynomial(x)

plt.plot(x,f(x),'blue')
plt.plot(x,y,'cyan')
plt.plot(x_nodes,y_val,'go')

plt.xlabel('x')
plt.ylabel('y')
plt.xlim(-1.1,1.1)
plt.ylim(-0.3,1.2)
plt.legend(['f(x)','Lagrange/Chebyshev','points'])
plt.show()


'''
Created on 2 Jul 2013

@author: Matthew Daggitt
'''

from scipy import stats
import numpy as np

from core.geom import Line

def calculateSingleLineRegression(xs,ys):
    """ Returns the least squares regression line through the provided coordinates """
    if len(xs) == 2:
        # Case needed as stats.linregress occasionaly throughs warnings for length 2 datasets
        slope = (ys[1]-ys[0])/(xs[1]-xs[0])
        intercept = ys[0] - slope*xs[0]
    else:
        slope, intercept = stats.linregress(xs, ys)[0:2]

    return Line(slope,intercept)

def residualSumOfSquares(xs,ys,func):
    """Return the residual sum of squares."""
    return sum([(func(xs[i])-ys[i])**2 for i in range(len(xs))])

def meanRelativeSquaredError(xs,ys,func):
    """Return the mean relative squared error."""
    return sum([((func(x)-y)/y)**2 for x, y in zip(xs,ys)])/len(xs)

def calculateMultiLineRegression(xs, ys, numberOfSegments):
    """
    Fits a specified number of linear segments to the provided data

    Arguments
    xs -- the x coordinates of the data
    ys -- the y coordinates of the data
    numberOfSegments -- the number of linear segments to fit to the data

    Returns
    segmentLines:list           --  list of Line objects each representing a linear segment
    segmentLimits:list          --  list of n+1 integers, segmentLines[i] is valid between segmentLimits[i] and segmentLimits[i+1]
    """

    xs, ys = zip(*sorted(zip(xs,ys), key=lambda x: x[0]))

    uniqueXValues = set(xs)
    m = len(uniqueXValues)
    if len(uniqueXValues) < numberOfSegments*2:
        raise ValueError("Cannot perform linear regression for " + str(numberOfSegments) + " segment" +
                         (" " if numberOfSegments == 1 else "s ") +
                         "with only " + str(m) + " unique x-value" + (" " if m == 1 else "s "))

    n = len(xs)
    allLines = {}
    allErrors = {}

    for j in range(1,n):
        for i in range(0,j):
            segXs, segYs = xs[i:j+1], ys[i:j+1]
            allLines[(i,j)] = calculateSingleLineRegression(segXs,segYs)
            allErrors[(i,j)] = residualSumOfSquares(segXs,segYs,allLines[(i,j)].calcY) if allLines[(i,j)] is not None else float("inf")

    minTraversal = _findMinTraversal(allErrors,n,numberOfSegments)
    segmentLines = [allLines[(x,y)] for (x,y) in minTraversal]
    segmentLimits = _calculateSegments(xs,ys,segmentLines,minTraversal)

    return (segmentLines, segmentLimits)

def _calculateSegments(xs,ys,lines,minTraversal):
    bounds = [xs[0]]
    for i in range(0,len(lines)-1):
        intersectionPoint = lines[i].intersection(lines[i+1])
        if intersectionPoint is None or lines[i].m > lines[i+1].m:
            bounds.append((xs[minTraversal[i][1]]+xs[minTraversal[i+1][0]])/2)
        else:
            bounds.append(intersectionPoint.x)
    bounds.append(xs[-1])

    return bounds

def _findMinTraversal(scores,numberOfPoints,numberOfSteps):

    traversals = _compilePossibleTraversals(0,numberOfPoints,numberOfSteps)

    minScore = float("inf")
    minTraversal = []
    for t in traversals:
        currentScore = 0
        for (x,y) in t:
            currentScore += scores[(x,y)]
        if currentScore < minScore:
            minScore = currentScore
            minTraversal = t

    return minTraversal

def _compilePossibleTraversals(start,end,n):
    if start == end and n == 0:
        return [[]]
    elif start == end or n == 0:
        return None
    else:
        possiblities = []
        for i in range(start+1,end):
            results = _compilePossibleTraversals(i+1,end,n-1)
            if results != None:
                for r in results:
                    r = r.insert(0,(start,i))
                possiblities += results
        return possiblities


from numpy.linalg import eig, inv, norm, cond
import numpy as np
from tabulate import tabulate

myA=[
 [-0.81417, -0.01937, 0.41372],
 [-0.01937, 0.54414, 0.00590],
 [0.41372, 0.00590, -0.81445]
]

def Jacobi(n, A, epsilon):
 E = np.eye(n)
 C = B = np.ones((n, n))
 Akplus1 = Ak = A
 ik = jk = 0
 diagA = np.triu(Ak, k=0)
 maxel = 0
 X = np.zeros((n, n))
 for i in range(n):
  for j in range(n):
   if abs(Ak[i][j]) > maxel:
    maxel = Ak[i][j]
    ik = i
    jk = j
   if maxel >= epsilon:
    Akplus1 = rotate(Ak, Akplus1, ik, jk, n)
  Ak = Akplus1
 eigval = np.diag(Akplus1)
 eigvect = C
 return eigval, eigvect

def rotate(Ak, Akplus1, ik, jk, n):
 d = np.sqrt((Ak[ik][ik] - Ak[jk][jk]) ** 2 + 4 * (Ak[ik][jk] ** 2))
 c = np.sqrt(0.5 * (1 + abs(Ak[ik][ik] - Ak[jk][jk]) / d))
 s = np.sign(Ak[ik][jk] * (Ak[ik][ik] - Ak[jk][jk])) * np.sqrt(0.5 * (1 - abs(Ak[ik][ik] - Ak[jk][jk]) / d))
 Akplus1[ik][ik] = c ** 2 * Ak[ik][ik] - 2 * c * s * Ak[ik][jk] + s ** 2 * Ak[jk][jk]
 Akplus1[jk][jk] = s ** 2 * Ak[ik][ik] - 2 * c * s * Ak[ik][jk] + c ** 2 * Ak[jk][jk]
 Akplus1[ik][jk] = Akplus1[jk][ik] = 0
 for l in range(n):
  for m in range(n):
   if l != ik & l != jk & m != ik & m != jk:
    Akplus1[l][m] = Ak[l][m]
   elif l != ik & l != jk:
    Akplus1[l][ik] = Akplus1[ik][l] = c * Ak[l][ik] + s * Ak[l][jk]
    Akplus1[l][jk] = Akplus1[jk][l] = -s * Ak[l][ik] + c * Ak[l][jk]
 return Akplus1

def frobnorm(A):
 sum = 0.0
 for i in range(len(A)):
  for j in range(len(A[0])):
   sum += abs(A[i][j])
 return sum ** 0.5

def aposter(A, Y, l):
 return frobnorm(A @ Y - l * Y) / frobnorm(Y)

def powermethod(n, A, epsilon):
 Yk = [1, 1, 1]
 maxl = maxk = 0
 l = l_ = 0
 for i in range(n):
     k = 0
     while (k >= 0):
         Ykplus1 = A @ Yk
         l_ = Ykplus1[i] / Yk[i]
         if abs(l - l_) < epsilon & abs(l_) > maxl:
             maxl = l_
             maxk = k
             k = 0
             break
         l = l_
         k += 1
     Yk = Ykplus1
 return maxl, maxk, Ykplus1

def dotproductmethod():
 Y0 = np.ones((3, 1))

print("Вариант 1:")
# 1)
epsilon = 0.000001
eig = Jacobi(3, myA, epsilon)
#eig1 = np.linalg.eig(myA)
print(eig[0])
#rint(eig1[0])

# 2)
epsilon = 0.001


"""
Author: Shreck Ye
Date: June 16, 2019
Time complexity: O(log(N))
Note: the solution does not run on LeetCode because importing NumPy is currently not supported by LeetCode.

Let's think in the mathematical way. Obviously, the recursion formula represents a linear relationship.
By viewing it as a recursion formula of a single vector F_n = (f_n, f_{n + 1})' with a transition matrix M = (0, 1; 1, 1),
which is (f_{n + 1}, f_{n + 2})' = (0, 1; 1, 1) (f_n, f_{n + 1})' namely F_{n + 1} = M F_n,
we can get the result using matrix exponentiation and reduce the number of recursions.
"""
import numpy as np

F_0 = np.array([0, 1])
M = np.array([[0, 1], [1, 1]])

class Solution:
    def fib(self, N: int) -> int:
        return np.matmul(np.linalg.matrix_power(M, N), F_0)[0]

# Test cases
s = Solution()
print(s.fib(0), s.fib(1), s.fib(2), s.fib(3), s.fib(4), s.fib(5))


import numpy as np

def TrapezoidalMethod(f, a, b, n):
    I = 0
    for i in np.linspace(a,b,num=n,endpoint=False)[1:]:
        I = I + f(i)
    I = ( b - a ) * ( I * 2 + f(a) + f(b) ) / ( 2 * n)
    return I

def Simpsons13Rule(f, a, b ):
    return (b - a) / 6 * ( f(a)  + 4 * f((a + b) / 2) + f(b) )

def Simpsons13Rule_n(f, a, b, n):
    h = (b - a)/n
    even = []
    odd = []
    for i, v in enumerate(np.linspace(a, b, num= n , endpoint=False)[1:]):
        if not(i % 2):
            even.append(v)
        elif (i % 2):
            odd.append(v)
    I = (b - a) * (f(a) + 4 * sum(map(f, even)) + 2 * sum(map(f, odd)) + f(b))/( 3 * n )
    return I

def GaussQuadrature_nPoint(f, a, b, n):
    X, C = np.polynomial.legendre.leggauss(n)

    g = lambda t: f(t *(b - a)/2 + (b + a)/2) * (b - a)/2

    I = 0
    for i in range(1 - 1, n + 1 - 1):
        I += C[i] + g(X[i])
    return I


from typing import TypeVar
from numbers import Number
import math
import numpy as np

__version__ = None

try:
    import pkg_resources

    __version__ = pkg_resources.get_distribution("datasci").version
except Exception:
    pass

Numeric = TypeVar("Numeric", Number, np.ndarray)

def logistic(x: Numeric) -> Numeric:
    """
    Compute the standard logistic function,
    which maps x ∈ ℝ into the range (0, 1).
    As x → +∞, logistic(x) → 1
    As x → -∞, logistic(x) → 0
    """
    return 1 / (1 + np.exp(-x))

def logit(p: Numeric) -> Numeric:
    """
    Compute the logit (AKA log-odds) function,
    which maps a probability into ℝ.
    """
    assert 0.0 <= p <= 1.0, "p must be in the range [0, 1]"
    if p == 0:
        return -math.inf
    if p == 1:
        return math.inf
    return np.log(p / (1 - p))


import numpy as np
import matplotlib.pyplot as plt
from scipy.fftpack import dct, idct
from cosamp_fn import cosamp
import cvxpy as cvx

n = 4096 # High resolutions samples
t = np.linspace(0,1,n)
x =  np.cos(2 * 97 * np.pi * t) + np.cos(2 * 777 * np.pi * t)

## Randomly samples the signal
p = 128
perm = np.floor(np.random.rand(p) * n).astype(int)
# perm = np.random.choice(n, p, replace=False)
# perm.sort()
y = x[perm]

## Plot
time_window = np.array([1024, 1280])/n

fig,axes = plt.subplots(1,2)
axes = axes.reshape(-1)

axes[0].plot(t,x)
axes[0].plot(t[perm],y,'x',ms = 12)
axes[0].set_xlim(time_window[0],time_window[1])
axes[0].set_ylim(-2,2)

# ## Solve compressed sensing problem
Psi = dct(np.identity(n))
print(Psi.shape)
Theta = Psi[perm,:]
s = cosamp(Theta,y,10,epsilon=1.e-50,max_iter=1000)
xrecon = idct(s)

## Solve with other methods
# vx = cvx.Variable(n)
# objective = cvx.Minimize(cvx.norm(vx, 1))
# constraints = [Theta*vx == y]
# prob = cvx.Problem(objective, constraints)
# result = prob.solve(verbose=True)
# s = np.array(vx.value)
# s = np.squeeze(s)
# xrecon = idct(s)

## Plot
axes[0].plot(t,xrecon,'r')
# axes[1].set_xlim(time_window[0],time_window[1])
# axes[1].set_ylim(-2,2)

axes[1].imshow(Theta)

plt.show()

print("Norm")
print(np.linalg.norm(x-xrecon, ord=2))
print(np.linalg.norm(xrecon, ord=1))


import numpy as np

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

weights = np.array([0, 2, 1])

target = 0

# Calculate the predictions: preds
preds = (weights * input_data).sum()

# Calculate the error: error
error = preds - target

# Calculate the slope: slope
slope = input_data * error * 2

# Print the slope
print(slope)

## improving the model weights

# Set the learning rate: learning_rate
learning_rate = 0.01

# Calculate the predictions: preds
preds = (weights * input_data).sum()

# Calculate the error: error
error = preds - target

# Calculate the slope: slope
slope = 2 * input_data * error

# Update the weights: weights_updated
weights_updated = weights - (learning_rate * slope)

# Get updated predictions: preds_updated
preds_updated = (weights_updated * input_data).sum()

# Calculate updated error: error_updated
error_updated = preds_updated - target

# Print the original error
print(error)

# Print the updated error
print(error_updated)


#!/usr/bin/env python3

import math
from decimal import *

import numpy as np

# CHANGEME:
probtab = np.loadtxt(open("data/probability_table.csv", "rb"), delimiter=",").astype(Decimal)

print("The marginal probabilities of all the values of 𝑋")
print(probtab)
print("- " * 10)

px = np.sum(probtab, axis=0)
py = np.sum(probtab, axis=1)

print("P(X):", px)
print("P(Y):", py)

# sum of all columns and rows must be one
assert sum(px) == 1
assert sum(py) == 1

print("Shannon’s entropy of the plaintext")
print("𝐻(𝑋)=−∑𝑃(𝑥)log2𝑃(𝑥)")
hx = -sum([i * math.log2(i) for i in px])
print("-(", end="")
for i in px:
  print(f"{i:.3f}*log2({i:.3f})", end=" + ")
print(f") = {hx:.3f} bits")
print("- " * 10)

print("The conditional entropy 𝐻(𝑋|𝑌)")
print("𝐻(𝑋|𝑌)=−∑y∑x𝑃(𝑥,𝑦)log2𝑃(𝑥|𝑦)")
hxy = 0
print("-(", end="")
for k, i in zip(py, probtab):
  for j in i:
    hxy += (j * math.log2(j / k))
    print(f"{j}*log2({j}/{k})", end=" + ")
hxy = -hxy
print(f") = {hxy:.3f} bits")
print("- " * 10)

print("The mutual information")
print("𝐼(𝑋,𝑌)=𝐻(𝑋)−𝐻(𝑋|𝑌)")
print(f"{hx:.3f} - {hxy:.3f} = {(hx - hxy):.3f} bits")

if (hx - hxy) == 0:
  print("The cipher is perfectly secret")

else:
  print("The cipher is not perfectly secret because I(X,Y) != 0")
  # The uncertainty about the plaintext is reduced afer receieving the ciphertext


#-------------------------------------------------------------------------------
# Name:        module1
# Purpose:
#
# Author:      milad
#
# Created:     25/11/2020
# Copyright:   (c) milad 2020
# Licence:     <your licence>
#-------------------------------------------------------------------------------

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

Rn = np.random.seed(100)
N = 20
beta0 = -4
beta1 = 2
x = np.random.randn(N)
e = np.random.randn(N)

#If positive int_like arguments are provided, randn generates an array of shape (d0, d1, ..., dn),
#filled with random floats sampled from a univariate “normal” (Gaussian) distribution of mean 0 and variance 1.
# A single float randomly sampled from the distribution is returned if no argument is provided.

y = beta0 + beta1 * x + e

true_x = np.linspace(min(x), max(x), num=50)
true_y = beta0 + beta1*true_x

fig, ax = plt.subplots()
sns.scatterplot(x, y, s= 40, label = 'Data')
sns.lineplot(true_x, true_y, color = 'red', label = 'True Model')
ax.set_xlabel('x', fontsize = 14)
ax.set_title(fr"$y = {beta0} + ${beta1}$x + \epsilon$", fontsize = 16)
ax.set_ylabel('y', fontsize=14, rotation=0, labelpad=10)
ax.legend(loc = 4)
sns.despine() # will put the frame around the figure

plt.show()


#!usr/bin/python3
# -*- coding : utf8 -*-

import numpy as np;

def cost_function(X, coef, y, scoring):
    """
        Compute the error committed by the linear
        model on the new coefficients.

        Parameters :
        -----------
        X : ndarray of shape (n_samples, n_features+1)
            Train data

        coef : ndarray of shape (n_features+1,)
            New coefficients

        y : ndarray of shape (n_samples,)
            True value

        scoring : str
            Name of the cost function

        Return :
        -------
        error : float
    """

    residuals = np.dot(X, coef) - y;

    if scoring == 'mse': # Mean Squared Error
        return np.sum(np.square(residuals)) / X.shape[0];

    elif scoring == 'rmse': # Root Mean Squared Error
        return np.sqrt(np.sum(np.square(residuals)) / X.shape[0]);

    elif scoring == 'mae': # Mean Absolute Error
        return np.sum(np.absolute(residuals)) / X.shape[0];

    else:
        raise ValueError("Something wrong with the cost function name.");

def gradient_vector(X, coef, y):
    """
        Calculate the gradient vector of the function to be minimized:
        (1/n) * sum_{i=1}^n (<X_i, coef> - y_i)^2  full batch
        (1/m) * sum_{i=1}^m (<X_i, coef> - y_i)^2  mini batch (m < n)
        (<X_i, coef> - y_i)^2  for stochastic

        Parameters :
        -----------
        X : ndarray of shape (n_rows, n_features+1)
            Train data on which we'll compute gradient

        coef : ndarray of shape (n_features+1,)
            Actual coefficients

        y : ndarray of shape (n_rows,)
            True value

        Return :
        -------
        gradient : ndarray of shape (n_features+1,)
    """

    y_pred = np.dot(X, coef);
    residuals = y_pred - y;

    return (2/X.shape[0]) * np.dot(X.T, residuals);

def check_batch(batch, n_samples):
    """
        Assure that batch isn't be larger than size of
        train data

        Parameters :
        -----------
        batch : int
            Size of mini train data to considered

        n_samples : int
            Size of train data
    """

    if batch > n_samples:
        raise ValueError(("'batch' is larger than the size"
                          " of the dataset (n_samples)."
                          " It must be less to n_samples."));

def idx_batch(batch, n_samples):
    """
        Get some indexes from train data

        Parameters :
        -----------
        batch : int
            Size of mini train data to considered

        n_samples : int
            Size of train data

        Return :
        -------
        array : ndarray of shape (batch, )
            Contains all of index of mini train
    """

    idx = [];
    repeat = 0;

    while repeat != batch:
        tmp = np.random.RandomState().randint(n_samples);

        if tmp not in idx:
            idx.append(tmp);
            repeat = repeat + 1;

    return np.array(idx);

class LinearRegression():
    """
        LinearRegression fits a linear model with coefficients to minimize
        the residual sum of squares between the observed targets in the
        dataset, and the targets predicted by the linear approximation.

        Parameters
        ----------
        n_iter : int, default=100
            Number of epochs to fit the model

        learning_rate : float, default=0.01
            Step size to control the rate of convergence

        batch : int, default=None
            If 'None' Vanilla method is used
            Else, either sgd, momentum or nesterov is used

        momentum : float, default=None
            Accelarate the rate of convergence

        opt : {'gd', 'sgd', 'momentum', 'nesterov'}, default=None
            'gd' = Gradient Descent,
            'sgd' = Stochastic Gradient Descent,
            'momentum' = Momentum,
            'nesterov' = Nesterov Acceleration

        scoring : {'mse', 'rmse', 'mae'}, default='rmse'
            'mse' = Mean Squared Error,
            'rmse' = Root Mean Squared Error,
            'mae' = Mean Absolute Error

        Attributes
        ----------
        coef_ : ndarray of shape (n_features+1, )
            Estimated coefficients for the linear regression problem

        costs_ : ndarray of shape (n_iter, )
            Error committed during model fitting

        coef_history : ndarray of shape (n_iter, features+1)
            Coefficient of all epochs
    """

    def __init__(self, n_iter=100, learning_rate=0.01, batch=None, momentum=None, opt=None, scoring='rmse'):

        # Check 'n_iter'
        if isinstance(n_iter, int) is not True or n_iter <= 0:
            raise ValueError("'n_iter' should be an integer greater than 0."
                             " You  gave %s." % str(n_iter));

        # Check 'learning_rate'
        if learning_rate is not None and (isinstance(learning_rate, float) is not True\
                                     or learning_rate <= 0):
            raise ValueError("'learning_rate' should be an float greater than 0."
                             " You  gave %s." % str(learning_rate));
        if learning_rate is None:
            raise ValueError("'learning_rate' should be an float greater than 0."
                             " You  gave %s." % str(learning_rate));

        # Check 'batch'
        if batch is not None and (isinstance(batch, int) is not True or batch <= 0):
            raise ValueError("'batch' should be an integer greater than 0."
                             " You  gave %s." % str(batch));
        elif batch is not None and opt == 'gd':
            raise ValueError("'gd' don't need batch. Set 'batch' to None.");
        elif batch is None and opt in ['sgd', 'momentum', 'nesterov']:
            raise ValueError("If 'sgd', 'momentum' or 'nesterov' is"
                             " used, 'batch' don't must being to None.");

        # Check 'momentum'
        if momentum is not None and (isinstance(momentum, float) is not True or momentum <= 0):
            raise ValueError("'momentum' should be an float greater than 0."
                             " You  gave %s." % str(momentum));

        # Check 'opt'
        names_opt = ['gd', 'sgd', 'momentum', 'nesterov'];
        if opt is not None and opt not in names_opt:
            raise ValueError("'opt' should be an string."
                             " Either 'gd', 'sgd', 'momentum' or 'nesterov'."
                             " You  gave %s." % str(opt));

        # Check 'learning_rate' and 'opt'
        if learning_rate is not None and opt is None:
            raise ValueError("No optimization method was chosen."
                             " Choose one among ", names_opt);

        # Check 'opt' and 'momentum'
        if opt in ['momentum', 'nesterov'] and momentum is None:
            raise ValueError("'momentum' is None. Define it.");
        elif momentum is not None and opt in ['gd', 'sgd']:
            raise ValueError("'momentum' can only be defined for"
                             " 'momentum' and 'nesterov'. Set it to None");

        # Check 'scoring'
        names_scoring = ['mse', 'rmse', 'mae'];
        if scoring not in names_scoring:
            raise ValueError("'scoring' should be an string."
                             " Either 'mse', 'rmse' or 'mae'."
                             " You  gave %s." % str(opt));

        # Initialization
        self.n_iter = n_iter;
        self.learning_rate = learning_rate;
        self.batch = batch;
        self.momentum = momentum;
        self.opt = opt;
        self.scoring = scoring;

    def fit(self, X, y):
        """
            Fit the linear model

            Parameters
            ----------
                X : ndarray of shape (n_samples, n_features)
                    Training samples

                y : ndarray of shape (n_samples)
                    Target values

            Returns
            -------
                self
        """

        n_samples, n_features = X.shape;
        self.costs_ = np.empty((self.n_iter, ));
        self.coef_ = np.zeros((n_features+1, )); # +1 because we add intercept

        X = np.hstack((np.ones((n_samples, 1)), X)); # because we add intercept
        self.coef_history = np.empty((self.n_iter, n_features+1));

        if self.opt in ['momentum', 'nesterov']:
            vector = np.zeros((n_features+1,));

        for epoch in range(self.n_iter):

            if self.opt == 'gd':

                # GD Method
                gradient = gradient_vector(X, self.coef_, y);
                self.coef_ = self.coef_ - (self.learning_rate * gradient);

                # Cost
                self.costs_[epoch] = cost_function(X, self.coef_, y, self.scoring);
                self.coef_history[epoch] = self.coef_.T;

            if self.opt == 'sgd':

                check_batch(self.batch, n_samples);
                idx_samples = idx_batch(self.batch, n_samples);

                # SGD Method
                gradient = gradient_vector(X[idx_samples], self.coef_, y[idx_samples]);
                self.coef_ = self.coef_ - (self.learning_rate * gradient);

                # Cost
                self.costs_[epoch] = cost_function(X, self.coef_, y, self.scoring);
                self.coef_history[epoch] = self.coef_.T;

            if self.opt == 'momentum':

                check_batch(self.batch, n_samples);
                idx_samples = idx_batch(self.batch, n_samples);

                # Momentum Method
                gradient = gradient_vector(X[idx_samples], self.coef_, y[idx_samples]);
                vector = (self.momentum * vector) + (self.learning_rate * gradient);
                self.coef_ = self.coef_ - vector;

                # Cost
                self.costs_[epoch] = cost_function(X, self.coef_, y, self.scoring);
                self.coef_history[epoch] = self.coef_.T;

            if self.opt == 'nesterov':

                check_batch(self.batch, n_samples);
                idx_samples = idx_batch(self.batch, n_samples);

                # Nesterov Method
                nestrov = self.coef_ - (self.momentum * vector);
                gradient = gradient_vector(X[idx_samples], nestrov, y[idx_samples]);
                vector = (self.momentum * vector) + (self.learning_rate * gradient);
                self.coef_ = self.coef_ - vector;

                # Cost
                self.costs_[epoch] = cost_function(X, self.coef_, y, self.scoring);
                self.coef_history[epoch] = self.coef_.T;

        return self;

    def predict(self, X):
        """
            Predicts the values with the trained model.

            Parameters
            ----------
                X : ndarray of shape (n_samples, n_features)
                    Test samples

            Returns
            -------
                y : ndarray of shape (n_samples, )
                    Predicted values
        """

        X = np.hstack( (np.ones((X.shape[0], 1)), X) );

        return np.dot(X, self.coef_);


import numpy as np

def forward_substitution(L, b):
    y = np.full_like(b, 0)
    for k in range(len(b)):
        y[k] = b[k]
        for i in range(k):
            y[k] = y[k] - (L[k, i] * y[i])
        y[k] = y[k] / L[k, k]
    return y

def backward_substitution(U, y):
    x = np.full_like(y, 0)
    for k in range(len(x), 0, -1):
        x[k - 1] = (y[k - 1] - np.dot(U[k - 1, k:], x[k:])) / U[k - 1, k - 1]
    return x

def doolittle(A):
    N = len(A)
    L = np.zeros([N, N])
    U = np.zeros([N, N])
    for i in range(N):
        for k in range(i, N):
            for j in range(i, N):
                Utotal = 0
                for j in range(i):
                    Utotal += L[i, j] * U[j, k]
                U[i, k] = A[i, k] - Utotal
                if (i == k):
                    L[i, k] = 1
                else:
                    Ltotal = 0
                    for j in range(i):
                        Ltotal += L[k, j] * U[j, i]
                    L[k, i] = (A[k, i] - Ltotal) / U[i][i]
    return (L, U)

def computing_final_solution(A, b, algorithm_used):
    L, U = algorithm_used(A)

    print("L = " + str(L) + "\n")
    print("U = " + str(U) + "\n")

    y = forward_substitution(L, b)
    x = backward_substitution(U, y)

    return x

A = np.array([[2.34, -4.10, 1.78], [-1.98, 3.47, -2.2], [2.36, -15.17, -6.18]])
b = np.array([0.02, -0.73, -6.63])

print("The solution using Doolittle's algorithm:" + "\n")
print("x = " + str(computing_final_solution(A, b, doolittle)) + "\n")


import numpy as np

def rot_x(angle):
    """
    rotate with respect x axis (right hand criteria)
    :param angle:
    :return:
    """
    return np.array([[1,             0,              0],
                     [0, np.cos(angle), -np.sin(angle)],
                     [0, np.sin(angle),  np.cos(angle)]])

def rot_y(angle):
    """
    rotate with respect y axis (right hand criteria)
    :param angle:
    :return:
    """
    return np.array([[np.cos(angle),  0, np.sin(angle)],
                     [0,              1,             0],
                     [-np.sin(angle), 0, np.cos(angle)]])

def rot_z(angle):
    """
    rotate with respect z axis (right hand criteria)
    :param angle:
    :return:
    """
    return np.array([[np.cos(angle), -np.sin(angle), 0],
                     [np.sin(angle),  np.cos(angle), 0],
                     [0,                          0, 1]])

def rotate_vector_3d(vector, ang_x, ang_y, ang_z):
    return np.dot(rot_z(ang_z), np.dot(rot_y(ang_y), np.dot(rot_x(ang_x), vector)))

def rotate_vector(vector, orientation):
    """
    Rotate vector of dimensions (1-3)

    :param vector: vector to rotate
    :param orientation: angles (in radians) to rotate respect to axis x, y, z
    :return: rotated vector
    """

    norm = np.linalg.norm(vector)
    n_dim = len(vector)
    ang_x, ang_y, ang_z = orientation
    rotated_vector = np.dot(rot_z(ang_z)[:n_dim, :n_dim],
                        np.dot(rot_y(ang_y)[:n_dim, :n_dim],
                               np.dot(rot_x(ang_x)[:n_dim, :n_dim],
                                      vector)))

    # Rotated vector is defined to have same norm as original whatever orientation
    return rotated_vector/np.linalg.norm(rotated_vector) * norm


import math

import numpy as np
from scipy import optimize

def distance(point_1, point_2):
    """ calculates the euclidian distance between two points

    :param point_1:
    :param point_2:
    :return:
    """
    x1 = point_1[0]
    x2 = point_2[0]
    y1 = point_1[1]
    y2 = point_2[1]
    # Calculating distance
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

def find_circle(x1, y1, x2, y2, x3, y3):
    """finds center and radius of a circle passing through three points

    :param x1:
    :param y1:
    :param x2:
    :param y2:
    :param x3:
    :param y3:
    :return:
    """
    points = np.asarray([[2 * x1, 2 * y1, 1], [2 * x2, 2 * y2, 1], [2 * x3, 2 * y3, 1]])
    radii = np.asarray([[-(x1 ** 2 + y1 ** 2)], [-(x2 ** 2 + y2 ** 2)], [-(x3 ** 2 + y3 ** 2)]])
    try:
        solution = np.linalg.solve(points, radii)
        a = solution[0][0]
        b = solution[1][0]
        xc = -a
        yc = -b
        r = distance((x1, y1), (xc, yc))
    except np.linalg.LinAlgError:
        # a singular matrix occurs when the three points are approximately collinear, so set these values very large
        # (The small arc of a very large circle approximates a straight line)
        xc = 10000000000
        yc = 10000000000
        r = math.sqrt((x1 - xc) ** 2 + (y1 - yc) ** 2)

    return xc, yc, r

def fit(x, y, start_point, end_point):
    """ function that does the fitting

    :param x:
    :param y:
    :param start_point:
    :param end_point:
    :return:
    """
    # initial guess is just a point in the middle of the arc
    initial_guess = np.asarray([x[1], y[1]])

    # goal is to find the third point that determines the arc
    # the third point much be located in the bounding box of all provided points with a buffer of 20 pixels (?)
    lb = np.asarray([np.min(x), np.min(y)]) - 20
    ub = np.asarray([np.max(x), np.max(y)]) + 20
    bounds = optimize.Bounds(lb, ub)
    third_point = optimize.minimize(fun=cost, x0=initial_guess, args=(x, y, start_point, end_point), bounds=bounds)
    third_point = third_point.x
    xc, yc, radius = find_circle(start_point[0], start_point[1], third_point[0], third_point[1], end_point[0],
                                 end_point[1])
    return xc, yc, radius

def cost(third_point, x, y, start_point, end_point):
    """ cost function needs to minimized (sum of squared differences between actual radius and fit radius
    (least-squares)

    :return:
    """
    xc, yc, radius = find_circle(start_point[0], start_point[1], third_point[0], third_point[1], end_point[0],
                                 end_point[1])
    individual_radii = np.sqrt((x - xc) ** 2 + (y - yc) ** 2)
    residuals = individual_radii - radius
    least_squares = np.sum(np.square(residuals))

    return least_squares


import numpy as np

def mohr_c(stress_x, stress_y, shear):
    """
    inputs
        stress_x: int or float
        stress_y: int or float
        shead: int or float

    mohr_c() outputs two values for the circle, center, and radius

    output:
        C, R
        C is the x-value of the circle center
        R is the radius of the circle
    """
    C = (stress_x + stress_y) / 2
    R = ((((stress_x - stress_y) / 2) ** 2) + shear ** 2) ** 0.5
    return C, R

def c_array(C, R, n=100):
    """
    inputs:
        C: int or float, the x-value of a circle center
        R: int or float, the radius of the circle
        n: int, number of points (optional)

    c_array() outputs two NumPy arrays of x and y values that creates the circle

    output:
        x, y
        x is a NumPy array with n-values that contains the x-values to build the circle
        y is a NumPy array with n-values that contains the y-values to build the circle
    """
    t = np.linspace(0, 2*np.pi, n+1)
    x = R * np.cos(t) + C
    y = R * np.sin(t)
    return x,y

def X_Y(stress_x, stress_y, shear):
    """build the arrays that describe the line X-Y between known points"""
    X = np.array([stress_x, stress_y])
    Y = np.array([-shear, shear])
    return X,Y


# from numpy import *
import numpy as np
from numpy import linalg as la
from scipy.linalg import hilbert
import scipy.linalg
import pprint

# http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.set_printoptions.html
np.set_printoptions(precision=16)

# a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
#
# print a
#
# # The condition number of x is defined as the norm of x times the norm of the inverse of x [R37];
# # the norm can be the usual L2-norm (root-of-sum-of-squares) or one of a number of other matrix norms.
# # the manual: http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.linalg.cond.html
#
# # a problem with a low condition number is said to "well conditioned".
# # a problem with a high condition number is said to "ill conditioned".
#
# # linalg.inv(a) is the function for computing the inverse.
#
# # print '{0:.16e}'.format(positive_root)
#
# pprint.pprint(la.inv(a))
# print(la.inv(a))
# print(la.cond(a))
#
# # print '{0:.16e}'.format(la.inv(a))
# print '{0:.16e}'.format(la.cond(a))
#
# la.inv(a)
#
# b = la.inv(a)
#
# c = np.dot(a,b)
#
# print "This is c:"
# print c
#
# # clearly c is not the identity matrix so a IS singular.

# ***** Problem #3 *****
#
# A = hilbert(4)
#
# print A
#
# z = np.ones(4)
#
# print z
#
# b = np.dot(A,z)
#
# print(b)
#
# # a\b in MATLAB is equivalent to linalg.solve(a,b) in numpy
#
# print "This is the solution.", la.solve(A, b)
#
# for i in (4, 8, 12):
#
#     A = hilbert(i)
#     print A
#
#     z = np.ones(i)
#     print z
#
#     b = np.dot(A,z)
#     print b
#
#     x_hat = la.solve(A, b)
#     print "This is the solution", la.solve(A, b)
#
#     # condition_number = la.cond(A)
#     print 'This is the condition number:{0:.16e}'.format(la.cond(A))
#
#     numerator_error = np.subtract(x_hat, z)
#     numerator_error = la.norm(numerator_error)
#
#     denominator_error = la.norm(z)
#
#     relative_error = numerator_error / denominator_error
#
#     relative_error = np.divide(numerator_error, denominator_error)
#
#     print"This is the relative error: {0}".format(relative_error)
#
#     # now we want to calculate the relative residual.
#
#     c = np.dot(A, x_hat)
#
#     r = np.subtract(b, c)
#
#     # the norm should be a scalar so we should be able to just divide.
#     norm_r = la.norm(r)
#
#     norm_b = la.norm(b)
#
#     residual = np.divide(norm_r, norm_b)
#
#     print "This is the residual: {0}".format(residual)

# *********************** this is problem 4 **********************
#
# A = np.random.rand(12, 12)
# print A
#
# z = np.ones(12)
# print z
#
# b = np.dot(A, z)
# print b
#
# x_hat = la.solve(A, b)
# print "This is the solution", la.solve(A, b)
#
# # condition_number = la.cond(A)
# print 'This is the condition number:{0:.16e}'.format(la.cond(A))
#
# numerator_error = np.subtract(x_hat, z)
# numerator_error = la.norm(numerator_error)
#
# denominator_error = la.norm(z)
#
# relative_error = numerator_error / denominator_error
#
# relative_error = np.divide(numerator_error, denominator_error)
#
# print"This is the relative error: {0}".format(relative_error)
#
# # now we want to calculate the relative residual.
#
# c = np.dot(A, x_hat)
#
# r = np.subtract(b, c)
#
# # the norm should be a scalar so we should be able to just divide.
# norm_r = la.norm(r)
#
# norm_b = la.norm(b)
#
# residual = np.divide(norm_r, norm_b)
#
# print "This is the residual: {0}".format(residual)

# ****************** here is problem 5 **********************************
#
# A = hilbert(12)
#
# print A
#
# print 'This is the condition number:{0:.16e}'.format(la.cond(A))
#
#
# P, L, U = scipy.linalg.lu(A)
# print"this is the U:"
# pprint.pprint(U)
#
#
# print(U[:, 11])

# *********** here is problem 6 ***********************************

import numpy as np
from scipy import sparse

nx, ny = 3, 3
N  = nx*ny
main_diag = np.ones(N)*-4.0
side_diag = np.ones(N-1)
side_diag[np.arange(1,N)%nx==0] = 0
up_down_diag = np.ones(N-3)
diagonals = [main_diag,side_diag,side_diag,up_down_diag,up_down_diag]
laplacian = sparse.diags(diagonals, [0, -1, 1,nx,-nx], format="csr")
print laplacian.toarray()

import numpy,from numpy,import scipy,from scipy


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

def LSC_binary_classification(X, Y):
	'''
	Find the weight using
	least square method
	as W = (X.T * X)^(-1) * X.T * Y
	'''
	bias = np.ones((X.shape[0],1))
	X_augmented = np.concatenate((bias, X),axis=1)
	W = np.dot(np.linalg.pinv(np.dot(X_augmented.T, X_augmented)), np.dot(X_augmented.T, Y))
	return W

def FisherLDA_binary_classification(X, Y):
	'''
	Find the direction of projection which
	maximises the class separation, the projected points
	and the weights of the classifier
	Direction of projection = S_W ^ -1 * (mean1 - mean0)
	'''
	dataset = np.concatenate((X, Y), axis=1)
	classes = np.unique(Y)
	class_mean = []
	num_of_dim = 1
	for c in classes:
		class_mean.append(((1 / len(Y[Y==c])) * np.sum(dataset[dataset[:, -1]==c], axis = 0))[:-1])
	X_class1 = dataset[dataset[:, -1]==classes[0]]
	X_class2 = dataset[dataset[:, -1]==classes[1]]
	zero_mean_X_1 = X_class1[:, :-1] - class_mean[0]
	zero_mean_X_2 = X_class2[:, :-1] - class_mean[1]
	 # Using Fisher's LDA formula
	within_class_scatter = np.dot(zero_mean_X_1.T, zero_mean_X_1) + np.dot(zero_mean_X_2.T, zero_mean_X_2)
	W = np.dot(np.linalg.inv(within_class_scatter), (class_mean[1] - class_mean[0]))
	W = W / np.linalg.norm(W) # Unit vector along the direction of projection
	projected_data_scalar = np.dot(X, W) # Magnitude of the projected points
	projections = np.multiply(W.reshape(1,W.shape[0]),
		projected_data_scalar.reshape(projected_data_scalar.shape[0], 1)) # Magnitude * Unit vector gives the actual vector
	weight = LSC_binary_classification(projections, Y) # Classify the projected 1-D points using least square method
	return projections, W, weight

def classify_LSC_LDA(X, Y, dataset_name):
	'''
	Compare two classifiers using
	1. Least square method
	2. Fisher LDA
	'''
	x_line = np.linspace(-2, 4)
	plt.figure(figsize=(10,10))
	plt.suptitle("Fisher's discriminant vs Least square - C1 vs C2 - " + dataset_name, fontsize = 18)
	plt.xlabel("x1", fontsize = 16)
	plt.ylabel("x2", fontsize = 16)
	plt.scatter(X[:4, 0], X[:4, 1], c='r', marker='x', label='Class C1')
	plt.scatter(X[4:, 0], X[4:, 1], c='b', marker='o', label='Class C2')
	# Plot Least Square Method
	LSC_weights = LSC_binary_classification(X, Y)
	boundary_LSC = -LSC_weights[0] / LSC_weights[2] - (LSC_weights[1] / LSC_weights[2]) * x_line
	plt.plot(x_line, boundary_LSC, color='g', label='Least Square Classifier')
	# Plot LDA
	projections, direction, LDA_weights = FisherLDA_binary_classification(X, Y)
	direction_line = (direction[1] / direction[0]) * x_line
	boundary_LDA = -LDA_weights[0] / LDA_weights[2] - (LDA_weights[1] / LDA_weights[2]) * x_line
	# Plot Projections
	plt.scatter(projections[:4, 0], projections[:4, 1], c='r', marker='^', label = "C1 projection")
	plt.scatter(projections[4:, 0], projections[4:, 1], c='b', marker='<', label = "C2 projection")
	for i in range(len(projections)):
		plt.plot([X[i][0], projections[i][0]], [X[i][1], projections[i][1]], 'y--')
	# Plot Discriminant and Boundary
	plt.plot(x_line, direction_line, '--', color='black', label = "Fisher Discriminant")
	plt.plot(x_line, boundary_LDA, color='m', label = "Fisher LDA")
	plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.03), fancybox=True, shadow=True, ncol=3)

	plt.savefig(dataset_name + '.jpg', dpi = 150)
	# plt.show()
	plt.clf()

#Table 1
X = np.array([[3, 3], [3, 0], [2, 1], [0, 2], [-1, 1], [0, 0], [-1, -1], [1, 0]])
Y = np.array([[1], [1], [1], [1], [-1], [-1], [-1], [-1]])
classify_LSC_LDA(X, Y, 'Table 1')

#Table 2
X = np.array([[3, 3], [3, 0], [2, 1], [0, 1.5], [-1, 1], [0, 0], [-1, -1], [1, 0]])
Y = np.array([[1], [1], [1], [1], [-1], [-1], [-1], [-1]])
classify_LSC_LDA(X, Y, 'Table 2')


"""
A program that will accept a singlevariable function and a value of that variable and check
whether the input function is continuous at the point where the variable
assumes the value input
"""

from sympy import Symbol, sympify, Limit

def is_continuous(epxr, var, value):
    l_limit = Limit(expr, var, value, dir="-").doit()
    r_limit = Limit(expr, var, value, dir="+").doit()
    func_value = expr.subs({var: value})

    if l_limit == r_limit and func_value == l_limit:
        return True
    return False

if __name__ == "__main__":
    try:
        expr = sympify(input("Enter a function in one variable: "))
        var = Symbol(input("Enter the variable: "))
        value = float(input("Enter the point to check the continuity at: "))
    except Exception as err:
        print("Wrong input", err)
    else:
        result = is_continuous(expr, var, value)
        if result:
            print(f"{expr} is continuous at {value}")
        else:
            print(f"{expr} is not continuous at {value}")


import numpy as np
from scipy.stats import shapiro
from scipy.stats import anderson
from scipy.stats import normaltest

N = 10000
normal_values = np.random.normal(size=N)
zero_values = np.zeros(N)
print "Normal Values Shapiro", shapiro(normal_values)
print "Zeroes Shapiro", shapiro(zero_values)
print
print "Normal Values Anderson", anderson(normal_values)
print "Zeroes Anderson", anderson(zero_values)
print
print "Normal Values normaltest", normaltest(normal_values)
print "Zeroes normaltest", normaltest(zero_values)

'''
normaltest returns a 2-tuple of the chi-squared statistic, and the associated p-value.
Given the null hypothesis that x came from a normal distribution, the p-value represents
the probability that a chi-squared statistic that large (or larger) would be seen.

If the p-val is very small, it means it is unlikely that the data came from a normal
distribution
'''


"""
This module contains an implementation of an exponentially weighted moving average based on sample size.
The inspiration and context for this code was from a blog post by writen by Maksim Ivanov:
https://towardsdatascience.com/financial-machine-learning-part-0-bars-745897d4e4ba
"""

# Imports
import numpy as np
from numba import jit
from numba import float64
from numba import int64

@jit((float64[:], int64), nopython=False, nogil=True)
def ewma(arr_in, window):  # pragma: no cover
    """
    Exponentialy weighted moving average specified by a decay ``window`` to provide better adjustments for
    small windows via:
        y[t] = (x[t] + (1-a)*x[t-1] + (1-a)^2*x[t-2] + ... + (1-a)^n*x[t-n]) /
               (1 + (1-a) + (1-a)^2 + ... + (1-a)^n).

    :param arr_in: (np.ndarray), (float64) A single dimensional numpy array
    :param window: (int64) The decay window, or 'span'
    :return: (np.ndarray) The EWMA vector, same length / shape as ``arr_in``
    """
    arr_length = arr_in.shape[0]
    ewma_arr = np.empty(arr_length, dtype=float64)
    alpha = 2 / (window + 1)
    weight = 1
    ewma_old = arr_in[0]
    ewma_arr[0] = ewma_old
    for i in range(1, arr_length):
        weight += (1 - alpha)**i
        ewma_old = ewma_old * (1 - alpha) + arr_in[i]
        ewma_arr[i] = ewma_old / weight

    return ewma_arr


import numpy as np

class LogisticRegression:
    def __init__(
                 self,
                 lr=0.01,
                 num_iter=100000,
                 fit_intercept=True,
                 verbose=False
                 ):
        self.lr = lr
        self.num_iter = num_iter
        self.fit_intercept = fit_intercept

    def __add_intercept(self, X):
        intercept = np.ones((X.shape[0], 1))
        return np.concatenate((intercept, X), axis=1)

    def __sigmoid(self, z):
        return 1 / (1 + np.exp(-z))

    def __loss(self, h, y):
        return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean()

    def fit(self, X, y):
        """
        lr.fit(X,y)
        """
        if self.fit_intercept:
            X = self.__add_intercept(X)

        # weights Initializaation
        self.theta = np.zeros(X.shape[1])

        for i in range(self.num_iter):
            z = np.dot(X, self.theta)
            h = self.__sigmoid(z)
            gradient = np.dot(X.T, (h - y)) / y.size
            self.theta -= self.lr * gradient

            if self.verbose is True and i % 10000 == 0:
                z = np.dot(X, self.theta)
                h = self.__sigmoid(z)
                print(f"Loss :{self.__loss(h,y)} \t")

    def predict_prob(self, X):
        if self.fit_intercept:
            x = self.__add_intercept(X)

        return self.__sigmoid(np.dot(x, self.theta))

    def predict(self, X, threshold):
        """
        lr.predict(X,y)
        """
        return self.predict_prob(X) >= threshold


import scipy.signal

from library.image_tool_box import extend_image_array_with_padding
from library.image_tool_box import img_conv_kernel
from library.image_tool_box import print_image_array

image = [[1 , 2 , 3 , 4 , 5 , 6 , 7 ],
         [8 , 9 , 10, 11, 12, 13, 14],
         [15, 16, 17, 18, 19, 20, 21],
         [22, 23, 24, 25, 26, 27, 28],
         [29, 30, 31, 32, 33, 34, 35],
         [36, 37, 38, 39, 40, 41, 42],
         [43, 44, 45, 46, 47, 48, 49]]

filter_kernel_1 = [ [-1, 1, -1],
                    [-2, 3, 1],
                    [2, -6, 0]]

#
print( "Self-made 2D convolution demo:")

img_conv_ker = img_conv_kernel(image, filter_kernel_1)
print_image_array( img_conv_ker , "Decimal")

#
print( "SciPy's 2D convolution demo:")

output_1 = scipy.signal.convolve2d(image, filter_kernel_1, mode='same', boundary='fill', fillvalue=0)
print( output_1 )

'''

Example output:

Self-made 2D convolution demo:
        -2         -8         -7         -6         -5         -4         28
         3         -7        -10        -13        -16        -19         14
       -18        -28        -31        -34        -37        -40          0
       -39        -49        -52        -55        -58        -61        -14
       -60        -70        -73        -76        -79        -82        -28
       -81        -91        -94        -97       -100       -103        -42
      -101        -61        -63        -65        -67        -69        -57

SciPy's 2D convolution demo:
[[  -2   -8   -7   -6   -5   -4   28]
 [   3   -7  -10  -13  -16  -19   14]
 [ -18  -28  -31  -34  -37  -40    0]
 [ -39  -49  -52  -55  -58  -61  -14]
 [ -60  -70  -73  -76  -79  -82  -28]
 [ -81  -91  -94  -97 -100 -103  -42]
 [-101  -61  -63  -65  -67  -69  -57]]

'''


# coding: utf-8

import numpy as np
import matplotlib.pyplot as plt

#Define la función a integrar en 10 dimesiones
def funcion(a,b,c,d,e,f,g,h,i,j):
    return (a+b+c+d+e+f+g+h+i+j)**3

#Método que integra usando Monte Carlo. Tiene como entrada: función a integra, limítes inerior y superior, número de puntos para el cálcuo y número de veces que se integra. Devuelve el promedio de los valores obtenidos.
def Integra_10D(fun, lim1, lim2, NPoints, intentos):
    resultados = []
    for i in xrange(intentos):
        a = np.random.uniform(lim1,lim2,NPoints)
        b = np.random.uniform(lim1,lim2,NPoints)
        c = np.random.uniform(lim1,lim2,NPoints)
        d = np.random.uniform(lim1,lim2,NPoints)
        e = np.random.uniform(lim1,lim2,NPoints)
        f = np.random.uniform(lim1,lim2,NPoints)
        g = np.random.uniform(lim1,lim2,NPoints)
        h = np.random.uniform(lim1,lim2,NPoints)
        i = np.random.uniform(lim1,lim2,NPoints)
        j = np.random.uniform(lim1,lim2,NPoints)
        resultados.append((lim2-lim1)**10/NPoints*sum(fun(a,b,c,d,e,f,g,h,i,j)))
    return np.mean(resultados)

N = 10000
intentos = 20
res = Integra_10D(funcion,0.0,2.0,N,intentos) #Integra la función objetivo 20 veces con 1000 puntos.
print ('Valor de la integral con {} puntos y {} intentos = {}'.format(N,intentos,res))

eNes= []
valores = []
#Calcula la integral 20 veces para N = (2,4,8,...,8192)
for i in xrange(1,14):
    n = 2**i
    valores.append(Integra_10D(funcion, 0.0, 2.0, n, intentos))
    eNes.append(n)

#Grafica el valor de la integral vs. Número de puntos
plt.plot(eNes, valores)
plt.title(u'Valor integral vs. Número de puntos')
plt.xlabel('N')
plt.ylabel('Valor I')
plt.savefig('num_integral.pdf')
plt.close()

ValorAnalitico = 1126400.0 #Valor calculado en Wolfram Mathematica.
errores = 100*abs(ValorAnalitico - np.array(valores))/ValorAnalitico #Errores porcentuales de la integral númerica
x = 1/np.sqrt(np.array(eNes))

#Grafica errores porcentuales vs. 1/sqrt(N)
plt.plot(x, errores)
plt.title('Errores porcentuales vs. $1/\sqrt{N}$')
plt.xlabel('$1/\sqrt{N}$')
plt.ylabel('Error %')
plt.savefig('err_integral.pdf')
plt.close()


import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

def get_angles(pos, i, d_model):
    angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
    return pos * angle_rates

def positional_encoding(position, d_model):
    '''
    The positional encoding vector is added to the embedding vector.
    Embeddings represent a token in a d-dimensional space where tokens
    with similar meaning will be closer to each other. But the embeddings
    do not encode the relative position of words in a sentence.
    So after adding the positional encoding, words will be closer
    to each other based on the similarity of their meaning and their
    position in the sentence, in the d-dimensional space.
    '''
    angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                            np.arange(d_model)[np.newaxis, :],
                            d_model)

    # apply sin to even indices in the array; 2i
    angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])

    # apply cos to odd indices in the array; 2i+1
    angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])

    pos_encoding = angle_rads[np.newaxis, ...]

    return tf.cast(pos_encoding, dtype=tf.float32)

if __name__=='__main__':
    tf.compat.v1.enable_eager_execution()

    pos_encoding = positional_encoding(107, 256)
    print (pos_encoding.shape)

    plt.pcolormesh(pos_encoding[0], cmap='RdBu')
    plt.xlabel('Depth')
    plt.xlim((0, 256))
    plt.ylabel('Position')
    plt.colorbar()
    plt.show()


"""
"""

from math import factorial

from numpy import arctan2, array, cos, linspace, meshgrid, sin, sqrt, where, zeros

def polar_array(T, num):
    """
    Generate polar coordinates.
    """
    x = linspace(-T, T, num)
    X, Y = meshgrid(x, x)
    r = sqrt(X ** 2 + Y ** 2)
    th = arctan2(Y, X)
    return r, th

def rnm(n, m, rho):
    """
    Return an array with the zernike Rnm polynomial calculated at rho points.
    """
    Rnm = zeros(rho.shape)
    S = int((n - abs(m)) / 2)
    for s in range(0, S + 1):
        CR = (
            pow(-1, s)
            * factorial(n - s)
            / (
                factorial(s)
                * factorial(-s + (n + abs(m)) / 2)
                * factorial(-s + (n - abs(m)) / 2)
            )
        )
        p = CR * pow(rho, n - 2 * s)
        Rnm = Rnm + p
    Rnm[rho > 1.0] = 0
    return Rnm

def zernike(n, m, rho, theta):
    """
    **ARGUMENTS:**

    ===== ==========================================
    n     n order of the Zernike polynomial
    m     m order of the spatial frequency
    rho   Matrix containing the radial coordinates.
    theta Matrix containing the angular coordinates.
    ===== ==========================================

    """
    Rnm = rnm(n, m, rho)
    Nnm = sqrt(2 * (n + 1) / (1 + where(m == 0, 1, 0)))

    if m >= 0:
        Zmn = Nnm * Rnm * cos(m * theta)
    elif m < 0:
        Zmn = -Nnm * Rnm * sin(m * theta)

    return Zmn

def i2nm(i):
    """
    Return the n and m orders of the i'th zernike polynomial

    ========= == == == == == == == == == == == == == == == ===
    i          0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 ...
    n-order    0  1  1  2  2  2  3  3  3  3  4  4  4  4  4 ...
    m-order    0 -1  1 -2  0  2 -3 -1  1  3 -4 -2  0  2  4 ...
    ========= == == == == == == == == == == == == == == == ===
    """
    ia = array(i)
    n = (1 + (sqrt(8 * (ia) + 1) - 3) / 2).astype(int)
    ni = n * (n + 1) / 2
    m = -n + 2 * (i - ni)
    return n, m

def zerniken(i, rho, theta):
    """
    Return the normalized zernike function by the order i
    """
    n, m = i2nm(i)
    return zernike(n, m, rho, theta)


import numpy as np

def cost_function_regression(targets, predictions):
    return np.mean(np.square(np.subtract(targets, predictions)))

def cost_function_classification(targets, predictions):
    return np.mean(-targets * np.log(predictions))

if __name__ == '__main__':
    targets = np.array([1, 2, 3])
    predictions = np.array([0, 1, 8])

    print('targets             =', targets)
    print('predictions         =', predictions)

    print('Regression cost = ', cost_function_regression(targets, predictions))

    CLASSES = {
        0: 'cat',
        1: 'dog'
    }

    targets = np.array([0, 1, 1])  # cat, dog, dog
    good_predictions = np.array([0.1, 0.9, 0.9])  # predicted probability distribution
    bad_predictions = np.array([0.9, 0.1, 0.2])  # predicted probability distribution

    print('targets              =', targets)
    print('good predictions     =', good_predictions)
    print('bad predictions      =', good_predictions)

    print('Classification cost (good) = ', cost_function_classification(targets, good_predictions))
    print('Classification cost (bad)  = ', cost_function_classification(targets, bad_predictions))


import math
import random
import warnings
import sympy

def phi(n):
    amount = 0
    for k in range(1, n + 1):
        if math.gcd(n, k) == 1:
            amount += 1
    return amount

def gcd(a, b):
    """Calculate the Greatest Common Divisor of a and b.

    Unless b==0, the result will have the same sign as b (so that when
    b is divided by it, the result comes out positive).
    """
    while b:
        a, b = b, a%b
    return a

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

def modinv(a, m):
    gcd, x, y = egcd(a, m)
    if gcd != 1:
        return None  # modular inverse does not exist
    else:
        return x % m

n= 97289040915427312142046186233204893375
a= 61459853434867593821323745103091100940
b= 22119567361435062372463814709890918083

a_2 = 87467942514366097632147785951765210855
b_2 = 3291682454206668645932879948693825640

a_3 = 74945727802091171826938590498744274413
b_3 = 54949907590247169540755431623509626593

d= gcd(a,n)
d_2 = gcd (a_2,n)
d_3 = gcd(a_3,n)

print("For a = " + str(a))
print("d = GCD(a,n)= "+ str(d))

if(b%d==0):
    print("yes")
else:
    print("There is no solution since " + str(d) + " doesn't divide b")

print("\n")

print("For a2 = " + str(a_2))
print("d2 = GCD(a2,n) = " + str(d_2))
ainv=modinv(a_2,n)

if(b_2%d_2==0):
    print("There are " +str(d_2) +" number of solutions")
    ainv=modinv(a//d,n//d)
    while d_2 != 0:
        answer= ainv + ((d_2-1)*(n//d_2))
        print("One of the solution is: "+ str(answer))
        d_2 = d_2 - 1
else:
    print("There is no solution since 5 doesn't divide b")

print("\n")

print("For a3 = " + str(a_3))
print("d3 = GCD(a3,n) = " + str(d_3))

if (d_3==1):
    ainv=modinv(a_3,n)
    answer=(b_3*ainv)%n
    print("There is one solution: " +str(answer))
    print("\n")


# Monte Carlo Valuation of a European Option in a Black-Scholes World
# by Michal Lyskawinski
# 10/31/2016

from math import *
import numpy as np
# Initialize parameters
S = 100
r = 0.06
sig = 0.2
T = 1
K = 100
N = 300
M = 100
div = 0.03  # In percentage
option = 'Put'

# Precompute constants
dt = T/N
nu = r - div - 0.5*(sig**2)
nudt = nu*dt
sigsdt = sig*sqrt(dt)
lnS = log(S)

sum_CT = 0
sum_CT2 = 0

for j in range(1,M): # For each simulation
    lnST = lnS

    for i in range(1,N):
        eps = np.random.normal(0, 1)
        lnST = lnST + nudt + sigsdt*eps

    St = exp(lnST)
    if option == 'Call':
        CT = max(0, St - K)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT*CT
    elif option == 'Put':
        CT = max(0, K - St)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT * CT
    else:
        break

Value = sum_CT/M*exp(-r*T)
SD = sqrt((sum_CT2 - sum_CT*sum_CT/M)*exp(-2*r*T)/(M-1))
SE = SD/sqrt(M)

print('The Value of European',option,'Option is',Value)
print('The Standard Deviation of this Option is',SD)
print('The Standard Error in this case is',SE)


import numpy as np
import matplotlib.pyplot as plt

"""Some initial data for testing. Uncomment this docstring and uncomment print
statements to check working of the optmizers

X = 2* np.random.rand(100,1) #draws from a uniform distribution
y = 4 + 3*X + np.random.randn(100,1) #draws from a normal distribution
ones = np.ones(X.shape[0])
X_b = np.insert(X,0,ones,axis=1) """

#Normal Equation
def normal_equation(X,y,show_data = False,bias = False):

	if(show_data == True):
		plt.scatter(X,y)
		plt.show()

	ones = np.ones(X.shape[0])
	if(bias == True):
		X_b = np.insert(X,0,ones,axis=1)
	else:
		X_b = X
	XX = X_b.T.dot(X_b)
	XY = X_b.T.dot(y)
	Theta = np.linalg.inv(XX).dot(XY)
	return Theta

#print(normal_equation(X,y,bias=True))

#Batch Gradient Descent
def BGD(X,y,eta,iterations,bias=False):
	m = X.shape[0]
	if(bias == True):
		X_b = np.insert(X,0,np.ones(m),axis=1)
	else:
		X_b = X

	theta = np.random.randn(X_b.shape[1] , 1)

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

	return theta

#print(BGD(X_b,y,0.1,1000,100))

#Stochastic Gradient Descent
def SGD(X,y,epochs,t0,t1,bias = False):

	m = X.shape[0]

	if(bias == True):
		X_b = np.insert(X,0,np.ones(X.shape[0]),axis=1)
	else:
		X_b = X

	theta = np.random.randn(X_b.shape[1],1)
	for epoch in range(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]
			err = xi.dot(theta)-yi
			gradients = 2* xi.T.dot(err)
			eta = t0/((epoch*m+i)+t1)
			theta = theta - eta*gradients

	return theta

#print(SGD(X,y,50,5,50,bias=True))


"""
Number-theoretic operations, mostly about Z/nZ. Some functions are implemented
to avoid calling Sage routines.
"""

def order(g,p) :
	"""
	Compute multiplicative order of g mod p.
	"""
	from sage.all import Integers

	R = Integers(p)
	return R(g).multiplicative_order()

def _gcd(a,b) :
	if b == 0 :
		return a
	return _gcd(b, a%b)

def gcd(*args) :
	g = 0
	for arg in args :
		g = _gcd(arg, g)
		if g == 1 :
			break
	return g

def egcd(a,b) :
	"""
	Return (x,y,d) where d = gcd(a,b) and xa + yb = d.
	"""
	if b == 0 :
		return (1,0,a)
	# q = a//b, r = a%b, so a = qb + r.
	(q,r) = divmod(a,b)

	# xb + yr = d, where d = gcd(b,r) = gcd(a,b).
	(x,y,d) = egcd(b,r)

	# xb + y(a-qb) = d
	# ya + (x-yq)b = d.
	return (y, x-y*q, d)

def modinv(a,m) :
	"""
	Compute x such that ax = 1 (mod m). Returns None if no such b exists.
	"""

	x,y,d = egcd(a,m)
	if d != 1 :
		raise ArithmeticError(
			f'{a} does not have an inverse mod {m} : gcd is {d}')

	return x % m

def crt(xs,ps) :
	"""
	Given list ps of coprime residues, and list xs where xs[i] is a residue
	mod ps[i], find z (mod prod(ps)) such that z = xs[i] mod ps[i] over all i.
	"""
	from utils import prod

	if len(xs) != len(ps) :
		raise ValueError(
			'Number of residues different from number of moduli: ' + \
			str(len(xs)) + ', ' + str(len(ps))
		)

		if len(ps) == 1 :
			return xs[0] % ps[0]

	if gcd(*ps) != 1 : raise ValueError('Moduli not coprime: ' + str(ps))

	n = len(xs)

	# Formally, z lives in Z/PZ.
	P = prod(ps)
	Ns = [ P//ps[i] for i in range(n)]

	z = 0
	for (x,p,N) in zip(xs, ps, Ns) :
		# rN = 1 mod p
		r = modinv(N, p)
		# if r == None : raise ValueError('ps not coprime: ' + str(ps))

		# By construction, rNx = x mod p and, of course, rNx = 0 mod N
		# which implies rNx = 0 mod p' for all p' != p.
		z += r*N*x

	return z % P

def _factor(n) :
	"""
	Hand-rolled integer prime factorization using wheel factorization:
	https://en.wikipedia.org/wiki/Wheel_factorization

	Let P be the product of some of the first few primes, e.g. P = 2*3*5 = 30.
	The algorithm is based on the observation that a number is prime only if
	either it is one of 2, 3, or 5, or its residue in Z/PZ is coprime to all of
	2, 3, or 5 (in other words, its residue is a prime not equalling 2, 3, and 5).

	In the most elementary case, we use P = 2, where we iterate over only odd
	numbers between 3 and sqrt(n). Here we only have to perform trial division
	against 50% of the integers between 1 and sqrt(n); when P = 2*3 = 6, it
	turns out we need to do trial division against ~34% of the integers in the same set.
	"""
	from collections import Counter
	from itertools import count
	from math import isqrt

	multiplicities = Counter()

	if n <= 0 : return ValueError("Trying to factor nonpositive integer: " + str(n))

	while n % 2 == 0 :
		multiplicities[2] += 1
		n //= 2

	while n % 3 == 0 :
		multiplicities[3] += 1
		n //= 3

	# The "prime residues" mod 2*3 = 6 are 1 and 5. That is, with the exception
	# of 2 and 3, only the integers in the sets 6Z + 1 and 6Z + 5 are possibly
	# prime; the integers in the other sets are not.

	for z in count(0, 6) : # 6, 12, 18, ...
		if z*z > n :
			break

		if z != 0 :
			while n % (z+1) == 0 :
				multiplicities[z+1] += 1
				n //= z+1

		while n % (z+5) == 0 :
			multiplicities[z+5] += 1
			n //= z+5

	if n != 1 : multiplicities[n] += 1
	return multiplicities

def factor(n) :
	# For small n, use hand-rolled factoring algorithm to avoid slow imports.
	if n <= 2**16 :
		return _factor(n)

	from sage.all import factor
	return factor(n)


#! /usr/bin/env python
"""Utilities for normalizing angles and finding angular distances

Author: Mohak Bhardwaj
Date: Oct. 12, 2017

"""
import numpy as np

def normalize_angle_positive(angle):
  """Given an angle (in radians), normalizes the angle to lie [0, 2*pi)

  @param: angle in radians
  @returns: Normalized angle in randians
  """
  PI = np.pi
  return ((angle%(2.0*PI)) + 2.0*PI)%(2.0*PI)

def normalize_angle(angle):
  """Given an angle (in radians), normalizes the angle to lie (-pi, pi]

  @param: angle in radians
  @returns: Normalized angle in randians
  """
  PI = np.pi
  ang = normalize_angle_positive(angle)
  if ang > PI:
    ang -= 2*PI
  return ang

def angular_distance(ang1, ang2):
  """Given two angles it returns the angular distnace between them normalized between (-pi, pi]
  """
  return normalize_angle(ang2-ang1)


import numpy as np

# A function that normalizes data with pre-scecified mean and std.
def normalize(X,mu=0,std=1,flag_train=True):
    if(flag_train):
        mu = np.mean(X,0)
        std = np.std(X,0)

    X -= mu
    X /= std

    return X,mu,std

# A function to add the all-ones column
def add_interfit(X):
    col_ones = np.ones([X.shape[0],1])
    return np.concatenate([col_ones,X],1)

# A function to create the normalized function with polynomial features up to degree deg_max
def create_feature_matrix(X_0,deg_max,mu=0,std=1,flag_train=True):
    X = np.zeros([X_0.shape[0],deg_max])

    deg=1
    while deg<=deg_max:
        X[:,deg-1] = X_0**deg
        deg += 1

    X,train_mean,train_std = normalize(X,mu,std,flag_train)

    X = add_interfit(X)

    return X,train_mean,train_std

# A function to evaluate the LS solution
def LS_evaluate(X,T):
    return (X @ T.transpose())

# A function that calculates the error
def J_error(Y,Y_est):
    return np.mean((Y-Y_est)**2)

# A function that calculates the error + L2 penalization
def J_error_L2(Y,Y_est,T,l):
    return  J_error(Y,Y_est) + (l/Y.shape[0]) * np.sum(T**2)

# A function to compute the LS solution
def Ridge_solution(X,Y,l):
    A = l*np.eye(X.shape[1])
    A[0,0] = 0
    A += X.transpose() @ X

    return (np.linalg.inv(A) @ X.transpose() @ Y)

# A function to randomly split a data set
def split_set(X_0,Y_0,fraction):

    N = X_0.shape[0]
    N_split = np.round(fraction * X_0.shape[0]).astype(np.int32)
    mask = np.random.permutation(N)

    X_1 = X_0[mask[N_split:-1]]
    Y_1 = Y_0[mask[N_split:-1]]

    X_0 = X_0[mask[:N_split]]
    Y_0 = Y_0[mask[:N_split]]

    return X_0,X_1,Y_0,Y_1

# A function to compute the Ridge cost function for both train and validation/test sets using
# a provided value of the parameter vector T, polynomial degree and lambda_value

def eval_J_Ridge_given_T(X_train,Xvt,degree,Y_train,Yvt,l,T):

    # Xvt,Yvt --> We use this function to evaluate either validation error or test error

    # Lets compute the normalized feature matrices F_train, F_test
    F_train,train_mean,train_std = create_feature_matrix(X_train,degree,0,1,flag_train=True)

    F_vt,_,_ = create_feature_matrix(Xvt,degree,train_mean,train_std,flag_train=False)

    # We evaluate the Penalized MSE (MSE + L2 penalization)
    J_train = J_error_L2(Y_train,LS_evaluate(F_train,T),T,l)
    J_vt = J_error_L2(Yvt,LS_evaluate(F_vt,T),T,l)

    return J_train,J_vt,F_train,F_vt

Notebooks/Session 5 Gradient Descent/Ridge_functions.py


import numpy as np
from abc import ABC, abstractmethod
import scipy
from scipy import linalg
import scipy.linalg

class BIBO(ABC):
    """Abtract class for implement the algorithms. Which dertermine
        the stability of system
    """
    @abstractmethod
    def conclusion(self) -> int:
        """conclude about the stability of system.
            This function implement the algorithms.
        Returns:
            [int]: if 1, system is stable
                   if 0, this algorithms unable to conclude about stability os system
                   if -1, system is unstable
        """
        pass

class Gerschgorin(BIBO):

    def __init__(self,A: np.ndarray):
        self.A = A
    def conclusion(self)-> int:
        """conclude about the stability of system.
            This function implement the algorithms.
        Returns:
            [int]: if 1, system is stable
                   if 0, this algorithms unable to conclude about stability os system
                   if -1, system is unstable
        """
        A = self.A
        diag = np.diag(A)
        B = np.sum(np.abs(A), axis=1)
        C = B - np.abs(diag) + diag
        result = np.all((C) <=0)

        if result:
            return 1
        else:
            return 0

class Lyapunov(BIBO):

    def __init__(self, A, P=None, Q=None):
        """[summary]

        Args:
            A : nxn 2D-np.ndarray
                system matrix
            P : 2D-array
                an define-positive matrix, needed matrix solve Ricati equations. If solution is define positive matrix,
                system is stable. if P is provide, Q must be None. Defaults to None.
            Q : 2D-array
                the same as description of arg P,if Q is provide, P must be None. Defaults to None.
        """
        self.A = A
        shape = A.shape
        assert (P is None) ^ (Q is None), "please fill either P or Q"
        self. P = P
        self. Q = Q
        if P is not None:
            assert P.shape == A.shape, "Invalid shape of matrix P"
        else:
            assert Q.shape == A.shape, "Invalid shape of matrix Q"
    @staticmethod
    def is_definite_positive_matrix(M):
        """Determine a maxtrix if

        Args:
            M : 2D-ndarray

        Returns:
            [Boolean]: True if matrix is definite positive
        """
        condition1 = np.all(np.linalg.eigvals(M) > 0)
        condition2 = np.allclose(M,M.T)
        return  condition1 and condition2

    def conclusion(self):
        """conclude about the stability of system.
            This function implement the algorithms.
        Returns:
            [int]: if 1, system is stable
                   if 0, this algorithms unable to conclude about stability os system
                   if -1, system is unstable
        """
        if self.P is not None:
            Q = -(self.P @ self.A + self.A.T @ self. P)
            print(f'Q={Q}')
            result = Lyapunov.is_definite_positive_matrix(Q)

        else:
            P = linalg.solve_sylvester(a=self.A.T, b=self.A, q=-self.Q)
            print(f'P={P}')
            result = Lyapunov.is_definite_positive_matrix(M=P)
        if result:
            return 1
        else:
            return 0

class Hurwitz(BIBO):
    def __init__(self,A):
        self.A = A

    def conclusion(self,):
        """conclude about the stability of system.
            This function implement the algorithms.
        Returns:
            [int]: if 1, system is stable
                   if 0, this algorithms unable to conclude about stability os system
                   if -1, system is unstable
        """
        result =  np.all(scipy.linalg.eigvals(self.A).real <0)
        if result:
            return 1
        else:
            return -1


import numpy as np

def l1_norm(p1, p2):
    """
    Computes the L1 norm distance between two n-dimensional points
    :param p1: first point
    :param p2: second point
    :return: L1 norm distance value
    """
    return np.sum(np.abs(np.asarray(p1) - np.asarray(p2)))

def l2_norm(p1, p2):
    """
    Computes the L1 norm distance between two n-dimensional points
    :param p1: first point
    :param p2: second point
    :return: L1 norm distance value
    """
    return np.linalg.norm((np.asarray(p1), np.asarray(p2)))

def chebyshev(p1, p2):
    """
    Computes the Chebyshev distance, between two n-dimensional points
    :param p1: first point
    :param p2: second point
    :return: Chebyshev distance value
    """
    return np.max(np.abs(np.asarray(p1) - np.asarray(p2)))


# -*- coding: utf-8 -*-
"""
Ejercicio 2: Aproximación numérica de orden 1 y de orden 2 de la
derivada de la función f(x) = 1/x.

"""

import numpy as np
import matplotlib.pyplot as plt

# Función f(x)= 1/x y su derivada f'
f = lambda x:1/x      # función f
df = lambda x:(-1)/x**2     # derivada exacta f'

#---------------------------------------------------------------
# Derivación numérica de orden 1
#---------------------------------------------------------------
h = 0.01     # paso

a = 0.2     # extremo inferior del intervalo
b = 1.2     # extremo superior del intervalo
x = np.arange(a,b+h,h)     # vector con 1º coordenada a, última coordenanda b y diferencia entre coordenadas h

# Diferencias progresiva y regresivas
df_p = np.diff(f(x))/h      # vector que contiene los valores de las diferencias progresivas
df_r = df_p      # vector que contiene los valores de las diferencias regresivas

# Para los puntos interiores con aproximación centrada
df_c = (df_p[1:] + df_r[0:-1])/2
# Extremo izquierdo con aproximación progresiva (orden 1)
df_a = df_p[0]
# Extremo derecho con aproximación regresiva (orden 1)
df_b = df_r[-1]

# Construimos un vector que contenga todos los valores de la der. num de orden 1
# El vector será de la forma [aprox. en extremo izdo, aprox. en puntos interiores, aprox. en extremo dcho.]
df_a = np.array([df_a])  # transformamos df_a en un vector
df_b = np.array([df_b])  # transformamos df_b en un vector
Aprox_1 = np.concatenate((df_a,df_c,df_b))  # vector que contiene los valores de la der. num. de orden 1

Error_1 = np.linalg.norm(df(x)-Aprox_1)/np.linalg.norm(df(x))          # error de orden 1
print("Error con derivacion de orden 1 = ",Error_1)             # escribimos el error en pantalla

#---------------------------------------------------------------
# Derivación numérica de orden 2
#---------------------------------------------------------------
# Para los puntos interiores la aproximación centrada es la misma que en el caso anterior

# Extremo izquierdo: aproximación progresiva (orden 2)
df_a2 = (-3*f(x[0])+4*f(x[0]+h)-f(x[0]+2*h))/(2*h)
# Extremo derecho: aproximación regresiva (orden 2)
df_b2 = (f(x[-1]-2*h)-4*f(x[-1]-h)+3*f(x[-1]))/(2*h)

# Construimos un vector que contenga todos los valores de la der. num de orden 2
# El vector será de la forma [aprox. en extremo izdo, aprox. en puntos interiores, aprox. en extremo dcho.]
df_a2 = np.array([df_a2])       # transformamos df_a2 en un vector
df_b2 = np.array([df_b2])       # transformamos df_b2 en un vector
Aprox_2 = np.concatenate((df_a2,df_c,df_b2))     # vector que contiene los valores de la der. num. de orden 2

Error_2 = np.linalg.norm(df(x)-Aprox_2)/np.linalg.norm(df(x))     # error de orden 2
print("Error con derivacion de orden 2 = ",Error_2)        # escribimos el error en pantalla


import numpy
import csv
from bs4 import BeautifulSoup
import scipy.linalg as la
label=["sepal_length", "sepal_width", "petal_length", "petal_width", "class"]
Feature_number=4
Training_number=50
Iris_setosa= numpy.empty((50,4))
Iris_versicolor=numpy.empty((50,4))
Iris_virginica=numpy.empty((50,4))
Iris=numpy.empty((150,4))
def parser(data):
    x = list()
    for i in range(len(data)):
        temp = data[i].split(',')
        x.append([temp[0],temp[1],temp[2],temp[3]])
    return numpy.array(x).astype(numpy.float64)
def data_processing():
    with open('irisdata.xml') as f:
        irisdata = f.read()
    data = BeautifulSoup(irisdata, 'xml').text.split('\n')[1:-3]
    numpy.copyto(Iris,parser(data))
    numpy.copyto(Iris_setosa,Iris[:50])
    numpy.copyto(Iris_versicolor,Iris[50:100])
    numpy.copyto(Iris_virginica,Iris[100:150])

def get_covariance_matrix(A):
    A=numpy.array(A, dtype='f')
    mean_vector=numpy.mean(A,axis=0)
    cov_matrix = numpy.reshape(numpy.zeros(Feature_number*Feature_number), (Feature_number,Feature_number))

    for x in range(Feature_number):
        for y in range(len(A[:,x])):
            A[:,x][y]=float(A[:,x][y])-float(mean_vector[x])

    cov_matrix=(numpy.dot(numpy.transpose(A),A))/Training_number
    return cov_matrix

def eigvals_vectors(A):

    _matrix = get_covariance_matrix(A)
    eigvals, eigvectors = la.eig(_matrix)

    return eigvals, eigvectors

data_processing()
eigvals, eigvectors = eigvals_vectors(Iris_setosa)

print("Eigvals:\n",eigvals.real)
print("Eigvectors:\n",eigvectors)


"""
Module: libfmp.c2.c2_fourier
Author: Frank Zalkow, Meinard Müller
License: The MIT license, https://opensource.org/licenses/MIT

This file is part of the FMP Notebooks (https://www.audiolabs-erlangen.de/FMP)
"""
import numpy as np
from numba import jit
import librosa

@jit(nopython=True)
def generate_matrix_dft(N, K):
    """Generates a DFT (discrete Fourier transfrom) matrix

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        N: Number of samples
        K: Number of frequency bins

    Returns:
        dft: The DFT matrix
    """
    dft = np.zeros((K, N), dtype=np.complex128)
    for n in range(N):
        for k in range(K):
            dft[k, n] = np.exp(-2j * np.pi * k * n / N)
    return dft

@jit(nopython=True)
def generate_matrix_dft_inv(N, K):
    """Generates an IDFT (inverse discrete Fourier transfrom) matrix

    Notebook: C2/C2_STFT-Inverse.ipynb

    Args:
        N: Number of samples
        K: Number of frequency bins

    Returns:
        dft: The IDFT matrix
    """
    dft = np.zeros((K, N), dtype=np.complex128)
    for n in range(N):
        for k in range(K):
            dft[k, n] = np.exp(2j * np.pi * k * n / N) / N
    return dft

@jit(nopython=True)
def dft(x):
    """Compute the disrcete Fourier transfrom (DFT)

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        x: Signal to be transformed

    Returns:
        X: Fourier transform of x
    """
    x = x.astype(np.complex128)
    N = len(x)
    dft_mat = generate_matrix_dft(N, N)/N
    return np.dot(dft_mat, x)

@jit(nopython=True)
def idft(x):
    """Compute the inverse discrete Fourier transfrom (IDFT)

    Notebook: C2/C2_STFT-Inverse.ipynb

    Args:
        x: Signal to be transformed

    Returns:
        X: Fourier transform of x
    """
    x = x.astype(np.complex128)
    N = len(x)
    dft_mat = generate_matrix_dft_inv(N, N)
    return np.dot(dft_mat, x)

@jit(nopython=True)
def twiddle(N):
    """Generate the twiddle factors used in the computation of the fast Fourier transform (FFT)

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        N: Number of samples

    Returns:
        sigma: The twiddle factors
    """
    k = np.arange(N // 2)
    sigma = np.exp(-2j * np.pi * k / N)
    return sigma

@jit(nopython=True)
def twiddle_inv(N):
    """Generate the twiddle factors used in the computation of the Inverse fast Fourier transform (IFFT)

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        N: Number of samples

    Returns:
        sigma: The twiddle factors
    """
    n = np.arange(N // 2)
    sigma = np.exp(2j * np.pi * n / N)
    return sigma

@jit(nopython=True)
def fft(x):
    """Compute the fast Fourier transform (FFT)

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        x: Signal to be transformed

    Returns:
        X: Fourier transform of x
    """
    x = x.astype(np.complex128)
    N = len(x)
    log2N = np.log2(N)
    assert log2N == int(log2N), 'N must be a power of two!'
    X = np.zeros(N, dtype=np.complex128)

    if N == 1:
        return x
    else:
        this_range = np.arange(N)
        A = fft(x[this_range % 2 == 0])
        B = fft(x[this_range % 2 == 1])
        C = twiddle(N) * B
        X[:N//2] = A + C
        X[N//2:] = A - C
        return X

@jit(nopython=True)
def ifft_noscale(X):
    """Compute the inverse fast Fourier transform (IFFT) without the final scaling factor of 1/N

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        X: Fourier transform of x

    Returns:
        x: Inverse Fourier transform of x
    """
    X = X.astype(np.complex128)
    N = len(X)
    log2N = np.log2(N)
    assert log2N == int(log2N), 'N must be a power of two!'
    x = np.zeros(N, dtype=np.complex128)

    if N == 1:
        return X
    else:
        this_range = np.arange(N)
        A = ifft_noscale(X[this_range % 2 == 0])
        B = ifft_noscale(X[this_range % 2 == 1])
        C = twiddle_inv(N) * B
        x[:N//2] = A + C
        x[N//2:] = A - C
        return x

@jit(nopython=True)
def ifft(X):
    """Compute the inverse fast Fourier transform (IFFT)

    Notebook: C2/C2_DFT-FFT.ipynb

    Args:
        X: Fourier transform of x

    Returns:
        x: Inverse Fourier transform of x
    """
    return ifft_noscale(X) / len(X)

def stft_basic(x, w, H=8, only_positive_frequencies=False):
    """Compute a basic version of the discrete short-time Fourier transform (STFT)

    Notebook: C2/C2_STFT-Basic.ipynb

    Args:
        x: Signal to be transformed
        w: Window function
        H: Hopsize
        only_positive_frequencies: Return only positive frequency part of spectrum (non-invertible)

    Returns:
        X: The discrete short-time Fourier transform
    """
    N = len(w)
    L = len(x)
    M = np.floor((L - N) / H).astype(int) + 1
    X = np.zeros((N, M), dtype='complex')
    for m in range(M):
        x_win = x[m * H:m * H + N] * w
        X_win = np.fft.fft(x_win)
        X[:, m] = X_win

    if only_positive_frequencies:
        K = 1 + N // 2
        X = X[0:K, :]
    return X

def istft_basic(X, w, H, L):
    """Compute the inverse of the basic discrete short-time Fourier transform (ISTFT)

    Notebook: C2/C2_STFT-Inverse.ipynb

    Args:
        X: The discrete short-time Fourier transform
        w: Window function
        H: Hopsize
        L: Length of time signal

    Returns:
        x: Time signal
    """
    N = len(w)
    M = X.shape[1]
    x_win_sum = np.zeros(L)
    w_sum = np.zeros(L)
    for m in range(M):
        x_win = np.fft.ifft(X[:, m])
        # Avoid imaginary values (due to floating point arithmetic)
        x_win = np.real(x_win)
        x_win_sum[m * H:m * H + N] = x_win_sum[m * H:m * H + N] + x_win
        w_shifted = np.zeros(L)
        w_shifted[m * H:m * H + N] = w
        w_sum = w_sum + w_shifted
    # Avoid division by zero
    w_sum[w_sum == 0] = np.finfo(np.float32).eps
    x_rec = x_win_sum / w_sum
    return x_rec, x_win_sum, w_sum

@jit(nopython=True)
def stft(x, w, H=512, zero_padding=0, only_positive_frequencies=False):
    """Compute the discrete short-time Fourier transform (STFT)

    Notebook: C2/C2_STFT-Basic.ipynb

    Args:
        x: Signal to be transformed
        w: Window function
        H: Hopsize
        zero_padding: Number of zeros to be padded after windowing and before the Fourier transform of a frame
            (Note: The purpose of this step is to increase the frequency sampling.)
        only_positive_frequencies: Return only positive frequency part of spectrum (non-invertible)

    Returns:
        X: The discrete short-time Fourier transform
    """

    N = len(w)
    x = np.concatenate((np.zeros(N // 2), x, np.zeros(N // 2)))

    L = len(x)
    M = int(np.floor((L - N) / H)) + 1

    X = np.zeros((N + zero_padding, M), dtype=np.complex128)
    zero_padding_vector = np.zeros((zero_padding, ), dtype=x.dtype)

    for m in range(M):
        x_win = x[m * H:m * H + N] * w
        if zero_padding > 0:
            x_win = np.concatenate((x_win, zero_padding_vector))
        X_win = fft(x_win)
        # Note: X_win = np.fft.fft(x_win) does not work in combination with @jit
        X[:, m] = X_win

    if only_positive_frequencies:
        K = 1 + (N + zero_padding) // 2
        X = X[0:K, :]
    return X

@jit(nopython=True)
def istft(X, w, H, L, zero_padding=0):
    """Compute the inverse discrete short-time Fourier transform (ISTFT)

    Notebook: C2/C2_STFT-Inverse.ipynb

    Args:
        X: The discrete short-time Fourier transform
        w: Window function
        H: Hopsize
        L: Length of time signal
        zero_padding: Number of zeros to be padded after windowing and before the Fourier transform of a frame

    Returns:
        x_rec: Reconstructed time signal
    """
    N = len(w)
    L = L + N
    M = X.shape[1]
    w_sum = np.zeros(L)
    x_win_sum = np.zeros(L)
    w_sum = np.zeros(L)
    for m in range(M):
        start_idx, end_idx = m * H, m * H + N + zero_padding
        if start_idx > L:
            break

        x_win = ifft(X[:, m])
        # Note: x_win = np.fft.ifft(X[:, m]) does not work in combination with @jit
        if end_idx > L:
            end_idx = L
            x_win = x_win[:end_idx-start_idx]
            cur_w = w[:end_idx-start_idx]
        else:
            cur_w = w

        # Avoid imaginary values (due to floating point arithmetic)
        x_win_real = np.real(x_win)
        x_win_sum[start_idx:end_idx] = x_win_sum[start_idx:end_idx] + x_win_real
        w_shifted = np.zeros(L)
        w_shifted[start_idx:start_idx + len(cur_w)] = cur_w
        w_sum = w_sum + w_shifted
    # Avoid division by zero
    w_sum[w_sum == 0] = np.finfo(np.float32).eps
    x_rec = x_win_sum / w_sum
    x_rec = x_rec[N // 2:-N // 2]
    return x_rec

def stft_convention_fmp(x, Fs, N, H, pad_mode='constant', center=True, mag=False, gamma=0):
    """Compute the discrete short-time Fourier transform (STFT)

    Notebook: C2/C2_STFT-FreqGridInterpol.ipynb

    Args:
        x: Signal to be transformed
        Fs: Sampling rate
        N: Window size
        H: Hopsize
        pad_mode: Padding strategy is used in librosa
        center: Centric view as used in librosa
        mag: Computes magnitude STFT if mag==True
        gamma: Constant for logarithmic compression (only applied when mag==True)

    Returns:
        X: Discrete (magnitude) short-time Fourier transform
    """
    X = librosa.stft(x, n_fft=N, hop_length=H, win_length=N,
                     window='hann', pad_mode=pad_mode, center=center)
    if mag:
        X = np.abs(X)**2
        if gamma > 0:
            X = np.log(1 + gamma * X)
    F_coef = librosa.fft_frequencies(sr=Fs, n_fft=N)
    T_coef = librosa.frames_to_time(np.arange(X.shape[1]), sr=Fs, hop_length=H)
    # T_coef = np.arange(X.shape[1]) * H/Fs
    # F_coef = np.arange(N//2+1) * Fs/N
    return X, T_coef, F_coef


import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import pandas as pd

file = 'http://apmonitor.com/do/uploads/Main/tclab_dyn_data3.txt'
data = pd.read_csv(file)

# subset for training
n = 3000
tm = data['Time'][0:n].values
Q1s = data['Q1'][0:n].values
T1s = data['T1'][0:n].values

m = GEKKO()
m.time = tm

# Parameters to Estimate
K1 = m.FV(value=0.5,lb=0.1,ub=1.0)
tau1 = m.FV(value=150,lb=50,ub=250)
tau2 = m.FV(value=15,lb=10,ub=20)
K1.STATUS = 1
tau1.STATUS = 1
tau2.STATUS = 1

# Model Inputs
Q1 = m.Param(value=Q1s)
Ta = m.Param(value=23.0) # degC
T1m = m.Param(T1s)

# Model Variables
TH1 = m.Var(value=T1s[0])
TC1 = m.Var(value=T1s)

# Objective Function
m.Minimize((T1m-TC1)**2)

# Equations
m.Equation(tau1 * TH1.dt() + (TH1-Ta) == K1*Q1)
m.Equation(tau2 * TC1.dt()  + TC1 == TH1)

# Global Options
m.options.IMODE   = 5 # MHE
m.options.EV_TYPE = 2 # Objective type
m.options.NODES   = 2 # Collocation nodes
m.options.SOLVER  = 3 # IPOPT

# Predict Parameters and Temperatures
m.solve()

# Create plot
plt.figure(figsize=(10,7))

ax=plt.subplot(2,1,1)
ax.grid()
plt.plot(tm,T1s,'ro',label=r'$T_1$ measured')
plt.plot(tm,TC1.value,'k-',label=r'$T_1$ predicted')
plt.ylabel('Temperature (degC)')
plt.legend(loc=2)
ax=plt.subplot(2,1,2)
ax.grid()
plt.plot(tm,Q1s,'b-',label=r'$Q_1$')
plt.ylabel('Heater (%)')
plt.xlabel('Time (sec)')
plt.legend(loc='best')

# Print optimal values
print('K1: ' + str(K1.newval))
print('tau1: ' + str(tau1.newval))
print('tau2: ' + str(tau2.newval))

# Save and show figure
plt.savefig('tclab_2nd_order_fit.png')

# Validation
tm = data['Time'][n:3*n].values
Q1s = data['Q1'][n:3*n].values
T1s = data['T1'][n:3*n].values

v = GEKKO()
v.time = tm

# Parameters to Estimate
K1 = K1.newval
tau1 = tau1.newval
tau2 = tau2.newval
Q1 = v.Param(value=Q1s)
Ta = v.Param(value=23.0) # degC
TH1 = v.Var(value=T1s[0])
TC1 = v.Var(value=T1s[0])
v.Equation(tau1 * TH1.dt() + (TH1-Ta) == K1*Q1)
v.Equation(tau2 * TC1.dt()  + TC1 == TH1)
v.options.IMODE   = 4 # Simulate
v.options.NODES   = 2 # Collocation nodes
v.options.SOLVER  = 1

# Predict Parameters and Temperatures
v.solve(disp=True)

# Create plot
plt.figure(figsize=(10,7))

ax=plt.subplot(2,1,1)
ax.grid()
plt.plot(tm,T1s,'ro',label=r'$T_1$ measured')
plt.plot(tm,TC1.value,'k-',label=r'$T_1$ predicted')
plt.ylabel('Temperature (degC)')
plt.legend(loc=2)
ax=plt.subplot(2,1,2)
ax.grid()
plt.plot(tm,Q1s,'b-',label=r'$Q_1$')
plt.ylabel('Heater (%)')
plt.xlabel('Time (sec)')
plt.legend(loc='best')

# Save and show figure
plt.savefig('tclab_2nd_order_validate.png')
plt.show()


"""
Visualization of Limiting Matrix in Transition Matrix

By
Gurpinder Singh
20 May 2020
"""

import numpy as np
import matplotlib.pyplot as plt

def ChainGenerator(initialMatrix, numGen):
    """
    Input : a transition Matrix (Numpy array) and number of Generations

    Takes the nth dot product of the initial matrix with its n-1th dot product,
    stores the results in a 3D array called results.

    OUTPUT = Results
    """
    Results = np.zeros([len(initialMatrix), len(initialMatrix[0]), numGen]) #initializing the 3D array
    CurrentGen = initialMatrix[:] #making a copy of initial array
    for i in range(len(CurrentGen)): #appending the Gen0 values to Results
            for j in range(len(CurrentGen)):
                Results[i][j][0] = CurrentGen[i][j]

    for G in range(1, numGen): #looping over generations
        CurrentGen = np.dot(initialMatrix, CurrentGen) #taking dot products
        for i in range(len(CurrentGen)): #appending each dot product to results
            for j in range(len(CurrentGen)):
                Results[i][j][G] = CurrentGen[i][j]

    return Results

def plotMarkov(Results, labels, title="none", figureSize=(10,10)):
    """
    Plotting function to the results matrix generated by Chain Generator.
    Inputs:
        Labels is the array for row/column labels of the probabilities matrix.
        len(label) should be equal to len(Results)
    """
    plt.figure(figsize = figureSize)
    plt.title(title)
    GX = range(len(Results[0][0]))

    for i in range(len(Results)):
        for j in range(len(Results)):
            if labels[i] == labels[j]: #plots that show self Transition
                plt.plot(GX, Results[i][j], "--", label = labels[i] + " to " + labels[j], linewidth = 2.0)
            else: #other transitions are solid lines
                plt.plot(GX, Results[i][j], label = labels[i] + " to " + labels[j], linewidth = 2.0)
    plt.legend(loc = "best")
    plt.xlabel("Generations")
    plt.ylabel("Probabilities")
    plt.savefig(title.replace(" ", "_")+".jpg") #save the plot as .jpg

# #running the code! (on Assignment questions ofcourse)
# """
# Question Number 1, part 1
# """
# Mat1 = np.array([[.8, 0, .2],
#                 [.2, .7, .1],
#                 [.3, .3, .4]])

# labels1 = np.array(["Harvard", "Darthmouth", "Yale"])
# plotMarkov(ChainGenerator(Mat1, 30), labels1, "Student Transitions over 30 Generations")
# """
# Question Number 1, part 2
# """
# Mat2 = np.array([[1.0, 0, 0],
#                [.2, .7, .1],
#                [.3, .3, .4]])

# labels1 = np.array(["Harvard", "Darthmouth", "Yale"])
# plotMarkov(ChainGenerator(Mat2, 40), labels1, "Student Transitions over 40 Generations, with Harvard to Harvard = 1.0")

# """
# Question Number 2
# """
# Mat3 = np.array([[.8, .1, .1],
#                [.2, .6, .2],
#                [.25, .25, .5]])

# labels2 = np.array(["Professional", "Skilled worker", "Unskilled Worker"])
# plotMarkov(ChainGenerator(Mat3, 30), labels2, "Worker Transitions over 30 Generations")

"""
Absorbing Matrix
"""
Mat = np.array([[1, 0, 0, 0],
                [0, 1, 0, 0],
                [.1, 0, .8, .1],
                [.1, .1, .4, .4]])

labels = np.array(["F", "B", "G", "A"])
plotMarkov(ChainGenerator(Mat, 100), labels, "Absorbing Markov Chain", (12, 12))


from __future__ import division
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import pandas as pd
from helper import back_substitute

def ge_nop(A, b):
    n = len(A)
    for k in range(0, n-1):

        M = np.identity(A.shape[0])
        if A[k][k] == 0:
            raise RuntimeError("pivot element 0")
        for i in range(k+1, n):
            M[i][k] = -1 * (A[i][k] / A[k][k])
        U = np.array([[0] * n for i in range(n)], dtype=np.float64)
#         print(A,"\n", M)
        for i in range(0, n):
            for j in range(0, n):
                U[i, j] = np.dot(M[i], A.T[j])
        A = np.asarray(U)
#         print(A,"\n", M)
        b_dash = np.zeros(b.shape)
        for i in range(0, n):
            b_dash[i] = np.dot(M[i], b)
        b = b_dash
    return A, b

def main():
    try:
        A = np.array([[1, 2, 2], [4, 4, 2], [4, 6, 4]], dtype=np.float64)
        b = np.array([3, 6, 10], dtype=np.float64)
        U, b_n = ge_nop(A, b)
        x = back_substitute(U, b_n)
        print("A = \n", A)
        print("b = \n", b)
        print("Solution for Ax = b, x = \n", x.T)
    except RuntimeError as e:
        print(e)
        print("Faulty Matrix is, \n", A)

if __name__ == '__main__':
    main()


from sympy import primorial

###############################
###     Primorials          ###
###############################

#Explanation : The product of the first n primes.
#Example : 1, 2, 6, 30, 210, 2310, 30030, 510510, 9699690, 223092870, ...

n = int(input("Enter the nth value to find : "))
if(n == 0):
        print(n,"th primorial term is 1")
else:
        print(n,"th primorial term is ",primorial(n))


import numpy as np
from matplotlib import pyplot as plt

# Score
# 0 : Fail, 1 : Pass
data = np.array([
    [45, 0],
    [50, 0],
    [55, 0],
    [60, 1],
    [65, 1],
    [70, 1],
])

x = data[:, 0]
y = data[:, 1]

def sigmoid(x):  # 시그모이드 함수 정의
    return 1/(1+np.exp(-x))

w = np.random.uniform(low=0, high=1)
b = np.random.uniform(low=0, high=1)
print('w: ', w, 'b: ', b)

hypothesis = sigmoid(w * x + b)

# 방법 1 -> 최적화 어려움, local minimum에 빠질 우려가 있음
error = (hypothesis - y) ** 2
cost = error.mean()
print('cost: ', cost)  # 이와 같은 cost는 최적화가 어려움

# 방법 2
cost = y * np.log(hypothesis) + (1 - y) * np.log(1 - hypothesis)
cost = -cost.mean()
print('cost: ', cost)