# To add a new cell, type '#%%'
# To add a new markdown cell, type '#%% [markdown]'

#%% [markdown]

# # SciPy Family
#
# Python-based ecosystem [scipy.org](https://scipy.org)
#
# * SciPy Library - Fundamental library for scientific computing
# * NumPy - Numeric Python: Base N-dimensional array package
# * Pandas - Data structures (Dataframes) & analysis
# * Mathplotlib - Comprehensive 2D Plotting
# * Sympy - Symbolic mathematics
# * IPython - Enhanced Interactive Console
#
# The datatypes (dtype attribute) supported by Numpy is many:
# [Numpy basic data types](https://docs.scipy.org/doc/numpy/user/basics.types.html)

#%%
# might need to install numpy from the terminal
# %pip install numpy
# %pip3 install numpy
# %sudo pip install numpy
# %sudo pip3 install numpy
# %sudo -H pip3 install numpy
# %conda install numpy
# %pip freeze
# %pip list
# %pip show numpy

#%%
import numpy as np
# or from numpy import *
# import matplotlib.pyplot as plt
# import pandas as pd

#%%
#
# Review lists
list0 = [9,8,7]
list0b = [6,5,4]
#
# What are the lengths of list1 and list1b?

#%%
# What do you get with list0 + list0b?
#
list0+list0b

#%%
# explore data structures with list of list, how many dimensions?
list1 = [ [11,12,13,14], [21,22,23,24], [31,32,33,34]]
list1b = [ [41,42,43,44], [51,52,53,54], [61,62,63,64]]
#

#%%
# Again, what is list1 + list1b?
#
list1+list1b

#%%
# Question: How do you describe (in english) these two lists? What are the "shapes" of the objects?
#
# These are 3 by 4 matrices. Two-dimensional arrays.
#
# Question: how do you get the element '32' in list1?
#
#
#
# Question: how do you get the row of [31,32,33,34] in list1?
#
#
#
#%%
# Question: How to you get the column of 12, 22, 32 ???
#
#
#%%
[ row[1] for row in list1 ]

#%%
# OR Loop it
v3 = []
for row in list1:
  v3.append(row[1])
print(v3)

#%%

#%%
list2 = [ [11,12,13], [21,22,23], [31,32,33], [41,42,43] ] # two dimensional list (2-D array)  # (4,3)
# list2b = [ [51,52,53], [61,62,63], [71,72,73], [81,82,83]]

# How do you access the different parts of these two lists?

#%%
# How do you create a higher-dimensional list (say 2x3x4)?
#
# list3D = [ [ [111,112,113], [121,122,123], [131,132,133], [141,142,143] ]
# , [ [211,212,213], [221,222,223], [231,232,233], [241,242,243] ] ]

list3D = [ [ [ 111, 112, 113, 114 ], [ 121, 122, 123, 124 ], [131, 132, 133, 134] ] ,
           [ [ 211, 212, 213, 214 ], [ 221, 222, 223, 224 ], [231, 232, 233, 234] ] ]

#%%
# Now try numpy
import numpy as np
# Some basic attributes and simple functions of numpy arrays
a = np.arange(15) # numpy creates a range of 15 consecutive integers, like the range() function in basic python
print('a:',a)
a = np.arange(15).reshape(3,-1) # Using -1 for the last dimension lets numpy calculate directly
# a = np.arange(15).reshape(3,5) # Same result as line above
a = np.arange(24).reshape(2,3,-1) # 3d array
print('a:',a)

print('a.shape:',a.shape)
print('a.ndim:',a.ndim)
print('a.dtype:',a.dtype)
print('a.dtype.name:',a.dtype.name)
print('a.itemsize:',a.itemsize)
print('a.size:',a.size)
print('type(a):',type(a))
b = np.array([6, 7, 8])
print('b:',(b))
print('type(b):',type(b))
#
# The opposite of reshape, can use ravel()
print('a ravel:', a.ravel().shape)
print('a again:', a)
#
# IMPORTANT
# The a.ravel() function does NOT change a!!
# I create a true copy of a and ravel/unravel it only.
# Remember the differences in class/object definitions,
# it is critical what is the "return" value in
# those function/methods.
# If return self, you are getting back the object a.
# But this function return a separate true copy of
# the result instead. This is by design.
#
# A lot of other functions in numpy/pandas behave like that too.
#
# The same thing for reshape, for example
print('a reshape:', a.reshape(1,-1))
print('a.shape:',a.shape)
print('a:',a)
print('#',50*"-")

#%%
# If you really want to change a, try this:
# a = a.ravel() # exact same result as
a = a.reshape(-1)
print('a: ',a)
print('type a: ',type(a))
print('a.shape: ',a.shape)

print('#',50*"-")

#%%
# Other examples to create some simply numpy arrays
print('zeros:', np.zeros( (3,4) ))
print('ones:', np.ones( (2,3,4), dtype=np.int16 ))
print('empty:', np.empty( (2,3) ))
print('arange variation 1:', np.arange( 10, 30, 5 ))
print('arange variation 2:', np.arange( 0, 2, 0.3 ) )
print('complex:', np.array( [ [1,2], [3,4] ], dtype=complex ))
print('float:', np.arange(2, 10, dtype=float) )
from numpy import pi
x = np.linspace( 0, 2*pi, 100 )
f = np.sin(x)
print('sine of linspace:',f)
print('#',50*"-")

#%%
# import numpy as np
# Creating numpy arrays from python lists (and other list-like objects)
# Also look at the concept of "strides"
nparray1 = np.array(list1)
print("nparray1 = \n", nparray1)
print("type(nparray1) =", type(nparray1))
print("nparray1.dtype =", nparray1.dtype)  # int64
print("nparray1.shape =", nparray1.shape)
print("nparray1.strides =", nparray1.strides)  # each value is int64, hence 8-byte of memory, with four columns, it takes 8x4 = 32 bytes to the next row, same position. Strides = (32,8) to the next row and next column

#%%
# if we redo
nparray1 = np.array(list1, dtype= np.int32)
print("nparray1 = \n", nparray1)
print("type(nparray1) =", type(nparray1))
print("nparray1.dtype =", nparray1.dtype)  # int32
print("nparray1.shape =", nparray1.shape)
print("nparray1.strides =", nparray1.strides)  # now each value is int32, 4-byte, with four columns, it takes 4x4 = 16 bytes to next row.

#%%
# Try others
nparray2 = np.array(list2)
print("nparray2 = \n", nparray2)
print("type(nparray2) =", type(nparray2))
print("nparray2.dtype =", nparray2.dtype)  # int64
print("nparray2.shape =", nparray2.shape)

#%%
import sys
try:
  nparray12 = nparray1+nparray2
except ValueError as err :  # except (RuntimeError, TypeError, NameError):
  print("Value Error: {0}".format(err), " Try transpose...")
  nparray12 = nparray1+nparray2.T
except TypeError as err :  # except (RuntimeError, TypeError, NameError):
  print("Type Error: {0}".format(err))
except:
  print("unexpected error:", sys.exc_info()[0])

#%%
list4 = [ 5, 'a', 2, 3.5, True ]
list5 = [ 5, [1,4], 3, 1 ]

nparray4 = np.array(list4)
print("nparray4 = \n", nparray4)
print("type(nparray4) =", type(nparray4))
print("nparray4.dtype =", nparray4.dtype)
print("nparray4.shape =", nparray4.shape)

#%%
# list5 = [ 5, [1,4], 3, 1 ]
nparray5 = np.array(list5)
print("nparray5 = \n", nparray5)
print("type(nparray5) =", type(nparray5))
print("nparray5.dtype =", nparray5.dtype)
print("nparray5.shape =", nparray5.shape)

#%%
# If they are 2D-arrays, and have compatible dimensions, you can multiply them as matrices
tprod12 = np.dot(nparray1,nparray2)
print("tprod12.shape =", tprod12.shape)
mprod21 = np.dot(nparray2,nparray1)
print("mprod21.shape =", mprod21.shape)

#%%
# Also try the 3d-array that we constructed...
# In physics, those are called tensors.
nparray3D = np.array(list3D)
print("nparray3D = \n", nparray3D)
print("type(nparray3D) =", type(nparray3D))
print("nparray3D.dtype =", nparray3D.dtype)
print("nparray3D.shape =", nparray3D.shape)

#%%
# If they are 2D-arrays, and have compatible dimensions, you can multiply them as matrices
tprod32 = np.dot(nparray3D,nparray2)
print("tprod32.shape =", tprod32.shape)

#%%[markdown]
# speed and ease of use is the strength of numpy array, compared to python lists.
# The entire array must be of a single type, however.
# If we try to time or clock the code execution times, you will find similar functions
# is much faster than looping thru a python list.
# This is mainly because NumPy is written in C, and optimized these specialized
# operations in a well-designed library.

#%%
# filtering and indexing
print(nparray1[0:2,:2],'\n')
print(nparray1[:,-1:])

#%%
# Let us do something simpler.
# Obtain the third column of nparray1
print(nparray1)
v3 = nparray1[:,2]
print(v3) # it is a column vector, or array one by three (3,1)
print(v3.shape) # it is a column vector, or array one by three (3,1)

# Much easier than dealing with lists on the coding side of things. Speed is also maximized.

#%%
# BROADCASTING
#
# Let's practice slicing numpy arrays and using NumPy's broadcasting concept.
# Remember, broadcasting refers to a numpy array's ability to VECTORIZE operations,
# so they are performed on all elements of an object at once.
# If you need to perform some simple operations on all array elements,
#
nparray1squared = nparray1 ** 2
print(nparray1squared)

#%%
nparray1mod7 = nparray1 % 7 # remainder from dividing by 7
print(nparray1mod7)

#%%
nparray1b = np.array(list1b)
nparray1bovera = nparray1b / nparray1
print(nparray1bovera)

# Try some other operations, see if they work.

# Next try to do the above with loops or comprehensions?

#%%
# boolean indexing
print(nparray1)
npbool1greater = nparray1 > 21
print(npbool1greater)

#%%
print(nparray1[npbool1greater])

#%%
print(nparray1[npbool1greater].shape)

#%%
npbool1mod = nparray1 %2 ==1
print(npbool1mod)
print(nparray1[npbool1mod])
print(nparray1[npbool1mod].shape)

# Again, try to do these with loops or comprehensions?

#%%
# Let us look at filtering again.
x = np.arange(10)
print('x:\n',x)
print('x[1]:',x[1])
print('x[-2]:',x[-2])
x.shape = (2, 5)
print('x[1,3]:',x[1, 3])
print('x[1,-1]:',x[1,-1])
print('x[0]:',x[0])
print('x[1][2]:',x[1][2])
print('x[1,2]:',x[1,2]) # SAME as above
print('x[2:5]:',x[2:5])
print('x[-7]:',x[:-7])
print('x[1:7:2]:',x[1:7:2])
y = np.arange(35).reshape(5,7)
print('\ny:\n',y)
print('y[1:5:2,::3]:',y[1:5:2,::3])
v = np.arange(10,1,-1)
print('\nv:\n',v)
print('v[np.array([3, 3, 1, 8])]:',v[np.array([3, 3, 1, 8])])
print('#',50*"-")
#%%
# Some other basic numpy operations
a = np.array( [20,30,40,50] )
b = np.arange( 4 )
print('b:',b)
c = a-b
print('c:',c)
print('b**2:',b**2)
print('sine:',10*np.sin(a))
print('#',50*"-")

#%%
a = np.random.random((2,3))
print('a:',a)
print('a.sum:',a.sum())
print('a.min:',a.min())
print('a.max:',a.max())
print('#',50*"-")
#%%
b = np.arange(24).reshape(3,4,2)
print('b:',b)
print('b.sum axis 0:',b.sum(axis=0)) # sum of each column
print('b.min axis 1:',b.min(axis=1)) # min of each row
print('b.max axis 2:',b.min(axis=2)) # min of each row
print('b.cumsum axis 1:',b.cumsum(axis=1)) # cumulative sum along each row
print('#',50*"-")

#%%
# We looked at mostly the functions/methods within the numpy object, like
# a.sum()
# a.min() etc.
# As we discussed in OOP class, a lot of times, we can also have methods for the class itself.
# In this case, we can use the universal functions in the numpy library directly,
# like np.exp(), np.sqrt(). np.add() etc.
a = np.arange(3)
print('a:',a)
e = np.exp(a); print('exp:',e)
rt = np.sqrt(a); print('root:',rt)
b = np.array([2., -1., 4.])
sum = np.add(a, b)
print('sum:',sum)
print('#',50*"-")

#%%
# Indexing and slicing in Numpy
a = np.arange(10)**3;
print('a:', a)
print('a[2]:', a[2])
print('a[2:5]:', a[2:5]) # same as basic python syntax, not including 5.
a[:6:2] = -1000
print('a[::-1] :', a[ : :-1])
# reset a
a = np.arange(10)**3
for i in a:
  print('i-cube rt:', np.power(i,1/3))
