# # # *************** Numpy *****************

# NumPy is used for working with arrays
# NumPy is short for "Numerical Python"
# It also has functions for working in domain of linear algebra, fourier transform, and matrices

# In Python we have lists that serve the purpose of arrays, but they are slow to process.
# NumPy aims to provide an array object that is up to 50x faster than traditional Python lists
# NumPy arrays are stored at one continuous place in memory unlike lists, so processes can access and manipulate them very efficiently
# NumPy is a Python library and is written partially in Python, but most of the parts that require fast computation are written in C or C++

import numpy as np

a = np.array(42) # 0-D Array
b = np.array([1, 2, 3, 4, 5]) # 1-D Array
c = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) # 2-D Array
d = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]]) # 3-D Array
print(type(b)) # <class 'numpy.ndarray'>

# data type
# i - integer
# b - boolean
# u - unsigned integer
# f - float
# c - complex float
# m - timedelta
# M - datetime
# O - object
# S - string
# U - unicode string
# V - fixed chunk of memory for other type ( void )

# numpy.bool_                     Boolean (True or False) stored as a byte
# numpy.byte                      Platform-defined
# numpy.ubyte                     Platform-defined
# numpy.short                     Platform-defined
# numpy.ushort                    Platform-defined
# numpy.intc                      Platform-defined
# numpy.uintc                     Platform-defined
# numpy.int_                      Platform-defined
# numpy.uint                      Platform-defined
# numpy.longlong                  Platform-defined
# numpy.ulonglong                 Platform-defined
# numpy.half / numpy.float16      Half precision float: sign bit, 5 bits exponent, 10 bits mantissa
# numpy.single                    Platform-defined single precision float: typically sign bit, 8 bits exponent, 23 bits mantissa
# numpy.double                    Platform-defined double precision float: typically sign bit, 11 bits exponent, 52 bits mantissa
# numpy.longdouble                Platform-defined extended-precision float
# numpy.csingle                   Complex number, represented by two single-precision floats (real and imaginary components)
# numpy.cdouble                   Complex number, represented by two double-precision floats (real and imaginary components)
# numpy.clongdouble               Complex number, represented by two extended-precision floats (real and imaginary components)

# numpy.int8      Byte (-128 to 127)
# numpy.int16     Integer (-32768 to 32767)
# numpy.int32     Integer (-2147483648 to 2147483647)
# numpy.int64     Integer (-9223372036854775808 to 9223372036854775807)
# numpy.uint8     Unsigned integer (0 to 255)
# numpy.uint16    Unsigned integer (0 to 65535)
# numpy.uint32    Unsigned integer (0 to 4294967295)
# numpy.uint64    Unsigned integer (0 to 18446744073709551615)
# numpy.intp      Integer used for indexing, typically the same as ssize_t
# numpy.uintp     Integer large enough to hold a pointer
# numpy.float32
# numpy.float64 / numpy.float_         (builtin python float)
# numpy.complex64
# numpy.complex128 / numpy.complex_    (builtin python complex)

c = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], dtype=int) # integer
c = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], dtype='i') # integer
c = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], dtype='i4') # integer with 4 bytes
c = np.array([[1.5, 2, 3.5, 4, 5], [6, 0.4, 8, 9.1, 10]], dtype=np.float32) # folat with 32 bytes
c = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], dtype=np.int16) # integer with 16 bytes (-32768 to 32767)

array1 = np.zeros((3, 4), dtype=int)   # array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
array2 = np.ones((3, 4), dtype=int)    # array([[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]])
array3 = np.full((3, 4), 5, dtype=int) # array([[5, 5, 5, 5], [5, 5, 5, 5], [5, 5, 5, 5]])
array3 = np.eye(3) # array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])
array4 = np.arange(1, 6) # [1, 2, 3, 4, 5]
array5 = np.arange(12).reshape(3, 4) # array([[ 0,  1,  2,  3], [ 4,  5,  6,  7], [ 8,  9, 10, 11]])

array = np.array([[2, 5, 9], [5, 8, 3]]) # 2D array (metrics)
print('Array :', array)
print('Shape :', array.shape)
print('Dimension :', array.ndim)
print('Data Type :', array.dtype)

arr = np.array([1, 2, 3, 4], ndmin=5) # creating array of 5 Dimension

# ndarray supports indexing and negative indexing just like list
print(b[1])         # in list: lst[1]      1-D array
print(array[1, 2])  # in list: lst[1][2]   2-D array
print(array[0, -2]) # in list: lst[0][-2]  2-D array

# Array slicing
print(b[1:4]) # slicing 1-D array
print(b[1:4:2]) # slicing 1-D array with step
print(c[1, 1:4])   # slicing 2-D array (From the second element, slice elements from index 1 to index 3)
print(c[0:2, 2])   # slicing 2-D array (From both elements, return index 2)
print(c[0:2, 2:5]) # slicing 2-D array (From both elements, slice index 1 to index 4, returns a 2-D array)

# Copy vs View
array1 = np.array([[2, 5, 9], [5, 8, 3]], dtype=np.int8)

array2 = array1.copy()
array2[0, 1] = 10 # this will only change 'array2', not the 'array1'

array3 = array1.view()
array3[0, 1] = 10 # this will both change 'array1' and 'array3'

# Check copy / view
print('copy base : ', array2.base) # None
print('view base : ', array3.base) # <that array>

# Array Reshape
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])

newarr = arr.reshape(3, 4)     # reshaping to 2D array (3 * 4 = 12 = total elements)
newarr = arr.reshape(2, 3, 2)  # reshaping to 3D array (2 * 3 * 2 = 12 = total elements)
# will raise an error, if it can not be reshaped to the given shape

# Iterating
arr = np.array([[3, 2, 4], [5, 0, 1]])
for x in arr: # 2D array
  for y in x:
    print(y)

# Iterating n-D array using nditer()
arr = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
for x in np.nditer(arr):
  print(x)

# Enumerated Iteration Using ndenumerate()
for idx, x in np.ndenumerate(arr):
  print('Index :', idx, 'Value :', x)

# Joining arrays
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr = np.concatenate((arr1, arr2))  # [1 2 3 4 5 6]

arr3 = np.array([[1, 2], [3, 4]])
arr4 = np.array([[5, 6], [7, 8]])
arr = np.concatenate((arr3, arr4), axis=1) # along rows     | [[1 2 5 6], [3 4 7 8]]
arr = np.concatenate((arr3, arr4), axis=0) # along columns  | [[1 2], [3 4], [5 6], [7 8]]

# same as concatenation, the only difference is that stacking is done along a new axis
arr = np.stack((arr3, arr4), axis=1)  # [[1 4], [2 5], [3 6]]
arr = np.stack((arr3, arr4), axis=0)  # [[1 2 3], [4 5 6]]
# Stacking Along Rows, Columns and Height (depth)
arr = np.hstack((arr3, arr4))  # [1 2 3 4 5 6]
arr = np.vstack((arr3, arr4))  # [[1 2 3], [4 5 6]]
arr = np.dstack((arr1, arr2))  # [[[1 4], [2 5], [3 6]]]

# Spliting arrays (reverse operation of Joining)
arr = np.array([1, 2, 3, 4, 5, 6])
newarr = np.array_split(arr, 3)  # [array([1, 2]), array([3, 4]), array([5, 6])]
# If the array has less elements than required, it will adjust from the end accordingly
newarr = np.array_split(arr, 4)  # [array([1, 2]), array([3, 4]), array([5]), array([6])]

# Split the 2-D array into three 2-D arrays
arr = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]])
newarr = np.array_split(arr, 3)  # [array([[1, 2], [3, 4]]), array([[5, 6], [7, 8]]), array([[ 9, 10], [11, 12]])]

# Spliting along rows
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]])
newarr = np.array_split(arr, 3, axis=1) # [array([[ 1], [ 4], [ 7], [10], [13], [16]]), array([[ 2], [ 5], [ 8], [11], [14], [17]]), array([[ 3], [ 6], [ 9], [12], [15], [18]])]
newarr = np.hsplit(arr, 3)              # same
# Spliting along culmns
newarr = np.array_split(arr, 3, axis=0) # [array([[1, 2, 3], [4, 5, 6]]), array([[ 7,  8,  9], [10, 11, 12]]), array([[13, 14, 15], [16, 17, 18]])]
newarr = np.vsplit(arr, 3)              # same
# Spliting along Height (depth)
# newarr = np.dsplit(arr, 3) # height/depth split only works for 3 or higher dimension

# Searching Arrays
arr = np.array([1, 2, 3, 4, 5, 4, 4])
x = np.where(arr == 4)    # Find the indexes where the value is 4
x = np.where(arr%2 == 0)  # Find the indexes where the values are even

# Search Sorted (returns the index where the specified value would be inserted to maintain the search order)
arr = np.array([6, 7, 8, 9]) # a sorted arrary
x = np.searchsorted(arr, 7) # Find the indexes where the value 7 should be inserted : 2
x = np.searchsorted(arr, 7, side='right') # Find the indexes where the value 7 should be inserted, starting from the right : 2
x = np.searchsorted(arr, [4, 2, 6]) # Find the indexes where the values 2, 4, and 6 should be inserted : [2, 1, 3]

# Sorting
arr = np.array([3, 2, 0, 1])
newarr = np.sort(arr) # return the sorted array : [0, 1, 2, 3]

arr = np.array([[3, 2, 4], [5, 0, 1]])
print(np.sort(arr))  # [[2, 3, 4], [0, 1, 5]]

# Filter
arr = np.array([41, 42, 43, 44])
x = [True, False, True, False]
newarr = arr[x] # [41, 43]

filter_arr = arr > 42 # [False, False, True, True]
newarr = arr[filter_arr] # [43, 44]
# in one line : newarr = arr[arr > 42]

filter_arr = arr % 2 == 0 # [False, True, False, True]
newarr = arr[filter_arr] # [42, 44]

arr = np.array([[3, 2, 4], [5, 0, 1]])
newarr = arr[arr % 3 != 1] # [3 2 5 0]

# Reversing / Fliping
arr = np.array([41, 43, 42, 44])
arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

arr_flipped = np.flip(arr)          # array([44, 42, 43, 41])
arr_flipped = np.flip(arr2)         # array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])  | Flip in the both axis
arr_flipped = np.flip(arr2, axis=0) # array([[7, 8, 9], [4, 5, 6], [1, 2, 3]])  | Flip the rows | Alt.: flipud()
arr_flipped = np.flip(arr2, axis=1) # array([[3, 2, 1], [6, 5, 4], [9, 8, 7]])  | Flip only in the row | Alt.: fliplr ()

# Random
from numpy import random
arr = random.randint(100, size=(5)) # Generate a 1-D array containing 5 random integers from 0 to 100
arr = random.randint(100, size=(3, 5)) # Generate a 2-D array with 3 rows, each row containing 5 random integers from 0 to 100
arr = random.rand(3, 5) # Generate a 2-D array with 3 rows, each row containing 5 random numbers
arr = random.choice([3, 5, 7, 9], size=(3, 5))

# Generate a 1-D array containing 50 values, where each value has to be 3, 5, 7 or 9.
# The probability for the value to be 3 is set to be 0.1
# The probability for the value to be 5 is set to be 0.3
# The probability for the value to be 7 is set to be 0.6
# The probability for the value to be 9 is set to be 0
# The sum of all probability numbers should be 1
arr = random.choice([3, 5, 7, 9], p=[0.1, 0.3, 0.6, 0.0], size=(50))

arr = random.choice([3, 5, 7, 9], p=[0.1, 0.3, 0.6, 0.0], size=(3, 5)) # 2D array

arr = np.array([1, 2, 3, 4, 5])
random.shuffle(arr) # shuffle changes orginial array
newarr = random.permutation(arr)

x = np.random.uniform(0.0, 5.0, 250) # low : 0.0  &  high : 5.0  &  size : 250
x = np.random.normal(5.0, 1.0, 1000) # mean : 5.0  &  std: 1.0  &  size : 1000

# Numpy ufunc
# ufuncs stands for "Universal Functions" and they are NumPy functions that operates on the ndarray object
# ufuncs are used to implement vectorization in NumPy which is way faster than iterating over elements

# Create Your Own ufunc
# The frompyfunc() method takes the following arguments:
#   function - the name of the function.
#   inputs   - the number of input arguments (arrays).
#   outputs  - the number of output arrays.

def mycalc(x, y):
  return (x + y) / 2

mycalc = np.frompyfunc(mycalc, 2, 1) # here, 2 is number of input arrays, 1 is number of output arrays

print(mycalc([1, 2, 3, 4], [5, 6, 7, 8])) # [3.0 4.0 5.0 6.0]

print(type(np.add)) # <class 'numpy.ufunc'>
print(type(mycalc))  # <class 'numpy.ufunc'>

# ufunc Simple Arithmetic
arr1 = np.array([10, 11, 12, 13, 14, 15])
arr2 = np.array([20, 21, 22, 23, 24, 25])