#
#%%
# def f(i,j):
#   return 10*i+j
# b = np.fromfunction(f,(5,4),dtype=int); # The array values for point/coordinate (i,j,k) will be f(i,j,k).
#
# OR
# use the lambda function
b = np.fromfunction(lambda i,j: 10*i+j,(5,4),dtype=int); # The array values for point/coordinate (i,j,k) will be f(i,j,k).
print('b:', b)
print('b[2,3]:', b[2,3])
print('b[0.5,1]:', b[0:5, 1])
print('b[:,1]:', b[ : ,1])
print('b[1:3, :]:', b[1:3, : ])
print('b[-1]:', b[-1])  # same as b[4]  # same as b[4, : ]
print('b[4]:', b[4])
print('b[4,:]:', b[4, :])
print('#',50*"-")
#
# There is also a np.fromfile() function  to create.
#
# Remeber we tried slicing of 2-D list/array with basic python
# at the beginning of this file, and it was super difficult to
# get what we need.

#%%


## @file
#
# Functions related to spectral bounds of matrices.
#
# @author Nicolas Bock <nicolas.bock@freeon.org>
# @author Matt Challacombe <matt.challacombe@freeon.org>

import numpy as np

## Calculate the spectral bounds using the Gershgorin circle theorem.
#
# @param A The matrix.
#
# @return The bounds.
def gershgorin (A):
  bounds = np.zeros(2, dtype = 'float')
  bounds[0] = A[0, 0]
  bounds[1] = A[0, 0]

  for i in range(A.shape[0]):
    R = 0
    for j in range(A.shape[1]):
      if i != j:
        R += abs(A[i, j])

    if A[i, i]-R < bounds[0]:
      bounds[0] = A[i, i]-R
    if A[i, i]+R > bounds[1]:
      bounds[1] = A[i, i]+R

  return bounds

## Calculate the spectral bounds using a full eigensolve.
#
# @param A The matrix.
#
# @return The bounds.
def eigensolve (A):
  (eigenvalues, _) = np.linalg.eig(A)
  bounds = np.array([ np.min(eigenvalues), np.max(eigenvalues) ])
  return bounds

## Calculate the spectral bounds.
#
# @param A The matrix.
# @param estimate_type The type to use.
#
# @return The bounds.
def spectral_bounds (A, estimate_type = gershgorin):
  if A.ndim != 2:
    raise Exception("ndim has to be 2")
  if A.shape[0] != A.shape[1]:
    raise Exception("only square matrices are supported")

  return estimate_type(A)


import math
import numpy as np

'''a = [
        [14,6,-2,3,12],
        [3,15,2,-5,32],
        [-7,4,-23,2,-24],
        [1,-3,-2,16,14]
        ]'''

'''a = [
        [1,1/2,1/3,1],
        [1/2,1/3,1/4,0],
        [1/3,1/4,1/5,0],
        ]'''
a = [
        [-7,2,-3,4,-12],
        [5,-1,14,-1,13],
        [1,9,-7,13,31],
        [-12,13,-8,-4,-32]
        ]

n = len(a)
marcas = [i for i in range(0,n)]

def intercambioDeFilas(filaMayor,k):
    for i in range(0,len(a)+1):
        aux = a[k][i]
        a[k][i] = a[filaMayor][i]
        a[filaMayor][i]= aux

def intercambioDeColumnas(columnaMayor,k):
    #print(marcas)
    aux = marcas[k]
    marcas[k] = marcas[columnaMayor]
    marcas[columnaMayor] = aux
    for j in range(0,len(a)):
        aux = a[j][k]
        a[j][k] = a[j][columnaMayor]
        a[j][columnaMayor] = aux

def pivoteoTotal(k):
    mayor = 0
    filaMayor = k
    columnaMayor = k
    for r in range(k,n):
        for s in range (k,n):
            if (math.fabs(a[r][s]) > mayor):
                mayor = math.fabs(a[r][s])
                filaMayor = r
                columnaMayor = s

    if(mayor == 0):
        print("division 0")
        return;
    else:
        if(filaMayor != k):
            intercambioDeFilas(filaMayor,k)
        if(columnaMayor != k):
            intercambioDeColumnas(columnaMayor,k)
            #intercambioDeMarcas()

def elimination():
    for k  in range(0,n-1):
        pivoteoTotal(k)
        for i  in range (k + 1, n):
            multiplicador = a[i][k]/a[k][k]
            for j in range (k,n + 1):
                a[i][j] = a[i][j]-(multiplicador*a[k][j])

    return sustitucion()

def orderning(x):
    return [i for _,i in sorted(zip(marcas,x))]

def sustitucion():
    n = len(a) -1
    x = [i for i in range(n+1)]
    x[len(x)-1] = a[n][n+1]/a[n][n]

    for i in range(0,n+1):
        sumatoria = 0
        auxi = n - i
        sumatoria = 0
        for p in range(auxi+1,n+1):
            sumatoria = sumatoria + a[auxi][p]*x[p]
        x[auxi]=(a[auxi][n+1]-sumatoria)/a[auxi][auxi]
    #print marcas
    #duda error numero proximo a cero
    marcas.reverse()
    x.reverse()
    x = orderning(x)
    for i in range(0,len(x)):
        if(math.fabs(x[i]) < 10**-15):
            print("ojo numero muy proximo a 0 o es 0, lo aproximare a 0:",x[i])
            x[i] = 0
        print("X",i," =",x[i])

    return x

#elimination()
#for i in a:
#    print(i)
#intercambioDeFilas(0,2)


#!/usr/bin/env python3

import numpy as np

def normalEqn(X, y):
    #NORMALEQN Computes the closed-form solution to linear regression
    #   NORMALEQN(X,y) computes the closed-form solution to linear
    #   regression using the normal equations.

    #theta = zeros(size(X, 2), 1);

    # ====================== YOUR CODE HERE ======================
    # Instructions: Complete the code to compute the closed form solution
    #               to linear regression and put the result in theta.
    #

    # ---------------------- Sample Solution ----------------------

    theta = np.dot(np.dot(np.linalg.pinv(np.dot(X.T, X)), X.T), y)

    # -------------------------------------------------------------

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

    return theta


import numpy as np
import matplotlib.pyplot as plt
import scipy
import scipy.linalg as la
from copy import copy
import matplotlib.cm as cm
import matplotlib

from scipy.linalg import lu as lu1
from mpl_toolkits.mplot3d import Axes3D

def e_generator(i,j,size):
    length = (size-1)**2
    e = np.zeros(length)
    e[(i-1)*(size-1)+j-1] = 1
    return e

def A_generator(N):
    Q = np.zeros([(N-1)**2, (N-1)**2])

    for i in range((N-1)**2):
        Q[i][i] = 4.0
        if i % (N-1) != 0:
            Q[i][i-1] = -1.0;
        if i % (N-1) != N-2:
            Q[i][i+1] = -1.0;
        if i // (N-1) != 0.0:
            if(i == 1):
                print(i / (N-1))
            Q[i][(i-(N-1))] = -1.0;
        if i // (N-1) < N-2:
            Q[i][(i+(N-1))] = -1.0;

    return Q
    '''notice that Q is the matrix. Q graph is for plt.
    Black: value 4
    Gray: value -1
    White: value 0
    '''

def lu(A):
    '''actually this is p^-1'''
    n = A.shape[0]
    U = A.copy()
    L = np.eye(n, dtype=np.double)
    P = np.eye(n, dtype=np.double)
    #index of largest abs value

    for i in range(n-1):
        #find the largest pivot
        max = abs(U[i,i])
        index = i
        for k in range(i,n):
            if abs(U[k,i])>max:
                max = abs(U[k,i])
                index = k

        #permutation
        if index != i:
            #swap in U, notice that swap in python is weird
            U[[index,i],i:n] = U[[i,index],i:n]
            P[[index,i]]=P[[i,index]]
            L[[index,i],:i] = L[[i,index],:i]

        for j in range(i+1,n):
            L[j,i] = U[j,i] / U[i,i]
            U[j,i:] -= L[j,i]*U[i,i:]
            U[j,i] = 0
    return P,L,U

def plot_0_white(A):
    plt.imshow(A,cmap = 'PiYG',norm = matplotlib.colors.Normalize(vmin = -1,vmax = 1),interpolation = 'none')
    plt.show()

def find_nonzero_entries(A):
    n = A.shape[0]
    count = 0
    for i in range(n):
        for(j) in range(n):
            if A[i][j] != 0:
                count = count + 1
    return count

def find_lower_bandwidth(A):
    n = A.shape[0]
    lower_bandwidth = 0
    flag = -1
    for i in range(1,n):
        for j in range(0, n-i):
            if A[i+j][j] != 0:
                flag = 1
                break;
        if flag == 1:
            lower_bandwidth = lower_bandwidth + 1
            flag = -1
        else:
            return lower_bandwidth;
    return lower_bandwidth+1;
'''L is matrix, b is array, return value x is also array'''
def fsolve(L,b):
    n = L.shape[0]
    b[0] = b[0]/L[0,0]
    for i in range(1,n):
        for j in range(0, i):
            b[i] = b[i] - L[i,j]*b[j]

        b[i] = b[i]/L[i,i]
    return b

def bsolve(U,b):
    n = U.shape[0]
    b[n-1] = b[n-1]/U[n-1,n-1]
    for i in range(n-2,-1,-1):
        for j in range(n-1,i,-1):
            b[i] = b[i] - U[i,j]*b[j]
        b[i] = b[i]/U[i,i]
    return b

'''sol on question 1'''

Q = A_generator(10)

plt.imshow(Q,cmap = 'PiYG',norm = matplotlib.colors.Normalize(vmin = -1,vmax = 1),interpolation = 'none')
plt.show()

'''test on question 2, lu(A) is my func'''
'''
A = np.array([[2, 5, 8, 7], [5, 2, 2, 8], [7, 5, 6, 6], [5, 4, 4, 8]])
L,U,P = lu(A)
print(P)
print(L)
print(U)
'''

'''question3 uncomment this to see all pics'''
#
Q_10 = A_generator(10)
P1,L1,U1 = lu(Q_10)
num1 = find_nonzero_entries(L1)
lb1 = find_lower_bandwidth(L1)
print(num1)
print(lb1)

Q_20 = A_generator(20)
P2,L2,U2 = lu(Q_20)
num2 = find_nonzero_entries(L2)
lb2 = find_lower_bandwidth(L2)
print(num2)
print(lb2)

Q_30 = A_generator(30)
P3,L3,U3= lu(Q_30)
num3 = find_nonzero_entries(L3)
lb3 = find_lower_bandwidth(L3)

Q_40 = A_generator(40)
P4,L4,U4= lu(Q_40)
num4 = find_nonzero_entries(L4)
lb4= find_lower_bandwidth(L4)
print(num4)
print(lb4)

Q_50 = A_generator(50)
P5,L5,U5 = lu(Q_50)
num5 = find_nonzero_entries(L5)
lb5 = find_lower_bandwidth(L5)
print(num5)
print(lb5)
x = np.array([num1,num2,num3,num4,num5])
y = np.array([10,20,30,40,50])
plt.title("nonzeros over N")
plt.scatter(y, x, color="black")
plt.show()
x = np.array(([lb1,lb2,lb3,lb4,lb5]))
plt.title("bandwidth over N")
plt.scatter(y, x, color="black")
plt.show()
'''sanity test for q3, IGNORE FOLLOWING THINGS  '''
# print(find_nonzero_entries(A))
# print(find_lower_bandwidth(A))
'''question4'''
'''sanity check for bsolve, fsolve, egenerator, please ignore'''
i = 2
j = 2
size = 4
matrix_e = e_generator(i,j,size)
print(matrix_e)
a = np.array([[1, 3, 5, 9], [0, 1, 0, 11], [0, 0, 2, 9], [0, 0, 0, 4]])
b = np.array([4.0, 2.0, 4.0, 2.0])
sol = bsolve(a,b)
print(sol)
print(a.dot(sol))
size = 20
Q_50 = A_generator(size)
e = e_generator(5,5,size)
P,L,U  = lu(Q_50)
e = e.dot(P)
ux = fsolve(L,e)
x = bsolve(U,ux)
grid = np.zeros([(size-1),(size-1)])
ind = 0
for i in range(0,(size-1)):
    for j in range(0,(size-1)):
        grid[i,j] = x[ind]
        ind += 1
nx, ny = size-1, size-1
x = range(nx)
y = range(ny)

hf = plt.figure()
ha = hf.add_subplot(111, projection='3d')
X, Y = np.meshgrid(x, y)  # `plot_surface` expects `x` and `y` data to be 2D
ha.plot_surface(X, Y, grid)
plt.show()


import os
import sys
import scipy.io
import scipy.misc
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
from nst_utils import *
import numpy as np
import tensorflow as tf