newarr = np.add(arr1, arr2) # [30 32 34 36 38 40]
newarr = arr1 + arr2
newarr = np.subtract(arr1, arr2) # [-10 -10 -10 -10 -10 -10]
newarr = arr1 - arr2
newarr = np.multiply(arr1, arr2) # [200 231 264 299 336 375]
newarr = arr1 * arr2
newarr = np.divide(arr1, arr2) # [0.5 0.52380952 0.54545455 0.56521739 0.58333333 0.6]
newarr = arr1 / arr2
newarr = np.power(arr1, arr2) # [ 7766279631452241920  3105570700629903195  5729018530666381312 -4649523274362944347 -1849127232522420224  1824414961309619599]
newarr = arr1 ** arr2
newarr = np.mod(arr1, arr2) # [10 11 12 13 14 15]
newarr = arr1 % arr2
newarr = np.divmod(arr1, arr2) # (with remainders) (array([0, 0, 0, 0, 0, 0]), array([10, 11, 12, 13, 14, 15]))
newarr = arr1 // arr2 # [0, 0, 0, 0, 0, 0]

newarr = np.absolute([-1, -2, 1, 2, 3, -4]) # [1, 2, 1, 2, 3, 4]

# just like that we can give any other value, like:
newarr = arr1 + 2 # [12 13 14 15 16 17]
newarr = arr1 % 3 # [1 2 0 1 2 0]

# ufunc mathematical operations
arr = np.array([-3.1666, 3.6667])

# Remove the decimals, and return the float number closest to zero
newarr = np.trunc(arr)      # [-3.  3.]
newarr = np.fix(arr)        # [-3.  3.]
# round float
newarr = np.around(arr)  # [-3.  4.]
# round float to 2 decimal points
newarr = np.around(arr, 2)  # [-3.17  3.67]
# The floor() function rounds off decimal to nearest lower integer
newarr = np.floor(arr)      # [-4.  3.]
# The ceil() function rounds off decimal to nearest upper integer
newarr = np.ceil(arr)       # [-3.  4.]

arr = np.arange(1, 10)

newarr = np.log2(arr)  # base 2
newarr = np.log10(arr) # base 10
newarr = np.log(arr)   # base e

from math import log
nplog = np.frompyfunc(log, 2, 1)
# np do not provide func for log with any base, so we create our own
newarr = nplog(arr, 4)   # any base

# like these, there is:
# np.sin(), np.cos(), np.tan(), np.arcsin(), np.arccos(), np.arctan()
# np.sinh(), np.cosh(), np.tanh(), np.arcsinh(), np.arccosh(), np.arctanh()
# np.deg2rad(), np.rad2deg(), np.hypot()

arr = np.array([1, 2, 3, 4])
arr1 = np.array([1, 2, 3])
arr2 = np.array([1, 2, 3])

# Summations
newarr = np.sum([arr1, arr2]) # 12
newarr = np.sum([arr1, arr2], axis=1) # [6 6]

# Cummulative sum : partially adding the elements in array.
# E.g. The partial sum of [1, 2, 3, 4] would be [1, 1+2, 1+2+3, 1+2+3+4] = [1, 3, 6, 10]
newarr = np.cumsum(arr) # [1, 3, 6, 10]

# Products
newarr = np.prod(arr) # 24 because 1*2*3*4 = 24
newarr = np.prod([arr1, arr2], axis=1) # [6 6]

# Just like Cummulative sum, there is Cummulative Product
# E.g. The partial sum of [1, 2, 3, 4] would be [1, 1*2, 1*2*3, 1*2*3*4] = [1, 2, 6, 24]
newarr = np.cumprod(arr) # [1, 2, 6, 24]

# Discrete Difference
arr = np.array([10, 15, 25, 5])
newarr = np.diff(arr) # [5 10 -20] because 15-10=5, 25-15=10, and 5-25=-20

# LCD & GCD
lcd = np.lcm.reduce(arr) # 150
gcd = np.gcd.reduce(arr) # 5

# Compare two arrays and returns a new array containing the element-wise minima
np.minimum([2, 3, 4], [1, 5, 2]) # array([1, 3, 2])
# Compare two arrays and returns a new array containing the element-wise maxima
np.maximum([2, 3, 4], [1, 5, 2]) # array([2, 5, 4])

min_value = arr.min()
max_value = arr.max()
print(arr.T)              # transpose

# Statistics
mean = np.mean(arr)      # avarage value
median = np.median(arr)  # the midest value

std = np.std(arr)        # Standard Deviation
var = np.var(arr)        # Variance

ages = [5,31,43,48,50,41,7,11,15,39,80,82,32,2,8,6,25,36,27,61,31]
x = np.percentile(ages, 75)
# What is the 75 percentile? The answer is 43, meaning that 75% of the people are 43 or younger.

# Sets in Numpy (A set in mathematics is a collection of unique elements)
arr = np.array([4, 6, 8, 4, 6])
newarr = np.unique(arr)

arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([3, 4, 5, 6])

newarr = np.union1d(arr1, arr2)                         # Union
newarr = np.intersect1d(arr1, arr2, assume_unique=True) # Intersection
newarr = np.setdiff1d(arr1, arr2, assume_unique=True)   # Difference
newarr = np.setxor1d(arr1, arr2, assume_unique=True)    # Symmetric Difference

# assume_unique : which if set to True can speed up computation. It should always be set to True when dealing with sets


"""
   :copyright: (c) 2020 by Gustavo Landfried
   :license: BSD, see LICENSE for more details.
"""
#import os
#import math
import numpy as np
from numpy.random import normal as rnorm
from matplotlib import pyplot as plt

#__all__ = ["dW","wiener"]

"""
---//---//---//---//---//---//---//---//---//---//---//---//---//---//---//---

                      Geometrical Browninan Motion.

Below you will find the functions to create the geometrical Browninan Motion

---//---//---//---//---//---//---//---//---//---//---//---//---//---//---//---
"""

"""
First of all we need to define the Norbert Wiener process: A real valued
continuous time stochastic process of the one-dimensional Brownian motion.
    - $W_0=0$
    - $W_t$ has Gaussian independent increments centered at $W_{t-1}$
    - $W$ has continuous paths: $W_{t}$ is continuous in $t$.
"""

def dW(pos=0,step=1):
    """
    The Gaussian independent increments of Norbert Wiener process.
    """
    return rnorm(pos,step,1)[0]

def wiener(iterations,step_length):
    """
    Norbert Wiener process:
        A real valued continuous time stochastic process
        of the one-dimensional Brownian motion.
    """
    res = [0]
    for i in range(iterations):
        pos = res[-1]
        res.append(dW(pos,step_length))
    return res

def show_walks(n,iteratons=100,step_length=1):
    """
    Examples
    ---------
    show_walks(10)
    show_walks(10,1000,0.01) # See changes on scale
    """
    for i in range(n):
        plt.plot(wiener(iteratons,step_length))

"""
Now, we need to define a multiplicative process. We first will define the
constant multiplicative process, and then we will perturbed.
"""

def multiplicative_process(n,rate,dt):
    """
    A constant growth rate process

    Parameters
    ----------
    n : int
        Number of time steps
    rate : float
        Constant growth rate
    dt : TYPE
        Delta time between time steps
    """
    wealth = [1]
    time = [0]
    for i in range(n):
        wealth.append(wealth[-1]*rate)
        time.append(time[-1]+dt)
    return time, wealth

def show_mult(n=120,rate=1.01,dt=1/12):
    """
    show_mult(100,1.04,dt=1)
    show_mult(12*12,1.01,dt=1/12)
    """
    x, y = multiplicative_process(n,rate,dt)
    plt.plot(x,np.log10(y))

def gr_mult(rate,dt):
    """
    Constant growth rate in a multiplicative process

    Examples
    --------
    gr_mult(1.125,1)
    gr_mult(1.01,1/12)
    """
    return np.log(rate)/dt

"""
To perturb the process in a consistent way, we remind ourselves
that what's constant about the process in the absence of noise is the
growth rate
"""

def perturbed_payment(rate=1.01,dt=1/12,sigma=0.1):
    """
    perturbed_payment(1.125,1,1)
    """
    gamma = np.log(rate)/dt
    dv = gamma*dt + sigma*dW(0,dt)
    return max(dv,-0.9999)

def walk_perturbed_payment(n,rate,dt,sigma=100,log=False):
    """
    walk_perturbed_payment(100,1.01,1/12,3)
    """
    wealth = [1]
    for _ in range(n):
        dv = perturbed_payment(rate,dt,sigma)
        wealth.append(wealth[-1]+wealth[-1]*dv)
    return wealth

def multiplicative_perturbed_history(n=100,iterations=1000,rate=1.005,dt=1/12,sigma=2):
    res = []
    for i in range(n):
        res.append(walk_perturbed_payment(iterations,rate,dt,sigma))
    return res

def show_walk_perturbed_payment(n=10,iterations=1000,rate=1.005,dt=1/12,sigma=2):
    """
    show_walk_perturbed_payment(n=10,sigma=2)
    plt.show()
    show_walk_perturbed_payment(n=1,sigma=0)
    """
    for i in range(n):
        plt.plot(np.log10(walk_perturbed_payment(iterations,rate,dt,sigma)) )

def gross_domestic_product(wealths):
    return np.sum(wealths)

def gross_domestic_product_per_capita(wealths):
    return gross_domestic_product(wealths)/len(wealths)

def democratic_domestic_product(wealths):
    return np.prod(wealths)

def democratic_domestic_product_per_capita(wealths):
    return democratic_domestic_product(wealths)**(1/len(wealths))

def growth(state_t, state_0,dt=1):
    return (1/dt) * np.log(state_t/state_0)

def second_theil_index(gdp,ddp):
    return np.log(gdp) - np.log(ddp)

def gini(wealths):
    ys = np.array(sorted(wealths))
    denom = 2* sum([ (i+1)*ys[i] for i in range(len(ys))])
    numer = len(ys)* sum([ ys[i] for i in range(len(ys))])
    return denom/numer - (len(ys)+1)/len(ys)

"""
---//---//---//---//---//---//---//---//---//---//---//---//---//---//---//---

                    Other functions (some used in a post)

---//---//---//---//---//---//---//---//---//---//---//---//---//---//---//---
"""

def change_in_utility(w):
    """
    w = np.array(list(map(lambda x: 10.0**x,np.arange(-10,10))) )
    change_in_utility(w)
    """
    return 1/w

def utility(w):
    return np.log(w)

def show_utility(w):
    """
    w = np.arange(-10,10,step=0.1)
    show_utility(w)
    np.log(10**-10)
    """
    plt.plot(w,(utility(w)))
    plt.xticks(fontsize=12) # rotation=90
    plt.yticks(fontsize=12) # rotation=90
    plt.ylabel("Utility", fontsize=16 )
    plt.xlabel("Wealth", fontsize=16 )

"""
The Utility Theory considers that it is preferable to participate in this game
when the expected value is positive. Then, the Utility Theory systematically
observed how people acted contrary to what was considered optimal. Paired with
a firm belief in its models, this has led to a narrative of human irrationality
in large parts of economics.

But if we analyze what really

This is because we are facing a multiplicative process.
When the asymmetry between real physical effects of gains and losses is large, people quite reasonably 'pay to avoid losses' (e.g. buy insurance).
When it is not large, people don't unreasonably mind losses, and won't pay to avoid them.

"""

def simple_gamble(size):
    r = np.random.random()
    if r <= 0.5:
        res = 1.5*size
    else:
        res = 0.6*size
    return res

def walk_simple_gamble(iteratons):
    res = [1]
    for i in range(iteratons):
        res.append(simple_gamble(res[-1]))
    return res

def incest_rule(communities_size,exogamy=0.05):
    res = []
    migration_per_community = exogamy*sum(communities_size)/len(communities_size)
    for c in range(len(communities_size)):
        res.append(communities_size[c]*(1-exogamy) + migration_per_community)
    return res

def init_communities(n_communities):
    communities = []
    for i in range(n_communities):
        communities.append(1.0)
    return communities

def walk_incest(iteratons,n_communities,incest=1):
    communities = init_communities(n_communities)
    history = []
    history.append(communities)
    for i in range(iteratons):
        history.append(list(map(lambda x: simple_gamble(x), incest_rule(history[-1],incest) ) ))
    return history

def incest_rule_free_rider(communities_size,free_rider,exogamy=0.05):
    res = []
    n_free_riders = len(free_rider)
    size_not_free_rider = sum(map(lambda i: 0 if i in free_rider else communities_size[i], range(len(communities_size))))
    migration_per_community = exogamy*size_not_free_rider/len(communities_size)
    for c in range(len(communities_size)):#c=0
        res.append( communities_size[c]*(1-exogamy*int(not c in free_rider)) + migration_per_community)
    return res

def walk_incest_free_rider(iteratons,n_communities,n_free_riders=1,incest=1):
    communities = init_communities(n_communities)
    free_riders = list(range(n_free_riders))
    history = []
    history.append(communities)
    for i in range(iteratons):
        history.append(list(map(lambda x: simple_gamble(x), incest_rule_free_rider(history[-1],free_riders,incest) ) ))
    return history

"""
Share resource, but play you one game.
"""

def walk_sharing(iteratons=1000,n_communities=150):
    communities = init_communities(n_communities)
    history = []
    history.append(communities)
    for i in range(iteratons):
        proportion = list(map(lambda x: x/n_communities, history[-1] ))
        shared = sum(history[-1])
        shared = simple_gamble(shared) # El problema es jugar en grupo. Ley de los grandes n\'umeros.
        history.append(list(map(lambda x: shared*x, proportion)))
    return history


"""
Implementation of K-nearest neighbor (KNN) from scratch
where you can either use 2-loops (inefficient), 1-loop (better)
or a heavily vectorized zero-loop implementation.

Programmed by Aladdin Persson <aladdin.persson at hotmail dot com>
*    2020-04-24 Initial coding
"""

import numpy as np

class KNearestNeighbor:
    def __init__(self, k):
        self.k = k
        self.eps = 1e-8

    def train(self, X, y):
        self.X_train = X
        self.y_train = y

    def predict(self, X_test, num_loops=0):
        if num_loops == 0:
            distances = self.compute_distance_vectorized(X_test)

        elif num_loops == 1:
            distances = self.compute_distance_one_loop(X_test)

        else:
            distances = self.compute_distance_two_loops(X_test)

        return self.predict_labels(distances)

    def compute_distance_two_loops(self, X_test):
        """
        Inefficient naive implementation, use only
        as a way of understanding what kNN is doing
        """

        num_test = X_test.shape[0]
        num_train = self.X_train.shape[0]
        distances = np.zeros((num_test, num_train))

        for i in range(num_test):
            for j in range(num_train):
                # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
                distances[i, j] = np.sqrt(
                    self.eps + np.sum((X_test[i, :] - self.X_train[j, :]) ** 2)
                )

        return distances

    def compute_distance_one_loop(self, X_test):
        """
        Much better than two-loops but not as fast as fully vectorized version.
        Utilize Numpy broadcasting in X_train - X_test[i,:]
        """
        num_test = X_test.shape[0]
        num_train = self.X_train.shape[0]
        distances = np.zeros((num_test, num_train))

        for i in range(num_test):
            # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
            distances[i, :] = np.sqrt(
                self.eps + np.sum((self.X_train - X_test[i, :]) ** 2, axis=1)
            )

        return distances

    def compute_distance_vectorized(self, X_test):
        """
        Can be tricky to understand this, we utilize heavy
        vecotorization as well as numpy broadcasting.
        Idea: if we have two vectors a, b (two examples)
        and for vectors we can compute (a-b)^2 = a^2 - 2a (dot) b + b^2
        expanding on this and doing so for every vector lends to the
        heavy vectorized formula for all examples at the same time.
        """
        X_test_squared = np.sum(X_test ** 2, axis=1, keepdims=True)
        X_train_squared = np.sum(self.X_train ** 2, axis=1, keepdims=True)
        two_X_test_X_train = np.dot(X_test, self.X_train.T)

        # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
        return np.sqrt(
            self.eps + X_test_squared - 2 * two_X_test_X_train + X_train_squared.T
        )

    def predict_labels(self, distances):
        num_test = distances.shape[0]
        y_pred = np.zeros(num_test)

        for i in range(num_test):
            y_indices = np.argsort(distances[i, :])
            k_closest_classes = self.y_train[y_indices[: self.k]].astype(int)
            y_pred[i] = np.argmax(np.bincount(k_closest_classes))

        return y_pred

if __name__ == "__main__":
    X = np.loadtxt("example_data/data.txt", delimiter=",")
    y = np.loadtxt("example_data/targets.txt")

    X = np.array([[1, 1], [3, 1], [1, 4], [2, 4], [3, 3], [5, 1]])
    y = np.array([0, 0, 0, 1, 1, 1])

    KNN = KNearestNeighbor(k=1)
    KNN.train(X, y)
    y_pred = KNN.predict(X, num_loops=0)
    print(f"Accuracy: {sum(y_pred == y) / y.shape[0]}")


'''
[NOTE]:
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
'''

from scipy.spatial import distance

import unittest

class TestDistanceMethods(unittest.TestCase):

    def test_Manhattan_001(self):
        a,b=[[0,0]],[[24, 7]]
        self.assertEqual(distance.cdist(a,b,"cityblock"), 31.)

    def test_Manhattan_002(self):
        a,b=[[24,7]],[[3,3]]
        self.assertEqual(distance.cdist(a,b,"cityblock"), 25.)

    def test_Chessboard_001(self):
        a,b=[[0,0]],[[24, 7]]
        self.assertEqual(distance.cdist(a,b, 'chebyshev'),24.)

    def test_Chessboard_002(self):
        a,b=[[24,7]],[[3,3]]
        self.assertEqual(distance.cdist(a,b,"chebyshev"), 21.)
if __name__ == '__main__':
    unittest.main()


from __future__ import print_function

import numpy as np
from numgrad import eval_numerical_gradient

def tanh_grad(x):
    return 1 - np.tanh(x) ** 2

if __name__ == '__main__':
    x = np.array([1.0, 2.1, 0.3, 0.7])
    print('tanh', np.tanh(x))
    print('tanh_grad', tanh_grad(x))

    # Note: eval_numerical_gradient works for scalar functions. Therefore we'll
    # run it for each element of tanh separately.
    print('Numerical gradient')
    for i in range(x.shape[0]):
        print(i, eval_numerical_gradient(lambda z: np.tanh(z)[i], x))


import unittest
import numpy as np
from LieGA import *

EPSILON = np.eye(3)*10**-5

class LieGATest(unittest.TestCase):
    def test_eul2rotm(self):
        eul = np.array([-30, 15, -15])*np.pi/180.0
        R = SO3.eul2rotm(eul)
        ans = np.array([[ 0.9330127, 0.09914387, 0.34591587,],
                         [-0.25, 0.87000995, 0.42495021],
                         [-0.25881905, -0.48296291, 0.8365163 ]])
        diff = np.abs(R-ans)
        self.assertLess(diff.tolist(), EPSILON.tolist())

        eul = np.array([180, 90, -90]) * np.pi / 180.0
        R = SO3.eul2rotm(eul)
        ans = np.array([[0, -1, 0],
                        [0, 0, 1],
                        [-1, 0, 0]])
        diff = np.abs(R - ans)
        self.assertLess(diff.tolist(), EPSILON.tolist())

    def test_rotm2eul(self):
        R = np.array([[0.9330127, 0.09914387, 0.34591587, ],
                        [-0.25, 0.87000995, 0.42495021],
                        [-0.25881905, -0.48296291, 0.8365163]])
        eul = SO3.rotm2eul(R)

        ans = np.array([-30, 15, -15]) * np.pi / 180.0
        diff = np.abs(eul - ans)
        self.assertLess(diff.tolist(), (np.ones(3)*10**-5).tolist())

        R = np.array([[0, -1, 0],
                        [-1, 0, 0],
                        [0, 0, -1]])
        eul = SO3.rotm2eul(R)
        ans = np.array([180, 0, -90]) * np.pi / 180.0
        diff = np.abs(eul - ans)
        self.assertLess(diff.tolist(), (np.ones(3) * 10 ** -5).tolist())

    def test_get_exp(self):
        w = np.array([10, -20 , 30])*np.pi/180.0
        dR = SO3.get_exp(w)
#        print(dR)


"""Is channel trace-preserving."""
from typing import List, Union

import numpy as np

from toqito.matrix_props import is_identity
from toqito.channels import partial_trace

def is_trace_preserving(
    phi: Union[np.ndarray, List[List[np.ndarray]]],
    rtol: float = 1e-05,
    atol: float = 1e-08,
    sys: Union[int, List[int]] = 2,
    dim: Union[List[int], np.ndarray] = None,
) -> bool:
    r"""
    Determine whether the given channel is trace-preserving [WatTP18]_.

    A map :math:`\Phi \in \text{T} \left(\mathcal{X}, \mathcal{Y} \right)` is
    *trace-preserving* if it holds that

    .. math::
        \text{Tr} \left( \Phi(X) \right) = \text{Tr}\left( X \right)

    for every operator :math:`X \in \text{L}(\mathcal{X})`.

    Given the corresponding Choi matrix of the channel, a neccessary and sufficient condition is

    .. math::
        \text{Tr}_{\mathcal{Y}} \left( J(\Phi) \right) = \mathbb{I}_{\mathcal{X}}

    In case :code:`sys` is not specified, the default convention is that the Choi matrix
    is the result of applying the map to the second subsystem of the standard maximally
    entangled (unnormalized) state.

    The dimensions of the subsystems are given by the vector :code:`dim`. By default,
    both subsystems have equal dimension.

    Alternatively, given a list of Kraus operators, a neccessary and sufficient condition is

    .. math::
        \sum_{a \in \Sigma} A_a^* B_a = \mathbb{I}_{\mathcal{X}}

    Examples
    ==========

    The map :math:`\Phi` defined as

    .. math::
        \Phi(X) = X - U X U^*

    is not trace-preserving, where

    .. math::
        U = \frac{1}{\sqrt{2}}
        \begin{pmatrix}
            1 & 1 \\
            -1 & 1
        \end{pmatrix}.

    >>> import numpy as np
    >>> from toqito.channel_props import is_trace_preserving
    >>> unitary_mat = np.array([[1, 1], [-1, 1]]) / np.sqrt(2)
    >>> kraus_ops = [[np.identity(2), np.identity(2)], [unitary_mat, -unitary_mat]]
    >>> is_trace_preserving(kraus_ops)
    False

    As another example, the depolarizing channel is trace-preserving.

    >>> from toqito.channels import depolarizing
    >>> from toqito.channel_props import is_trace_preserving
    >>> choi_mat = depolarizing(2)
    >>> is_trace_preserving(choi_mat)
    True

    References
    ==========
    .. [WatTP18] Watrous, John.
        "The theory of quantum information."
        Section: "Linear maps of square operators".
        Cambridge University Press, 2018.

    :param phi: The channel provided as either a Choi matrix or a list of Kraus operators.
    :param rtol: The relative tolerance parameter (default 1e-05).
    :param atol: The absolute tolerance parameter (default 1e-08).
    :param sys: Scalar or vector specifying the size of the subsystems.
    :param dim: Dimension of the subsystems. If :code:`None`, all dimensions are assumed to be
                equal.
    :return: True if the channel is trace-preserving, and False otherwise.
    """
    # If the variable `phi` is provided as a list, we assume this is a list
    # of Kraus operators.
    if isinstance(phi, list):
        phi_l = [A for A, _ in phi]
        phi_r = [B for _, B in phi]

        k_l = np.concatenate(phi_l, axis=0)
        k_r = np.concatenate(phi_r, axis=0)

        mat = k_l.conj().T @ k_r
    else:
        mat = partial_trace(input_mat=phi, sys=sys, dim=dim)
    return is_identity(mat, rtol=rtol, atol=atol)


# region Starting Values
import numpy as np
np.set_printoptions(suppress=True)
from math import sin

rounding = 5
N = 5
numberOfUnknown = 12
lengthOfRowForMatrix = numberOfUnknown + 1

differenceBetweenTwoPoints = 2

firstIndex = 1
secondIndex = 3
thirdIndex = 7
lastIndex = 12

Xarray = [3, 5, 7, 9, 11]

# region xValues for Faults

XarrayForFault1 = [3, 3.25, 3.5, 3.75, 4]
XarrayForFault2 = [4.25, 4.5, 4.75, 5, 5.25]
XarrayForFault3 = [5.5, 5.75, 6, 6.25, 6.5]
XarrayForFault4 = [6.75, 7, 7.25, 7.5, 7.75]
XarrayForFault5 = [8, 8.25, 8.5, 8.75, 9]
XarrayForFault6 = [9.25, 9.5, 9.75, 10, 10.25]
XarrayForFault7 = [10.5, 10.75, 11]

# endregion xValues for Faults

# My Sin Function
def MySinFun(x) -> float:
    element = sin(3 / 2 * x) + (x * 3) ** (1 / 3)
    return element

Yarray = [MySinFun(Xarray[0]), MySinFun(Xarray[1]), MySinFun(Xarray[2]), MySinFun(Xarray[3]), MySinFun(Xarray[4])]

# region yValues Faults

YarrayForFault1 = [MySinFun(XarrayForFault1[0]), MySinFun(XarrayForFault1[1]), MySinFun(XarrayForFault1[2]), MySinFun(XarrayForFault1[3]), MySinFun(XarrayForFault1[4])]
YarrayForFault2 = [MySinFun(XarrayForFault2[0]), MySinFun(XarrayForFault2[1]), MySinFun(XarrayForFault2[2]), MySinFun(XarrayForFault2[3]), MySinFun(XarrayForFault2[4])]
YarrayForFault3 = [MySinFun(XarrayForFault3[0]), MySinFun(XarrayForFault3[1]), MySinFun(XarrayForFault3[2]), MySinFun(XarrayForFault3[3]), MySinFun(XarrayForFault3[4])]
YarrayForFault4 = [MySinFun(XarrayForFault4[0]), MySinFun(XarrayForFault4[1]), MySinFun(XarrayForFault4[2]), MySinFun(XarrayForFault4[3]), MySinFun(XarrayForFault4[4])]
YarrayForFault5 = [MySinFun(XarrayForFault5[0]), MySinFun(XarrayForFault5[1]), MySinFun(XarrayForFault5[2]), MySinFun(XarrayForFault5[3]), MySinFun(XarrayForFault5[4])]
YarrayForFault6 = [MySinFun(XarrayForFault6[0]), MySinFun(XarrayForFault6[1]), MySinFun(XarrayForFault6[2]), MySinFun(XarrayForFault6[3]), MySinFun(XarrayForFault6[4])]
YarrayForFault7 = [MySinFun(XarrayForFault7[0]), MySinFun(XarrayForFault7[1]), MySinFun(XarrayForFault7[2])]