'''
How do you ensure the generated image G matches the content of the image C?

As we saw in lecture, the earlier (shallower) layers of a ConvNet tend to detect lower-level features such as edges and simple textures,
and the later (deeper) layers tend to detect higher-level features such as more complex textures as well as object classes.

We would like the "generated" image G to have similar content as the input image C.
Suppose you have chosen some layer's activations to represent the content of an image.
In practice, you'll get the most visually pleasing results if you choose a layer in the middle of the network--neither too shallow nor too deep.
(After you have finished this exercise, feel free to come back and experiment with using different layers, to see how the results vary.)

So, suppose you have picked one particular hidden layer to use. Now, set the image C as the input to the pretrained VGG network, and run forward propagation.
Let a(C)a(C) be the hidden layer activations in the layer you had chosen. (In lecture, we had written this as a[l](C)a[l](C),
but here we'll drop the superscript [l][l] to simplify the notation.)
This will be a nH×nW×nCnH×nW×nC tensor. Repeat this process with the image G: Set G as the input, and run forward progation. Let
a(G)
a(G)
be the corresponding hidden layer activation. We will define as the content cost function as:

Jcontent(C,G)=14×nH×nW×nC∑all entries(a(C)−a(G))2(1)
Jcontent(C,G)=14×nH×nW×nC∑all entries(a(C)−a(G))2

Here, nH,nWnH,nW and nCnC are the height, width and number of channels of the hidden layer you have chosen, and appear in a normalization term in the cost.
For clarity, note that a(C)a(C) and a(G)a(G) are the volumes corresponding to a hidden layer's activations.
In order to compute the cost Jcontent(C,G)Jcontent(C,G), it might also be convenient to unroll these 3D volumes into a 2D matrix, as shown below.
(Technically this unrolling step isn't needed to compute JcontentJcontent,
but it will be good practice for when you do need to carry out a similar operation later for computing the style const JstyleJstyle.)
'''
def compute_content_cost(a_C, a_G):
    """
    Computes the content cost

    Arguments:
    a_C -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image C
    a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image G

    Returns:
    J_content -- scalar that you compute using equation 1 above.
    """

    m, n_H, n_W, n_C = a_G.shape

    new_shape = [int(m), int(n_H * n_W), int(n_C)]

    # Reshape a_C and a_G
    a_C_unrolled = tf.reshape(a_C, new_shape)
    a_G_unrolled = tf.reshape(a_G, new_shape)

    # compute the cost with tensorflow
    J_content = (.25 / float(int(n_H * n_W * n_C))) * tf.reduce_sum(np.power(a_G_unrolled - a_C_unrolled, 2))

    return J_content

'''
The style matrix is also called a "Gram matrix."
In linear algebra, the Gram matrix G of a set of vectors (v1,…,vn)(v1,…,vn) is the matrix of dot products, whose entries are Gij=vTivj=np.dot(vi,vj)Gij=viTvj=np.dot(vi,vj).
In other words, Gij compares how similar vivi is to vjvj: If they are highly similar, you would expect them to have a large dot product, and thus for Gij to be large.

Note that there is an unfortunate collision in the variable names used here.
We are following common terminology used in the literature, but GG is used to denote the Style matrix (or Gram matrix) as well as to denote the generated image GG.
We will try to make sure which GG we are referring to is always clear from the context.

In NST, you can compute the Style matrix by multiplying the "unrolled" filter matrix with their transpose:

The result is a matrix of dimension (nC,nC)(nC,nC) where nCnC is the number of filters. The value Gij measures how similar the activations of filter ii are to the activations of filter jj.

One important part of the gram matrix is that the diagonal elements such as Gii also measures how active filter ii is.
For example, suppose filter ii is detecting vertical textures in the image.
Then Gii measures how common vertical textures are in the image as a whole: If Gii is large, this means that the image has a lot of vertical texture.

By capturing the prevalence of different types of features (Gii), as well as how much different features occur together (Gij), the Style matrix GG measures the style of an image.
'''
def gram_matrix(A):
    """
    Argument:
    A -- matrix of shape (n_C, n_H*n_W)

    Returns:
    GA -- Gram matrix of A, of shape (n_C, n_C)
    """

    GA = tf.matmul(A, tf.transpose(A))

    return GA

'''
Style cost

After generating the Style matrix (Gram matrix), your goal will be to minimize the distance between the Gram matrix of the "style" image S and that of the "generated" image G.
For now, we are using only a single hidden layer a[l]a[l], and the corresponding style cost for this layer is defined as:

J[l]style(S,G)=14×nC2×(nH×nW)2∑i=1nC∑j=1nC(G(S)ij−G(G)ij)2(2)
Jstyle[l](S,G)=14×nC2×(nH×nW)2∑i=1nC∑j=1nC(Gij(S)−Gij(G))2

where G(S)G(S) and G(G)G(G) are respectively the Gram matrices of the "style" image and the "generated" image,
computed using the hidden layer activations for a particular hidden layer in the network.
'''
def compute_layer_style_cost(a_S, a_G):
    """
    Arguments:
    a_S -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image S
    a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image G

    Returns:
    J_style_layer -- tensor representing a scalar value, style cost defined above by equation (2)
    """

    # Retrieve dimensions from a_G
    m, n_H, n_W, n_C = a_G.get_shape().as_list()

    # Reshape the images to have them of shape (n_C, n_H*n_W)
    a_S = tf.reshape(a_S, [n_H * n_W, n_C])
    a_S = tf.transpose(a_S)
    a_G = tf.reshape(a_G, [n_H * n_W, n_C])
    a_G = tf.transpose(a_G)

    # Computing gram_matrices for both images S and G
    GS = gram_matrix(a_S)
    GG = gram_matrix(a_G)

    # Computing the loss
    factor = (.5 / (n_H * n_W * n_C)) ** 2
    J_style_layer = factor * tf.reduce_sum(np.power(GS - GG, 2))

    return J_style_layer

'''
Style Weights

So far you have captured the style from only one layer. We'll get better results if we "merge" style costs from several different layers.
After completing this exercise, feel free to come back and experiment with different weights to see how it changes the generated image GG. But for now, this is a pretty reasonable default:

STYLE_LAYERS = [
    ('conv1_1', 0.2),
    ('conv2_1', 0.2),
    ('conv3_1', 0.2),
    ('conv4_1', 0.2),
    ('conv5_1', 0.2)]

You can combine the style costs for different layers as follows:

Jstyle(S,G)=∑lλ[l]J[l]style(S,G)
Jstyle(S,G)=∑lλ[l]Jstyle[l](S,G)

where the values for λ[l]λ[l] are given in STYLE_LAYERS.

    - The style of an image can be represented using the Gram matrix of a hidden layer's activations.
        However, we get even better results combining this representation from multiple different layers.
        This is in contrast to the content representation, where usually using just a single hidden layer is sufficient.
    - Minimizing the style cost will cause the image GG to follow the style of the image SS.

In the inner-loop of the for-loop below, a_G is a tensor and hasn't been evaluated yet.
It will be evaluated and updated at each iteration when we run the TensorFlow graph in model_nn() .below

The style of an image can be represented using the Gram matrix of a hidden layer's activations.
However, we get even better results combining this representation from multiple different layers.
This is in contrast to the content representation, where usually using just a single hidden layer is sufficient.

Minimizing the style cost will cause the image GG to follow the style of the image SS.
'''
def compute_style_cost(model, STYLE_LAYERS):
    """
    Computes the overall style cost from several chosen layers

    Arguments:
    model -- our tensorflow model
    STYLE_LAYERS -- A python list containing:
                        - the names of the layers we would like to extract style from
                        - a coefficient for each of them

    Returns:
    J_style -- tensor representing a scalar value, style cost defined above by equation (2)
    """

    # initialize the overall style cost
    J_style = 0

    for layer_name, coeff in STYLE_LAYERS:
        # Select the output tensor of the currently selected layer
        out = model[layer_name]

        # Set a_S to be the hidden layer activation from the layer we have selected, by running the session on out
        a_S = sess.run(out)

        # Set a_G to be the hidden layer activation from same layer. Here, a_G references model[layer_name]
        # and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
        # when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
        a_G = out

        # Compute style_cost for the current layer
        J_style_layer = compute_layer_style_cost(a_S, a_G)

        # Add coeff * J_style_layer of this layer to overall style cost
        J_style += coeff * J_style_layer

    return J_style

def total_cost(J_content, J_style, alpha=10, beta=40):
    """
    Computes the total cost function

    Arguments:
    J_content -- content cost coded above
    J_style -- style cost coded above
    alpha -- hyperparameter weighting the importance of the content cost
    beta -- hyperparameter weighting the importance of the style cost

    Returns:
    J -- total cost as defined by the formula above.
    """

    ### START CODE HERE ### (≈1 line)
    J = alpha * J_content + beta * J_style
    ### END CODE HERE ###

    return J

#
# model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
#
# content_image = scipy.misc.imread("images/louvre.jpg")
# imshow(content_image)
#
# style_image = scipy.misc.imread("images/monet_800600.jpg")
# imshow(style_image)
#

# Reset the graph
tf.reset_default_graph()

# Start interactive session
sess = tf.InteractiveSession()

content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)
style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)

# Now, we initialize the "generated" image as a noisy image created from the content_image.
# By initializing the pixels of the generated image to be mostly noise but still slightly correlated with the content image,
# this will help the content of the "generated" image more rapidly match the content of the "content" image.
generated_image = generate_noise_image(content_image)
imshow(generated_image[0])

model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")

'''
Cost Content computing:
'''

# Assign the content image to be the input of the VGG model.
# Select the output tensor of layer conv4_2
# Set a_C to be the hidden layer activation from the layer we have selected. This has been evaluated since sess.run(out) and value assigned to a_C.
# In the next step, a_G is added as part of the grapth, being the output at that layer.
sess.run(model['input'].assign(content_image))
out = model['conv4_2']
a_C = sess.run(out)
# Set a_G to be the hidden layer activation from same layer. Here, a_G references model['conv4_2']
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out
# Compute the content cost
J_content = compute_content_cost(a_C, a_G)

'''
Style Content computing:
'''
# Assign the input of the model to be the "style" image
sess.run(model['input'].assign(style_image))
# Compute the style cost
J_style = compute_style_cost(model, STYLE_LAYERS)
J = total_cost(J_content, J_style, alpha = 10, beta = 40)
# define optimizer (1 line)
optimizer = tf.train.AdamOptimizer(2.0)
# define train_step (1 line)
train_step = optimizer.minimize(J)

# Right now, the grap consists a tree which top is the function tran_step -> J_total_cost ->
#           J_content: a_C value assigned already, and a_G attached at the chosen layer.
#           J_style: Inside function compute_style_cost. a_G is attached to each of the layers at the list and attached to the graph at the function compute_style_cost

def model_nn(sess, input_image, num_iterations=200):
    # Initialize global variables (you need to run the session on the initializer)
    ### START CODE HERE ### (1 line)
    sess.run(tf.global_variables_initializer())
    ### END CODE HERE ###

    # Run the noisy input image (initial generated image) through the model. Use assign().
    ### START CODE HERE ### (1 line)
    sess.run(model['input'].assign(input_image))
    ### END CODE HERE ###

    for i in range(num_iterations):

        # Run the session on the train_step to minimize the total cost
        ### START CODE HERE ### (1 line)
        somthing = sess.run(train_step)
        ### END CODE HERE ###

        # Compute the generated image by running the session on the current model['input']
        ### START CODE HERE ### (1 line)
        generated_image = sess.run(model['input'])
        ### END CODE HERE ###

        # Print every 20 iteration.
        if i % 20 == 0:
            Jt, Jc, Js = sess.run([J, J_content, J_style])
            print("Iteration " + str(i) + " :")
            print("total cost = " + str(Jt))
            print("content cost = " + str(Jc))
            print("style cost = " + str(Js))

            # save current generated image in the "/output" directory
            save_image("output/" + str(i) + ".png", generated_image)

    # save last generated image
    save_image('output/generated_image.jpg', generated_image)

    return generated_image

model_nn(sess, generated_image)

Convolutional Neural Networks/Week 4/Art Generation/Art_Generation_Neural_Transfer.py


import random
import numpy as np
import matplotlib.pyplot as plt
import math
import time
import os
import collections.abc
container_abcs = collections.abc
import scipy.io as sio
import numpy.linalg as LA
import scipy.spatial.distance as scipy_spatial_dist

def intx(x):
    return(int(x[0]), int(x[1]))

def cos_cdis(x, y, semi_sphere=False):
    # input: x: mxp, y: nxp
    # output: y, mxn
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html#scipy.spatial.distance.cdist
    ### compute cosine distance
    ### scipy: same 0, opposite 2, orthorgonal 1, dist = 1-AB/(|A||B|)
    dist_cos = scipy_spatial_dist.cdist(x, y, 'cosine')  # num_points_ x num_points
    # ### map to: same 1, opposite -1, orthorgonal 0, dist = AB/(|A||B|)
    dist_cos *= -1.0
    dist_cos += 1.0

    if semi_sphere is True: dist_cos = np.abs(dist_cos) #  dist = abs|AB/(|A||B|)|
    dist_cos_arc = np.arccos(dist_cos)
    return dist_cos_arc