# endregion xValues Faults

# endregion Starting Values

# region Prints

# Print matrix
def PrintMatrixAsNp(matrixName,matrix):
    print("\n", matrixName,"=")
    npMatrix = np.array(matrix)
    print(npMatrix.round(rounding))

# Print vector
def PrintVectorAsNp(vectorName, vector):
    print("\n", vectorName,"=")
    npMatrix = np.array(vector)
    print(npMatrix.round(rounding))

# Print Lagrange
def PrintLagrange(Xarray, Yarray):
    PrintVectorAsNp("X", Xarray)
    PrintVectorAsNp("Y", Yarray)
    print("\nLagrange Polynom:")
    firstPart = f"\n{Yarray[0]} * ((x - {Xarray[1]})/({Xarray[0]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[0]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[0]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[0]} - {Xarray[4]})) +"
    secondPart = f"{Yarray[1]} * ((x - {Xarray[0]})/({Xarray[1]} - {Xarray[0]})) * ((x - {Xarray[2]})/({Xarray[1]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[1]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[1]} - {Xarray[4]})) +"
    thirdPart = f"{Yarray[2]} * ((x - {Xarray[0]})/({Xarray[2]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[2]} - {Xarray[1]})) * ((x - {Xarray[3]})/({Xarray[2]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[2]} - {Xarray[4]})) +"
    fourthPart = f"{Yarray[3]} * ((x - {Xarray[0]})/({Xarray[3]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[3]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[3]} - {Xarray[2]})) * ((x - {Xarray[4]})/({Xarray[3]} - {Xarray[4]})) +"
    fifthPart = f"{Yarray[4]} * ((x - {Xarray[0]})/({Xarray[4]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[4]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[4]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[4]} - {Xarray[3]}))"
    print(firstPart, secondPart, thirdPart, fourthPart, fifthPart, "\n")

# endregion Prints

# Implementing Lagrange Interpolation
def Lagrange(X_array, Y_array, pointToShow, show) -> float:
    # Create some prerequisites
    resultAsYpoint = 0
    for k in range(len(Y_array)):
        # Create some prerequisites
        tempPoint1 = 0
        tempPoint2 = 0

        tempPointArray = [tempPoint1, tempPoint2]

        tempPointArray[0] = 1
        tempPointArray[1] = 1

        for m in range(len(X_array)):
            if m == k:
                for z in range(2):
                    tempPointArray[z] = tempPointArray[z] * 1

            else:
                tempPointArray[1] = tempPointArray[1] * (X_array[k] - X_array[m])
                tempPointArray[0] = tempPointArray[0] * (pointToShow - X_array[m])

        resultAsYpoint = resultAsYpoint + Y_array[k] * tempPointArray[0] / tempPointArray[1]

    if show:
        print("Coef of",pointToShow,"element =",resultAsYpoint)

    return resultAsYpoint

def CreateMatrixForCramer(Xarray, Yarray) -> [list, list]:
    # Create some prerequisites
    matrixForCramer = list()
    rightPartForCramer = [0] * (lengthOfRowForMatrix - 1)

    # Call Our functions to create matrix
    FirstPartOfEquation(Xarray, Yarray, matrixForCramer)

    SecondPartOfEquation(Xarray, Yarray, matrixForCramer)

    ThirdPartOfEquation(Xarray, Yarray, matrixForCramer)

    FourthPartOfEquation(Xarray, Yarray, matrixForCramer)

    FifthPartOfEquation(Xarray, Yarray, matrixForCramer)

    # Create Right Part
    for i in range(len(matrixForCramer)):
        rightPartForCramer[i] = matrixForCramer[i][-1]

    # Clean matrix
    matrixForCramer = np.delete(matrixForCramer, np.s_[-1:], axis=1)

    return rightPartForCramer, matrixForCramer

#region PartsOfEquation

def FirstPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray)):
        queue = [0] * lengthOfRowForMatrix

        queue[i-firstIndex] = differenceBetweenTwoPoints; queue[i+secondIndex] = differenceBetweenTwoPoints ** 2
        queue[i+thirdIndex] = differenceBetweenTwoPoints ** 3; queue[lastIndex] = Yarray[i] - Yarray[i - 1]

        matrixForCramer.append(queue)

def SecondPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray) - 1):
        queue = [0] * lengthOfRowForMatrix

        queue[i] = 1; queue[i-firstIndex] = -1; queue[lastIndex] = 0
        queue[i+secondIndex] = -2 * differenceBetweenTwoPoints; queue[i+thirdIndex] = -3 * differenceBetweenTwoPoints ** 2

        matrixForCramer.append(queue)

def ThirdPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray) - 1):
        queue = [0] * lengthOfRowForMatrix

        queue[i+secondIndex+1] = 1; queue[i+secondIndex] = -1; queue[i+thirdIndex] = -3 * differenceBetweenTwoPoints; queue[lastIndex] = 0

        matrixForCramer.append(queue)

def FourthPartOfEquation(Xarray, Yarray, matrixForCramer):
    queue = [0] * lengthOfRowForMatrix

    queue[thirdIndex] = 1; queue[lastIndex-1] = 3 * differenceBetweenTwoPoints; queue[lastIndex] = 0

    matrixForCramer.append(queue)

def FifthPartOfEquation(Xarray, Yarray, matrixForCramer):
    queue = [0] * lengthOfRowForMatrix

    queue[secondIndex+1] = 1; queue[lastIndex] = 0

    matrixForCramer.append(queue)

#endregion PartsOfEquation

# Do Cramer Method
def Cramer(matrixForComputations, matrixForCramer, rightPartForCramer) -> list:
    # Create some prerequisites
    SIPcoeffs = list()

    for k in range(0, len(rightPartForCramer)):
        for h in range(0, len(rightPartForCramer)):
            # Get values for right part
            matrixForComputations[h][k] = rightPartForCramer[h]

            if k > 0:
                matrixForComputations[h][k - 1] = matrixForCramer[h][k - 1]

        # Add value to the vector
        SIPcoeffs.append(np.linalg.det(matrixForComputations) / np.linalg.det(matrixForCramer))

        # Rounding values
        SIPcoeffs[k] = round(SIPcoeffs[k], rounding)

    return SIPcoeffs

# Printing Lagrange Equation
PrintLagrange(Xarray, Yarray)

# region Faults
for i in range(N):
    Lagrange(Xarray, Yarray, Xarray[i], True)
    print("Fault of element", Xarray[i], "=", abs(MySinFun(Xarray[i]) - Lagrange(Xarray, Yarray, Xarray[i], False)))

print("\n")

for i in range(N):
    Lagrange(XarrayForFault1, YarrayForFault1, XarrayForFault1[i], True)
    print("Fault of element", XarrayForFault1[i], "=", abs(MySinFun(XarrayForFault1[i]) - Lagrange(XarrayForFault1, YarrayForFault1, XarrayForFault1[i], False)))

for i in range(N):
    Lagrange(XarrayForFault2, YarrayForFault2, XarrayForFault2[i], True)
    print("Fault of element", XarrayForFault2[i], "=", abs(MySinFun(XarrayForFault2[i]) - Lagrange(XarrayForFault2, YarrayForFault2, XarrayForFault2[i], False)))

for i in range(N):
    Lagrange(XarrayForFault3, YarrayForFault3, XarrayForFault3[i], True)
    print("Fault of element", XarrayForFault3[i], "=", abs(MySinFun(XarrayForFault3[i]) - Lagrange(XarrayForFault3, YarrayForFault3, XarrayForFault3[i], False)))

for i in range(N):
    Lagrange(XarrayForFault4, YarrayForFault4, XarrayForFault4[i], True)
    print("Fault of element", XarrayForFault4[i], "=", abs(MySinFun(XarrayForFault4[i]) - Lagrange(XarrayForFault4, YarrayForFault4, XarrayForFault4[i], False)))

for i in range(N):
    Lagrange(XarrayForFault5, YarrayForFault5, XarrayForFault5[i], True)
    print("Fault of element", XarrayForFault5[i], "=", abs(MySinFun(XarrayForFault5[i]) - Lagrange(XarrayForFault5, YarrayForFault5, XarrayForFault5[i], False)))

for i in range(N):
    Lagrange(XarrayForFault6, YarrayForFault6, XarrayForFault6[i], True)
    print("Fault of element", XarrayForFault6[i], "=", abs(MySinFun(XarrayForFault6[i]) - Lagrange(XarrayForFault6, YarrayForFault6, XarrayForFault6[i], False)))

for i in range(len(XarrayForFault7)):
    Lagrange(XarrayForFault7, YarrayForFault7, XarrayForFault7[i], True)
    print("Fault of element", XarrayForFault7[i], "=", abs(MySinFun(XarrayForFault7[i]) - Lagrange(XarrayForFault7, YarrayForFault7, XarrayForFault7[i], False)))

# endregion Faults

rightPartForCramer, matrixForCramer = CreateMatrixForCramer(Xarray.copy(), Yarray.copy())
matrixForComputations = list(map(list, matrixForCramer))

# Coefficients of spline interpolation polynomials
SIPcoeffs = Cramer(matrixForComputations, matrixForCramer, rightPartForCramer)
PrintVectorAsNp("Coefficients of spline interpolation polynomial", SIPcoeffs)


from scipy.optimize import minimize
import numpy as np
import matplotlib.pyplot as plt

# Jacobian
def jacobian(x):
    dx = 2 * x[0]
    dy = 2 * x[1]
    return np.array((dx, dy))

# Objective function formalization
def obj_fun():
    return lambda x: x[0] ** 2 + x[1] ** 2

# Objective function for plot
def plot_obj_fun(x):
    return x[0] ** 2 + x[1] ** 2

# Minimize objective function
def min_func(x):
    return minimize(obj_fun(), x, method='SLSQP', bounds=bounds, constraints=cons, options={'ftol': 1e-9,'disp':True})

# Minimize objective function with gradient
def min_func_jacobian(x):
    return minimize(obj_fun(), x, method='SLSQP', bounds=bounds, constraints=cons, jac=jacobian, options={'ftol': 1e-9,'disp':True})

# Constraint functions (inequalities have to be >= 0)
cons = ({'type': 'ineq', 'fun': lambda x: -0.5 + x[0]},
        {'type': 'ineq', 'fun': lambda x: x[0] + x[1] - 1},
        {'type': 'ineq', 'fun': lambda x: x[0]**2 + x[1]**2 - 1},
        {'type': 'ineq', 'fun': lambda x: 9*x[0]**2 + x[1]**2 - 9},
        {'type': 'ineq', 'fun': lambda x: x[0]**2 - x[1]},
        {'type': 'ineq', 'fun': lambda x: x[1]**2 - x[0]})

# Bounds, if any, e.g. x1 and x2 have to be positive
bounds = ((None, None),) * 2

# Initial guess
x0s = [np.asarray((-3, -5)), np.asarray((1, 1)), np.asarray((10, -10))]

# Save minimal points and variables to plot
minimals = []    # minimals[i][0] = x, minimals[i][1] = y, minimals[i][2] = z
for x0 in x0s:
    # Method SLSQP uses Sequential Least SQuires Programming to minimize a function
    # of several variables with any combination of bounds, equality and inequality constraints.
    res = min_func(x0)
    print(res)
    minimals.append((res.x[0], res.x[1], res.fun))

# define range for input
r_min, r_max = -5.0, 5.0
# sample input range uniformly at 0.7 increments
xaxis = np.arange(r_min, r_max, 0.2)
yaxis = np.arange(r_min, r_max, 0.2)
# create a mesh from the axis
x0, x1 = np.meshgrid(xaxis, yaxis)
# compute targets
results = np.array(plot_obj_fun((x0, x1)))
# create a surface plot with the jet color scheme
figure = plt.figure()
axis = figure.gca(projection='3d')
label_1 = "global minimum [" + str(minimals[1][0]) + ", " + str(minimals[1][1]) + ", " + str(minimals[1][2]) + "]"
axis.scatter(minimals[1][0], minimals[1][1], minimals[1][2], color='red', edgecolor='black', label=label_1)
plt.legend(prop={'size': 6})
axis.plot_surface(x0, x1, results, cmap='jet')
# show the plot
plt.show()


import numpy as np
import pickle
import cvxopt
from cvxopt import matrix, solvers

import sklearn
from sklearn.metrics import classification_report, confusion_matrix , accuracy_score, mean_squared_error
from sklearn.model_selection import train_test_split

from python.svmutil import *

import time
import sys
import warnings

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

path_train = sys.argv[1];
path_test = sys.argv[2];
binary = int(sys.argv[3]);
part = sys.argv[4];

train_array = np.genfromtxt(path_train,delimiter=',');
train_array_x = train_array[:,0:784]/255;
train_array_y = train_array[:,784].reshape(train_array.shape[0],1);

test_array = np.genfromtxt(path_test,delimiter=',');
test_array_x = test_array[:,0:784]/255;
test_array_y = test_array[:,784].reshape(test_array.shape[0],1);

def linear_kernel(X,y):
	M = y * X;
	return np.dot(M, M.T);

def gaussian_kernel(X, sigma=0.05):
	m = X.shape[0];
	X2 = np.sum(np.multiply(X, X),axis=1, keepdims=True);
	K0 = X2 + X2.T - 2 * np.matmul(X, X.T)
	return np.power(np.exp(-sigma),K0);

def gaussian_kernel_elem(X1,X2, sigma = 0.05):
	return np.exp(-sigma * np.linalg.norm(X1-X2)**2)

class SVM(object):
	def __init__(self, kernel=linear_kernel, C=1.0):
		self.kernel = kernel;
		self.C = C;

	def fit(self, X, y):
		m,n = X.shape;
		if(self.kernel == linear_kernel):
			P = matrix(self.kernel(X,y));
		else:
			K = gaussian_kernel(X);
			P = matrix(y * y.T * K);
		q = matrix(np.ones(m) * -1.0);
		A = matrix(y,(1,m));
		b = matrix(0.0);

		G1 = np.eye(m) * -1.0;
		G2 = np.eye(m);
		G = matrix(np.vstack((G1, G2)));
		h1 = np.zeros(m);
		h2 = np.ones(m) * self.C;
		h = matrix(np.hstack((h1, h2)));
		solvers.options['show_progress'] = False
		solution = solvers.qp(P, q, G, h, A, b);

		alpha = np.ravel((solution['x'])).reshape(m,1);
		self.supp_vec_flag = (alpha > 1e-5).ravel();
		self.indices = np.arange(len(alpha))[self.supp_vec_flag];
		self.alpha = alpha[self.supp_vec_flag];
		self.supp_vec = X[self.supp_vec_flag];
		self.supp_vec_y = y[self.supp_vec_flag];

		if(self.kernel==linear_kernel):
			self.w = np.sum(self.alpha * self.supp_vec * self.supp_vec_y, axis = 0, keepdims=True).T;
			b1 = np.min(np.matmul(X[(y == 1).ravel()], self.w));
			b2 = np.max(np.matmul(X[(y == -1).ravel()], self.w));
			self.b = -(b1+b2)/2;
		else:
			self.w = None;
			self.b = 0.0;

	def predict(self, X):
		if (self.kernel == linear_kernel):
			return np.sign(np.dot(X, self.w) + self.b);
		else:
			m = X.shape[0];
			pred = np.zeros(m);
			for i in range(m):
				temp = 0;
				s=0;
				#if(i%100==0):
					#print(i)
				for alpha, supp_vec, supp_vec_y in zip(self.alpha, self.supp_vec, self.supp_vec_y):
					s += alpha * supp_vec_y * gaussian_kernel_elem(X[i],supp_vec);
				pred[i] = s + self.b;
			return np.sign(pred);

if(binary == 0):
	X_train_bin = train_array_x[(train_array_y==3).ravel() | (train_array_y==4).ravel()];
	Y_train_bin = train_array_y[(train_array_y==3).ravel() | (train_array_y==4).ravel()];
	Y_train_bin = -1.0 * (Y_train_bin==3) + 1.0 * (Y_train_bin==4);

	X_test_bin = test_array_x[(test_array_y==3).ravel() | (test_array_y==4).ravel()];
	Y_test_bin = test_array_y[(test_array_y==3).ravel() | (test_array_y==4).ravel()];
	Y_test_bin = -1.0 * (Y_test_bin==3) + 1.0 * (Y_test_bin==4);

	if(part == 'a'):
		clf = SVM();
		clf.fit(X_train_bin, Y_train_bin);
		preds = clf.predict(X_test_bin);
		print("Linear Score: ", round(accuracy_score(preds,Y_test_bin),5))

	if(part == 'b'):
		clf = SVM(kernel=gaussian_kernel);
		clf.fit(X_train_bin, Y_train_bin);
		preds = clf.predict(X_test_bin);
		print("RBF Score: ", round(accuracy_score(preds,Y_test_bin),5))

	if(part == 'c'):
		model = svm_train(Y_train_bin.ravel(),X_train_bin, '-s 0 -t 0 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(Y_test_bin.ravel(),X_test_bin,model, '-q');
		print("Linear Score: ", round(accuracy_score(label_predict,Y_test_bin),5))

		model = svm_train(Y_train_bin.ravel(),X_train_bin, '-s 0 -t 2 -g 0.05 -q');
		label_predict_g, accuracy_g, decision_values_g=svm_predict(Y_test_bin.ravel(),X_test_bin,model,'-q');
		print("RBF Score: ", round(accuracy_score(label_predict_g,Y_test_bin),5))

else:
	if(part == 'a'):
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				globals()["Xtrain_" + suffix] = train_array_x[(train_array_y==i).ravel() | (train_array_y==j).ravel()];
				temp = train_array_y[(train_array_y==i).ravel() | (train_array_y==j).ravel()];
				globals()["Ytrain_" + suffix] = -1.0 * (temp==i) + 1.0 * (temp==j);
				globals()["clf_" + suffix] = SVM(kernel=gaussian_kernel);
				globals()["clf_" + suffix].fit(globals()["Xtrain_" + suffix], globals()["Ytrain_" + suffix]);

		preds_train = np.zeros((train_array_y.shape[0],10,10))
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				temp = globals()["clf_" + suffix].predict(train_array_x);
				preds_train[:,i,j] = (temp-1)/(-2);
				preds_train[:,j,i] = (temp+1)/2;

		preds_train = np.abs(preds_train)
		preds_train = np.argmin(np.sum(preds_train, axis=1), axis = 1)
		print("Training Accuracy: ", accuracy_score(preds_train,train_array_y))

		preds_test = np.zeros((test_array_y.shape[0],10,10))
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				temp = globals()["clf_" + suffix].predict(test_array_x);
				preds_test[:,i,j] = (temp-1)/(-2);
				preds_test[:,j,i] = (temp+1)/2;

		preds_test = np.abs(preds_test)
		preds_test = np.argmin(np.sum(preds_test, axis=1), axis = 1)
		print("Test Accuracy: ", accuracy_score(preds_test,test_array_y))

	if(part=='b'):
		model = svm_train(train_array_y.ravel(),train_array_x, '-s 0 -t 2 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(train_array_y.ravel(),train_array_x,model,'-q');
		print("Train Accuracy :", accuracy_score(label_predict, train_array_y));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Accuracy :", accuracy_score(label_predict, test_array_y));

	if(part =='c'):
		model = svm_train(train_array_y.ravel(),train_array_x, '-s 0 -t 2 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');

		conf_mat = np.zeros((10,10));
		for i in range(test_array_y.shape[0]):
			conf_mat[int(test_array_y[i])][int(label_predict[i])] = conf_mat[int(test_array_y[i])][int(label_predict[i])] + 1;
		print(conf_mat.astype(int));

	if(part == 'd'):
		x_train, x_dev,y_train, y_dev = train_test_split(train_array_x, train_array_y, test_size = 0.1);
		x_train = train_array_x;
		y_train = train_array_y;
		print("C = 1e-5 (Accuracies - Dev & Test");
		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1e-5 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 1e-3 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1e-3 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 1 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 5 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 5 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 10 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 10 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));


import numpy as np
import sys

def compute_hypothesis_for_a_line(theta0, theta1, dataset):
    e = 0
    length = len(dataset)
    for el in dataset:
        e += (1/length) * pow(el[1] - (theta0 * el[0] + theta1), 2)

    return e

def comp(dataset, current_thetha_zero, current_thetha_one, learning_rate):
    theta_zero = 0
    theta_one = 0
    length = len(dataset)
    for el in dataset:
        x = el[0]
        y = el[1]
        theta_zero += -(2/length) * (y - (current_thetha_one*x + current_thetha_zero))
        theta_one += -(2/length) * x * (y - (current_thetha_one * x + current_thetha_zero))
    new_theta_zero = current_thetha_zero - (learning_rate * theta_zero)
    new_theta_one = current_thetha_one - (learning_rate * theta_one)

    return [new_theta_zero, new_theta_one]

def run_gradient_descent(dataset, theta0, theta1, learning_rate, num_interations):
    temp_theta_zero = theta0
    temp_theta_one = theta1
    for i in range(num_interations):
        temp_theta_zero, temp_theta_one = comp(
            np.array(dataset), temp_theta_zero, temp_theta_one, learning_rate)

    return [temp_theta_zero, temp_theta_one]

def runScript():
    dataset = np.genfromtxt('dataset.csv', delimiter=',')
    learning_rate = 0.000009999
    starting_m = 0
    starting_b = 0
    num_interations = 1000
    print('Starting gradient descent x={0} y={1} e={2}'.format(starting_m, starting_b, compute_hypothesis_for_a_line(starting_m, starting_b, dataset)))
    [theta1, theta0] = run_gradient_descent(dataset, starting_b, starting_m, learning_rate, num_interations)
    print('Result {0} {1}'.format(theta0, theta1))
    print('Ending gradient descent x={0} y={1} e={2}'.format(theta0, theta1, compute_hypothesis_for_a_line(theta0, theta1, dataset)))

runScript()


#El promedio histórico del salario de los egresados de una universidad
#es salario_historico, la desviación estándar es
#de std_salario_historico.

#Calcule con un método Monte Carlo el salario promedio
#salario_promedio que una muestra aleatoria de  n_egresados debe tener
#para considerar que cualquier otro grupo de n_egresados tiene una
#probabilidad p_excede de tener un salario promedio que excede
#salario_promedio.

#Esta probabilidad se debe calcular con una función llamada
#probabilidad_excede. Las variables de entrada de las función son (en ese orden):
#salario_historico, std_salario_historico, n_egresados, p_excede, donde
#todas las variables de entrada son de tipo entero, excepto p_excede
#que es de tipo float. Suponga que el valor de p_excede es un múltiplo
#entero de 0.001. La función solamente debe devolver el valor pedido
#del salario (un número de tipo entero).

#La solución debe estar en un archivo llamado
#"ApellidoNombre_Ejercicio14.py" donde Apellido y Nombre debe
#reemplazarlos con su apellido y nombre.  Suba ese archivo como
#respuesta a esta actividad. Al ejecutar "python
#ApellidoNombre_Ejercicio14.py" o al llamar la función no se debe
#producir ningún error ni escribir nada en la terminal.  Calificaremos
#la función con valores de n_egresados hasta 10000. Se considera que la
#función no corre si cada llamada de la función se demora más de 10
#segundos en correr.

#Solamente puede utilizar las funciones y métodos vistos en clase
#(videos o clases sincrónicas, o que ya se encuentren en el
# repositorio).

import numpy as np

def probabilidad_excede(salario_historico, std_salario_historico, n_egresados, p_excede):
    n_sample = 10000000
    x = np.random.normal(loc=salario_historico,
                         scale=std_salario_historico/np.sqrt(n_egresados),
                         size=n_sample)
    x = np.sort(x)
    salario_promedio = int(x[n_sample - int(p_excede*n_sample)])
    return salario_promedio

#a = probabilidad_excede(50000, 1000, 4, 0.158)


'''
Generic file to set up the decision problem (i.e., optimization problem) under consideration
Must have functions:
  get_num_decisions(): returns number of decision variables (i.e., dimension of cost vector)
  find_opt_decision(): returns for a matrix of cost vectors the corresponding optimal decisions for those cost vectors

This particular file sets up a shortest path decision problem over a 4 x 4 grid network, where driver starts in
southwest corner and tries to find shortest path to northeast corner.
'''

from gurobipy import *
import numpy as np

dim = 4 #(creates dim * dim grid, where dim = number of vertices)
Edge_list = [(i,i+1) for i in range(1, dim**2 + 1) if i % dim != 0]
Edge_list += [(i, i + dim) for i in range(1, dim**2 + 1) if i <= dim**2 - dim]
Edge_dict = {} #(assigns each edge to a unique integer from 0 to number-of-edges)
for index, edge in enumerate(Edge_list):
    Edge_dict[edge] = index
D = len(Edge_list) # D = number of decisions

def get_num_decisions():
  return D

Edges = tuplelist(Edge_list)
# Find the optimal total cost for an observation in the context of shortes path
m_shortest_path = Model('shortest_path')
m_shortest_path.Params.OutputFlag = 0
flow = m_shortest_path.addVars(Edges, ub = 1, name = 'flow')
m_shortest_path.addConstrs((quicksum(flow[i,j] for i,j in Edges.select(i,'*')) - quicksum(flow[k, i] for k,i in Edges.select('*',
  i)) == 0 for i in range(2, dim**2)), name = 'inner_nodes')
m_shortest_path.addConstr((quicksum(flow[i,j] for i,j in Edges.select(1, '*')) == 1), name = 'start_node')
m_shortest_path.addConstr((quicksum(flow[i,j] for i,j in Edges.select('*', dim**2)) == 1), name = 'end_node')

def shortest_path(cost):
    # m_shortest_path.setObjective(quicksum(flow[i,j] * cost[Edge_dict[(i,j)]] for i,j in Edges), GRB.MINIMIZE)
    m_shortest_path.setObjective(LinExpr( [ (cost[Edge_dict[(i,j)]],flow[i,j] ) for i,j in Edges]), GRB.MINIMIZE)
    m_shortest_path.optimize()
    return {'weights': m_shortest_path.getAttr('x', flow), 'objective': m_shortest_path.objVal}