def catersian_to_sphere(xyz):
    #  input: xyz, nx3, in the gaussian_catersian coordinate
    #  output: angles, (alpha, beta), nx2, in the gaussian sphere coordinate
    #  beta: elevation; alpha: azimuth.
    ### norm = [sin(alpha)cos(beta), sin(beta), cos(alpha)cos(beta)]
    num_points = len(xyz)
    angle = np.zeros((num_points,2))
    angle[:,1] = np.arcsin(xyz[:,1])
    inner = xyz[:,0] / np.cos(angle[:,1])
    inner = np.clip(inner, a_min=-1.0, a_max=1.0)
    angle[:,0] = np.arcsin(inner)
    return angle

def sphere_to_catesian(angles):
    #  input: angles (alphas, betas), nx2, in the gasussian sphere coordinate
    #  output: xyz (x,y,z), nx3, in the gaussian_catersian coordinate
    #  beta: elevation; alpha: azimuth.
    ### n = [sin(alpha)cos(beta), sin(beta), cos(alpha)cos(beta)]
    num_points = len(angles)
    xyz = np.zeros((num_points,3))
    xyz[:,0] = np.sin(angles[:,0]) * np.cos(angles[:,1])
    xyz[:,1] = np.sin(angles[:,1])
    xyz[:,2] = np.cos(angles[:,0]) * np.cos(angles[:,1])

    return xyz

def gold_spiral_sampling_patch(v, alpha, num_pts):
    v1 = orth(v)
    v2 = np.cross(v, v1)
    v, v1, v2 = v[:, None], v1[:, None], v2[:, None]
    # indices = np.arange(num_pts) + 0.66
    indices = np.arange(num_pts) + 0.5
    phi = np.arccos(1 + (math.cos(alpha) - 1) * indices / num_pts)
    theta = np.pi * (1 + 5 ** 0.5) * indices
    r = np.sin(phi)
    return (v * np.cos(phi) + r * (v1 * np.cos(theta) + v2 * np.sin(theta))).T

def orth(v):
    x, y, z = v
    o = np.array([0.0, -z, y] if abs(x) < abs(y) else [-z, 0.0, x])
    o /= LA.norm(o)
    return o


"""N-Dimensional Norm

Defines the norm of 2 points in N dimensional space with different norms.
"""

import numpy as np

def norm(a, pow=2):
    """ Lp Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,). Defines a point in 2-D space.
        pow (float): The norm used for distance (Default: 2)

    Returns:
        (float) The distance between point a and point b with Lp Norm.
    """
    return np.float_power(np.sum([np.float_power(np.abs(a[i]), pow) for i in range(len(a))]), 1./pow)

def manhattan(a):
    """Manhattan Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L1 norm.
    """
    return norm(a, pow=1)

def euclidean(a):
    """Euclidean Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L2 norm.
    """
    return norm(a, pow=2)

def minkowski(a):
    """Minkowski Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L3 norm.
    """
    return norm(a, pow=3)

def chebyshev(a):
    """Chebyshev Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L-Infinity norm.
    """
    return np.max([np.abs(a[i]) for i in range(len(a))])


## module powell
''' xMin,nCyc = powell(F,x,h=0.1,tol=1.0e-6)
    Powell's method of minimizing user-supplied function F(x).
    x    = starting point
    h   = initial search increment used in 'bracket'
    xMin = mimimum point
    nCyc = number of cycles
'''
import numpy as np
from goldSearch import *
import math

def powell(F,x,h=0.1,tol=1.0e-6):

    def f(s): return F(x + s*v)    # F in direction of v

    n = len(x)                     # Number of design variables
    df = np.zeros(n)               # Decreases of F stored here
    u = np.identity(n)             # Vectors v stored here by rows
    for j in range(30):            # Allow for 30 cycles:
        xOld = x.copy()            # Save starting point
        fOld = F(xOld)
      # First n line searches record decreases of F
        for i in range(n):
            v = u[i]
            a,b = bracket(f,0.0,h)
            s,fMin = search(f,a,b)
            df[i] = fOld - fMin
            fOld = fMin
            x = x + s*v
      # Last line search in the cycle
        v = x - xOld
        a,b = bracket(f,0.0,h)
        s,fLast = search(f,a,b)
        x = x + s*v
      # Check for convergence
        if math.sqrt(np.dot(x-xOld,x-xOld)/n) < tol: return x,j+1
      # Identify biggest decrease & update search directions
        iMax = np.argmax(df)
        for i in range(iMax,n-1):
            u[i] = u[i+1]
        u[n-1] = v
    print("Powell did not converge")


import numpy as np

def student_id():
    """
    Returns a tuple of student's ID and TAU email address.
    """
    return 123456789, r'reference@mail.tau.ac.il'

class QuadraticFunction:

    """
    Implementation of a quadratice function
    f(x) = 1/2 * x^T*Q*x + b^T*x
    with its gradient and Hessian matrix.
    """

    def __init__(self, Q, b):

        """
        Initializes a quadratic function.

        Arguments:
            Q : (N,N) numpy array

            b : (N,) numpy array
        """

        self.Q = Q
        self.b = b

    def __call__(self, x):

        """
        Evaluate f(x).

        Arguments:
            x : (N,) numpy array

        Return:
            fx : evaluation of f(x)
        """

        fx = 0.5 * np.dot(x, np.dot(self.Q, x)) + np.dot(self.b,x)

        return fx

    def grad(self, x):

        """
        Evaluates g(x), the gradient of f(x).

        Arguments:
            x : (N,) numpy array

        Output:
            gx : (N,) numpy array
                 evaluation of the function's gradient at x
        """

        gx = 0.5 * np.dot(self.Q.T + self.Q, x) + self.b

        return gx

    def hessian(self, x):

        """
        Evaluate H(x), the Hessian matrix of f(x).

        Arguments:
            x : (N,) numpy array

        Return:
            hx : (N,N) numpy array,
                 evaluation of the function's Hessian at point x
        """

        hx = 0.5 * (self.Q.T + self.Q)

        return hx

class NewtonOptimizer:

    """
    Newton's method optimizer with constant step size.
    """

    def __init__(self, func, alpha, init):

        """
        Arguments:
            func : objective function handle/reference

            alpha : a scalar step size

            init : (N,) numpy array, initial guess
        """

        self.func = func
        self.alpha = alpha
        self.curr_x = init

    def step(self):

        """
        Executes a single step of Newton's method.

        Return:
            next_x : (N,) numpy array, next x
        """

        gx = self.func.grad(self.curr_x)
        hx = self.func.hessian(self.curr_x)
        self.curr_x = self.curr_x - self.alpha * np.dot(np.linalg.inv(hx), gx)

        return self.curr_x, gx, hx

    def optimize(self, threshold, max_iters):

        """
        Execution of optimization flow

        Arguments:
            threshold : stopping criteria |x_k-▁x_(k-1) |<threshold

            max_iters : maximal number of iterations (stopping criteria)

        Return:
            fmin : objective function evaluated at the minimum

            minimizer : the optimal ▁x

            num_iters : number of iterations
        """

        for iter_idx in range(max_iters):
            prev_x = self.curr_x
            _, _, _ = self.step()  # update self.curr_x internally
            if np.linalg.norm(self.curr_x - prev_x) < threshold:
                break

        return self.func(self.curr_x), self.curr_x, iter_idx+1

class ConjGradOptimizer:

    """
    Conjugate Gradients optimizer.
    """

    def __init__(self, func, init):

        """
        Arguments:

            func : quadratice function handle/reference

            init : (N,) numpy array, initial guess
        """

        self.func = func
        self.Q = self.func.Q
        self.curr_x = init
        self.curr_grad = func.grad(self.curr_x)
        self.curr_dir = -self.curr_grad
        self.curr_alpha = self.update_alpha()

    def optimize(self):

        """
        Executation of optimization flow

        Return:
            fmin : objective function evaluated at the minimum

            minimizer : the optimal ▁x

            num_iters : number of iterations
        """

        num_iters = self.curr_x.shape[0]  # number of iteration is constant
        for iter_idx in range(num_iters):
            _, _, _, _ = self.step()

        return self.func(self.curr_x), self.curr_x, num_iters

    def step(self):

        """
        Executes a single step of Newton's method.

        Return:
            next_x : (N,) numpy array

            next_grad_x : (N,) numpy array

            next_dir : (N,) numpy array

            next_alpha : scalar
        """

        self.curr_x = self.update_x()
        self.curr_grad, prev_grad = self.update_grad()
        self.curr_dir = self.update_dir(prev_grad)
        self.curr_alpha = self.update_alpha()

        return self.curr_x, self.curr_grad, self.curr_dir, self.curr_alpha

    def update_grad(self):

        """
        Calculates and returns (g_k, g_(k-1)) the new and previous gradients respctively.
        """

        return self.func.grad(self.curr_x), self.curr_grad

    def update_dir(self, prev_grad):

        """
        Calculates and returns the new direction d_k
        """

        beta = np.dot(self.curr_grad, self.curr_grad - prev_grad) / np.dot(prev_grad, prev_grad)

        return -self.curr_grad + beta * self.curr_dir

    def update_alpha(self):

        """
        Calculates and returns the new step size alpha_k
        """

        numer = np.dot(self.curr_dir, self.curr_grad)
        denom = np.dot(np.dot(self.curr_dir, self.Q), self.curr_dir)

        return - numer / denom

    def update_x(self):

        """
        Calculates and returns x_k
        """

        return self.curr_x + self.curr_alpha * self.curr_dir