def find_opt_decision(cost):
    weights = np.zeros(cost.shape)
    objective = np.zeros(cost.shape[0])
    for i in range(cost.shape[0]):
        temp = shortest_path(cost[i,:])
        for edge in Edges:
            weights[i, Edge_dict[edge]] = temp['weights'][edge]
        objective[i] = temp['objective']
    return {'weights': weights, 'objective':objective}


import matplotlib
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
from sympy import *
from sympy.diffgeom import *
import matplotlib.pyplot as plt
import numpy as np

from common1 import plot_latex

R = 10
GRID = 10

def draw_shape():
    fig: Figure = plt.figure()  # figsize=(200, 200)
    ax: Axes3D = fig.add_subplot(111, projection='3d')
    # 经度
    u = np.linspace(0, 2 * np.pi, GRID * 2 + 1)
    # 纬度
    v = np.linspace(0, np.pi, GRID)

    x = R * np.outer(np.cos(u), np.sin(v))
    y = R * np.outer(np.sin(u), np.sin(v))
    z = R * np.outer(np.ones(np.size(u)), np.cos(v))
    ax.plot_wireframe(x, y, z, color='lightblue')
    return ax

def coords():
    rho = R
    u, v = symbols('u, v')
    x, y, z = symbols('x, y, z')
    m = Manifold('M', 3)
    # 定义一个patch
    patch = Patch('P', m)
    # 为这个patch建立坐标系，一个是笛卡尔，一个是极坐标
    rect: CoordSystem = CoordSystem('rect', patch)
    polar: CoordSystem = CoordSystem('spherical', patch)
    polar.connect_to(rect,
                     [u, v],
                     [rho * sin(u) * cos(v), rho * sin(u) * sin(v), rho * cos(u)])

    # jacobian
    j = polar.jacobian(rect, [u, v])
    # print("jocobian=", j)
    print("jacobian latex=", latex(j))
    # plot_latex(latex(j))
    m = j.T @ j
    # print("metrics tensor-----------------------", m)
    # pprint(m)
    print("metrics tensor latex=", latex(m))
    # plot_latex((latex(j)))
    return polar, rect

def plot_uv(polar, u, v, color="green"):
    pt = polar.coord_tuple_transform_to(rect, [u, v])
    pt1: Point3D = Point3D(pt)
    print(pt1)
    # line = Line3D(Point3D(0, 0, 0), Point3D(5, 5, 5))
    # ax.add_line(line)
    ax.plot([0, pt1.x], [0, pt1.y], [0, pt1.z], color=color)
    print(pt)

ax = draw_shape()
polar, rect = coords()

# 纵向的两个轴，给定的坐标其实是球面的，只是绘制从球心开始绘制而已。
# u是纬度值， v是经度。
# 当纬度是0或者是pi, 经度任取。分别是南极和北极
plot_uv(polar, 0, 0, color='r')
plot_uv(polar, pi, 0, color='black')

# 横向的四个球轴，位于赤道
plot_uv(polar, np.pi / 2, 0, color='r')
plot_uv(polar, np.pi / 2, pi / 2, color="yellow")
plot_uv(polar, np.pi / 2, pi, color="green")
plot_uv(polar, np.pi / 2, 3 * pi / 2, color="blue")

# 在上半球找一个点
plot_uv(polar, np.pi / 4, pi, color="blue")

"""接下来就是利用christoffel symbol计算测地线了"""
# 怎样才能在3d网格上绘制活动标架呢？并最终绘制测地线（两个点之间的大圆）
# 选取球面上的任意一点，计算这一点的变换矩阵，并绘制局部笛卡尔坐标系
# 如果一个曲面是不规则的，那要如何计算雅可比矩阵，Christoffel Symbol呢？

plt.show()


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

e = math.e # e = 2.718281828459045
pi = math.pi # pi = 3.141592653589793

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

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

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

def cosec(x):
    return 1/sin(x)

def sec(x):
    return 1/cos(x)

def cot(x):
    return 1/tan(x)

def factorial(x):
    '''
    Factorial/Repeted Multiplication of number x.
    factorial(3) # 3*2*1
    >>> 6
    '''
    return np.math.factorial(x)

def sqrt(x):
    '''
    Square Root of a number x.
    sqrt(49)
    >>> 7.0
    '''
    return round(x**(0.5), 2)

def cbrt(x):
    '''
    Cube Root of a Number x.
    print(cbrt(125))
    >>> 5.0
    '''
    return round(x**(1./3.), 2)

def ln(x):
    return np.log(x)

def log(x, base=10):
    '''
    Find Logarithm of a number x.
    log(100)
    >>> 2.0

    log(64, 2)
    >>> 6.0
    '''
    return ln(x)/ln(base)

def d(x, dx, func):
    '''
    d(x, dx, func)
    x -> Range/Domain of the Function
    dx -> How small each successive part is
    func -> It is the function to be differenciated.
    '''
    dx = float(dx)
    eq = f"({func.replace('x', f'(x + {dx})') + f' - {func}'})/dx"
    # print(eq)
    return eval(eq)

class Grapher:
    def __init__(self, fx, lb=-10, ub=10, y_lim=[None, None], step=0.01, label=True, grid=False, xlabel='x', title=None, lol="upper left", linestyle='-', mode='light', style=None):
        self.fx = fx
        self.lb = float(lb)
        self.ub = float(ub)
        self.y_lim = y_lim
        self.step = float(step)
        self.grid = grid
        self.label = label
        self.x = np.arange(self.lb, self.ub, step=self.step)
        self.xlabel = xlabel
        self.ylabel = f'f ({xlabel})'
        self.title = title
        self.lol = lol
        self.linestyle = linestyle
        self.mode = mode
        self.style = style

    def valid_function(self, f):
        '''
        Converts a human readable str(equation) to a more executable form.
        sinx -> sin(x)
        2^4 -> 2**4
        '''
        eq = f.replace('^', '**')
        if 'x' not in eq and len(eq) > 0:
            eq += '* x/x'

        eq = eq.replace('sinx', 'sin(x)')
        eq = eq.replace('cosx', 'cos(x)')
        eq = eq.replace('tanx', 'tan(x)')
        eq = eq.replace('cosecx', 'cosec(x)')
        eq = eq.replace('secx', 'sec(x)')
        eq = eq.replace('cotx', 'cot(x)')
        eq = eq.replace('lnx', 'ln(x)')
        eq = eq.replace('logx', 'log(x)')
        eq = eq.replace('d(', f'd(x, {self.step}, ')

        return eq

    def plot(self):
        '''
        Plot the graphs of many equations on one graph to see the relations between the graphs.
        You can use it to see how well Taylor Series match with the actuall graph of a function.

        Eg:
        g = Grapher(['sin(x)', 'x - ((x**3)/factorial(3)) + (x**5)/factorial(5)'], lb=-3, ub=3, grid=True)
        g.plot()
        '''
        if self.mode == 'dark':
            plt.style.use('dark_background')

        if self.style is not None:
            plt.style.use(self.style)

        x = self.x
        for f in self.fx:
            eq = self.valid_function(f)
            y = eval(eq)
            plt.plot(x, y, self.linestyle, label=f'{self.ylabel} = {str(f)}')
        if self.label:
            plt.legend(loc=self.lol)

        if self.grid:
            plt.grid()

        plt.xlabel(self.xlabel)
        plt.ylabel(self.ylabel)
        if self.title is not None:
            plt.title(self.title)

        plt.axis((self.lb, self.ub, self.y_lim[0], self.y_lim[1]))
        plt.show()

    def examples(self):
        '''
        See some graphing examples.
        '''
        g = Grapher(['cos(x)', '1 - ((x^2)/factorial(2)) + (x^4)/factorial(4)'], lb=-3, ub=3, label=True, lol='lower center', title='Taylor Polynomial for cos(x)')
        g.plot()
        g = Grapher(['sinx', 'd("sinx")'], step=0.001, mode='dark')
        g.plot()

    def author(self):
        '''
        Well, wanna know about me? Run this method!
        '''
        plt.text(-9, 3, """Hi, I am Shubham. Hope you are enjoying Grapher.
Try plotting something crazy!
How About x^x or sinx and its derivative.
And now, you need not even calculate it.
Just type d("x^x") or d("sinx").
Well, close this window to see them.""")
        plt.plot(np.arange(-10, 10), np.arange(-10, 10))
        plt.show()
        g = Grapher(['x^x', 'd("x^x")'], 0, 1.5)
        g.plot()
        g = Grapher(['sinx', 'd("sinx")'], mode='dark')
        g.plot()

if __name__ == '__main__':
    # g = Grapher(['cos(x)', '1 - ((x^2)/factorial(2)) + (x^4)/factorial(4)'], lb=-3, ub=3, label=True, lol='lower center', title='Taylor Polynomial for cos(x)')
    g = Grapher(['1 + 1/x', 'x'], step=0.001, y_lim=[-5, 5])
    g.plot()


#
import sklearn.metrics as skm
import numpy as np

# binary problem

# true labels
y_true = np.array([0, 0, 1, 0, 0, 1, 0, 0, 1, 1], dtype=float)

# classificator predict scores for each object
y_scores = np.array([0.01, 0.12, 0.89, .99, .05, .76, .14, .87, .44, .32])

# casts scores to labels,
y_pred = y_scores > 0.5

# compute confusion_matrix
cm = skm.confusion_matrix(y_true, y_pred)
print(cm)

# compute classification
print(skm.classification_report(y_true, y_pred))

report = skm.classification_report(y_true, y_pred, output_dict=True)
print(report)

#%%  multiclass problem
y_true = [2, 0, 2, 2, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 2, 2, 0, 2, 1, 0, 2, 2]

cm = skm.confusion_matrix(y_true, y_pred)
print(cm)

labels = ["A", "B", "C"]
print(skm.classification_report(y_true, y_pred, target_names=labels))
report = skm.classification_report(y_true, y_pred, output_dict=True)
print(report)

# multilabel problems

# 2 label binary problem
y_true = np.array([
    [0, 0], [1, 1], [0, 0], [1, 1]
])
y_pred = np.array([
    [0, 0], [1, 1], [0, 1], [0, 1]
])

cm = skm.multilabel_confusion_matrix(y_true, y_pred)
print(cm)
print(skm.classification_report(y_true, y_pred,
                                target_names=['label0==1', 'label1==1']))

# 3 label binary problem
y_true = np.array([
    [0, 0, 0], [1, 1, 1], [0, 0, 0], [1, 1, 1]
])
y_pred = np.array([
    [0, 0, 0], [1, 1, 1], [0, 1, 0], [0, 1, 1]
])

cm = skm.multilabel_confusion_matrix(y_true, y_pred)
print(cm)
print(skm.classification_report(y_true, y_pred,
                                target_names=['label1==1', 'label2==1', 'label3==1']))

# 2 label multiclass problem
# !!!!! multi label multi output not supported
# y_true = np.array([
#                     [0,0], [1,1], [2,2], [1,1]
#                   ])
# y_pred = np.array([
#                     [0,0], [1,1], [2,2], [0,1]
#                   ])
# cm = skm.multilabel_confusion_matrix(y_true, y_pred)
# print(cm)
# print( skm.classification_report(y_true,y_pred, target_names=['label1==1', 'label2==1'] ))

# %%


import numpy as np

print(np.__version__)
# 1.17.3

a_bool = np.array([True, True, False, False])
b_bool = np.array([True, False, True, False])

print(a_bool.dtype)
# bool

print(b_bool.dtype)
# bool

print(a_bool & b_bool)
# [ True False False False]

print(a_bool | b_bool)
# [ True  True  True False]

print(a_bool ^ b_bool)
# [False  True  True False]

print(~a_bool)
# [False False  True  True]

print(type(a_bool & b_bool))
# <class 'numpy.ndarray'>

print((a_bool & b_bool).dtype)
# bool

# print(a_bool and b_bool)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print(np.logical_and(a_bool, b_bool))
# [ True False False False]

print(np.logical_or(a_bool, b_bool))
# [ True  True  True False]

print(np.logical_xor(a_bool, b_bool))
# [False  True  True False]

print(np.logical_not(a_bool))
# [False False  True  True]

c_int = np.arange(4)
print(c_int)
# [0 1 2 3]

print(np.logical_not(c_int))
# [ True False False False]

d_int = c_int + 4
print(d_int)
# [4 5 6 7]

print(np.logical_not(d_int))
# [False False False False]

print(np.logical_and(c_int, d_int))
# [False  True  True  True]

print(c_int & d_int)
# [0 1 2 3]

a_bool_2d = np.array([[True, True, False, False], [False, False, True, True]])
print(a_bool_2d)
# [[ True  True False False]
#  [False False  True  True]]

print(a_bool_2d & b_bool)
# [[ True False False False]
#  [False False  True False]]

print(np.logical_and(a_bool_2d, a_bool))
# [[ True  True False False]
#  [False False False False]]

print(a_bool & True)
# [ True  True False False]

print(np.logical_and(a_bool, True))
# [ True  True False False]

print(c_int)
# [0 1 2 3]

print(c_int < 2)
# [ True  True False False]