class FastRoute:

    """
    Implementation of fast route problem.
    """

    def __init__(self, start_x, start_y, finish_x, finish_y, velocities):

        """
        Initializes a FastRoute board.

        Arguments:
            start_x : x coordinate of starting point

            start_y : y coordinate of starting point

            finish_x : x coordinate of finish point

            finish_y : y coordinate of finish point

            velocities : an N-dimensional numpy vector,
                         the i-th element is the velocity at the i-th layer
        """

        self.start_x, self.start_y = start_x, start_y
        self.finish_x, self.finish_y = finish_x, finish_y
        self.velocities = velocities
        self.y = np.linspace(self.start_y, self.finish_y, len(velocities)+1)

    def __call__(self, x):

        """
        Calculates the time of the route given by a vector of x's

        Arguments:
            x : (N-1,) numpy array of the x coordinates of crossing points

        Return:
            total_time : the total travel time of the route
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        times = distances / self.velocities
        total_time = np.sum(times)

        return total_time

    def grad(self, x):
        """
        Evaluates the gradient of the objective function at (x[1],...,x[n-1])
        for the route given by a vector of x's

        Argument:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            grad : (N-1,) numpy array, the gradient of the objective w.r.t to (x[1],...,x[n-1])

            dt/dx_k = (x_k - x_(k-1)) / (v_k * d_k) - (x_(k+1) - x_k / (v_(k+1) * d_(k+1)) for k={1,2,...,n-1}
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        grad = (x[1:-1] - x[:-2]) / (self.velocities[:-1] * distances[:-1]) - (x[2:] - x[1:-1]) / (self.velocities[1:] * distances[1:])

        return grad

    def hessian(self, x):

        """
        Evaluates the hessian of the objective function at x=(x[1],...,x[n-1])
        for the route given by a vector of x's.
        See detailed calculation in solution pdf file.

        Arguemtns:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            hx : (N-1,N-1) numpy array, the hessian of the objective evaluated at (x[1],...,x[n-1])
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        term_1 = np.power(self.y[1:-1] - self.y[:-2], 2) / (self.velocities[:-1] * np.power(distances[:-1], 3))
        term_2 = np.power(self.y[2:] - self.y[1:-1], 2) / (self.velocities[1:] * np.power(distances[1:], 3))
        N = len(self.velocities)
        hx = np.zeros((N-1, N-1))
        for i in range(N-1):
            hx[i,i] = term_1[i] + term_2[i]
            if i > 0:
                hx[i-1,i] = -term_1[i]
            if i < N-2:
                hx[i+1,i] = -term_2[i]

        return hx

    def find_distances(self, x):

        """
        Calculates a vector of N distances.

        Arguments:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            (N-1,) numpy array of the distance traveled in each segment
        """

        return np.sqrt(np.power(x[1:] - x[:-1], 2) + np.power(self.y[1:] - self.y[:-1], 2))

    def expand_x(self, x):

        """
        Adds the starting point and finish point to vector x

        Arguments:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            (N-1,) numpy array with x coordinates of route
            including start and finsh point
        """

        return np.append(np.insert(x, 0, self.start_x), self.finish_x)

def find_fast_route(objective, init, alpha=1, threshold=1e-3, max_iters=1e3):

    """
    Optimizes FastRoute objective using Newton’s method optimizer to
    find a fast route between the starting point and finish point.

    Arguments:
    	objective : an initialized FastRoute object with preset start and finish points,
                    velocities and initialization vector.

        init : (N-1,) numpy array, initial guess for the crossing points

	    alpha : step size for the NewtonOptimizer

	    threshold : stopping criteria |x_(k+1)- x_k |<threshold

	    max_iters : maximal number of iterations (stopping criteria)

    Return:
        route_time : scalar

        route : (N-1,) numpy array with x coordinate of the optimal route,
                    i.e., a vector of x-coordinates of crossing points (not including start and finish point)

        num_iters : number of iteration
    """

    opt = NewtonOptimizer(func=objective, alpha=alpha, init=init)
    route_time, route, num_iters = opt.optimize(threshold=threshold, max_iters=max_iters)

    return route_time, route, num_iters


import numpy as np

def LogCoshMetric(XGBoost=False):
    """
    Calculates the [Log Cosh Error](https://openreview.net/pdf?id=rkglvsC9Ym) as an alternative to
    Mean Absolute Error.
    Args:
        XGBoost (Bool): Set to True if using XGBoost. We assume LightGBM as default use.
                        Note that you should also set `maximize=False` in the XGBoost train function

    """
    def log_cosh_error(yhat, dtrain, XGBoost=XGBoost):
        """
        Root Mean Squared Log Error.
        All input labels are required to be greater than -1.

        yhat: Predictions
        dtrain: The XGBoost / LightGBM dataset
        XGBoost (Bool): If XGBoost is to be implemented
        """

        y = dtrain.get_label()
        elements = np.log(np.cosh(yhat - y))
        if XGBoost:
            return 'LogCosh', float(np.sum(elements) / len(y))
        else:
            return 'LogCosh', float(np.sum(elements) / len(y)), False

    return log_cosh_error

def RMSPEMetric(XGBoost=False):
    """
    Calculates the Root Mean Squared Percentage Error:
    https://www.kaggle.com/c/optiver-realized-volatility-prediction/overview/evaluation

    The corresponding Loss function is Squared Percentage Error.
    Args:
        XGBoost (Bool): Set to True if using XGBoost. We assume LightGBM as default use.
                        Note that you should also set `maximize=False` in the XGBoost train function

    """
    def RMSPE(yhat, dtrain, XGBoost=XGBoost):
        """
        Root Mean Squared Log Error.
        All input labels are required to be greater than -1.

        yhat: Predictions
        dtrain: The XGBoost / LightGBM dataset
        XGBoost (Bool): If XGBoost is to be implemented
        """

        y = dtrain.get_label()
        elements = ((y - yhat) / y) ** 2
        if XGBoost:
            return 'RMSPE', float(np.sqrt(np.sum(elements) / len(y)))
        else:
            return 'RMSPE', float(np.sqrt(np.sum(elements) / len(y))), False

    return RMSPE


#!/usr/bin/python3
"""
Generate/plot a circle with radius that varies as a sine wave.

If output of image is desired, include any string as a
command line argument.

"""
import sys

import matplotlib.pyplot as plt
import numpy as np

#### Circle-Sine Parameters ####
xCenter = 5.0
yCenter = 2.0
Radius = 5.0
nPts = 300
freq = 11 # Number of "lumps" on the circle
amplitude = 0.75
################################

if len(sys.argv) == 2:
    outFlag = True
else:
    outFlag = False

fig = plt.figure(facecolor='lightgrey')
ax = fig.add_subplot(111)
ax.patch.set_facecolor('black')

ax.set_aspect('equal')
ax.grid(True, which='both')

# Insert X and Y axes
ax.axhline(y=0, color='w')
ax.axvline(x=0, color='w')

# Remove #'s to suppress tick labels
#ax.set_xticklabels([])
#ax.set_yticklabels([])

# Incremental angles around the circle
angle = np.linspace(0, 2*np.pi, nPts, dtype=float)

# Treat the full circumference as 1 second, i.e. basis for Hz
# Array of points in "one second"
t = np.linspace(0, 1.0, nPts, dtype=float)

# Vary the radius according to sine wave frequency and amplitude
delRadius = amplitude*np.sin(2.0*np.pi*freq*t)

# Convert to x-y coordinates
x = (Radius+delRadius)*np.cos(angle)
x = x + xCenter
y = (Radius+delRadius)*np.sin(angle)
y = y + yCenter

print('len(x) = ', len(x))
print('xmin = {0:8.2f}  xmax = {1:8.2f}'.format(min(x), max(x)))
print('ymin = {0:8.2f}  ymax = {1:8.2f}'.format(min(y), max(y)))

# s is marker size
ax.scatter(x, y, marker='o', s=4.0, color='red')

# Conditionally output image
if outFlag == True:
    fig.savefig('genCircleSine.png', facecolor='lightgrey', format='png')

plt.show()


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binom
from sklearn.metrics import auc

def standardize(grid, p):
	#using stupid rectangular rule, much smarter numerical integration methods exist
	#should also work for grids of variable width

	assert p.shape[0] == grid.shape[0]

	mid = 0.5 * (p[1:] + p[:-1])

	x = grid[1:] - grid[:-1]

	standardizationConstant = np.sum(mid * x)

	#print(auc(grid, p / standardizationConstant))

	return p / standardizationConstant

N = 200
grid = np.linspace(0.0, 1.0, N)
binWidth = grid[1] - grid[0]

prior = np.ones(grid.shape[0])
#prior /= np.sum(prior)
prior = standardize(grid, prior)

#W,W,W
likelihood = binom.pmf(3, 3, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(grid, prior, 'k--', label='prior')

ax.plot(grid, posterior, color='C0', label='WWW')

#W,W,W,L
likelihood = binom.pmf(3, 4, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C1', label='WWWL')

#L,W,W,L,W,W,W
likelihood = binom.pmf(5, 7, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C2', label='LWWLWWW')

ax.set_title('2M1')

plt.legend()

##---------------------------------------------------------

prior = np.ones(grid.shape[0])
prior[grid<0.5] = 0.0
#prior /= np.sum(prior)
prior = standardize(grid, prior)

#W,W,W
likelihood = binom.pmf(3, 3, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(grid, prior, 'k--', label='prior')

ax.plot(grid, posterior, color='C0', label='WWW')

#W,W,W,L
likelihood = binom.pmf(3, 4, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C1', label='WWWL')

#L,W,W,L,W,W,W
likelihood = binom.pmf(5, 7, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C2', label='LWWLWWW')

ax.set_title('2M2')
plt.legend()

plt.show()


import numpy as np

# B/B,B/W,W/W
#
# ways black = b/b, b/b,b/w = 3 ways to produce black from bag~ 3/6 ~ 0.5
# ways_white = b/w,w/w,w/w = 3 ways to produce white~ 0.5
#

# ways when one side is black, that the other side is black -> b/b,b/b
# it cant be b/w from the ways of producing black card, because it would
# produce a white card when turned

#so form all ways a card could be black, those which could produce a black on the\
# other side are 2/3

#[b/b, b/w, w/w]
# inspired by https://github.com/cavaunpeu/statistical-rethinking/blob/master/chapter-2/homework.R
# ways cards can produce first black
ways_producing_black = np.array([2.,1.,0.])

priors = ways_producing_black / ways_producing_black.sum()
print(priors)

#[b/b,b/w,w/w]
# ways cards produce second black
ways_can_produce_black_on_other_side = np.array([2., 0., 0.])
posterior = ways_can_produce_black_on_other_side*priors/ways_can_produce_black_on_other_side.sum()
print(posterior)
print('prob that other side is black = '+str(posterior[0]))
print(posterior[0] == 2./3.)

# inspired by https://rpubs.com/andersgs/my_solutions_chapter2_statrethink
# b1/b2, b1/w1, w1/w2
#
# card |pick|other side
#
# b1/b2| 1 | 1
# b2/b1| 1 | 1
# b1/w1| 1 | 0
# w1/b1| 0 | 0
# w1/w2| 0 | 0
# w2/w1| 0 | 0
#
# to produce black on other side 2
# from to produce black on the first side 3
# 2/3

#bayes theorem
# probability that the second side is black given that the first side is black
# P(2black|1black) = P(1black|2black)*P(2black)/P(1black)
# probability that a card has black on both sides is 1 card from 3 cards
# P(2black) = 1/3
# probability that a card has 1 black side from 6 producing ways of card settings
# is 3, so 3/6 = 1/2
# P(1black) = 1/2
# probability that 1st side is black given the second side is black, e.g.
# that both sides are black is 1
# P(1black|2black) = 1 (hm)
# 1*1/3 / 1/2 ~ 2/3


# Approximate 2d posterior using PyMc3
# https://www.ritchievink.com/blog/2019/06/10/bayesian-inference-how-we-are-able-to-chase-the-posterior/
# We use the same data and model as in posteriorGrid2d.py

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

import pymc3 as pm

figdir = "../figures"
import os
def save_fig(fname):
    if figdir: plt.savefig(os.path.join(figdir, fname))

data = np.array([195, 182])

# lets create a grid of our two parameters
mu = np.linspace(150, 250)
sigma = np.linspace(0, 15)[::-1]
mm, ss = np.meshgrid(mu, sigma)  # just broadcasted parameters
likelihood = stats.norm(mm, ss).pdf(data[0]) * stats.norm(mm, ss).pdf(data[1])
aspect = mm.max() / ss.max() / 3
extent = [mm.min(), mm.max(), ss.min(), ss.max()]
# extent = left right bottom top

prior = stats.norm(200, 15).pdf(mm) * stats.cauchy(0, 10).pdf(ss)
# Posterior - grid
unnormalized_posterior = prior * likelihood
posterior = unnormalized_posterior / np.nan_to_num(unnormalized_posterior).sum()

plt.figure()
plt.imshow(posterior, cmap='Blues', aspect=aspect, extent=extent)
plt.xlabel(r'$\mu$')
plt.ylabel(r'$\sigma$')
plt.title('Grid approximation')
save_fig('bayes_unigauss_2d_grid.pdf')
plt.show()

with pm.Model():
    # priors
    mu = pm.Normal('mu', mu=200, sd=15)
    sigma = pm.HalfCauchy('sigma', 10)
    # likelihood
    observed = pm.Normal('observed', mu=mu, sd=sigma, observed=data)
    # sample
    trace = pm.sample(draws=10000, chains=1)

pm.traceplot(trace);

plt.figure()
plt.scatter(trace['mu'], trace['sigma'], alpha=0.01)
plt.xlim([extent[0], extent[1]])
plt.ylim([extent[2], extent[3]])
plt.ylabel('$\sigma$')
plt.xlabel('$\mu$')
plt.title('MCMC samples')
save_fig('bayes_unigauss_2d_pymc3_post.pdf')
plt.show()


import numpy as np
from prml.nn.tensor.constant import Constant
from prml.nn.tensor.tensor import Tensor
from prml.nn.function import Function

class Determinant(Function):

    def forward(self, x):
        x = self._convert2tensor(x)
        self.x = x
        self._equal_ndim(x, 2)
        self.output = np.linalg.det(x.value)
        if isinstance(self.x, Constant):
            return Constant(self.output)
        return Tensor(self.output, function=self)

    def backward(self, delta):
        dx = delta * self.output * np.linalg.inv(self.x.value.T)
        self.x.backward(dx)

def det(x):
    """
    determinant of a matrix
    Parameters
    ----------
    x : (d, d) tensor_like
        a matrix to compute its determinant
    Returns
    -------
    output : (d, d) tensor_like
        determinant of the input matrix
    """
    return Determinant().forward(x)


'''
This code file contains all the functions for implementation of gradient
descent on a custom function. The comments and docstrings explain the code,
but for a more detailed explanation, please go through the Readme.md file
in this folder.

'''

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

def func(x):
  '''
    We want to model our dataset on the sine function as it has some amount
    of complexity.
  '''
  return np.sin(x)

def create_dataset(n):
  x = np.linspace(0,10,num=n)
  # instead of using the sine function on its own, we are adding noise randomly
  y = func(x)+np.random.randn(n)*0.1
  return x,y

def show_dataset(x,y):
    # This function just displays the dataset
    a = np.linspace(0,10,num=70)
    plt.plot(a,np.sin(a),color='green')
    plt.scatter(x,y)
    plt.show()

def gradient(f,w,x,y,h,val):
  # This function is used to calculate gradients of the loss function for a certain weight.
  b,yhat = f(y,x,w)
  w[val]+=h
  a,yhat = f(y,x,w)
  g = (a-b)/h
  return g,yhat

def predict(x,w):
    #This function is useful for predicting results.
  x_v = [x**i for i in range(len(w))]
  return np.dot(x_v,w)

def loss(y,x,w):
  # We would be using Mean squared error as the loss function
  yhat = [predict(x,w) for x in x]
  l = sum((y-yhat)**2) / len(w)
  return l,yhat

def model_creation(lossfn,x,y,w,learning_rate,epoch):
  # This is a generic function for creating the model
  t=time.time()
  loss2=[]
  print(lossfn(y,x,w))
  yhat=[]
  grads=[0,0,0,0]
  for i in range(epoch):
    #print(grads)
    j=0
    while(j<4):
      grads[j],yhat = gradient(lossfn,w,x,y,0.0000000001,j) #calculating gradients
      j+=1
    j=0
    #print(grads)
    while(j<4):
      w[j]-= learning_rate*grads[j]  #updating weights
      j+=1
    l,_= lossfn(y,x,w)
    loss2.append(l)
    if i%1000 == 0:
      print("Loss at epoch {} is {}".format(i,l)) #prints loss for every 1000 epochs
  print("Loss at epoch {} is {}".format(epoch,l))
  return loss2,yhat

def show_output(x,y,yhat):
    a = np.linspace(0,10,num=70)
    plt.plot(a,np.sin(a),color='green')
    plt.scatter(x,y,color='black')
    plt.scatter(x,yhat,color='red')
    plt.plot(x,yhat)
    plt.show()

if __name__ == '__main__':
    # Here is the driver code
    w_vector = np.random.randn(4)
    # We will be fitting a 3 degree polynomial
    x,y = create_dataset(30)
    # The above line creates the dataset and the line below displays it.
    show_dataset(x,y)
    # The model is trained
    l,yhat = model_creation(loss,x,y,w_vector,1e-7,50000)
    #The results are shown
    show_output(x,y,yhat)


import numpy as np

def get_phi(row):
    """Helper function for rotating data.

    row: 2D-entry in matrix with shape (?, 2)
    """
    return np.arctan2(row[0], row[1])

def rotate_2D(d, angle):
    """Rotate a 2-dimensional figure.

    Parameters
    ============

    d: the 2-dimensional data to rotate
    angle: the angle (in radians) to rotate the data around 0
    centered_around_zero: Boolean specifying whether the data is centered around zero.
    """
    try:
        assert d.shape[1] == 2
    except AssertionError:
        raise ValueError(
            "Error: data has {} dimensions, but should only be 2. ".format(d.shape[1])
        )

    rot = angle - np.pi / 2
    phis = np.apply_along_axis(get_phi, 1, d)
    phi_new = phis + rot
    r = np.sqrt(d[:, 0] ** 2 + d[:, 1] ** 2)

    x = r * np.cos(phi_new)
    y = r * np.sin(phi_new)
    return np.concatenate((x.reshape(-1, 1), y.reshape(-1, 1)), axis=1)


import numpy as np
from functools import reduce
def cov_cond(c,a):
    """生成条件数为c，第一主元方向向量为a的协方差矩阵
    Input:
    c: 条件数， int
    a: 第一主元方向向量， shape=(dim,1)
    Output:
    sigma: 协方差矩阵 ， shape=(dim,dim)
    preci: 精度矩阵
    """
    dim = a.shape[0]   # 获取维度
    e = np.sort(1/np.linspace(1, c, dim))   # 特征值
    a_div_norm = a/np.linalg.norm(a, axis=0)
    z = np.eye(dim) - 2.0*a_div_norm.dot(a_div_norm.T) # 构建豪斯霍尔德矩阵
    sigma = reduce(np.dot, (z, np.diag(e), z.T))
    preci = reduce(np.dot, (z, np.linalg.inv(np.diag(e)), z.T))
    return sigma, preci

if __name__ == "__main__":
    c = 10; a = np.random.randn(50)[:,np.newaxis]
    sigma, _ = cov_cond(c, a)
    print(np.linalg.cond(sigma, 2))


# =========================================================================
#
#    Graduate Course: Finite Element Analysis - Spring 2022 @JBNU
#
#  ========================================================================
#
#   Problem 1: A spring system
#
#   Last updated: 14/03/2022 by Minh-Chien Trinh (mctrinh@jbnu.ac.kr)
#
#   Copyright 2022 Minh-Chien Trinh. All rights reserved.
#
# =========================================================================

import numpy as np

# Element nodes
eNode = np.array([[1,2], [2,3], [2,4]])

# Number of elements
nElem = np.size(eNode,0)                # axis=0 returns the number of row

# Number of nodes
nNode = 4

# Initialization
Umat = np.zeros((nNode,1))              # Displacement vector
Fmat = np.zeros((nNode,1))              # Force vector
Kmat = np.zeros((nNode,nNode))          # Stiffness matrix
# Applied load at node 2
Fmat[1,0] = 10                          # Python index from 0

for i in range(nElem):
    # Element degree of freedom
    eDof = eNode[i,:]

    # Row index
    rIndex = np.zeros((np.size(eDof),np.size(eDof)), dtype=int)
    rIndex[0:2,0] = eDof - 1
    rIndex[0:2,1] = eDof - 1

    # Column index
    cIndex = np.zeros((np.size(eDof),np.size(eDof)), dtype=int)
    cIndex[0,0:2] = eDof - 1
    cIndex[1,0:2] = eDof - 1

    # The contribution of the element to the stiffness matrix
    Kmat[rIndex,cIndex] = Kmat[rIndex,cIndex] + np.array([[1,-1], [-1, 1]])

# Apply boundary condition
# Fix/prescribed degree of freedom
fixDof = np.array([[0], [2], [3]])
# Free/active degree of freedom
activeDof = np.setdiff1d(np.arange(0,nNode,1), fixDof)

# Solution
# Indexing of activeDof
iIndex = np.zeros((1,1), dtype=int)
iIndex[0,0] = activeDof
jIndex = np.zeros((1,1), dtype=int)
jIndex[0,0] = activeDof
# Evaluate displacement at activeDof
Umat[activeDof,0] = np.dot(np.linalg.inv(Kmat[iIndex,jIndex]),Fmat[activeDof,0])
print(Umat)

# Force vector
Fmat = np.dot(Kmat,Umat)
print(Fmat)

# Reactions
Reaction = np.zeros(np.size(fixDof))
Reaction = Fmat[fixDof,0]
print(Reaction)


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

"""AFN on correlated random time series.

The AFN algorithm is not impervious to correlated stochastic data.
Here we use a time series coming from an AR(1) process and see that the
E2(d) curve has a nontrivial appearance, falsely giving the appearance
of determinism.

Two points are to be noted:

1. Although the E2(d) curves have a nontrivial appearance, they are
   different from the curves seen for deterministic data.  Here the
   value of E2(d) first decreases with d and then increases, whereas
   for deterministic data, E2(d) is seen to increase right from the
   beginning.

2. Imposing a minimum temporal separation equal to the autocorrelation
   time of the series while searching for near neighbors solves the
   problem.
"""

from nolitsa import delay, dimension, utils
import matplotlib.pyplot as plt
import numpy as np

# Generate stochastic data.
N = 5 * 1000
x = np.empty(N)

np.random.seed(999)
eta = np.random.normal(size=(N), loc=0, scale=1.0)
a = 0.99

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

x = utils.rescale(x)

# Calculate the autocorrelation time.
tau = np.argmax(delay.acorr(x) < 1.0 / np.e)

# AFN without any minimum temporal separation.
dim = np.arange(1, 10 + 2)
F, Fs = dimension.afn(x, tau=tau, dim=dim, window=0)
F1, F2 = F[1:] / F[:-1], Fs[1:] / Fs[:-1]

# AFN with a minimum temporal separation (equal to the autocorrelation
# time) between near-neighbors.
dim = np.arange(1, 10 + 2)
E, Es = dimension.afn(x, tau=tau, dim=dim, window=tau)
E1, E2 = E[1:] / E[:-1], Es[1:] / Es[:-1]

plt.figure(1)
plt.title(r'AR(1) process with $a = 0.99$')
plt.xlabel(r'i')
plt.ylabel(r'$x_i$')
plt.plot(x)

plt.figure(2)
plt.title(r'AFN without any minimum temporal separation')
plt.xlabel(r'Embedding dimension $d$')
plt.ylabel(r'$E_1(d)$ and $E_2(d)$')
plt.plot(dim[:-1], F1, 'bo-', label=r'$E_1(d)$')
plt.plot(dim[:-1], F2, 'go-', label=r'$E_2(d)$')
plt.legend()

plt.figure(3)
plt.title(r'AFN with a minimum temporal separation of $%d$' % tau)
plt.xlabel(r'Embedding dimension $d$')
plt.ylabel(r'$E_1(d)$ and $E_2(d)$')
plt.plot(dim[:-1], E1, 'bo-', label=r'$E_1(d)$')
plt.plot(dim[:-1], E2, 'go-', label=r'$E_2(d)$')
plt.legend()

plt.show()


# Question 3 Algorithm 1 (inefficient) Lab Assignment 03
# @AB Satyaprakash, 180123062

# imports
from math import exp, sqrt
import numpy as np
import time

# functions ---------------------------------------------------------------------------

def recursion(n, K, S, T, r, sig, M, u, d, p, q, dt):

    if n == M:
        return max(S - K, 0)

    up = recursion(n + 1, K, S * u, T, r, sig, M, u, d, p, q, dt)
    dn = recursion(n + 1, K, S * d, T, r, sig, M, u, d, p, q, dt)
    pc = (exp(-r * dt)) * (p * up + q * dn)
    return pc

# -------------------------------------------------------------------------------------
# Given Initial Values -- and taking K = 100
S0, K, T, r, sig = 100, 100, 1, 0.08, 0.2
# Can't handle values of M beyond 20 ..
Mlist = [5, 10, 20]

for M in Mlist:
    dt = T/M

    u = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p = (exp(r*dt)-d)/(u-d)
    q = 1-p
    loopbackOptionPrice = recursion(0, K, S0, T, r, sig, M, u, d, p, q, dt)
    print('The initial european call option price for M = {} is {}'.format(M, loopbackOptionPrice))

# Test computation time for M = 15: ------------------------------------------------
dt = T/M
u = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
d = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
p = (exp(r*dt)-d)/(u-d)
q = 1-p
start = time.time()
timeTempPrice = recursion(0, K, S0, T, r, sig, M, u, d, p, q, dt)
end = time.time()

print('Computational time for M = 15 is {}s'.format(end-start))

# Question 3 algorithm 1 ends -------------------------------------------------------------

Semester 6/MA 374 (Financial Engg. Lab)/Lab 3/180123062_AB_q3_1.py


#!/usr/bin/env python
# -*- coding: utf8 -*-
"""
    @author lichuan89@126.com
    @date   2016/09/12
    @note
"""

import numpy as np
import copy

# 参考:
#   https://blog.csdn.net/cckchina/article/details/79915181
#   https://zhuanlan.zhihu.com/p/37740860

def logistic(z):
    o = copy.deepcopy(z)
    o[o > 0] = 1 / (1 + np.exp(-o[o > 0]))
    o[o <= 0] = np.exp(o[o <= 0]) / (1 + np.exp(o[o <= 0]))
    return o

def logistic_deriv(y):
    return np.multiply(y, (1 - y))

def softmax(z):
    return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)

def softmax_deriv(y):
    return np.multiply(y, (1 - y))

def relu(z):
    return np.maximum(z, 0)

def relu_deriv(y):
    o = np.ones(y.shape)
    o[y < 0] = 0
    return o

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

def tanh_deriv(y):
    return 1 - y ** 2

def arctan(z):
    return np.arctan(z)

def arctan_deriv(z):
    return 1 / (1 + z ** 2)

def crossEntropy_cost(y, t):
    """
    交叉熵损失函数
    """
    o = copy.deepcopy(y)
    o[o < 1e-10] = 1e-10
    o[o > 0.99] = 1 - 1e-10
    return - np.multiply(t, np.log(o)).sum() / o.shape[0]

def crossEntropy_cost_deriv(y, t):
    """
    交叉熵损失函数对预测值y的梯度
    """
    return (y - t) / y / (1 - y) / y.shape[0]


import numpy as np
import scipy.stats as sp

def cosd(angle):
    """Compute cosine of angle in degrees.

    Parameters
    ----------
    angle: float
        Angle in degrees
    """

    return np.cos(np.pi * angle/180)

def sind(angle):
    """Compute sine of angle in degrees.

        Parameters
        ----------
        angle: float
            Angle in degrees
        """

    return np.sin(np.pi * angle/180)

def tand(angle):
    """Compute tangent of angle in degrees.

        Parameters
        ----------
        angle: float
            Angle in degrees
        """

    return np.tan(np.pi * angle/180)

def arctand(angle):
    """Compute arctangent of angle in degrees.

        Parameters
        ----------
        angle: float
            Angle in degrees
        """

    return np.arctan(angle) * 180/np.pi

def cart2pol(x, y):
    """Convert cartesian coordinates to polar coordinates.

    Parameters
    ----------
    x: float
        x coordinate
    y: float
        y coordinate

    Returns
    -------
    phi: float
        Angle in radians
    rho: float
        Magnitude
    """

    rho = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)

    return phi, rho

def pol2cart(phi, rho):
    """Convert polar coordinates to cartesian coordinates.

        Parameters
        ----------
        phi: float
            Angle in radians
        rho: float
            Magnitude

        Returns
        -------
        x: float
            x coordinate
        y: float
            y coordinate

        """

    x = rho * np.cos(phi)
    y = rho * np.sin(phi)

    return x, y

def iqr(data):
    """This function computes the iqr consistent with Matlab

    Parameters
    ----------
    data: np.ndarray
        Data for which the statistic is required

    Returns
    -------
    sp_iqr: float
        Inner quartile range

    """

    # If 2-D array use only 1st row
    if len(data.shape) > 1:
        data_1d = data.flatten()
    else:
        data_1d = data

    # Remove nan elements
    idx = np.where(np.isnan(data_1d) == False)[0]
    data_1d = data_1d[idx]

    # Compute statistics
    q25, q50, q75 = sp.mstats.mquantiles(data_1d, alphap=0.5, betap=0.5)
    sp_iqr = q75 - q25

    return sp_iqr

def iqr_2d(data):
    """This function computes the iqr consistent with Matlab

    Parameters
    ----------
    data: np.ndarray
        Data for which the statistic is required

    Returns
    -------
    sp_iqr: float
        Inner quartile range

    """

    # Remove nan elements
    data = np.array(data)
    idx = np.where(np.isnan(data) == False)[0]
    data = data[idx]

    # Compute statistics
    q25, q50, q75 = sp.mstats.mquantiles(data, alphap=0.5, betap=0.5)
    sp_iqr = q75 - q25
    return sp_iqr

def azdeg2rad(angle):
    """Converts an azimuth angle in degrees to radians.

    Parameters
    ----------
    angle: float, np.ndarray(float)
        Azimuth angle in degrees

    Returns
    -------
    direction: float, np.ndarray(float)
        Angle in radians
    """

    # Convert to radians
    direction = np.deg2rad(90-angle)

    # Create postive angle
    idx = np.where(direction < 0)[0]
    if len(idx) > 1:
        direction[idx] = direction[idx] + 2 * np.pi
    else:
        direction = direction + 2 * np.pi

    return direction

def rad2azdeg(angle):
    """Converts an angle in radians to an azimuth in degrees.

    Parameters
    ----------
    angle: float, np.ndarray(float)
        Angle in radians

    Returns
    -------
    deg: float, np.ndarray(float)
        Azimuth in degrees
    """

    if isinstance(angle, float):
        deg = np.rad2deg(angle)
        deg = 90 - deg
        if deg < 0:
            deg += 360

        return deg
    else:
        # Multiple values
        deg = np.rad2deg(angle)
        deg = 90 - deg
        sub_zero = np.where(deg < 0)
        deg[sub_zero] = deg[sub_zero] + 360

        return deg

def nandiff(values):
    """Computes difference in consecutive values with handling of nans.

    Parameters
    ----------
    values: np.ndarray()
        1-D array of numbers

    Returns
    -------
    final_values: np.ndarray()
        1-D array of differences of consecutive non nan numbers
    """

    final_values = []
    for n in range(len(values) - 1):
        # Check for nan and add nan to final values
        if np.isnan(values[n]):
            final_values.append(np.nan)
        else:
            # Search for next non nan number and compute difference
            i = n + 1
            while np.isnan(values[i]) and i < len(values) - 1:
                i += 1

            final_values.append(values[i] - values[n])

    return np.array(final_values)

def valid_number(data_in):
    """Check to see if data_in can be converted to float.

    Parameters
    ----------
    data_in: str
        String to be converted to float

    Returns
    -------
    data_out: float
        Returns a float of data_in or nan if conversion is not possible
    """

    try:
        data_out = float(data_in)
    except ValueError:
        data_out = np.nan
    return data_out

def nans(shape, dtype=float):
    """Create array of nans.

    Parameters
    ----------
    shape: tuple
        Shape of array to be filled with nans
    dtype: type
        Type of array

    Returns
    -------
    a: np.ndarray(float)
        Array of nan
    """
    a = np.empty(shape, dtype)
    a.fill(np.nan)
    return a

def checked_idx(transects):
    """Create list of transect indices of all checked transects.

    Parameters
    ----------
    transects: list
        List of TransectData objects

    Returns
    -------
    checked: list
        List of indices

    """
    checked = []
    for n, transect in enumerate(transects):
        if transect.checked:
            checked.append(n)

    return checked

def units_conversion(units_id='SI'):
    """Computes the units conversion from SI units used internally to the
    desired display units.

    Parameters
    ----------
    units_id: str
        String variable identifying units (English, SI) SI is the default.

    Returns
    -------
    units: dict
        dictionary of unit conversion and labels
    """

    if units_id == 'SI':
        units = {'L': 1,
                 'Q': 1,
                 'A': 1,
                 'V': 1,
                 'label_L': '(m)',
                 'label_Q': '(m3/s)',
                 'label_A': '(m2)',
                 'label_V': '(m/s)',
                 'ID': 'SI'}

    else:
        units = {'L': 1.0 / 0.3048,
                 'Q': (1.0 / 0.3048)**3,
                 'A': (1.0 / 0.3048)**2,
                 'V': 1.0 / 0.3048,
                 'label_L': '(ft)',
                 'label_Q': '(ft3/s)',
                 'label_A': '(ft2)',
                 'label_V': '(ft/s)',
                 'ID': 'English'}

    return units

def convert_temperature(temp_in, units_in, units_out):
    """Converts temperature from F to C or C to F.

    Parameters
    ==========
    temp_in: np.array
        temperature in units_in
    units_in: str
        C for Celcius or F for Fahrenheit
    units_out: str
        C for Celcius or F for Fahrenheit

    Returns
    =======
    temp_out: np.array
        temperature in units_out
    """

    temp_out = None
    if units_in == 'F':
        if units_out == 'C':
            temp_out = (temp_in - 32) * (5./9.)
        else:
            temp_out = temp_in

    elif units_in == 'C':
        if units_out == 'C':
            temp_out = temp_in
        else:
            temp_out = (temp_in * (9./5.)) + 32

    return temp_out


import csv
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
import time
import sys
import warnings

if not sys.warnoptions:
    warnings.simplefilter("ignore")

path_X = sys.argv[1];
path_Y = sys.argv[2];
learning_rate = float(sys.argv[3]);
time_gap = float(sys.argv[4]);
# Read the CSV files to create X_linear and Y_linear

read_1 = [];
with open(path_X) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    for row in reader: # each row is a list
        read_1.append(row)

X_rawest = np.asarray(read_1)
mu = np.mean(X_rawest);
sigma = np.std(X_rawest);
X_raw = (X_rawest - mu)/sigma;
X_linear = np.c_[ np.ones((X_raw.shape[0],1)), X_raw]

read_2 = [];
with open(path_Y) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    for row in reader: # each row is a list
        read_2.append(row)

Y_linear = np.asarray(read_2)

m = Y_linear.shape[0];
#Function to compute cost

def compute_cost(X,Y,theta):
    h = np.matmul(X,theta);
    J = np.sum(np.square(h-Y))/(2*m);
    return float(J);

#Function for Gradient Descent

def grad_descent(X,Y,theta,alpha):
    h = np.matmul(X,theta);
    d_theta = np.zeros(theta.shape);
    d_theta[0] = np.matmul((h-Y).T,X[:,0])/m;
    d_theta[1] = np.matmul((h-Y).T,X[:,1])/m;
    theta = theta - alpha * d_theta;
    return theta;

#Function to do the regression

def linear_reg(X,Y,theta,alpha, max_iter):
    J = [];
    theta0_iter = [];
    theta1_iter = [];
    J.append(compute_cost(X,Y,theta));
    theta0_iter.append(theta[0]);
    theta1_iter.append(theta[1]);
    for i in range (max_iter):
        theta = grad_descent(X,Y,theta,alpha);
        J.append(compute_cost(X,Y,theta));
        theta0_iter.append(theta[0]);
        theta1_iter.append(theta[1]);
        delta = np.abs(J[i+1] - J[i])/alpha;
        if(delta<np.power(10.0,-10)):
            #print("Number of iterations ", i);
            #print("Final cost ", J[i]);
            break;
    return theta,J, theta0_iter, theta1_iter;

def plot_contour(X,Y,eta,time_gap):
  fig, ax = plt.subplots()
  plt.figure(fig.number)
  X_3d = np.arange(-1, 3, 0.05)
  Y_3d = np.arange(-1, 1, 0.05)

  Z_3d = np.zeros((X_3d.shape[0],Y_3d.shape[0]))
  for i in range(X_3d.shape[0]):
      for j in range(Y_3d.shape[0]):
          Z_3d[i,j] = compute_cost(X_linear, Y_linear,np.array([X_3d[i], Y_3d[j]]))/m;
  X_3d, Y_3d = np.meshgrid(X_3d, Y_3d)

  plt.contour(X_3d,Y_3d,Z_3d.T,10);
  plt.title('Learning Rate = %.2f' %eta);
  theta = np.zeros((2,1));
  [theta,J, t0_iter, t1_iter] = linear_reg(X_linear,Y_linear,theta,eta, max_iter);
  rounder = 1;
  for i in range(round(len(J)/rounder)):
    x0 = t0_iter[i*rounder];
    x1 = t1_iter[i*rounder];
    if(x0*x0 + x1*x1 > 100):
      break;
    scat = ax.scatter(x0,x1,c='r');
    fig.canvas.draw();
    plt.pause(time_gap)
  return fig;

theta = np.zeros((2,1));
alpha = learning_rate;
max_iter = np.power(10,3);
[theta,J, t0_iter, t1_iter] = linear_reg(X_linear,Y_linear,theta,alpha, max_iter);
t0_iter = np.array(t0_iter);
t1_iter = np.array(t1_iter);
theta_final = np.zeros((2,1));
theta_final[0] = theta[0]-mu*theta[1]/sigma;
theta_final[1] = theta[1]/sigma;
print("Theta 0 : ", theta_final[0,0]);
print("Theta 1 : ", theta_final[1,0]);
input("Press Enter to go to part (b)")
fig,ax = plt.subplots()
plt.plot(X_rawest,Y_linear,'ro');
x1 = np.arange(np.min(X_rawest)-5, np.max(X_rawest)+5, 0.01)
y1 = theta_final[0] + theta_final[1]*x1;
plt.plot(x1,y1,'k',linewidth=2.0,);
plt.xlabel('Inputs')
plt.ylabel('Outputs')
plt.title('Linear Regression')
plt.show(block=False);
input("Press Enter to go to part (c)")
plt.close(fig)
plt.ion();
fig1 = plt.figure(1)
ax1 = fig1.add_subplot(111, projection='3d')

X_3d = np.arange(0, 2, 0.05)
Y_3d = np.arange(-1, 1, 0.05)
Z_3d = np.zeros((X_3d.shape[0],Y_3d.shape[0]))
for i in range(X_3d.shape[0]):
    for j in range(Y_3d.shape[0]):
        Z_3d[i,j] = compute_cost(X_linear, Y_linear,np.array([X_3d[i], Y_3d[j]]))/m;
X_3d, Y_3d = np.meshgrid(X_3d, Y_3d)

ax1.plot_surface(X_3d, Y_3d, Z_3d, cmap='magma',alpha=0.7)
plt.title('3D Plot for Error Function vs Theta');
ax1.view_init(elev=45,azim=20);
J_array=[]
rounder=1;
for i in range(round(len(J)/rounder)):
  x0 = t0_iter[i*rounder,0];
  x1 = t1_iter[i*rounder,0];
  J_cost = J[i*rounder];
  ax1.scatter(xs=x0,ys=x1,zs=J_cost,c='r');
  fig1.canvas.draw();
  plt.pause(time_gap);
input("Press Enter to go to part (d)")
plt.close(fig1);
plt.ioff()
figx = plot_contour(X_linear,Y_linear,alpha,time_gap);
input("Press Enter to go to part (e)")
plt.close(figx);
eta_array = [0.1, 0.5, 0.9, 1.3, 1.7, 2.1, 2.5];
for eta in eta_array:
  figy = plot_contour(X_linear,Y_linear,eta,time_gap);
  time.sleep(2);
  plt.close(figy);


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

# code starts here
df = pd.read_csv(path)

p_a = len(df[df['fico']>700])/len(df)
print("Probability of event that fico credit score is greater than 700 : ",p_a)

p_b = len(df[df['purpose']=='debt_consolidation'])/len(df)
print("Probability of event that purpose == 'debt_consolation' : ",p_b)

df1 = df[df['purpose']=='debt_consolidation']
p_a_b = len(df1[df1['purpose']=='debt_consolidation'])/len(df1[df1['fico']>700])
print("Probability of event for the event purpose == 'debt_consolidation' given 'fico' credit score is greater than 700 : ",p_a_b)

result = p_a_b==p_a
print(result)

# code ends here

# --------------
# code starts here

prob_lp = len(df[df['paid.back.loan']=='Yes'])/len(df)
print(prob_lp)

prob_cs = len(df[df['credit.policy']=='Yes'])/len(df)
print(prob_cs)

new_df = df[df['paid.back.loan']=='Yes']

prob_pd_cs = len(new_df[new_df['credit.policy']=='Yes'])/len(new_df[new_df['paid.back.loan']=='Yes'])
print(prob_pd_cs)

bayes = (prob_pd_cs*prob_lp)/prob_cs
print(bayes)

# code ends here

# --------------
# code starts here

df['purpose'].value_counts(normalize=True).plot(kind='bar')
plt.title("Probability Distribution of Purpose")
plt.ylabel("Probability")
plt.xlabel("Number of Purpose")
plt.show()

df1 = df[df['paid.back.loan']=='No']

df1['purpose'].value_counts(normalize=True).plot(kind='bar')
plt.title("Probability Distribution of Purpose")
plt.ylabel("Probability")
plt.xlabel("Number of Purpose")
plt.show()

# code ends here

# --------------
# code starts here

inst_median = np.median(df['installment'])

inst_mean = np.mean(df['installment'])

df['installment'].plot(kind='hist', bins=50)
plt.axvline(x=inst_median,color='r')
plt.axvline(x=inst_mean,color='g')
plt.show()

df['log.annual.inc'].plot(kind='hist', bins=50)
plt.show()

# code ends here


"""
Objetivo: Resolver questão 2, as primeiras duas matirzes primeiro laboratorio.
"""
import numpy as np
from scipy.linalg import toeplitz

a = np.eye(5)       #gera uma matriz identidade do tamanho do parametro
#print(f'Matriz a = \n{a}')
b = np.array([[2, 0, 0, 0, 0],
              [2, 2, 0, 0, 0],
              [2, 2, 2, 0, 0],
              [2, 2, 2, 2, 0],
              [2, 2, 2, 2, 2]])
#print(f'Matriz b = \n{b}')
c = np.diag(np.diag(b))     #extrai a diagonal em questãodiag(matiz, k)
#print(c)
d = np.diagflat([[1, 2], [3, 4]], 1)        #escreve uma matriz onde geral é zero menos a diagonal
                                            # definida nos parametros, q tem q ser escrita de 2 em 2
#print(d)
e = toeplitz([1, 1, 0, 3], [0, 2, 0, 2])    #Escreve uma matriz([d, d-1, d-2, ...], [d(n), d+1, d+2, ...])
#print(e)
b_2 = toeplitz([2, 2, 2, 2, 2], [0, 0, 0, 0, 0])
#print(b_2)
f = np.arange(10, 2, -1)        #gera matriz (começo, fim, passo) mas so uma dimensao
f = f.reshape(2, 4)             #redefine o numero de dimensoes(linha, coluna),se falta ou sobre termo ela n roda
print(f)


import numpy as np
import matplotlib.pyplot as plt

def FE_logistic(u0, dt, N):
    u = np.zeros(N+1)
    u[0] = u0
    for n in range(N):
        u[n+1] = u[n] + dt*(u[n] - u[n]**2)
    return u

def quadratic_roots(a, b, c):
    delta = b**2 - 4*a*c
    r2 = (-b + np.sqrt(delta))/float(2*a)
    r1 = (-b - np.sqrt(delta))/float(2*a)
    return r1, r2

def BE_logistic(u0, dt, Nt, choice='Picard',
                eps_r=1E-3, omega=1, max_iter=1000):
    if choice == 'Picard1':
        choice = 'Picard'
        max_iter = 1

    u = np.zeros(Nt+1)
    iterations = []
    u[0] = u0
    for n in range(1, Nt+1):
        a = dt
        b = 1 - dt
        c = -u[n-1]
        if choice in ('r1', 'r2'):
            r1, r2 = quadratic_roots(a, b, c)
            u[n] = r1 if choice == 'r1' else r2
            iterations.append(0)

        elif choice == 'Picard':
            def F(u):
                return a*u**2 + b*u + c

            u_ = u[n-1]
            k = 0
            while abs(F(u_)) > eps_r and k < max_iter:
                u_ = omega*(-c/(a*u_ + b)) + (1-omega)*u_
                k += 1
            u[n] = u_
            iterations.append(k)

        elif choice == 'Newton':
            def F(u):
                return a*u**2 + b*u + c

            def dF(u):
                return 2*a*u + b

            u_ = u[n-1]
            k = 0
            while abs(F(u_)) > eps_r and k < max_iter:
                u_ = u_ - F(u_)/dF(u_)
                k += 1
            u[n] = u_
            iterations.append(k)
    return u, iterations

def CN_logistic(u0, dt, Nt):
    u = np.zeros(Nt+1)
    u[0] = u0
    for n in range(0, Nt):
        u[n+1] = (1 + 0.5*dt)/(1 + dt*u[n] - 0.5*dt)*u[n]
    return u

def quadratic_root_goes_to_infinity():
    """
    Verify that one of the roots in the quadratic equation
    goes to infinity.
    """
    for dt in 1E-7, 1E-12, 1E-16:
        a = dt
        b = 1 - dt
        c = -0.1
        print((dt, quadratic_roots(a, b, c)))

print('sympy calculations')
import sympy as sym
dt, u_1, u = sym.symbols('dt u_1 u')
r1, r2 = sym.solve(dt*u**2 + (1-dt)*u - u_1, u)
print(r1)
print(r2)
print((r1.series(dt, 0, 2)))
print((r2.series(dt, 0, 2)))

T = 9
try:
    dt = float(sys.argv[1])
    eps_r = float(sys.argv[2])
    omega = float(sys.argv[3])
except:
    dt = 0.8
    eps_r = 1E-3
    omega = 1
N = int(round(T/float(dt)))

u_BE3, iter_BE3 = BE_logistic(0.1, dt, N, 'Picard', eps_r, omega)
print(iter_BE3)
print(('Picard mean no of iterations (dt=%g):' % dt, int(round(np.mean(iter_BE3)))))

u_FE = FE_logistic(0.1, dt, N)
u_BE1, _ = BE_logistic(0.1, dt, N, 'r1')
u_BE2, _ = BE_logistic(0.1, dt, N, 'r2')
u_BE31, iter_BE31 = BE_logistic(0.1, dt, N, 'Picard1', eps_r, omega)
u_BE3, iter_BE3 = BE_logistic(0.1, dt, N, 'Picard', eps_r, omega)
u_BE4, iter_BE4 = BE_logistic(0.1, dt, N, 'Newton', eps_r, omega)
u_CN = CN_logistic(0.1, dt, N)

from numpy import mean
print(('Picard mean no of iterations (dt=%g):' % dt, int(round(np.mean(iter_BE3)))))
print(('Newton mean no of iterations (dt=%g):' % dt, int(round(np.mean(iter_BE4)))))

t = np.linspace(0, dt*N, N+1)
plt.plot(t, u_FE, t, u_BE2, t, u_BE3, t, u_BE31, t, u_BE4, t, u_CN)
plt.legend(['FE', 'BE exact', 'BE Picard', 'BE Picard1', 'BE Newton', 'CN gm'])
plt.title('dt=%g, eps=%.0E' % (dt, eps_r))
plt.xlabel('t')
plt.ylabel('u',)
filestem = 'logistic_N%d_eps%03d' % (N, np.log10(eps_r))
plt.savefig(filestem + '_u.png')
plt.savefig(filestem + '_u.pdf')
plt.figure()
plt.plot(list(range(1, len(iter_BE3)+1)), iter_BE3, 'r-o',
         list(range(1, len(iter_BE4)+1)), iter_BE4, 'b-o')
plt.legend(['Picard', 'Newton'])
plt.title('dt=%g, eps=%.0E' % (dt, eps_r))
plt.axis([1, N+1, 0, max(iter_BE3 + iter_BE4)+1])
plt.xlabel('Time level')
plt.ylabel('No of iterations')
plt.savefig(filestem + '_iter.png')
plt.savefig(filestem + '_iter.pdf')


# https://ml-cheatsheet.readthedocs.io/en/latest/loss_functions.html
from tensor import Tensor
import numpy as np
from math import *

# Loss functions

def cross_entropy(y_pred, y_true):
  return -sum([y_true[i]*log2(y_pred[i]) for i in range(len(y_pred))])

def binary_classification(y_pred, y_true):
  return -(y_true * log(y_pred) + (1 - y_true) * log(1 - y_pred))

# ----

if __name__ == "__main__":
  predictions = np.array([[0.25,0.25,0.25,0.25],
                          [0.01,0.01,0.01,0.96]])
  targets = np.array([[0,0,0,1], [0,0,0,1]])
  ans = 0.71355817782  #Correct answer
  x = cross_entropy(predictions, targets)
  print(np.isclose(x,ans))

  p = [0.10, 0.40, 0.50]
  q = [0.80, 0.15, 0.05]

  print(cross_entropy(q, p))


import numpy as np
from scipy.special import softmax
import seaborn as sns
import matplotlib.pyplot as plt

if __name__ == '__main__':
    data_size = 100
    true_grad = 3
    true_intercept = 1
    true_sig = 1

    x = np.linspace(0, 10, data_size)
    # y = m x + c
    y_obs = true_grad * x + true_intercept + np.random.normal(loc=0, scale=true_sig, size=data_size)

    M = 2000000
    m = M // 20  # M/m is usually around 20
    print(f'Generating {M} initial samples, and {m} re-samples')

    # sample M params from initial prior
    grad_prior = np.random.uniform(-10, 10, M)  # m ~ U(-10, 10)
    intercept_prior = np.random.uniform(-10, 10, M)  # c ~ U(-10, 10)
    sig_prior = np.random.uniform(0.1, 20, M)  # sig ~ U(0.1, 10)

    # calculate importance weights, assuming that we model y ~ N(mu, sig)
    exponent = 1 / (2 * sig_prior ** 2) \
               * np.sum([(y_obs[i] - (grad_prior * x[i] + intercept_prior)) ** 2 for i in range(data_size)], axis=0)

    log_weights = - data_size * np.log(sig_prior * np.sqrt(2 * np.pi)) - exponent

    weights = softmax(log_weights)

    # resample params using the above weights to get posterior
    grad_posterior = np.random.choice(grad_prior, m, p=weights)
    intercept_posterior = np.random.choice(intercept_prior, m, p=weights)
    sig_posterior = np.random.choice(sig_prior, m, p=weights)

    # report summary stats
    print(f'True gradient: {true_grad}')
    print(f'True intercept: {true_intercept}')
    print(f'True sigma: {true_sig}')
    print(f'Gradient posterior: mean={np.mean(grad_posterior):.3} - sd={np.std(grad_posterior):.3}')
    print(f'Intercept posterior: mean={np.mean(intercept_posterior):.3} - sd={np.std(intercept_posterior):.3}')
    print(f'Sigma posterior: mean={np.mean(sig_posterior):.3} - sd={np.std(sig_posterior):.3}')

    # plot the new samples
    fig, axes = plt.subplots(1, 4, figsize=(12, 3))

    axes[0].set_title('Data')
    axes[1].set_title('Gradient Posterior')
    axes[2].set_title('Intercept Posterior')
    axes[3].set_title('Sigma Posterior')

    axes[0].plot(x, y_obs, 'x')
    sns.distplot(grad_posterior, ax=axes[1])
    sns.distplot(intercept_posterior, ax=axes[2])
    sns.distplot(sig_posterior, ax=axes[3])
    plt.show()

    fig, ax = plt.subplots(figsize=(5, 5))
    ax.set_xlabel('Gradient')
    ax.set_ylabel('Intercept')
    ax.set_title('Joint distribution p(m, c)')
    sns.kdeplot(grad_posterior, intercept_posterior, shade=True, ax=ax)
    plt.show()


from scipy.optimize import curve_fit
import numpy as np

def uptake_func(po2, a, b, c):
    return a - ((b * po2) / (c + po2))

def fit():
    uptake = [10.91, 10.9, 7.7, 5.6, 1.0, 0.628, 0.5]
    po2 = [0.0, 0.0076, 0.76, 3.8, 38, 60, 152]
    #po2 = [0.0, 0.76, 3.8, 38, 152]

    pars, cov = curve_fit(f=uptake_func, xdata=po2, ydata=uptake,
                          p0=[10.9, 10.7, 2.5])
                          # sigma=[1, 1, 10, 10, 10, 10, 1])

    stdevs = np.sqrt(np.diag(cov))  # Calculate the residuals
    # res = y_dummy - power_law(x_dummy, *pars)
    print(pars)
    print(cov)

if __name__ == "__main__":
    # execute only if run as a script
    fit()