print(c_int % 2 == 0)
# [ True False  True False]

print((c_int < 2) & (c_int % 2 == 0))
# [ True False False False]

# print(c_int < 2 & c_int % 2 == 0)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

# print(c_int < (2 & (c_int % 2)) == 0)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print(np.logical_and(c_int < 2, c_int % 2 == 0))
# [ True False False False]


import numpy as np
from sympy import zeros, eye, simplify, sqrt
from sympy import Matrix as mat

from ..genericas import print_verbose, norma, matriz_inversa
from .funcs_gram_schmidt import gram_schmidt

def householder(x, y, signo="+", normalizar=False):
    """Aplica la transformación de Householder. x e y pueden ser vectores fila o columna,
    en cuyo caso aplicamos la transformación de una forma u otra.

    Args:
        x (vector): vector origen
        y (vector): vector destino
        signo (str, optional): + o -, si - aplica la transformación inversa. Defaults to "+".
        normalizar (bool, optional): Normaliza los vectores para la transformación. Defaults to False.

    Returns:
        dict: {'H': la aplicación de transformación, 'e': el vector de eje de cambio.}
    """
    if signo == "+":
        mult = 1
    else:
        mult = -1

    if normalizar:
        x = simplify(x / norma(x))
        y = simplify(y / norma(y))

    if simplify(norma(x)) != simplify(norma(y)):
        print(
            f"AVISO!!! x tiene norma {norma(x)} e y tiene norma {norma(y)}. "
            "Para Householder las normas tienen que ser iguales."
        )

    if simplify(norma(x - y)) == 0:  # x==y
        e = zeros(x.shape[0], x.shape[1])
    else:
        e = mult * (x - y) / (norma(x - y))

    if (e.shape[0] == 1) and (e.shape[1] >= 1):  # El vector es fila:
        H = eye(x.shape[1]) - 2 * e.T * e
    else:
        H = eye(x.shape[0]) - 2 * e * e.T

    if (e.shape[0] == 1) and (e.shape[1] >= 1) and (simplify(x * H) != y):
        print(
            "AVISO FILAS!!! xH != y. Para Householder las normas tienen que ser iguales."
        )
    elif (e.shape[1] == 1) and (e.shape[0] >= 1) and (simplify(H * x) != y):
        print("AVISO!!! Hx != y. Para Householder las normas tienen que ser iguales.")

    return simplify(H), simplify(e)

def factorizacion_QR(m, verbose=True, metodo="gram_schmidt"):
    """Aplica el método QR para la factorización de la matriz m. m = QR, con Q ortogonal y R triangular superior.

    Args:
        m (matriz): matriz a factorizar.
        verbose (bool, optional): Imprime mensajes informativos. Defaults to True.
        metodo (str, optional): "gram_schmidt" o "householder".
        Householder funciona mejor para vectores proximos,
        donde gram-schmidt deja de ser estable. Defaults to "gram_schmidt".

    Returns:
        dict: {'Q', 'R' , 'D'} ('D' en gram-schmidt solo).
    """
    if metodo == "gram_schmidt":
        print_verbose("Aplicamos QR con Gram Schmidt", verbose)

        dict_gs = gram_schmidt(m)
        P, C = dict_gs["P"], dict_gs["c"]

        D = zeros(m.shape[0], m.shape[1])
        for col in range(m.shape[0]):
            D[col, col] = sqrt((P[:, col].T * P[:, col])[0])

        Q = simplify(P * (matriz_inversa(D)))
        R = simplify(D * (eye(m.shape[0]) + C))

        print_verbose(
            f"Q es \n{np.array(Q)}\nR es \n{np.array(R)}\nD es \n{np.array(D)}", verbose
        )
    elif metodo == "householder":
        print_verbose("Aplicamos QR con Householder", verbose)
        lista_p = [eye(m.shape[0]) for _ in range(m.shape[0])]
        lista_e = []

        Q, R = eye(m.shape[0]), m.copy()
        for col in range(m.shape[1]):
            x = R[col:, col]
            y = mat([1] + [0] * (m.shape[0] - col - 1)) * norma(x)
            p_col = lista_p[col]
            H, e = householder(x, y)
            p_col[col:, col:] = H
            lista_e.append(e)

            R = simplify(p_col * R)
            Q = simplify(Q * p_col)

        print_verbose(f"Q es \n{np.array(Q)}\nR es \n{np.array(R)}", verbose)
    else:
        raise "El método no existe. Los disponibles son gram_schmidt y householder"

    if m != simplify(Q * R):
        print("AVISO!!! A != QR")

    if metodo == "gram_schmidt":
        return {"Q": Q, "R": R, "D": D}
    elif metodo == "householder":
        return {"Q": Q, "R": R}


import sys
import numpy as np
import torch
import json
from torch.autograd import Variable
import math
from random import choices
import random

def exponential_map_sd(x, v):
    r"""Exponential map on \mathbb{S}^D.
    Args:
        x: points on \mathbb{S}^D, embedded in \mathbb{R}^{D+1}
        v: vectors in the tangent space T_x \mathbb{S}^D
    Returns:
        Image of exponential map
    """
    v_norm = np.linalg.norm(v, axis=1, keepdims=True)
    return x * np.cos(v_norm) + v / v_norm * np.sin(v_norm)

def sample_sd(d=2, num_samples=1):
    xs = np.random.randn(d + 1, num_samples)
    xs /= np.linalg.norm(xs, axis=0)
    return xs.transpose()

def vMF_dens(x, mu, kappa):
    if kappa > 0:
        pdf_constant = kappa / ((2 * np.pi) * (1. - np.exp(-2. * kappa)))
        return pdf_constant * np.exp(kappa * (mu.dot(x) - 1.))
    else:
        return 1. / (4 * np.pi)

def sample_vMF(mu, kappa, num_samples):
    """Generate num_samples N-dimensional samples from von Mises Fisher
    distribution around center mu \in R^N with concentration kappa.
    """
    dim = len(mu)
    result = np.zeros((num_samples, dim))
    for nn in range(num_samples):
        # sample offset from center (on sphere) with spread kappa
        w = _sample_weight(kappa, dim)

        # sample a point v on the unit sphere that's orthogonal to mu
        v = _sample_orthonormal_to(mu)

        # compute new point
        result[nn, :] = v * np.sqrt(1. - w ** 2) + w * mu

    return result

def _sample_weight(kappa, dim):
    """Rejection sampling scheme for sampling distance from center on
    surface of the sphere.
    """
    dim = dim - 1  # since S^{n-1}
    b = dim / (np.sqrt(4. * kappa ** 2 + dim ** 2) + 2 * kappa)
    x = (1. - b) / (1. + b)
    c = kappa * x + dim * np.log(1 - x ** 2)

    while True:
        z = np.random.beta(dim / 2., dim / 2.)
        w = (1. - (1. + b) * z) / (1. - (1. - b) * z)
        u = np.random.uniform(low=0, high=1)
        if kappa * w + dim * np.log(1. - x * w) - c >= np.log(u):
            return w

def _sample_orthonormal_to(mu):
    """Sample point on sphere orthogonal to mu."""
    v = np.random.randn(mu.shape[0])
    proj_mu_v = mu * np.dot(mu, v) / np.linalg.norm(mu)
    orthto = v - proj_mu_v
    return orthto / np.linalg.norm(orthto)

def spherical_to_euclidean(sph_coords):
    if sph_coords.ndim == 1:
        sph_coords = np.expand_dims(sph_coords, 0)
    theta, phi = np.split(sph_coords, 2, 1)
    return np.concatenate((
        np.sin(phi) * np.cos(theta),
        np.sin(phi) * np.sin(theta),
        np.cos(phi)
    ), 1)

def euclidean_to_spherical(euc_coords):
    if euc_coords.ndim == 1:
        euc_coords = np.expand_dims(euc_coords, 0)
    x, y, z = np.split(euc_coords, 3, 1)
    return np.concatenate((
        math.atan2(y, x),
        math.atan2(np.sqrt(x ** 2 + y ** 2) / z)
    ), 1)

def batch_outer(x, y):
    return torch.bmm(x.unsqueeze(2), y.unsqueeze(1))

def softplus(x):
    return torch.logaddexp(x, torch.tensor([0.]))

def softplus_inv(x):
    return torch.log(-1. + torch.exp(x))

def softmax(x):
    ex = torch.exp(x - torch.max(x))
    return ex / torch.sum(ex)

class RadialFlowVmfMixBase:
    def __init__(self, N, inputs, mean_dir_list, conc_list, mix_prob_list):
        self._N = N  # no. of layers
        self._inputs = inputs
        self._mean_dir_list = mean_dir_list
        self._conc_list = conc_list
        self._mix_prob_list = mix_prob_list
        self.RF = RadialFlow(N, inputs)
        self._ncomp = len(mix_prob_list)
        self._log_pdf_const_list = [self.vmf_log_pdf_const(conc) for conc in conc_list]
        self._pdf_const_list = [self.vmf_pdf_const(conc) for conc in conc_list]

    def vmf_pdf_const(self, conc):
        num = conc
        den = 2 * np.pi * (np.exp(conc) - np.exp(-conc))
        return num / den

    def vmf_log_pdf_const(self, conc):
        pdf_const = self.vmf_pdf_const(conc)
        return np.log(pdf_const)

    def vmf_density(self, x, mean_dir, conc, pdf_const):
        return pdf_const * np.exp(conc * np.dot(x, mean_dir))

    def vmf_log_density(self, x, mean_dir, conc, log_pdf_const):
        return conc * np.dot(x, mean_dir) + log_pdf_const

    def loss(self, theta_all, mu_all, beta_all, prop_mini_batch=.2):
        ljs_inputs = 0.
        for x in self._inputs:
            u = torch.rand(1)
            if u < prop_mini_batch:
                z, ljs = self.RF.serial(theta_all, mu_all, beta_all, x)
                # print(ljs)

                dens_base = 0.
                for k in range(self._ncomp):
                    dens_base += self._mix_prob_list[k] * self.vmf_density(x, self._mean_dir_list[k],
                                                                           self._conc_list[k],
                                                                           self._pdf_const_list[k])

                log_dens_base = torch.log(torch.tensor(dens_base))
                # print(log_dens_base)

                ljs_inputs += ljs + log_dens_base

        return - ljs_inputs

    def log_density(self, theta_all, mu_all, beta_all, x):
        ljs = 0.

        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.RF.comp_flow(theta, mu, beta, x)
            ljs += lj

        dens_base = 0
        x = x.detach().numpy()
        for k in range(self._ncomp):
            dens_base += self._mix_prob_list[k] * self.vmf_density(x, self._mean_dir_list[k], self._conc_list[k],
                                                                   self._pdf_const_list[k])
        log_dens_base = torch.log(torch.tensor(dens_base))

        ljs += log_dens_base

        return ljs

class RadialFlow:
    def __init__(self, N, inputs):
        self._N = N  # no. of layers
        self._inputs = inputs

    def comp_flow(self, theta, mu, beta, x):
        """WARNING WORKS ONLY FOR p = 1"""
        x = x.to(torch.float32)
        nx = len(x)
        theta = softmax(theta)
        mu = mu / torch.linalg.norm(mu)
        beta = torch.exp(beta)

        p = len(theta)
        if p > 1:
            sys.exit("This code only works for p = 1")
        alpha = torch.arccos(torch.matmul(x, mu.T))

        num_e = mu - torch.cos(alpha) * x
        den_e = 1. / torch.sin(alpha)
        e = num_e * den_e

        f_prime = - torch.sin(alpha) * torch.exp(beta * (torch.cos(alpha) - 1))

        v_theta = - theta * f_prime * e

        alpha_theta = (torch.linalg.norm(v_theta, dim=1)).unsqueeze(0).T

        e_theta = v_theta / alpha_theta

        H1 = batch_outer(e_theta, e_theta)
        H2 = alpha_theta * torch.cos(alpha_theta) / torch.sin(alpha_theta)
        IdMatrices = torch.eye(3).reshape((1, 3, 3)).repeat(nx, 1, 1)
        H3 = IdMatrices - batch_outer(x, x) - batch_outer(e_theta, e_theta)
        H_theta = H1 + H2.reshape(nx, 1, 1) * H3

        K_sum = torch.zeros(nx, 3, 3)
        for i in range(p):
            f1 = - torch.sin(alpha[:, i]) * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2_1 = - torch.cos(alpha[:, i]) * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2_2 = beta[i] * torch.sin(alpha[:, i]) ** 2 * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2 = torch.add(f2_1, f2_2)

            K1 = f2.reshape(nx, 1, 1) * batch_outer(e, e)
            K2 = f1 * torch.cos(alpha[:, i]) / torch.sin(alpha[:, i])
            K3 = IdMatrices - batch_outer(x, x) - batch_outer(e, e)
            K = K1 + K2.reshape(nx, 1, 1) * K3
            K_sum += theta[i] * K

        # transformed
        v_theta_norm = (torch.linalg.norm(v_theta, dim=1)).unsqueeze(0).T
        z = x * torch.cos(v_theta_norm) + v_theta / v_theta_norm * torch.sin(v_theta_norm)

        # log jacobian
        lj1 = 2 * torch.log(torch.sin(v_theta_norm) / v_theta_norm)
        lj2 = torch.logdet(batch_outer(x, x) + H_theta + K_sum).reshape(nx, 1)
        lj = lj1 + lj2

        return z, lj

    def serial(self, theta_all, mu_all, beta_all, x):
        ljs = 0.
        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.comp_flow(theta, mu, beta, x)
            ljs += lj
        return x, ljs

    def loss(self, theta_all, mu_all, beta_all, prop_mini_batch=.3):
        # print(beta_all)
        # print(mu_all)
        x_all = self._inputs
        nsamp = np.random.binomial(len(x_all), prop_mini_batch)
        idx = np.random.choice(len(x_all),
                               size=nsamp,
                               replace=False)
        x = x_all[idx, :]
        z, ljs = self.serial(theta_all, mu_all, beta_all, x)

        return - sum(ljs)

    def weighted_loss(self, theta_all, mu_all, beta_all, weights, prop_mini_batch=.3):
        ljs_inputs = 0.

        for x, w in zip(self._inputs, weights):
            u = torch.rand(1)
            if u < prop_mini_batch:
                z, ljs = self.serial(theta_all, mu_all, beta_all, x)
                ljs_inputs += ljs * w
                # z1 = z.detach().numpy()
        return - ljs_inputs

    def log_density(self, theta_all, mu_all, beta_all, x):
        ljs = 0.
        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.comp_flow(theta, mu, beta, x)
            ljs += lj
        return ljs

rows = []
with open('pacific.csv') as f:
    for line in f:
        # strip whitespace
        line = line.strip()
        # separate the columns
        line = line.split(',')
        # save the line for use later
        rows.append(line)

data = []
for i in range(2, len(rows)):
    if rows[i][0] != rows[i - 1][0]:
        data.append(rows[i - 1])

theta_vec1 = []
theta_vec2 = []
for d in data:
    theta_vec1.append(float(d[6][:-1]))
    theta_vec2.append(float(d[7][:-1]))
theta_vec2 = [360. - theta_vec2[i] for i in range(len(theta_vec2))]

theta_vec1 = [(theta_vec1[i] + 90.) * np.pi / 180. for i in range(len(theta_vec1))]
theta_vec2 = [(theta_vec2[i] + 180.) * np.pi / 180. for i in range(len(theta_vec2))]
theta_vec = [np.array([theta1, theta2]) for (theta1, theta2) in zip(theta_vec1, theta_vec2)]

print("### Bootstrapping ###")

for i in range(50):

    print("Bootstrap sample ", i+1)

    np.random.seed(20000+i)
    torch.manual_seed(20000+i)
    random.seed(20000+i)

    nobs = len(theta_vec)
    n_samp = np.random.poisson(nobs, size=1)[0]

    theta_vec_samp = choices(theta_vec, k=n_samp)

    #theta_vec_list = [theta.tolist() for theta in theta_vec]

    inputs = np.array([spherical_to_euclidean(theta_vec_samp[i])[0] for i in range(len(theta_vec_samp))])

    inputs = torch.from_numpy(inputs)

    N = 30
    p = 1
    RF = RadialFlow(N, inputs)

    mu_all = Variable(0.5 * torch.randn((N, p, 3)), requires_grad=True)
    theta_all = Variable(0.5 * torch.randn((N, p)), requires_grad=True)
    beta_all = Variable(0.5 * torch.randn((N, p)), requires_grad=True)

    optimizer = torch.optim.SGD([theta_all, mu_all, beta_all], lr=1e-5)
    loss_list = [10 ** 10]

    for t in range(100):
        # Forward pass:

        loss = RF.loss(theta_all, mu_all, beta_all)
        # print(t, loss.item())

        # Zero gradients, perform a backward pass, and update the weights.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    dens_list = []

    ngrids = 200

    theta_vec1 = np.linspace(.001, np.pi - .001, ngrids)
    phi_vec1 = np.linspace(.001, 2 * np.pi - .001, ngrids)

    X_sph = np.array([(x, y) for x in theta_vec1 for y in phi_vec1])
    X = [spherical_to_euclidean(x_sph)[0] for x_sph in X_sph]
    X = np.array(X)
    X = torch.from_numpy(X)

    dens = torch.exp(RF.log_density(theta_all, mu_all, beta_all, X))

    dens = torch.mul(dens, n_samp)

    dens_list = np.concatenate((X_sph, dens.detach()), axis=1).tolist()

    write_path = 'pacific_end_dens_est_radial_np_v' + str(i+20000) + '.json'
    with open(write_path, 'w') as f:
        json.dump(dens_list, f)


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
from statsmodels.stats.weightstats import ztest
np.random.seed(0)

#%% One-Sample tests
N = 100
mu = 0.2
sigma = 1
x = np.random.randn(100)*sigma + mu

#%% Two-sided test
print(ztest(x))

#%% Two-sided test manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = mu_hat / (sigma_hat / np.sqrt(N))
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z, p)

#%% One-sided test
ztest(x, alternative="larger")

#%% One-sided test manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = mu_hat / (sigma_hat / np.sqrt(N))
p = 1-norm.cdf(abs(z))
print(z,p)

#%% REference value
ztest(x, value=0.05)

#%% REference value manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = (mu_hat-0.05) / (sigma_hat / np.sqrt(N))
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z, p)

#%% Two-sample tests
N_0 = 1000
mu_0 = 0.2
sigma_0 = 1
x_0 = np.random.randn(N_0)*sigma_0 + mu_0

N_1 = 100
mu_1= 0.5
sigma_1 = 1
x_1 = np.random.randn(N_1)*sigma_1 + mu_1

#%%
ztest(x_0, x_1)

#%% Two-sided test manual
mu_hat_0 = x_0.mean()
mu_hat_1 = x_1.mean()
y = mu_hat_0 - mu_hat_1
s2_hat_0 = x_0.var(ddof=1)
s2_hat_1 = x_1.var(ddof=1)
s_hat = np.sqrt(s2_hat_1/N_1 + s2_hat_0/N_0)
z = y / s_hat
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z,p)

#%% False alarm (Type I error) 5% of times
result = []
for i in range(10_000):
    x0 = np.random.randn(100)
    x1 = np.random.randn(100)
    z,p = ztest(x0, x1)
    result.append(p<0.05)

print(np.mean(result))

import numpy,from scipy,from statsmodels


import numpy as np

def compute_cost(X, y, theta):
    # Initialize some useful values
    m = y.size
    cost = 0

    # ===================== Your Code Here =====================
    # Instructions : Compute the cost of a particular choice of theta.
    #                You should set the variable "cost" to the correct value.

    for i in range(m):
        cost += (theta[0]+theta[1]*X[i][1]-y[i])*(theta[0]+theta[1]*X[i][1]-y[i])
    cost/=m*2
    # ==========================================================

    return cost


import numpy as np

angfac = 180.0/np.pi

# some useful subroutines

# get an angle between [0,2pi]
def mod_two_pi(x):
    twopi = 2.0*np.pi
    #y=x
    #while (y > twopi):
    #    y -= twopi;
    #while (y < 0.0):
    #    y += twopi;
    #return y
    return x%twopi

def mod_two_pi_arr(x):
    nvec = np.size(x)
    mvec = x*0.0
    for i in range(0,nvec):
        mvec[i] = mod_two_pi(x[i])
    return mvec

# length of a vector
def len_vec(x,y,z):
    r= np.sqrt(x*x + y*y + z*z)
    return r

# normalize a vector
def normalize_vec(x,y,z):
    r = len_vec(x,y,z)
    return x/r, y/r, z/r

def dotprod(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product
    return z

# return cross product of two vectors
def crossprod(ax,ay,az,bx,by,bz):
    cx = ay*bz-az*by;
    cy = az*bx-ax*bz;
    cz = ax*by-ay*bx;
    return cx,cy,cz

# return normalized cross product of two vectors
def crossprod_unit(ax,ay,az,bx,by,bz):
    cx,cy,cz = crossprod(ax,ay,az,bx,by,bz)
    cc = len_vec(cx,cy,cz)
    return cx/cc, cy/cc, cz/cc

# return the vector part of a that is perpendicular to b direction
def aperp(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product = ab cos theta
    bmag = len_vec(bx,by,bz)
    #theta = np.acos(z/(amag*bmag))
    cx = ax - z*bx/bmag
    cy = ay - z*by/bmag
    cz = az - z*bz/bmag
    return cx,cy,cz

# return the vector part of a that is parallel to b direction
def apar(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product = ab cos theta
    bmag = len_vec(bx,by,bz)
    #theta = np.acos(z/(amag*bmag))
    cx = z*bx/bmag
    cy = z*by/bmag
    cz = z*bz/bmag
    return cx,cy,cz


from statistics import mean

import matplotlib.pyplot as plt
import numpy as np
from pandas import read_csv
from sympy import Symbol, pi, pprint

from lsq import trigLSQ

rawData = read_csv('data/data.csv', header=0, names=['x', 'y'])
x = rawData['x']; y = rawData['y']

noMonths = len(rawData)
noYears = int(noMonths/12)

# t = 1,2, 3, 4, 5, ..., 60
# x = t*pi/30 --> pi/30, pi/15, ..., 2pi
data60 = {
    'x': [(2*pi*t)/noMonths for t in range(1, noMonths+1)],
    'y': [p for p in y]
}

data12 = {
    'x': [(2*pi*t)/12 for t in range(1, 12+1)],
    'y': [mean([y[i + n*12] for n in range(noYears)]) for i in range(12)]
}

order60 = 11 # max 29
order12 = 4 # max 5
F60, aux60 = trigLSQ(data60, order60); F60 = F60.subs(Symbol('x'), (2*pi/noMonths)*Symbol('t'))
F12, aux12 = trigLSQ(data12, order12); F12 = F12.subs(Symbol('x'), (2*pi/12)*Symbol('t'))

f60 = lambda t: float(F60.subs(Symbol('t'), t).evalf()) # f60(t)
f12 = lambda t: float(F12.subs(Symbol('t'), t).evalf()) # f12(t)

x12 = np.linspace(0, 12, 100); y12 = 0
for j, i in aux12[0]: y12 += aux12[1][j][0] * (np.cos(i['index'] * 2*np.pi/12 * x12) if i['cos'] else np.sin(i['index'] * 2*np.pi/12 * x12))

x60 = np.linspace(0, noMonths, 100); y60 = 0
for j, i in aux60[0]: y60 += aux60[1][j][0] * (np.cos(i['index'] * 2*np.pi/noMonths * x60) if i['cos'] else np.sin(i['index'] * 2*np.pi/noMonths * x60))

with open('./output/60-month_period.out', 'w') as f:
    f.write(f't: {list(range(1, noMonths+1))}\n\n')
    f.write(f'x: {data60["x"]}\n\n')
    f.write(f'y: {data60["y"]}\n\n')
    f.write(f'F(t) = {F60}\n\n')
    f.write(f'F(72) = {f60(72)}')

with open('./output/12-month_period.out', 'w') as f:
    f.write(f't: {list(range(1, 12+1))}\n\n')
    f.write(f'x: {data12["x"]}\n\n')
    f.write(f'y: {data12["y"]}\n\n')
    f.write(f'F(t) = {F12}\n\n')
    f.write(f'F(72) = {f12(72)}')

print('\nFunção considerando período de 5 anos: \nF(t) = ')
pprint(F60)
print(f'\nF(72) = {f60(72)}', end='\n\n')

print('\nFunção considerando período de 1 ano médio: \nF(t) = ')
pprint(F12)
print(f'\nF(72) = {f12(72)}')

plt.figure(1)
plt.title('Sobreposição da função aproximada com os pontos resultantes da média entre 5 anos')
plt.scatter(range(1, 13), data12['y'], label='Pontos fornecidos')
plt.plot(range(1, 12+1), data12['y'], '--', label='Ligação entre os pontos')
plt.plot(x12, y12, '-', lw=2.5, label=f'Função aproximada F(t) (ordem {order12})')
plt.legend(loc='upper right')
plt.ylabel('Precipitação')
plt.xlabel('Mês (t)')
plt.gcf().set_size_inches(9,6)
plt.savefig('output/12-month_period.png', dpi=100)

plt.figure(2)
plt.title('Sobreposição da função aproximada com os pontos fornecidos')
plt.scatter(range(1, noMonths+1), data60['y'], label='Pontos fornecidos')
plt.plot(range(1, noMonths+1), data60['y'], '--', label='Ligação entre os pontos')
plt.plot(x60, y60, '-', lw=2.5, label=f'Função aproximada F(t) (ordem {order60})')
plt.legend(loc='upper left')
plt.ylabel('Precipitação')
plt.xlabel('Mês (t)')
plt.gcf().set_size_inches(9,6)
plt.savefig('output/60-month_period.png', dpi=100)

plt.show()


"""
  Name     : c9_07_find_minimum_expential_function.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
# define a function
a=3.4
b=2.0
c=0.8
def f(x):
    return a-b*np.exp(-(x - c)**2)

x=np.arange(-3,3,0.1)
y=f(x)
plt.title("y=a-b*exp(-(x-c)^2)")
plt.xlabel("x")
plt.ylabel("y")
plt.plot(x,y)
plt.show()

# find the minimum
solution= optimize.brent(f)
print(solution)