# coding: utf-8

# In[2]:

# numpy and scipy are pretty awesome python libraries for scientific computing
# Let's quickly do a round up of what these 2 libraries can help us do

# Python lists are amazing, but many times you'll want to put your data into grids
# and perform mathematical operations with them. A numpy array is a way to put your data
# in a matrix (grid).
# Numpy then has a number of cool functions to manipulate this data
import numpy as np
import scipy
# An array can have any number of dimensions
# A 1-d array is like a list (The number of dimensions is also known as rank)
array1d=np.array([0,2,4,6,8])
print(array1d)

# In[3]:

# A 2-d array is like a 2-d grid. Create it using a list of lists
array2d=np.array([[1,2,3],[4,5,6]])

print(array2d)

# In[4]:

print(type(array1d))

# In[5]:

# shape is a tuple which contains the size of the array ie (the number of rows, the number of
# columns)
print(array1d.shape)

# In[6]:

print(array2d.shape)

# In[7]:

# You can index elements of an array pretty much like you do with lists
# Like with lists, the indexing starts from 0
print(array1d[0])

# With a 2d array the syntax is slightly different. If you had a list of lists, and you
# wanted the element in the ith row and the jth column you would index it by saying
# listOfLists[i][j]
# In a numpy array, you can index the element in the ith row and jth column as
# arrayNumpy[i,j]

print(array2d[1,2])
# this will print the element in the second row and third column of array2d (indexing starts from
# 0)

# In[8]:

# Just like with lists, you can use : to specify "from the beginning" or "till the end"
print(array1d[1:])

# In[9]:

print(array2d[1,:1]) # This will print the elements in the second row , from the first element in
# the second row, till the second element (not including the second element)

# In[10]:

print(array2d[1,1:]) # The elements in the second row, from the second element onwards

# In[11]:

# In all the cases above, the result of the indexing is a numpy array which is a subset
# of the original array
subarray2d=array2d[1,1:]
print(type(subarray2d))

# In[12]:

# There are other cool ways to index numpy arrays
new2dArray=np.array([[1,2,4,5,9],[3,4,6,6,10],[15,3,2,14,7]])

# Let's say you want the elements with indices [1,2], [0,4], [2,3]

newSubArray=new2dArray[[1,0,2],[2,4,3]] # Arrays of integers to specify the indices
print(newSubArray)

# In[13]:

# You can also use boolean indexing, ie subset the array to all the values which satisfy
# a certain condition
new2dArray[new2dArray>10]

# In[14]:

# new2dArray>10 will return an array with the same size and shape as the original array
# Each element of that array will be a boolean which says whether the corresponding element
# of the original array satisfies the given condition
new2dArray>10

# In[15]:

# You can create some standard kinds of arrays using built-in functions in numpy.
# Ex: Arrays with all 0s, constant arrays, random numbers etc

arrayOfZeros=np.zeros((2,2),dtype='int64') # Creates an array with all zeros. dtype is an optional
# argument, its default value is float
arrayOfOnes=np.ones((1,2)) # note how the size of the array is passed in as a tuple
arraywithConstantValue = np.full(new2dArray.shape,7) # Creates an array with the same
# size and shape as new2dArray, fills it with value 7 for all elements

identityMatrix = np.eye(2)
# Creates a 2x2 identity matrix ie a square grid of numbers with 2 rows and 2 columns
# All the diagonal elements will be 1s and all the non-diagonal elements will be 0s

print(arrayOfZeros,"\n",arrayOfOnes,"\n",arraywithConstantValue,"\n",identityMatrix)

# In[16]:

# YOu can also fill an array with random numbers
arrayOfRandomNumbers = np.random.random((2,2))
# This will create a 2x2 array with random numbers between the values 0 and 1.
# To generate random numbers with a specific distribution, check out the
# numpy documentation
print(arrayOfRandomNumbers)

# In[17]:

# You can change the size and shape of your array
transposeArray=np.transpose(new2dArray)

print(new2dArray.shape, transposeArray.shape)

# In[18]:

# You can reshape array into any shape as long as the total number of elements remains
# the same
reshapedArray=np.reshape(new2dArray,[1,15])
print(reshapedArray)

# In[19]:

reshapedArray=np.reshape(new2dArray,[15,1])
print(reshapedArray)

# In[20]:

# Normal mathematical operators like +,-,/,* can be used to perform element wise
# operations. Both arrays have to be of the same dimension

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

# In[21]:

array1+array2

# In[22]:

# instead of the math operators you can use functions equivalent to the operators
# +,-,/,* are equivalent to add, subtract, divide,multiply
np.subtract(array1,array2)

# In[23]:

# Broadcasting is a way in which you can add matrices or arrays of different dimensions
array3=[[1,4,7]]

array1+array3 # This will add array3 to each row of array1

# In[24]:

# The rule of thumb for broadcasting is that the arrays need to be able to align
# along at least 1 dimension. For more details check the documentation

# In[25]:

# One important operation you'll need is to perform an inner product of 2 arrays
# or multiply 2 matrices. Matrix multiplication is equivalent to taking inner products
# of every row of the first matrix with every column of the second matrix
# To multiply 2 matrices, you will need the number of columns of the left array
# to be equal to the number of rows of the right array

np.dot(array1,array2)# wont work because we have both 2x3 arrays

# In[26]:

np.dot(array1,array2.T) # .T is shorthand for using np.transpose()

# In[27]:

# You can add or multiply all the elements along 1 dimension (or axis), This is
# like compressing the array along that axis
np.sum(array1,axis=0)

# In[28]:

np.sum(array2,axis=1)

# In[29]:

# stack arrays together using vstack or hstack. These functions take in a list/tuple/
# array of arrays and then stack them vertically or horizontally to make a new array
np.vstack((array1,array2))

# In[30]:

np.hstack((array2,array1))

# In[31]:

# Scipy has many modules that help us compute mathematical functions
# one example is the spatial module, given 2 points represented by
# np arrays, scipy can help you find the distance between those points
# The distance metric could be any one of a number of options ex: euclidean
# cosine, correlation, hamming etc
# pdist will compute pairwise distances between the rows in a numpy array

from scipy.spatial.distance import correlation, cosine, pdist, squareform

array1=np.array([0,1,0])
array2=np.array([1,0,0])

correlation(array1,array2)

# In[33]:

allPoints=np.vstack([array1,array2])
d=squareform(pdist(allPoints, 'euclidean'))
# The distance metric can be changed to cosine, correlation or any other distance
# the complete list of options is available in scipy documentation
# This will compute the pairwise distance between all rows of allPoints
# d will be a square matrix with d[i,j] being the Euclidean distance between
# allPoints[i,:] and allPoints[j,:]
print(d)

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:


from __future__ import division
from scipy import*
from scipy import linalg
import pylab
import sys
sys.path.append("/home/ludger/lib/python2.6/site-packages/")
import statistics

#Task 1: Evaluate the density of a bivariate distribution at a single point
def density(x,mu,sigma):
        inv_sigma=linalg.inv(sigma)
        x_minus_mu=x-mu
        return exp(-0.5*dot(dot(transpose(x_minus_mu),inv_sigma),x_minus_mu))/(2*pi*sqrt(
linalg.det(sigma) ) )

mu=array([0,0])
sigma=array([[4,1],[1,4]])

#Task 2: Metropolis Hastings

#Set the standard deviation of the proposal
sigma_prop=2.5
#Set the desired sample size
n=1000
#Set the starting values
x=array([0,0])
accepted_n=0
f=density(x,mu,sigma)
X1=[x[0]]
X2=[x[1]]

for i in xrange(1,n):
        x_0=x[0]+random.normal(0,sigma_prop)
        x_1=x[1]+random.normal(0,sigma_prop)
        new_x=array([x_0,x_1])
        new_f=density(new_x,mu,sigma)
        if (random.random()<(new_f/f)):
                accepted_n+=1
                x=new_x
                f=new_f
        X1.append(x[0])
        X2.append(x[1])

#Proportion of accepted values
print "The proportion of accepted values is", accepted_n/n

#Task 3: Diagnostics

#Sample plots for both values
pylab.figure(0)
pylab.plot(X1,'b')
pylab.title("Sample path of X_1")
pylab.figure(1)
pylab.plot(X2,'r')
pylab.title("Sample path of X_2")

#Cumulative averages
X1_cummean = cumsum( X1 ) / ( 1 + arange( len( X1 )))
X2_cummean = cumsum( X2 ) / ( 1 + arange( len( X1 )))
pylab.figure( 2 )
pylab.plot( X1_cummean,"b" )
pylab.title("Empirical mean of X_1")

pylab.figure( 3 )
pylab.plot( X2_cummean,"r" )
pylab.title("Empirical mean of X_2")
pylab.show()

#Autocorrelation
X1_sd = sqrt( var( X1 ) )
X2_sd = sqrt( var( X2 ) )
X1_autocorr = statistics.correlation( X1[1: ], X1[:-1])
X2_autocorr = statistics.correlation( X2[1: ], X2[:-1])
print "The autocorrelation of X_1 is", X1_autocorr
print "The autocorrelation of X_2 is", X2_autocorr

#Effective sample size
X1_ess = n * ( 1 - X1_autocorr ) / ( 1 + X1_autocorr )
X2_ess = n * ( 1 - X2_autocorr ) / ( 1 + X2_autocorr )
print "The effective sample size of X_1 is", X1_ess
print "The effective sample size of X_2 is", X2_ess

#Task 4: Repeat with sigma_prop = 0.1, sigma_prop = 10

#Task 5: Repeat with sigma = array([[4,2.8],[2.8,4]])


# Generic import
import numpy as np

def central_difference(f, x, *args):
    """
    Description:
    It returns the numerical derivative of input function "f"
    at the point "x", i.e. df(x)/dx, by using the central
    difference formula.

    Args:
        - f: function handle.
        - x: point to evaluate the derivative.
        - *args: additional input parameters of the function.

    Output:
        - df(x)/dx
    """
    # The optimal step when using the CDF should scale
    # with respect to the cubic root of "eps".
    cbrt_eps = (np.finfo(float).eps) ** (1.0 / 3.0)

    # Number of input parameters.
    D = x.size

    # Preallocate for efficiency.
    df = np.zeros(D)

    # Auxilliary vector.
    e = np.zeros(D)

    # Check all 'D' directions (coordinates of x).
    for i in range(D):
        # Switch ON i-th direction.
        e[i] = 1.0

        # Step size (for the i-th variable).
        h = cbrt_eps if (x[i] == 0.0) else x[i] * cbrt_eps

        # Move a small way in the i-th direction of x+h.
        fplus = f(x + h * e, *args)

        # Move a small way in the i-th direction of x-h.
        fminus = f(x - h * e, *args)

        # Central difference formula for approximation.
        df[i] = (fplus - fminus) / (2.0 * h)

        # Switch OFF i-th direction.
        e[i] = 0.0
    # _end_if_

    return df

# _end_def_

def forward_difference(f, x, *args):
    """
    Description:
    It returns the numerical derivative of input function "f"
    at the point "x", i.e. df(x)/dx, by using the forward
    difference formula.

    Args:
        - f: function handle.
        - x: point to evaluate the derivative.
        - *args: additional input parameters of the function.

    Output:
        - df(x)/dx
    """
    # Step size.
    h = 1.0E-6

    # Number of input parameters.
    D = x.size

    # Preallocate for efficiency.
    df = np.zeros(D)

    # Auxilliary vector.
    e = np.zeros(D)

    # Compute the f(x) only once.
    fx = f(x, *args)

    # Check all 'D' directions (coordinates of x).
    for i in range(D):
        # Switch ON i-th direction.
        e[i] = 1.0

        # Move a small way in the i-th direction of x+h.
        fplus = f(x + h * e, *args)

        # Central difference formula for approximation.
        df[i] = (fplus - fx) / h

        # Switch OFF i-th direction.
        e[i] = 0.0
    # _end_if_

    return df

# _end_def_

def backward_difference(f, x, *args):
    """
    Description:
    It returns the numerical derivative of input function "f"
    at the point "x", i.e. df(x)/dx, by using the backward
    difference formula.

    Args:
        - f: function handle.
        - x: point to evaluate the derivative.
        - *args: additional input parameters of the function.

    Output:
        - df(x)/dx
    """
    # Step size.
    h = 1.0E-6

    # Number of input parameters.
    D = x.size

    # Preallocate for efficiency.
    df = np.zeros(D)

    # Auxilliary vector.
    e = np.zeros(D)

    # Compute the f(x) only once.
    fx = f(x, *args)

    # Check all 'D' directions (coordinates of x).
    for i in range(D):
        # Switch ON i-th direction.
        e[i] = 1.0

        # Move a small way in the i-th direction of x+h.
        fminus = f(x - h * e, *args)

        # Central difference formula for approximation.
        df[i] = (fx - fminus) / h

        # Switch OFF i-th direction.
        e[i] = 0.0
    # _end_if_

    return df

# _end_def_

def numerical_derivative(f, x, method='cdf', *args):
    """
    Description:
    It returns the numerical derivative of input function "f"
    at the point "x", i.e. df(x)/dx, by calling the relevant
    numerical method to compute the derivative.

    Args:
        - f: function handle.
        - x: point to evaluate the derivative.
        - method: numerical integration (default = 'cdf')
        - args: additional input parameters of the function.

    Raises:
        - ValueError: If the input method is not recognized.
    """

    # Call the right method or throw an error.
    if method == "cdf":
        return central_difference(f, x, *args)
    elif method == "fwd":
        return forward_difference(f, x, *args)
    elif method == "bwd":
        return backward_difference(f, x, *args)
    else:
        raise ValueError(" Unknown method of differentiation.")

# _end_def_

def flat_list(x=None):
    """
    Description:
    It returns a list that contains all the elements form the input list of lists.
    It should work for any number of levels.

    Example:
        >>> x = flat_list([1, 'k', [], 3, [4, 5, 6], [[7, 8]], [[[9]]]])
        >>> x
        >>> [1, 'k', 3, 4, 5, 6, 7, 8, 9]

    Args:
        - x (list): List of lists of objects.

    Raises:
        - TypeError: If the input is not a list object.
    """

    # Check for empty input.
    if x is None:
        return []
    # _end_if_

    # Input 'x' should be a list.
    if not isinstance(x, list):
        raise TypeError(" Input should be a list.")
    # _end_if_

    # Define the return list.
    flat_x = []

    # Go through all the list elements.
    for item_x in x:
        # Check for embedded lists.
        if isinstance(item_x, list):
            # Check for empty entries.
            if not item_x:
                continue
            # _end_if_

            # Note the recursive call in "flat_list".
            for ix in flat_list(item_x):
                flat_x.append(ix)
            # _end_for_
        else:
            # If the item is not a list.
            flat_x.append(item_x)
        # _end_if_
    # _end_for_

    # Return the flatten list.
    return flat_x

# _end_def_

def split_dataset_at_feature(data, axis=0):
    """
    Description:
    Splits the input data set at the requested feature, given by the index number 'idx'.

    Args:
        - data: (list of lists) input data-set.
        - axis: feature index to split the data-set.

    Note:
        Default index is '0' (i.e. the first feature of the data set).

    Raises:
        ValueError if the index is out of bounds.
    """

    # Data must be a list.
    if not isinstance(data, list):
        raise TypeError(" Input data should be a list.")
    # _end_if_

    # Index must be integer.
    if not isinstance(axis, int):
        raise TypeError(" Axis value should be integer.")
    # _end_if_

    # Get the number of features.
    # Note:  The last column is assumed to contain the class label,
    # therefore it should never be included for splitting the data.
    n_features = len(data[0]) - 1

    # If there is only one feature, there is nothing to split.
    if n_features == 1:
        return data
    # _end_if_

    # Sanity check.
    if axis < 0 or axis >= n_features:
        raise ValueError(" Feature index is out of bounds.")
    # _end_if_

    # Every entry in the dictionary will contain a sub-set of the
    # original data-set, split at a value of the feature.
    partition_data = {}

    # Split the data-set into sub-sets.
    for record in data:
        # First part of the record.
        part_1 = record[:axis]

        # Second part of the record.
        part_2 = record[axis + 1:]

        # Combine the two parts in one record.
        part_1.extend(part_2)

        # Construct a key (string) entry.
        key = "chID_{0}:{1}".format(idx, record[axis])

        # Check if this key has been seen.
        if key not in node_dict.keys():
            # If not then add it to the dictionary.
            partition_data[key] = []
        # _end_if_

        # Add the record on the dictionary.
        partition_data[key].append(part_1)
    # _end_for_

    return partition_data

# _end_def_


import numpy as np

A = np.array([[n+m*10 for n in range(5)] for m in range(5)])
np.dot(A, A)
"""
array([[ 300,  310,  320,  330,  340],
       [1300, 1360, 1420, 1480, 1540],
       [2300, 2410, 2520, 2630, 2740],
       [3300, 3460, 3620, 3780, 3940],
       [4300, 4510, 4720, 4930, 5140]])
"""

v1 = np.arange(0, 5)
np.dot(A, v1)   # array([ 30, 130, 230, 330, 430])
np.dot(v1, v1)  # 30

# También podemos hacer casting al tipo matrix. Eso cambia el comportamiento de los operadores +, -, * para usar álgebra matricial
M = np.matrix(A)
v = np.matrix(v1).T # vector columna
v
"""
matrix([[0],
        [1],
        [2],
        [3],
        [4]])
"""
M * M
"""matrix([[ 300,  310,  320,  330,  340],
        [1300, 1360, 1420, 1480, 1540],
        [2300, 2410, 2520, 2630, 2740],
        [3300, 3460, 3620, 3780, 3940],
        [4300, 4510, 4720, 4930, 5140]])
"""

M * v
"""
matrix([[ 30],
        [130],
        [230],
        [330],
        [430]])
"""

v.T * v # matrix([[30]])

v + M*v
"""
matrix([[ 30],
    [131],
    [232],
    [333],
    [434]])
"""

# Si intentamos operar entre elementos con dimensiones no compatibles, nos da error
v = np.matrix([1,2,3,4,5,6]).T
v
"""
matrix([[1],
        [2],
        [3],
        [4],
        [5],
        [6]])

"""

M * v   # Error: ValueError: shapes (5,5) and (6,1) not aligned: 5 (dim 1) != 6 (dim 0)


    #!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/10/16 19:48
# @Author   : Iydon
# @File     : course4.4.py

import numpy as np

# Composite Numerical Integration
def trapezoidal_rule(xs:list, fxs:list) -> float:
    """
    Trapezoidal Rule:
    /int_{x_0}^{x_1}f(x)dx = (h/2)[f(x_0)+f(x_1)] - E(f)
    E(f) = (h^3/12)f''(/xi)
    """
    return (xs[-1]-xs[0]) / 2 * sum(fxs)

def simpson_rule(xs:list, fxs:list) -> float:
    """
    Simpson's Rule:
    /int_{x_0}^{x_2}f(x)dx = (h/3)[f(x_0)+4f(x_1)+f(x_2)] - E(f)
    E(f) = (h^5/90)f^{(4)}(/xi)
    """
    return (xs[-1]-xs[0]) / 6 * (fxs[0]+4*fxs[1]+fxs[2])

def midpoint_rule(xs:list, fxs:list) -> float:
    return (xs[-1]-xs[0]) * fxs[1]

def composite_trapezoidal_rule(xs:list, fxs:list) -> float:
    result = 0
    for i in range(len(xs)-1):
        result += trapezoidal_rule(xs[i:i+2], fxs[i:i+2])
    return result

def composite_simpson_rule(xs:list, fxs:list) -> float:
    result = 0
    for i in range(len(xs)//2):
        result += simpson_rule(xs[2*i:2*i+3], fxs[2*i:2*i+3])
    return result

def composite_midpoint_rule(xs:list, fxs:list) -> float:
    result = 0
    for i in range(len(xs)//2):
        result += midpoint_rule(xs[2*i:2*i+3], fxs[2*i:2*i+3])
    return result

f  = np.exp
xs = [i/2 for i in range(9)]
print(composite_simpson_rule(xs, [f(x) for x in xs]))
print(composite_trapezoidal_rule(xs, [f(x) for x in xs]))
print(composite_midpoint_rule(xs, [f(x) for x in xs]))

pi = np.pi
f  = np.sin
xs = [i*pi/359 for i in range(360)]
print(composite_midpoint_rule(xs, [f(x) for x in xs]))

pi = np.pi
f  = np.sin
xs = [i*pi/20 for i in range(21)]
print(composite_simpson_rule(xs, [f(x) for x in xs]))
print(composite_trapezoidal_rule(xs, [f(x) for x in xs]))
print(composite_midpoint_rule(xs, [f(x) for x in xs]))

f  = lambda x: x**3*np.exp(x)
xs = [-2, -1, 0, 1, 2]
print(composite_trapezoidal_rule(xs, [f(x) for x in xs]))
xs = [i/2 for i in range(-4,5)]
print(composite_simpson_rule(xs, [f(x) for x in xs]))

f  = lambda x: np.exp(2*x)*np.sin(3*x)
xs = [2*i/2169 for i in range(2170)]
print(composite_trapezoidal_rule(xs, [f(x) for x in xs]))
xs = [2*i/54 for i in range(55)]
print(composite_simpson_rule(xs, [f(x) for x in xs]))

print(-14.213977129862521)


import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
import random
import torch
import torch.optim as optim
import utils

print('通过实际例子查看梯度下降的问题')
eta = 0.4

def f_2d(x1, x2):
    return 0.1 * x1 ** 2 + 2 * x2 ** 2

def gd_2d(x1, x2, s1, s2):
    return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)

print('竖直方向波动较大')
utils.show_trace_2d(f_2d, utils.train_2d(gd_2d))
print('在竖直方向越过最优解并发散')
eta = 0.6
utils.show_trace_2d(f_2d, utils.train_2d(gd_2d))

print('使用动量法进行改进')
eta, gamma = 0.4, 0.5

def momentum_2d(x1, x2, v1, v2):
    v1 = gamma * v1 + eta * 0.2 * x1
    v2 = gamma * v2 + eta * 4 * x2
    return x1 - v1, x2 - v2, v1, v2

utils.show_trace_2d(f_2d, utils.train_2d(momentum_2d))
print('用更大的学习率，也不会发散')
eta = 0.6
utils.show_trace_2d(f_2d, utils.train_2d(momentum_2d))

print('自行实现动量法优化')
features, labels = utils.get_nasa_data()

def init_momentum_states():
    v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
    v_b = torch.zeros(1, dtype=torch.float32)
    return (v_w, v_b)

def sgd_momentum(params, states, hyperparams):
    for p, v in zip(params, states):
        v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data
        p.data -= v.data

print('momentum = 0.02, lr = 0.02')
utils.train_opt(sgd_momentum, init_momentum_states(), {'lr': 0.02, 'momentum': 0.5}, features, labels)
print('momentum = 0.9, lr = 0.02')
utils.train_opt(sgd_momentum, init_momentum_states(), {'lr': 0.02, 'momentum': 0.9}, features, labels)
print('momentum = 0.9, lr = 0.004')
utils.train_opt(sgd_momentum, init_momentum_states(), {'lr': 0.004, 'momentum': 0.9}, features, labels)

print('框架实现')
utils.train_opt_pytorch(optim.SGD, {'lr': 0.004, 'momentum': 0.9}, features, labels)


from sympy import *
x, y, z = symbols('x y z')
init_printing(use_unicode=False)

# simplify - not specific type of simplification

pprint(simplify(sin(x)**2 + cos(x)**2))

pprint(simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1)))

pprint(simplify(gamma(x)/gamma(x - 2)))

pprint(simplify(x**2 + 2*x + 1))

# expand - expand polynomial to canonical form ax^2+bx+c etc.

pprint(expand((x + 1)**2))

pprint(expand((x + 2)*(x - 3)))

pprint(expand((x + 1)*(x - 2) - (x - 1)*x))

# factor - factors a polynomial

pprint(factor(x**3 - x**2 + x - 1))

pprint(factor(x**2*z + 4*x*y*z + 4*y**2*z))

# returns list of factors

pprint(factor_list(x**2*z + 4*x*y*z + 4*y**2*z))

# non-polynomials

pprint(expand((cos(x) + sin(x))**2))

pprint(factor(cos(x)**2 + 2*cos(x)*sin(x) + sin(x)**2))

# collect - based on a variable (i.e. x)
# gather the coefficients into a term
# multiplied by a power of x.

expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
pprint(expr)

collected_expr = collect(expr, x)
pprint(collected_expr)

# coeff gives coefficient of the given
# power of x - x^2 in this example

pprint(collected_expr.coeff(x, 2))

# cancel - arrange as fraction?

pprint(cancel((x**2 + 2*x + 1)/(x**2 + x)))

expr = 1/x + (3*x/2 - 2)/(x - 4)
pprint(expr)

pprint(cancel(expr))

expr = (x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1)
pprint(expr)

pprint(cancel(expr))

# factors numerator in this example

pprint(factor(expr))

# apart - seems to split into multiple fractional terms
# opposite of cancel kind of?

expr = (4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x)

pprint(expr)
print(' ')

pprint(apart(expr))
print(' ')

pprint(cancel(apart(expr)))
print(' ')

# trig simp - like simplify for general
# there are specific simps later

pprint(acos(x))
print(' ')
pprint(cos(acos(x)))
print(' ')
pprint(asin(1))
print(' ')

pprint(trigsimp(sin(x)**2 + cos(x)**2))
print(' ')
pprint(trigsimp(sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4))
print(' ')
pprint(trigsimp(sin(x)*tan(x)/sec(x)))
print(' ')

pprint(trigsimp(cosh(x)**2 + sinh(x)**2))
print(' ')
pprint(trigsimp(sinh(x)/tanh(x)))
print(' ')

# expand_trig kind of like expand

def spprint(o):
    pprint(o)
    print(' ')

spprint(expand_trig(sin(x + y)))
spprint(expand_trig(tan(2*x)))

# trigsimp opposite of expand_trig kinda

spprint(trigsimp(expand_trig(sin(x + y))))

# powers - can state things about the numbers
# real, positive, or by default complex

x, y = symbols('x y', positive=True)
a, b = symbols('a b', real=True)
z, t, c = symbols('z t c')

spprint(powsimp(x**a*x**b))

spprint(powsimp(x**a*y**a))

# z, t, c might be complex

spprint(powsimp(t**c*z**c))

# force simp

spprint(powsimp(t**c*z**c, force=True))

spprint((z*t)**2)

spprint(sqrt(x*y))

# simp doesn't work because of automatic splitting

spprint(powsimp(z**2*t**2))
spprint(powsimp(sqrt(x)*sqrt(y)))

# expanding the base or exponent

spprint(expand_power_exp(x**(a + b)))

spprint(expand_power_base((x*y)**a))

# same doesn't work with complex but can force

spprint(expand_power_base((z*t)**c))

spprint(expand_power_base((z*t)**c, force=True))

# doesn't work with number powers like above

spprint(x**2*x**3)
spprint(expand_power_exp(x**5))

# simplified nested powers
# can force when not appropriate

spprint(powdenest((x**a)**b))
spprint(powdenest((z**a)**b))
spprint(powdenest((z**a)**b, force=True))

# exponentials and logs

# log(x) = ln(x) log base e of x I think

spprint(ln(x))

x, y = symbols('x y', positive=True)
n = symbols('n', real=True)

spprint(expand_log(log(x*y)))
spprint(expand_log(log(x/y)))
spprint(expand_log(log(x**2)))
spprint(expand_log(log(x**n)))
spprint(expand_log(log(z*t)))

# force

spprint(expand_log(log(z**2)))
spprint(expand_log(log(z**2), force=True))

# logcombine kind of revers of expand_log

spprint(logcombine(log(x) + log(y)))
spprint(logcombine(n*log(x)))
spprint(logcombine(n*log(z)))
spprint(logcombine(n*log(z), force=True))

# "special" functions

x, y, z = symbols('x y z')
k, m, n = symbols('k m n')

spprint(factorial(n))

spprint(binomial(n, k))

spprint(binomial(5, 2))

spprint(gamma(z))

spprint(hyper([1, 2], [3], z))

# rewrite an expression in terms of a function

spprint(tan(x).rewrite(sin))
spprint(factorial(x).rewrite(gamma))

# get rid of hyper

spprint(hyperexpand(hyper([1, 1], [2], z)))

expr = meijerg([[1],[1]], [[1],[]], -z)
spprint(expr)
spprint(hyperexpand(expr))

# simplifiy combinatorials - binomial, etc.

n, k = symbols('n k', integer = True)
spprint(combsimp(factorial(n)/factorial(n - 3)))
spprint(combsimp(binomial(n+1, k+1)/binomial(n, k)))

# simplify gamma whatever that is. :)

spprint(gammasimp(gamma(x)*gamma(1 - x)))

# continued fraction example
# i think this is an example of using sympy and simplification

def list_to_frac(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr += i
        expr = 1/expr
    return l[0] + expr

spprint(list_to_frac([x, y, z]))

# result is sympy fraction

spprint(list_to_frac([1, 2, 3, 4]))

# get continued fraction

syms = symbols('a0:5')
spprint(syms)
a0, a1, a2, a3, a4 = syms
frac = list_to_frac(syms)
spprint(frac)

# put into standard form

frac = cancel(frac)
spprint(frac)

# put back into original form

l = []
frac = apart(frac, a0)
spprint(frac)
l.append(a0)
frac = 1/(frac - a0)
spprint(frac)

frac = apart(frac, a1)
spprint(frac)
l.append(a1)
frac = 1/(frac - a1)
frac = apart(frac, a2)
spprint(frac)
l.append(a2)
frac = 1/(frac - a2)
frac = apart(frac, a3)
spprint(frac)
l.append(a3)
frac = 1/(frac - a3)
frac = apart(frac, a4)
spprint(frac)
l.append(a4)
spprint(list_to_frac(l))


# https://github.com/llSourcell/Second_Order_Optimization_Newtons_Method/blob/master/newtons_method_root_finding.py
"""
Newton's method
Author: Daniel Homola
Licence: BSD 3-clause
"""

from scipy.optimize import newton
from sklearn.utils.testing import assert_almost_equal

def f(x):
    return 6*x**5-5*x**4-4*x**3+3*x**2

def df(x):
    return 30*x**4-20*x**3-12*x**2+6*x

def dx(f, x):
    # Distance between x-axis and function value at x
    return abs(0-f(x))

#def newtons_method(f, df, x0, e, print_res=False):
def newtons_method(x0, e, print_res=False):
    delta = dx(f, x0)
    n = 0
    while delta > e:
        x0 = x0 - f(x0)/float(df(x0))
        delta = dx(f, x0)
        n += 1
        if n == 1000:
            break

    if print_res:
        print 'Error: ', delta
        print 'Root is at: ', x0
        print 'f(x) at root is: ', f(x0)
        print 'No. of iterations: ', n
    return x0

#def test_with_scipy(f, df, x0s, e):
def test_with_scipy(x0s, e):
    for x0 in x0s:
        #my_newton = newtons_method(f, df, x0, e)
        my_newton = newtons_method(x0, e)
        scipy_newton = newton(f, x0, df, tol=e)
        assert_almost_equal(my_newton, scipy_newton, decimal=5)
        print 'Tests passed.'

if __name__ == '__main__':
    # run test
    x0s = [0, .4, 200]
    #test_with_scipy(f, df, x0s, 1e-5)
    test_with_scipy(x0s, 1e-5)

    for x0 in x0s:
        #newtons_method(f, df, x0, 1e-10, True)
        newtons_method(x0, 1e-10, True)


import numpy as np
from numpy import linalg as LA
import matplotlib.pyplot as plt

A = np.array([[1 , 2],[4 , 3]])
lamda, v = LA.eig(A)
print(f"eigenvalues\n{lamda}")
print(f"eigenvectors\n{v}")
v1 = v[:,0]
v2 = v[:,1]
print(f"v1\n{v1}")
print(f"v2\n{v2}")

plt.title('eigenvector v1 : blue , eigenvector v2 : green')
plt.plot([0,v1[0]],[0,v1[1]],'blue')
plt.plot([0,v2[0]],[0,v2[1]],'green')
plt.xlim((-1,1))
plt.ylim((-1,1))
plt.grid()
plt.show()

equation1 = np.dot(A,v1) - lamda[0]*v1 # should be 0
print(equation1)
equation2 = np.dot(A,v2) - lamda[1]*v2 # should be 0
print(equation2)


'''
The numerical simulation calculates the approximation of the original curves from the SIR differential equations
using Euler's method
Code is adapted from:
https://scipython.com/book/chapter-8-scipy/additional-examples/the-sir-epidemic-model/
'''
import numpy as np #numpy is used
from scipy.integrate import odeint
import matplotlib.pyplot as plt

# Total population, N.
N = 1000000
# Initial number of infected and recovered individuals, I0 and R0.
I0, R0 = 305, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - I0 - R0 #Susceptible is equal to the population less the infected and removed
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
beta, gamma = 0.3552, 0.1 #Infectiousness and recovery rates
# A grid of time points (in days)
t = np.linspace(0, 200, 200) #length of time in days

# The SIR model differential equations.
def deriv(y, t, N, beta, gamma):
    S, I, R = y
    dSdt = -beta * S * I / N #derivative of susceptible equation
    dIdt = beta * S * I / N - gamma * I #derivative of infected equation
    dRdt = gamma * I #derivative of removed equation
    return dSdt, dIdt, dRdt #return the value of the derivatives

# Initial conditions vector
y0 = S0, I0, R0
# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma))
S, I, R = ret.T

# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w')
ax = fig.add_subplot(111, axisbelow=True)
ax.plot(t, S/1000000, 'b', alpha=0.5, lw=2, label='Susceptible') #Plot the susceptible line as blue
ax.plot(t, I/1000000, 'r', alpha=0.5, lw=2, label='Infected') #plot the infected line as red
ax.plot(t, R/1000000, 'g', alpha=0.5, lw=2, label='Recovered with immunity') #plot the removed line as green
ax.set_xlabel('Time /days') #Set the label as time in days
ax.set_ylabel('Number (1000000s)') #population number is expressed in millions
ax.set_ylim(0,1.2) #the y-axis is limited to a maximum of 1.2
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
plt.title('SIR model for the Ebola Outbreak in Liberia, 2014')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.show() #Display the graph


import numpy as np

# Demonstrates array stacking.
#
# Run from the commandline with
#
#  python stacking.py
print "In: a = arange(9).reshape(3,3)"
a = np.arange(9).reshape(3,3)

print "In: a"
print a
#Out:
#array([[0, 1, 2],
#       [3, 4, 5],
#       [6, 7, 8]])

print "In: b = 2 * a"
b = 2 * a

print "In: b"
print b
#Out:
#array([[ 0,  2,  4],
#       [ 6,  8, 10],
#       [12, 14, 16]])

print "In: hstack((a, b))"
print np.hstack((a, b))
#Out:
#array([[ 0,  1,  2,  0,  2,  4],
#       [ 3,  4,  5,  6,  8, 10],
#       [ 6,  7,  8, 12, 14, 16]])

print "In: concatenate((a, b), axis=1)"
print np.concatenate((a, b), axis=1)
#Out:
#array([[ 0,  1,  2,  0,  2,  4],
#       [ 3,  4,  5,  6,  8, 10],
#       [ 6,  7,  8, 12, 14, 16]])

print "In: vstack((a, b))"
print np.vstack((a, b))
#Out:
#array([[ 0,  1,  2],
#       [ 3,  4,  5],
#       [ 6,  7,  8],
#       [ 0,  2,  4],
#       [ 6,  8, 10],
#       [12, 14, 16]])

print "In: concatenate((a, b), axis=0)"
print np.concatenate((a, b), axis=0)
#Out:
#array([[ 0,  1,  2],
#       [ 3,  4,  5],
#       [ 6,  7,  8],
#       [ 0,  2,  4],
#       [ 6,  8, 10],
#       [12, 14, 16]])

print "In: dstack((a, b))"
print np.dstack((a, b))
#Out:
#array([[[ 0,  0],
#        [ 1,  2],
#        [ 2,  4]],
#
#       [[ 3,  6],
#        [ 4,  8],
#        [ 5, 10]],
#
#       [[ 6, 12],
#        [ 7, 14],
#        [ 8, 16]]])

print "In: oned = arange(2)"
oned = np.arange(2)

print "In: oned"
print oned
#Out: array([0, 1])

print "In: twice_oned = 2 * oned"
twice_oned = 2 * oned

print "In: twice_oned"
print twice_oned
#Out: array([0, 2])

print "In: column_stack((oned, twice_oned))"
print np.column_stack((oned, twice_oned))
#Out:
#array([[0, 0],
#       [1, 2]])

print "In: column_stack((a, b))"
print np.column_stack((a, b))
#Out:
#array([[ 0,  1,  2,  0,  2,  4],
#       [ 3,  4,  5,  6,  8, 10],
#       [ 6,  7,  8, 12, 14, 16]])

print "In: column_stack((a, b)) == hstack((a, b))"
print np.column_stack((a, b)) == np.hstack((a, b))
#Out:
#array([[ True,  True,  True,  True,  True,  True],
#       [ True,  True,  True,  True,  True,  True],
#       [ True,  True,  True,  True,  True,  True]], dtype=bool)

print "In: row_stack((oned, twice_oned))"
print np.row_stack((oned, twice_oned))
#Out:
#array([[0, 1],
#       [0, 2]])

print "In: row_stack((a, b))"
print np.row_stack((a, b))
#Out:
#array([[ 0,  1,  2],
#       [ 3,  4,  5],
#       [ 6,  7,  8],
#       [ 0,  2,  4],
#       [ 6,  8, 10],
#       [12, 14, 16]])

print "In: row_stack((a,b)) == vstack((a, b))"
print np.row_stack((a,b)) == np.vstack((a, b))
#Out:
#array([[ True,  True,  True],
#       [ True,  True,  True],
#       [ True,  True,  True],
#       [ True,  True,  True],
#       [ True,  True,  True],
#       [ True,  True,  True]], dtype=bool)


import numpy as np

def calcAngle_py(v1,v2):

    v1 = v1/np.linalg.norm(v1)
    v2 = v2/np.linalg.norm(v2)

    return np.arccos(np.dot(v1,v2).clip(-1,1))

def calcRodriguesMtx_py(theta,k):
    """
    Get Rodrigues' rotation matrix for
    theta rad rotation (counter clockwise) w.r.t. a unit vector k

    see:
    https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula

    """

#     assert k.size == 3 and not np.allclose(k,np.zeros(3))

    # Ensure that k is a unit vector
    k = k/np.linalg.norm(k)

    # Cross-product matrix
    K = np.array([
            [    0,-k[2], k[1]],
            [ k[2],    0,-k[0]],
            [-k[1], k[0],   0]])

    return np.eye(3)+ K*np.sin(theta) + (K@K*(1-np.cos(theta)))

def calcAreaPoly_py(verts):
    """
    ref: http://geomalgorithms.com/a01-_area.html
    """

    L = verts.shape[0]
    assert L > 2, 'length of verts must be at least 3'

    # Get sequence of index
    idx1 = np.arange(L)
    idx2 = np.arange(1,L+1)
    idx2[-1] = 0

    # Get the unit normar vector
    n = np.cross(verts[1]-verts[0],verts[2]-verts[0])
    n /= np.linalg.norm(n)

    return abs(np.dot(np.sum(np.cross(verts[idx1],verts[idx2]),axis=0),n)/2)

    # assert L == 4, "Currently handle 4 vertices"

    # # Get the unit normar vector
    # n = np.cross(verts[1]-verts[0],verts[2]-verts[0])
    # n /= np.linalg.norm(n)

    # return abs(np.dot(np.cross(verts[2]-verts[0],verts[3]-verts[1]),n)/2)

def checkBlockage(plane1,plane2,planeB,nargout=1):
    """
    Check wheter plane1 and plane2 are blocked by planeB.

    plane1, plane2, planeB are simple planes defined in the
    rectplane class.

    """

    from owcsimpy.geoutils.cutils import calcArea3DPoly

    # Unpack the tuples
    _,ctrPoint1,_,_ = plane1
    _,ctrPoint2,_,_ = plane2
    normalVectB,ctrPointB,vertsB,areaB = planeB

    # Check intersection
    # see http://geomalgorithms.com/a05-_intersect-1.html
    u = ctrPoint2-ctrPoint1
    w = ctrPoint1-ctrPointB

    # print(np.dot(normalVectB,u))

    if np.allclose(np.dot(normalVectB,u),0):
        # Line segment and the planeB is parallel
        isBlocked = False
        intersectingPoint = None
    else:

        # Parametric value of the intersecting point
        ti = -np.dot(normalVectB,w)/np.dot(normalVectB,u)

        # The intersecting point
        intersectingPoint = ctrPoint1+ti*u

        # insert V[0] as V[n], see
        # http://geomalgorithms.com/a01-_area.html
        verts = np.append(vertsB,vertsB[0]).reshape(-1,3) # num of col is 3

        listTriangleVerts = [np.array([intersectingPoint.tolist(),verts[idx].tolist(),
                      verts[idx+1].tolist(),intersectingPoint.tolist()])
                     for idx in range(4)]

        totalarea = sum([calcArea3DPoly(3,triVerts[:,0],
            triVerts[:,1],triVerts[:,2],normalVectB)
        for triVerts in listTriangleVerts])

        if np.allclose(areaB,totalarea):
            isBlocked = True
        else:
            isBlocked = False

    if nargout == 1:
        return isBlocked
    elif nargout == 2:
        return isBlocked,intersectingPoint
    elif nargout == 3:
        return isBlocked,intersectingPoint,totalarea

def checkBlockageQHull(plane1,plane2,planeB,nargout=1):
    """
    Using ConvexHull.

    This is slower, but worth having as it's more intuitive.
    We can use this to have a third opinion.

    Check wheter plane1 and plane2 are blocked by planeB.

    plane1, plane2, planeB are simple planes defined in the
    rectplane class.

    """

    from scipy.spatial import Delaunay

    # Unpack the tuples
    _,ctrPoint1,_,_ = plane1
    _,ctrPoint2,_,_ = plane2
    normalVectB,ctrPointB,vertsB,areaB = planeB

    # Check intersection
    # see http://geomalgorithms.com/a05-_intersect-1.html
    u = ctrPoint2-ctrPoint1
    w = ctrPoint1-ctrPointB

    if np.allclose(np.dot(normalVectB,u),0):
        # Line segment and the planeB is parallel
        isBlocked = False
        intersectingPoint = None
    else:

        # Parametric value of the intersecting point
        ti = -np.dot(normalVectB,w)/np.dot(normalVectB,u)

        # The intersecting point
        intersectingPoint = ctrPoint1+ti*u

        verts = vertsB
        epsilon = 1e-3
        # for vert in vertsB:
        #     verts = np.append(verts,vert+normalVectB*epsilon)
        verts = np.append(verts,verts+normalVectB*epsilon)

        verts = verts.reshape(-1,3)

        delaunay = Delaunay(verts)
        isBlocked = delaunay.find_simplex(intersectingPoint)>=0

    if nargout == 1:
        return isBlocked
    elif nargout == 2:
        return isBlocked,intersectingPoint


import math
import numpy as np

class PurityFuncs:

    @staticmethod
    def entropy(labels):
        """ Calculate label entropy

        Arguments:
            labels {np.ndarray} -- A single dimensional array that we wish to calculate the entropy of
        Returns:
            {int} -- The entropy of the labels
        """

        total = len(labels)
        unique_labels, counts = np.unique(labels, return_counts=True)
        label_counts = dict(zip(unique_labels, counts))

        entropy = 0
        for label in unique_labels:
            prob = label_counts[label] / total
            entropy -= prob * math.log2(prob)
        return entropy

    @staticmethod
    def gini(labels):
        """ Calculates gini impurity of a given node's labels

        Arguments:
            labels {np.ndarray} -- A single dimensional array that we wish to calculate the entropy of
        Returns:
            {int} -- The gini impurity of the node
                For J classes, an impurity of (J-1)/J indicates an even distribution
                e.g. gini(['a','a','b','b','c','c') = 2 / 3 = 0.66667
        """

        total = len(labels)
        _, counts = np.unique(labels, return_counts=True)
        return 1 - np.sum(list(map(lambda p: (p / total) ** 2, counts)))


import pandas as pd
from sympy import Symbol, diff, factorial
from sympy.core.add import Add
from sympy.core.mul import Mul
from typing import Union

def n_diff(f: Union[Add, Mul], variable: Symbol, n: int):
	for _ in range(n):
		f = f.diff(variable)
	return f

def taylor(f: Union[Add, Mul], variable: Symbol, x0: float = 0.0, n: int = 10):
	fn = n_diff(f, variable, n)
	if n > 0:
		return ((fn.subs({variable: x0}) / factorial(n)) * (variable - x0) ** n) + taylor(f, variable, x0, n - 1)
	else:
		return (fn.subs({variable: x0}) / factorial(n)) * (variable - x0) ** n

def function(x: float):
	return x ** 2 - 4

def bisection(f: function, a: float, b: float, tol=0.001, iterations=100, history: pd.DataFrame = None):
	p = (a + b) / 2
	if history is None: history = pd.DataFrame(columns=["a", "b", "p", "f(p)"])

	if f(a) * f(b) > 0: Exception("No root exists between {} and {}".format(a, b))

	history = history.append({
		"a": a,
		"b": b,
		"p": p,
		"f(p)": f(p)
	}, ignore_index=True)

	if abs(f(p)) > tol and iterations > 0:
		if f(a) * f(p) > 0:
			a = p
		else:
			b = p
		return bisection(f, a, b, tol, iterations - 1, history)
	else:
		return p, history

def does_converges(g: Union[Add, Mul], variable: Symbol, x0):
	return abs(g.diff(variable).subs({variable: x0}).simplify()) < 1

def direct_iteration(f: Union[Add, Mul], variable: Symbol, variable_start: float = 0.0, iterations: int = 100):
	if does_converges(f, variable, variable_start):
		if iterations > 0:
			print(variable_start)
			variable_start = f.subs({variable: variable_start}).simplify()
			print(variable_start.evalf())
			print()
			return direct_iteration(f, variable, variable_start, iterations - 1)
		else:
			print(variable_start.evalf())
			return variable_start
	else:
		print("Does not converge at starting point given")

def newtons_method(f: Union[Add, Mul], variable: Symbol, x0: float = 0.0, tol: float = 0.001, iterations: int = 100):
	f_ = f.diff(variable)
	for i in range(iterations):
		numerator = f.subs({variable: x0})
		denominator = f_.subs({variable: x0})
		x1 = x0 - numerator / denominator
		print(x1)
		if abs(x0 - x1) > tol:
			return x1
		else:
			x0 = x1
	return x0


"""
...This file implements methods for matrix manipulation, including:

    1. matrix_power() ->
        divide-and-conquer strategies for
        finding a matrix exponential.
        This function makes use of Strassen's algorithm to optimize its runtime.

    2. matrix_multiply() ->
        an implementation of Strassen's O(n^log_2(7))
        algorithm for matrix faster matrix multiplication by substituting
        brute-force multiplication with strategic multiplications and additions.

    3. matrix_sum() ->
        Given two matrices, find their sum using brute-force methods.

    4. matrix_difference() ->
        Given two matrices A, B in order, find A - B using brute_force methods.

    5. matrix_get() ->
        A helper function that, given an n x n matrix such that n % 2 = 0
        and a position 0 <= x <= 3, returns the subset of the matrix.

    6. matrix_compile() ->
        A helper function that, given a larger matrix A and a smaller matrix B
        such that dim(A) = 2 * dim(B), saves the values in B into a
        specified subset of A.

    7. matrix_identity() ->
        A function that, given a dimension n, returns the (n x n) identity matrix.

    8. matrix_transpose() ->
        Given an (m x n) matrix, returns the (n x m) transpose of the matrix.

    IMPORTANT NOTE:
        It is usual to represent matrices as an array of arrays, wherein each sub-array
        represents either a column or row of the matrix.

        In this implementation, each sub-array is interpreted to be a column of the matrix.
        It's important that the caller create/implement matrices with similar
        semantics, or the calculations are not guaranteed to be correct.

    (c) Amittai J. Wekesa (github: @siavava), May 2021.
...
"""

from timeit import timeit
import numpy as np
from numpy.random import randint
from mpl_toolkits import mplot3d
from matplotlib import pyplot as plt
from math import log, log2, ceil

def matrix_power(matrix: list, exponential: int) -> list:
    """
    Compute the power of a matrix.
    """
    if exponential == 0:
        return matrix_identity(len(matrix))
    elif exponential == 1:
        return matrix
    else:
        half_power: list = matrix_power(matrix, exponential // 2)
        power: list = matrix_multiply(half_power, half_power)
        if (exponential % 2) == 0:
            return power
        else:
            return matrix_multiply(matrix, power)

def matrix_identity(n: int) -> list:
    """
    Generate the n-sized identity matrix.
    """
    if n == 0:
        return [0]
    identity: list = []
    for col in range(n):
        column: list = []
        for row in range(n):
            if col == row:
                column.append(1)
            else:
                column.append(0)
        identity.append(column)
    return identity

def matrix_generate(n: int, maximum=10**4) -> list:
    """
    Generate a random square matrix of given size
    Maximum size of element
    """
    if n == 0:
        return [0]
    m: list = []
    for col in range(n):
        column: list = []
        for row in range(n):
            column.append(randint(maximum))
        m.append(column)
    return m

def matrix_transpose(matrix: list) -> list:
    """
    Compute the transpose of a matrix
    """
    transpose: list = []
    for row in range(len(matrix[1])):
        column = []
        for col in range(len(matrix)):
            column.append(matrix[col][row])
        transpose.append(column)
    return transpose

def matrix_multiply(mat_a: list, mat_b: list):
    """
    Multiply rwo matrices.
    """
    n: int = len(mat_a)
    if n != len(mat_b):
        print("ERROR: Cannot multiply non-square matrices using Strassen. Stop.")
        return None
    elif n % 2 != 0:
        print("ERROR: Cannot work on matrices whose dimensions are not a factor of two.")
        print("An update will be coming soon!")
        return None

    if n > 2:
        mode = "matrix"
    else:
        mode = "nums"

    # sub-nums
    if mode == "nums":
        # Step 1: compute sums
        s1 = mat_b[1][0] - mat_b[1][1]
        s2 = mat_a[0][0] + mat_a[1][0]
        s3 = mat_a[0][1] + mat_a[1][1]
        s4 = mat_b[0][1] - mat_b[0][0]
        s5 = mat_a[0][0] + mat_a[1][1]
        s6 = mat_b[0][0] + mat_b[1][1]
        s7 = mat_a[1][0] - mat_a[1][1]
        s8 = mat_b[0][1] + mat_b[1][1]
        s9 = mat_a[0][0] - mat_a[0][1]
        s10 = mat_b[0][0] + mat_b[1][0]

        # Step 2: compute products
        p1 = mat_a[0][0] * s1
        p2 = mat_b[1][1] * s2
        p3 = mat_b[0][0] * s3
        p4 = mat_a[1][1] * s4
        p5 = s5 * s6
        p6 = s7 * s8
        p7 = s9 * s10

        # Step 3: compile results
        results = [[0, 0], [0, 0]]
        results[0][0] = p5 + p4 - p2 + p6
        results[1][0] = p1 + p2
        results[0][1] = p3 + p4
        results[1][1] = p5 + p1 - p3 - p7

        return results

    # sub-matrices
    elif mode == "matrix":
        # compute matrix sums
        s1 = matrix_difference(matrix_get(mat_b, 1), matrix_get(mat_b, 3))
        s2 = matrix_sum(matrix_get(mat_a, 0), matrix_get(mat_a, 1))
        s3 = matrix_sum(matrix_get(mat_a, 2), matrix_get(mat_a, 3))
        s4 = matrix_difference(matrix_get(mat_b, 2), matrix_get(mat_b, 0))
        s5 = matrix_sum(matrix_get(mat_a, 0), matrix_get(mat_a, 3))
        s6 = matrix_sum(matrix_get(mat_b, 0), matrix_get(mat_b, 3))
        s7 = matrix_difference(matrix_get(mat_a, 1), matrix_get(mat_a, 3))
        s8 = matrix_sum(matrix_get(mat_b, 2), matrix_get(mat_b, 3))
        s9 = matrix_difference(matrix_get(mat_a, 0), matrix_get(mat_a, 2))
        s10 = matrix_sum(matrix_get(mat_b, 0), matrix_get(mat_b, 1))

        # Step 2: compute matrix products
        p1 = matrix_multiply(matrix_get(mat_a, 0), s1)
        p2 = matrix_multiply(s2, matrix_get(mat_b, 3))
        p3 = matrix_multiply(s3, matrix_get(mat_b, 0))
        p4 = matrix_multiply(matrix_get(mat_a, 3), s4)
        p5 = matrix_multiply(s5, s6)
        p6 = matrix_multiply(s7, s8)
        p7 = matrix_multiply(s9, s10)

        # Step 3: compile results
        results = [[0] * n] * n
        results = matrix_compile(results, matrix_sum(matrix_sum(p5, p4), matrix_difference(p6, p2)), 0)
        results = matrix_compile(results, matrix_sum(p1, p2), 1)
        results = matrix_compile(results, matrix_sum(p3, p4), 2)
        results = matrix_compile(results, matrix_difference(matrix_sum(p5, p1), matrix_sum(p3, p7)), 3)

        # return compiled results
        return results

def matrix_sum(mat_a: list, mat_b: list):
    """
    Compute the sum of two compatible matrices.
    """
    n: int = len(mat_a)
    if n != len(mat_b):
        print("ERROR: cannot add incompatible matrices. Stop.")
        return None
    results = []
    for col in range(n):
        column = []
        for row in range(n):
            column.append(mat_a[col][row] + mat_b[col][row])
        results.append(column)
    return results

def matrix_difference(mat_a: list, mat_b: list):
    """
    Compute the difference of two matrices.
    """
    n: int = len(mat_a)
    if n != len(mat_b):
        print("ERROR: cannot subtract incompatible matrices. Stop.")
        return None
    results = []
    for col in range(n):
        column = []
        for row in range(n):
            column.append(mat_a[col][row] - mat_b[col][row])
        results.append(column)
    return results

def matrix_compile(main: list, sub: list, pos: int):
    """
    Compile sub-matrices into a main matrix.
    """
    n = len(sub)
    total = len(main)
    if len(main) != 2 * n:
        print("ERROR: Cannot merge matrices of incompatible dimension. Stop.")
        return None

    results = []

    if pos == 0:
        for col in range(total):
            column = []
            for row in range(total):
                if col in range(n) and row in range(n):
                    column.append(sub[col][row])
                else:
                    column.append(main[col][row])
            results.append(column)
        return results

    elif pos == 1:
        for col in range(total):
            column = []
            for row in range(total):
                if col in range(n, total) and row in range(n):
                    column.append(sub[col - n][row])
                else:
                    column.append(main[col][row])
            results.append(column)
        return results

    elif pos == 2:
        for col in range(total):
            column = []
            for row in range(total):
                if col in range(n) and row in range(n, total):
                    column.append(sub[col][row - n])
                else:
                    column.append(main[col][row])
            results.append(column)
        return results

    elif pos == 3:
        for col in range(total):
            column = []
            for row in range(total):
                if col in range(n, total) and row in range(n, total):
                    column.append(sub[col - n][row - n])
                else:
                    column.append(main[col][row])
            results.append(column)
        return results

def matrix_get(main: list, pos: int):
    """
    Get a subset of a matrix.
    """
    n = len(main)
    if n % 2 != 0:
        print("ERROR: Cannot split matrix with odd num of cols/rows. Stop.")
        return None

    mid = n // 2
    sub = []

    if pos == 0:
        for col in range(mid):
            column = []
            for row in range(mid):
                column.append(main[col][row])
            sub.append(column)
        return sub

    elif pos == 1:
        for col in range(mid):
            column = []
            for row in range(mid):
                column.append(main[mid + col][row])
            sub.append(column)
        return sub

    elif pos == 2:
        for col in range(mid):
            column = []
            for row in range(mid):
                column.append(main[col][mid + row])
            sub.append(column)
        return sub

    elif pos == 3:
        for col in range(mid):
            column = []
            for row in range(mid):
                column.append(main[mid + col][mid + row])
            sub.append(column)
        return sub

if __name__ == '__main__':
    a = [[1, 1], [1, 0]]
    b = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
    d = [[1, 2, 0, 0], [3, 4, 0, 0], [0, 0, 5, 6], [0, 0, 7, 8]]
    c = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]

    h = [[1, 2], [3, 4]]
    print(matrix_multiply(d, d))
    print(matrix_power(d, 5))
    print(matrix_multiply(c, d))
    print(matrix_identity(4))
    print(matrix_transpose(b))
    print(matrix_power(b, 113))
    print(f"random matrix: {matrix_generate(8)}")

    # Add code to do the rest of this problem
    sizes: list = []
    runtimes: list = []
    data = []
    ns = [2 ** t for t in range(1, 7)]

    titles = {'family': 'serif', 'color': 'blue', 'size': 20}
    axes = {'family': 'serif', 'color': 'darkred', 'size': 15}
    power = 4
    for n in ns:
        print(f"Computing with size = {n}, power = {power}")
        matrix = matrix_generate(n, 10)
        sizes.append(n)
        data.append(timeit("matrix_power(matrix, 4)",
                                       number=10, globals=globals()))

    # Generate plots
    plt.subplot(121)
    plt.grid(True, which="both")
    plt.title("exponential = 4", fontdict=titles)
    plt.xlabel("size", fontdict=axes)
    plt.ylabel("runtime", fontdict=axes)
    plt.plot(sizes, data, "--ro")

    plt.subplot(122)
    plt.title("size = 2", fontdict=titles)
    plt.xlabel("exponential", fontdict=axes)
    plt.ylabel("runtime", fontdict=axes)
    size = 2
    maxInt = 10
    matrix = matrix_generate(size, maxInt)
    powers = []
    static_runtimes = []
    for power in range(600):
        print(f"Computing size = 2, power = {power}.")
        powers.append(power)
        static_runtimes.append(timeit("matrix_power(matrix, power)",
                                       number=10, globals=globals()))
    plt.plot(powers, static_runtimes, "--b+")
    plt.savefig('./output/matrix-powers.png', dpi=300, transparent=False)
    plt.show()
    print("FINISHED!")


#For Python 3
#System of Ordinary Differential Equations: SIR epidemiology model

import numpy as np
import scipy.integrate
import matplotlib.pyplot as plt

"""
The system of ODEs that we will solve is (SIR model):
    S'(t) = -b*S(t)*I(t)/N
    I'(t) = b*S(t)*I(t)/N - g*I(t)
    R'(t) = g*I(t)
    Constants: b, N, g
Initial Conditions:
    S(0) = 999
    I(0) = 1
    R(0) = 0
Interval:
    0 <= t <= 200
"""

def SIR(y,t,b,N,g):
    S, I, R = y
    #S = number of susceptible
    #I = number of infections
    #R = number of recovered or deceased
    #N is the total population, b is the average number of contacts per person per time
    #g is transition (recovery) rate
    #https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model

    dydt = [-1*b*S*I/N,
            b*S*I/N - g*I,
            g*I]
    return dydt

def SIRD(y,t,b,N,g,m):
    S, I, R, D = y
    #S = number of susceptible
    #I = number of infections
    #R = number of recovered
    #D = number of deaths
    #N is the total population, b is the average number of contacts per person per time
    #g is recovery rate, m is the mortality rate
    #https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIRD_model

    dydt = [-1*b*S*I/N,
            b*S*I/N - g*I - m*I,
            g*I,
            m*I]
    return dydt

def solve_SIRmodel(b,N,g,ti,tf,y0_SIR,solve_SIR=True,m=0,y0_SIRD=[]):
    """
    INPUT:
    b, N, g, ti, tf, y0_SIRD        #see SIR function
    OPTIONAL INPUT:
    ::boolean:: solve_SIR           #whether or not to solve SIR, default True; if False, solve for SIRD system
    m, y0_SIRD                      #see SIRD function
    """
    t = np.linspace(ti, tf, 5000)
    if solve_SIR == True:
        #https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html
        #SIR model:
        sol_SIR = scipy.integrate.odeint(SIR, y0_SIR, t, args=(b, N, g))
        plt.title('SIR Model')
        plt.plot(t, sol_SIR[:,0], 'y', label='S(t)')
        plt.plot(t, sol_SIR[:,1], 'r', label='I(t)')
        plt.plot(t, sol_SIR[:,2], 'b', label='R(t)')
        plt.legend(loc='best')
        plt.xlabel('t')
        plt.grid()
        plt.show()
    else:
        #SIRD model (includes death rate):
        sol_SIRD = sol_SIR = scipy.integrate.odeint(SIRD, y0_SIRD, t, args=(b, N, g, m))
        plt.title('SIRD Model')
        plt.plot(t, sol_SIRD[:,0], 'y', label='S(t)')
        plt.plot(t, sol_SIRD[:,1], 'r', label='I(t)')
        plt.plot(t, sol_SIRD[:,2], 'b', label='R(t)')
        plt.plot(t, sol_SIRD[:,3], 'black', label='D(t)')
        plt.legend(loc='best')
        plt.xlabel('t')
        plt.grid()
        plt.show()

def main():
    b = 0.2
    N = 1000
    g = 0.1

    y0_SIR = [999,1,0] #Initial Conditions for SIR model

    m = 0.02
    y0_SIRD = [999,1,0,0] #Initial Conditions for SIRD model

    #set of b and g values to plot
    b_set = [0.2,0.5,0.12,0.4]
    g_set = [0.1,0.1,0.07,0.3]
    try:
        assert(len(b_set) == len(g_set)) #check if both lists are same length
    except AssertionError:
        print("ERROR: Lists not the same length")
        return False

    #Interval
    ti = 0
    tf = 200

    for i in range(0,len(b_set),1):
        solve_SIRmodel(b_set[i],N,g_set[i],ti,tf,y0_SIR)

    solve_SIRmodel(b,N,g,ti,tf,y0_SIR,False,m,y0_SIRD)

if __name__ == "__main__":
    main()


import numpy as np

def QRDecomposition(A):
	n = np.shape(A)[0] #pegando o tamanho das linhas de A
	m = np.shape(A)[1] #pegando o tamanho das colunas de A
	Q = np.zeros((n,m)) #declarando a matriz Q
	R = np.zeros((m,m)) #declarando a matriz R

	for j in range(0, m):
		A_column = A[:, j] #pegando as colunas da matriz A
		V = np.zeros(n) #declarando o vetor V
		V = A_column #V igual a coluna j de A
		for i in range (0, j):
			R[i,j] = Q[:,i].dot(A_column) #fazendo o calculo do R[i,j] = coluna i de Q * coluna j de A ( i != j)
			V -= (Q[:,i].dot(A_column))*Q[:,i] #fazendo o calculo de V
		R[j,j] = np.linalg.norm(V) # R[j,j] = norma da coluna j de A (i == j)
		Q[:,j] = V/np.linalg.norm(V) #normalizando V e atribuindo a coluna j de Q

	return Q, R

def QRDecompositionModificada(A):
	n = np.shape(A)[0] #pegando o tamanho das linhas de A
	m = np.shape(A)[1] #pegando o tamanho das colunas de A
	Q = np.zeros((n,m)) #declarando a matriz Q
	R = np.zeros((m,m)) #declarando a matriz R
	V = np.copy(A) #copiando A para V

	for j in range(0, m):
		for i in range (0, j):
			R[i,j] = Q[:,i].dot(V[:,j]) #fazendo o calculo do R[i,j] = coluna i de Q * coluna j de V ( i != j)
			V[:,j] -= (Q[:,i].dot(V[:,j]))*Q[:,i] #fazendo o calculo de V
		R[j,j] = np.linalg.norm(V[:,j]) # R[j,j] = norma da coluna j de V (i == j)
		Q[:,j] = V[:,j]/np.linalg.norm(V[:,j]) #normalizando V e atribuindo a coluna j de Q

	return Q, R

A = np.array([[1,2],[1,3],[-2,0]], dtype='double')
B = np.array([[3,1], [4,-1]], dtype = 'double')

print('Decomposicao QR classica\n')

(Q, R) = QRDecomposition(A)
print('{}\n\n{}\n\n{}'.format(Q, R, Q.dot(R)))
print('\n')
(Q, R) = QRDecomposition(B)
print('{}\n\n{}\n\n{}'.format(Q, R, Q.dot(R)))

print('\n\n')

A = np.array([[1,2],[1,3],[-2,0]], dtype='double')
B = np.array([[3,1], [4,-1]], dtype = 'double')

print('Decomposicao QR do Python\n')

(Q_python,R_python) = np.linalg.qr(A)
print('{}\n\n{}\n\n{}'.format(Q_python, R_python, Q_python.dot(R_python)))
print('\n')
(Q_python,R_python) = np.linalg.qr(B)
print('{}\n\n{}\n\n{}'.format(Q_python, R_python, Q_python.dot(R_python)))

print('\n\n')

A = np.array([[1,2],[1,3],[-2,0]], dtype='double')
B = np.array([[3,1], [4,-1]], dtype = 'double')

print('Decomposicao QR modificada\n')

(Q, R) = QRDecompositionModificada(A)
print('{}\n\n{}'.format(Q, R))
print('\n')
(Q, R) = QRDecomposition(B)
print('{}\n\n{}'.format(Q, R))


import numpy as np
from math import log2

def mean(X):
    """ Average value of a series of data """
    X = np.array(X)
    return (np.sum(X) / len(X))

def softmax(X):
    exps = np.exp(X - np.max(X))
    return exps / np.sum(exps)

def softmax_grad(softmax):
    # Reshape the 1-d softmax to 2-d so that np.dot will do the matrix multiplication
    s = softmax.reshape(-1,1)
    return np.diagflat(s) - np.dot(s, s.T)

def stablesoftmax(x):
    """Compute the softmax of vector x in a numerically stable way."""
    shiftx = x - np.max(x)
    exps = np.exp(shiftx)
    return exps / np.sum(exps)

def cross_entropy(y_hat, y):
    eps = 1e-15
    y = np.squeeze(y)
    c = sum((y * np.log(y_hat + eps)) + ((1 - y) * np.log(1 - y_hat + eps))) / -len(y)
    return c

def sigmoid(X):
    """ Sigmoid Function """
    return (1 / (1 + np.exp(-X)))

def relu(X):
    if (isinstance(X, np.ndarray)):
        return np.array([max(n, 0) for n in X])
    else:
        return max(X, 0)

def delta_cross_entropy(X,y):
    """
    X is the output from fully connected layer (num_examples x num_classes)
    y is labels (num_examples x 1)
    	Note that y is not one-hot encoded vector.
    	It can be computed as y.argmax(axis=1) from one-hot encoded vectors of labels if required.
    """
    m = y.shape[0]
    grad = softmax(X)
    grad[range(m),y] -= 1
    grad = grad/m
    return grad

def softmax_crossentropy_with_logits(logits, reference_answers):
    logits_for_answers = logits[np.arange(len(logits)), reference_answers]
    xentropy = - logits_for_answers + np.log(np.sum(np.exp(logits) + 1e-14, axis=-1))
    return xentropy

def grad_softmax_crossentropy_with_logits(logits, reference_answers):
    ones_for_answers = np.zeros_like(logits)
    ones_for_answers[np.arange(len(logits)), reference_answers] = 1

    softmax = np.exp(logits) / np.exp(logits).sum(axis=-1, keepdims=True)

    return (- ones_for_answers + softmax) / logits.shape[0]

if __name__ == '__main__':
    X = np.array([1, 2, 3, 4, 5])
    z = [1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0]
    print(sigmoid(X))
    print(softmax(z))
    y = np.array([0.1, 0.3])
    ypred = np.array([0.5, 0.6])

    print()
    print(cross_entropy(y, ypred))
    x2 = np.array([-1, -2.8, -0, 1.4, 1])
    print(relu(x2))


#ZADANIE 2
#Wykorzystaj metode eliminacji Gaussa do znalezienia
#rozwiazan ukladow rownan a) – d). Wykonaj obliczenia przy
#uzyciu udostepnionej funkcji i programu napisanego w Python.
#Przeanalizuj i opisz w sprawozdaniu dzialanie funkcji realizującej
#metodę eliminacji Gaussa.

import numpy as np

def gaussElimin(a,b):
    n = len(b)
    #procedura eliminacji
    for k in range(0, n-1):
        for i in range(k+1, n):
            if a[i, k] != 0.0:
                lam = a[i, k] / float(a[k, k])
                a[i, k+1:n] = a[i, k+1:n] - lam * a[k, k+1:n]
                b[i] = b[i] - lam * b[k]
    #procedura wyliczania rozwiazania i zapisu do macierzy b
    for k in range(n-1, -1, -1):
        b[k] = (b[k] - np.dot(a[k, k+1:n], b[k+1:n])) / float(a[k, k])
    return b

def testA():
    #macierze jako float
    a = np.array([[3.0, 1.0, -1.0], [1.0, 2.0, -1.0], [1.0, 1.0, 5.0]])
    b = np.array([181.05, 108.35, 142.55])
    print('Rozwiazanie(np.linalg.solve):')
    print(np.linalg.solve(a, b))
    Iout = gaussElimin(a,b)
    print('Rozwiazanie(gaussElimin):')
    print(Iout)

def testB():
    #macierze jako float
    a = np.array([[1.0, -1.0, 2.0], [3.0, 2.0, 1.0], [2.0, -3.0, -2.0]])
    b = np.array([5.0, 10.0, -10.0])
    print('Rozwiazanie(np.linalg.solve):')
    print(np.linalg.solve(a, b))
    Iout = gaussElimin(a,b)
    print('Rozwiazanie(gaussElimin):')
    print(Iout)

def testC():
    #macierze jako float
    a = np.array([[5.0, 1.0, 1.0, 1.0], [2.0, -1.0, -1.0, 1.0], [3.0, -1.0, 2.0, -2.0], [5.0, -4.0, 3.0, -2.0]])
    b = np.array([685.0, 165.0, 256.0, 361.0])
    print('Rozwiazanie(np.linalg.solve):')
    print(np.linalg.solve(a, b))
    Iout = gaussElimin(a,b)
    print('Rozwiazanie(gaussElimin):')
    print(Iout)

def testD():
    #macierze jako float
    a = np.array([[1.0, 3.0, 5.0], [2.0, 5.0, 1.0], [2.0, 3.0, 8.0]])
    b = np.array([10.0, 8.0, 3.0])
    print('Rozwiazanie(np.linalg.solve):')
    print(np.linalg.solve(a, b))
    Iout = gaussElimin(a,b)
    print('Rozwiazanie(gaussElimin):')
    print(Iout)

if __name__ == "__main__":
    print(30 * '-')
    print("Funkcja A")
    testA()
    print(30 * '-')

    print(30 * '-')
    print("Funkcja B")
    testB()
    print(30 * '-')

    print(30 * '-')
    print("Funkcja C")
    testC()
    print(30 * '-')

    print(30 * '-')
    print("Funkcja D")
    testD()
    print(30 * '-')

#Funkcja napisana samodzielnie w Python zwraca identyczne wyniki
#jak funkcja biblioteczna numpy.linalg.solve().
#Metoda eliminacji Gaussa wykorzystuje dwie fazy obliczen.
#Pierwsza faza jest faza eliminacji, ktora sprowadza problem obliczeniowy do Ux = c.
#Druga faza jest faza wstecznego podstawienia, ktora polega na podstawianiu
#uzyskanych rozwiazan za niewiadome.
#Wystepujaca w programie funkcja sklada sie z dwoch algorytmow.
#Pierwszy algorytm realizuje faze eliminacji.
#Drugi algorytm odpowiada za wyliczenie rozwiazan i ich zapis do macierzy b.


#gradient descent learning
#steven 29/02/2020 Initial
#11/03/2020  add plotSeekPoint to plot the seek process
#import random
import matplotlib.pyplot as plt
import numpy as np
from plotCommon import plotSub

def difference_derivative(f,x,h=0.00001): #one parameter fuction derivate
    return (f(x+h)-f(x))/h

def question_linearFuc(x):
    a = 6.5   #must a > 0
    b = 1.88 #the final optimum x
    c = -8.8  #the final minimum value
    return a*(x - b)**2 + c

def question_linearFucMin():
    """seek x, make f(x) = ax^2 + bx + c (a>0)  minimize
    this fuction is always as the simplest(one feature data) cost fuction in machine learning
    """

    #tolerance = 0.0000000001  #1.0e-15
    #max_iter = 10000
    iter = 0
    alpha = 0.001 #0.01 #learning rate
    seekList=[]

    x = -10  #random.random()
    stepInter = 0
    while True:
        gradient = difference_derivative(question_linearFuc,x,h=0.0001)

        x_next = x - alpha * gradient

        if iter % 20 == 0:
            print(iter," :",x,gradient,x_next,question_linearFuc(x),stepInter)
            seekList.append(x)

        #stepInter = (question_linearFuc(x) - question_linearFuc(x_next))**2
        stepInter = abs(question_linearFuc(x) - question_linearFuc(x_next))
        #if (stepInter < tolerance) or (iter > max_iter) :
        #if (stepInter - tolerance == 0) or (iter > max_iter) :
        #if (stepInter == 0) or (iter > max_iter) or (x - x_next == 0):
        if (stepInter == 0) or (x - x_next == 0):
            break

        x = x_next
        iter += 1

    print (iter,'result: x=',x, 'minValue:',question_linearFuc(x))

    plotSeekPoint(seekList)

def plotSeekPoint(seekList):
    _, ax = plt.subplots()

    x = np.linspace(-10,10,20)
    y = question_linearFuc(x)
    ax.plot(x,y) #plot function curve

    print(len(seekList))
    x = np.array(seekList)
    y = question_linearFuc(x)
    ax.scatter(x, y, s=10, color='r', alpha=0.75) #plot seekpoint in the seeking process
    plt.show()

def question_CurvePeakAndValley():
    """seek x, findout all curve's peak and valleys, that is where the derivative equal to 0"""
    def func(x):
        #return x**4
        #return  (x**3 + 3*x**2-6*x-8)/4
        return x**5 + 2*x**4 + 3*x**3 + 8*x**2 + 10*x

    x = np.linspace(-1.25,-0.65, 100)
    z0=[]
    for i, _ in enumerate(x):
        if i>=len(x)-1:
            break
        d1 = difference_derivative(func,x[i])
        d2 = difference_derivative(func,x[i+1])
        if d1*d2<=0:
            print('x = ',x[i])
            z0.append(x[i])
        #print('d1,d2,* = ',d1,d2,d1*d2)

    ax = plt.subplot(1,1,1)
    plotSub(x, func(x), ax, label='func',color='b')
    for k in z0:
        plt.vlines(k, np.min(func(x)), func(x[-1]),linestyles='dotted', color='r')
    #plt.axis('square')
    plt.legend()
    plt.grid()
    plt.show()

def main():
    #question_linearFucMin()
    question_CurvePeakAndValley()

if __name__ == '__main__':
    main()


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

def trapezoid(f, a, b, pieces, graph=False):
    """Find the integral of the function f between a and b using pieces trapezoids
    Args:
        f: function to integrate
        a: lower bound of integral
        b: upper bound of integral
        pieces: number of pieces to chop [a,b] into

    Returns:
        estimate of integral
    """
    integral = 0
    h = b - a
    if (graph):
        x = np.linspace(a,b,100)
        plt.plot(x,f(x),label="f(x)")
        ax = plt.subplot(111)
    #initialize the left function evaluation
    fa = f(a)
    for i in range(pieces):
        #evaluate the function at the left end of the piece
        fb = f(a+(i+1)*h/pieces)
        integral += 0.5*h/pieces*(fa + fb)
        if (graph):
            verts = [(a+i*h/pieces,0),(a+i*h/pieces,fa), (a+(i+1)*h/pieces,fb),(a+(i+1)*h/pieces,0)]
            poly = Polygon(verts, facecolor='0.8', edgecolor='k')
            ax.add_patch(poly)
        #now make the left function evaluation the right for the next step
        fa = fb

    if (graph):
        ax.set_xticks((a,b))
        ax.set_xticklabels(('a','b'))
        plt.xlabel("x")
        plt.ylabel("f(x)")
        if (pieces > 1):
            plt.title("Trapezoid Rule with " + str(pieces) + " pieces")
        else:
            plt.title("Trapezoid Rule with " + str(pieces) + " piece")
        plt.show()
    return integral

def trapezoid_rec(f, a, b, epsilon = 1.0e-6, old = -1.0e16, depth=0, graph=False):
    """Find the integral of the function f between a and b using pieces trapezoids
    Args:
        f: function to integrate
        a: lower bound of integral
        b: upper bound of integral
        old: keeps track of how much the estimate changes from recursion to level
        depth: how many levels of recursion do we have, python only allows so many

    Returns:
        estimate of integral
    """
    h = b - a
    #break interval into two pieces and do trapezoid on each
    if (graph) and (depth == 0):
        x = np.linspace(a,b,100)
        plt.plot(x,f(x),label="f(x)")

    new_estimate_left = 0.25*h*(f(a) + f(a+0.5*h))
    new_estimate_right = 0.25*h*(f(a+0.5*h) + f(b))

    #check to see if the sum of the two pieces is close to the old guess
    if (np.fabs(new_estimate_left + new_estimate_right - old) > epsilon) and (depth < 100):
        #if not, then call trapezoid_rec again on each half-interval
        integral = (trapezoid_rec(f,a,a+0.5*h,epsilon=epsilon, old=new_estimate_left,depth=depth+1,graph=graph) +
                    trapezoid_rec(f,a+0.5*h,b,epsilon=epsilon,old=new_estimate_right,depth=depth+1,graph=graph))

        return integral
    else:
        #halving the interval didn't change much so we can stop

        if (graph):
            ax = plt.subplot(111)
            verts = [(a,0),(a,f(a)), (a+0.5*h,f(a+0.5*h)),(a+0.5*h,0)]
            poly = Polygon(verts, facecolor='0.8', edgecolor='k')
            ax.add_patch(poly)
            verts = [(a+0.5*h,0),(a+0.5*h,f(a+0.5*h)), (b,f(b)),(b,0)]
            poly = Polygon(verts, facecolor='0.8', edgecolor='k')
            ax.add_patch(poly)

        if (graph) and (depth == 0):
            ax.set_xticks((a,b))
            ax.set_xticklabels(('a','b'))
            plt.xlabel("x")
            plt.ylabel("f(x)")
            plt.title("Trapezoid Rule with recursion")
            plt.show()
        return new_estimate_left + new_estimate_right

def quadratic_interp(a,f,x):
    """Compute at quadratic interpolant
    Args:
        a: array of the 3 points
        f: array of the value of f(a) at the 3 points
    Returns:
        The value of the linear interpolant at x
    """
    answer = (x-a[1])*(x-a[2])/(a[0]-a[1])/(a[0]-a[2])*f[0]
    answer += (x-a[0])*(x-a[2])/(a[1]-a[0])/(a[1]-a[2])*f[1]
    answer += (x-a[0])*(x-a[1])/(a[2]-a[0])/(a[2]-a[1])*f[2]
    return answer

def simpsons(f, a, b, pieces, graph=False):
    """Find the integral of the function f between a and b using Simpson's rule
    Args:
        f: function to integrate
        a: lower bound of integral
        b: upper bound of integral
        pieces: number of pieces to chop [a,b] into

    Returns:
        estimate of integral
    """
    integral = 0
    h = b - a
    one_sixth = 1.0/6.0
    if (graph):
        x = np.linspace(a,b,100)
        plt.plot(x,f(x),label="f(x)")
        ax = plt.subplot(111)

    #initialize the left function evaluation
    fa = f(a)
    for i in range(pieces):
        #evaluate the function at the left end of the piece
        fb = f(a+(i+1)*h/pieces)
        fmid = f(0.5*(a+(i+1)*h/pieces+ a+i*h/pieces))
        integral += one_sixth*h/pieces*(fa + 4*fmid + fb)
        if (graph):
            ix = np.arange(a+i*h/pieces, a+(i+1)*h/pieces, 0.001)
            iy = quadratic_interp(np.array([a+i*h/pieces,0.5*(a+(i+1)*h/pieces+ a+i*h/pieces),a+(i+1)*h/pieces]),
                                  np.array([fa,fmid,fb]),ix)
            verts = [(a+i*h/pieces,0)] + list(zip(ix,iy)) + [(a+(i+1)*h/pieces,0)]
            poly = plt.Polygon(verts, facecolor='0.8', edgecolor='k')
            ax.add_patch(poly)
        #now make the left function evaluation the right for the next step
        fa = fb

    if (graph):
        ax.set_xticks((a,b))
        ax.set_xticklabels(('a','b'))
        plt.xlabel("x")
        plt.ylabel("f(x)")
        plt.title("Simpsons Rule with " + str(pieces) + " pieces")
        plt.show()
    return integral

def RichardsonExtrapolation(fh, fhn, n, k):
    """Compute the Richardson extrapolation based on two approximations of order k
    where the finite difference parameter h is used in fh and h/n in fhn.
    Inputs:
    fh:  Approximation using h
    fhn: Approximation using h/n
    n:   divisor of h
    k:   original order of approximation

    Returns:
    Richardson estimate of order k+1"""
    n = decimal.Decimal(n)
    k = decimal.Decimal(k)
    numerator = decimal.Decimal(n**k * decimal.Decimal(fhn) - decimal.Decimal(fh))
    denominator = decimal.Decimal(n**k - decimal.Decimal(1.0))
    return float(numerator/denominator)

def Romberg(f, a, b, MaxLevels = 10, epsilon = 1.0e-6, PrintMatrix = False):
    """Compute the Romberg integral of f from a to b
    Inputs:
    f:  integrand function
    a: left edge of integral
    b: right edge of integral
    MaxLevels: Number of levels to take the integration to

    Returns:
    Romberg integral estimate"""

    estimate = np.zeros((MaxLevels,MaxLevels))

    estimate[0,0] = trapezoid(f,a,b,pieces=1)
    count = 1
    converged = 0
    while not(converged):
        estimate[count,0] = trapezoid(f,a,b,pieces=2**count)
        for extrap in range(count):
            estimate[count,1+extrap] = RichardsonExtrapolation(estimate[count-1,extrap],
                                                               estimate[count,extrap],2,2**(extrap+1))

        converged = np.fabs(estimate[count,count] - estimate[count-1,count-1]) < epsilon
        if (count == MaxLevels-1): converged = 1
        count += 1
    if (PrintMatrix):
        print(estimate[0:count,0:count])
    return estimate[count-1, count-1]

def RombergSimpson(f, a, b, MaxLevels = 10, epsilon = 1.0e-6, PrintMatrix = False):
    """Compute the Romberg integral of f from a to b
    Inputs:
    f:  integrand function
    a: left edge of integral
    b: right edge of integral
    MaxLevels: Number of levels to take the integration to

    Returns:
    Romberg integral estimate"""

    estimate = np.zeros((MaxLevels,MaxLevels))

    estimate[0,0] = simpsons(f,a,b,pieces=1)
    count = 1
    converged = 0
    while not(converged):
        estimate[count,0] = simpsons(f,a,b,pieces=2**count)
        for extrap in range(count):
            estimate[count,1+extrap] = RichardsonExtrapolation(estimate[count-1,extrap],
                                                               estimate[count,extrap],n=2,k=2+2.0**(extrap+1))

        converged = np.fabs(estimate[count,count] - estimate[count-1,count-1]) < epsilon
        if (count == MaxLevels-1): converged = 1
        count += 1
    if (PrintMatrix):
        print(estimate[0:count,0:count])
    return estimate[count-1, count-1]


# 面试题9：斐波那契数列题目一：写一个函数，输入n，
# 求斐波那契（Fibonacci）数列的第n项。斐波那契数列的定义如下：
import numpy as np
def fib(n):
    if n <= 2:
        return 1

    return fib(n-1)+fib(n-2)

memo = {}
def fib_memo(n):
    if n <= 2:
        return 1
    if n in memo:
        return memo[n]

    memo[n] = fib(n-1)+fib(n-2)

    return memo[n]

def fib_iter(n):

    a,b = 1,1

    if n <= 2:
        return 1

    for _ in range(n-2):
        a,b = b,a+b

    return b

def matrixMulti(A,B):
    length = len(A)
    width = len(B[0])
    res = [[0]*width for _ in range(length)]
    print(res)
    for i in range(length):
        for j in range(width):
            for k in range(len(B)):
                # print("i:{},j:{},k:{}".format(i,j,k))
                # print("".format(i,k,A[i][k],k,j,B[k][j]))
                # print("res[{}][{}]:{}".format(i,j,res[i][j]))
                res[i][j] += A[i][k] * B[k][j]
                # print("A[{}][{}]:{}*B[{}][{}]:{}=res[{}][{}]:{}".format(i,k,A[i][k],k,j,B[k][j],i,j,res[i][j]))
    return res

def fibLog(matrix,n):

    res = [[1,0],[0,1]]

    tmp = matrix

    while n > 0:

        if n & 1 != 0:
            res = np.dot(res,tmp)

        tmp = np.dot(tmp,tmp)

        n >>= 1
    return res

    # if n == 0:
    #     return [[1,0],[0,1]]

    # if n == 1:
    #     return matrix

    # res = fibLog(matrix,n>>1)

    # res = np.dot(res,res)

    # if n & 0x1 == 1:
    #     res = np.dot(matrix,res)

    return res

if __name__ == "__main__":
    # print(fib(5))
    # print(fib(10))
    # print(fib_memo(10))
    # print(fib_iter(10))

    # A = [[1,2,3],[4,5,6]]
    # B = [[1,2],[3,4],[5,6]]
    # print(matrixMulti(A,B))
    # print(matrixMulti([[1,1],[1,0]],[[1,1],[1,0]]))
    print(fibLog([[1,1],[1,0]],3))
    # print(np.dot([[1,1],[1,0]],[[1,1],[1,0]]))


#########################################################################
#            SIMPSON'S RULES
#
# This module (based on Mike Zingale's code)
#    1) calculates the integral of f(x) = sin (pi x) over the
#     interval [0,1], using N = 3,7,15 and 31 slabs/intervals, for odd Ns.
#    2) Plots the absolute error vs delta = (b-a)/N.
#
#    (Marina von Steinkirch, spring 2013)
#########################################################################

import math
import numpy
import pylab

""" Functions """

def func(x):
    """ function to integrate"""
    fx = numpy.sin(math.pi*x)
    return fx

def exact_int(a,b):
    """ analytic value of the integral """
    I = (numpy.cos(math.pi*a) - numpy.cos(math.pi*b ))/math.pi
    return I

def simpson_int_odd(a,b,f,N):
    """ calculates remaining odd slabs """
    xedge = numpy.linspace(a,b,N+1)
    delta = xedge[1] - xedge[0]
    Is_odd = (delta/12.0)*(-f(xedge[N-2]) + 8.0*f(xedge[N-1]) + 5.0*f(xedge[N]))
    return Is_odd

def simpson_int(a,b,f,N):
    """"do a Simpson's integration by breaking up the domain [a,b] into N, considering N is even."""
    # MZ -- you were passing in the wrong N, so the xedge was wrong
    xedge = numpy.linspace(a,b,N+1)
    delta = xedge[1] - xedge[0]
    Is = 0.0
    n = 0
    # MZ: with the proper N, this loop executed too many times
    while n < N-1:
        Is += (delta/3.0)*(f(xedge[n]) + 4.0*f(xedge[n+1]) + f(xedge[n+2]))
        n += 2

    return Is

def printing(slab, I, Is, ea):
    """ print output """
    print "\nNumber of slabs to be Integrated: ", slab
    print "Simpson's Integral value: ", Is
    print "Analytic Integral value: ", I
    print "Absolute error: ", ea
    return 0

""" Variables """
CONST_A = 0.0
CONST_B = 1.0
CONST_N_SLABS = [3, 7, 15, 31]

ea_array = []
delta_array = []

""" Main Function"""
""" Since the intervals are odd, we need to do extra calculations in the edges """
for i in range (len(CONST_N_SLABS)):
    slab = CONST_N_SLABS[i]

    if not slab%2 == 0:
        Is_odd = simpson_int_odd(CONST_A, CONST_B, func, slab)
        edge = slab - 1

    else:
        edge = slab
        Is_odd = 0.0

    # MZ: you don't call this with edge -- your linspace needs to know the
    # correct N (not N-1) to get the right xedge values, so you call
    # with slab
    Is = simpson_int(CONST_A, CONST_B, func, slab)
    Is = Is + Is_odd
    I = exact_int(CONST_A, CONST_B)

    ea =   abs(Is - I)
    delta = abs(CONST_B -CONST_A)/slab

    printing(slab, I, Is, ea)

    ea_array.append(ea)
    delta_array.append(delta)

""" Plotting ea vs delta"""
pylab.loglog(delta_array, ea_array,  'bo')
pylab.xlabel('$\delta$ = (b-a)/N')
pylab.ylabel('Absolute Error')
pylab.grid(True)
pylab.savefig("simp.png")

""" Plotting ea vs N"""
pylab.clf()
pylab.cla()
pylab.loglog(CONST_N_SLABS, ea_array,  'go')
pylab.xlabel('N')
pylab.ylabel('Absolute Error')
pylab.grid(True)
pylab.savefig("simp2.png")

print "\n\nDone!"


## Import the packages
import numpy as np
from scipy import stats

## Define 2 random distributions
#Sample Size
N = 10
#Gaussian distributed data with mean = 2 and var = 1
a = np.random.randn(N) + 2
#Gaussian distributed data with with mean = 0 and var = 1
b = np.random.randn(N)

## Calculate the Standard Deviation
#Calculate the variance to get the standard deviation

#For unbiased max likelihood estimate we have to divide the var by N-1, and therefore the parameter ddof = 1
var_a = a.var(ddof=1)
var_b = b.var(ddof=1)

#std deviation
s = np.sqrt((var_a + var_b)/2)
s

## Calculate the t-statistics
t = (a.mean() - b.mean())/(s*np.sqrt(2/N))

## Compare with the critical t-value
#Degrees of freedom
df = 2*N - 2

#p-value after comparison with the t
p = 1 - stats.t.cdf(t,df=df)

print("t = " + str(t))
print("p = " + str(2*p))
### You can see that after comparing the t statistic with the critical t value (computed internally) we get a good p value of 0.0005 and thus we reject the null hypothesis and thus it proves that the mean of the two distributions are different and statistically significant.

## Cross Checking with the internal scipy function
t2, p2 = stats.ttest_ind(a,b)
print("t = " + str(t2))
print("p = " + str(p2))

print("ACRL test!")
a =  np.array([0.92,0.93,0.93,0.96,0.90])
b =  np.array([0.87,0.00,0.76,0.91,0.87])
c =  np.array([0.87,0.76,0.91,0.87])
b[1] = c.mean()
t2, p2 = stats.ttest_ind(a,b)
print("improve: " + str(a.mean()-b.mean()))
print("t = " + str(t2))
print("p = " + str(p2))

print("Time test!")
a =  np.array([102, 105, 98])
b =  np.array([1.08, 1.25, 1.15, 1.24, 1.15])
t2, p2 = stats.ttest_ind(a,b)
print("improve: " + str(a.mean()/b.mean()))
print("t = " + str(t2))
print("p = " + str(p2))


import numpy as np
import pandas as pd

def asset_over_time_apy(P:int, r:float, n:int, t:float) -> pd.DataFrame:
    """
    Compute the ending value of some investment after a certain amount of time with compound interest.

    Parameters
    ---------
    P: int
        Initial investment
    r: float
        Annual Interest Rate
    n: int
        Number of compounding periods per year
    t: int
        Number of years

    Returns
    ---------
    amount: pd.DataFrame
        Final amount

    Examples
    ---------

    >>> # Suppose we invest $5,000 into an investment that compounds at 6% annually.
    >>> # Calculate the ending value of this investment after 10 years:
    >>> P, r, n, t = 5000, .06, 1, 10
    >>> df = asset_over_time_apy(P=5000, r=.06, n=1, t=10)
    >>> # Expected df[-1] = 8954.238483

    """

    df = pd.DataFrame(data={'time': np.linspace(start=1, stop=t, num=t)})
    df['value']= df.applymap(lambda t: P*(pow((1+r/n), n*t)))

    return df

if __name__ == "__main__":
   """
   Run for testing only and store example
   """

   """
   https://www.statology.org/compound-interest-in-python/
   Example 1: Compound Interest Formula with ANNUAL Compounding
   Suppose we invest $5,000 into an investment that compounds at 6% annually.
   Calculate the ending value of this investment after 10 years:
   """

   df = asset_over_time_apy(P=5000, r=.06, n=1, t=10)
   print(df)
   # Expected df[-1] = 8954.238483

   """
   Example 2: Compound Interest Formula with MONTHLY Compounding
   Suppose we invest $1,000 into an investment that compounds at 6% annually
   and is compounded on a monthly basis (12 times per year).
   Calculate the ending value of this investment after 5 years:
   """

   df = asset_over_time_apy(P=1000, r=.06, n=12, t=2)
   print(df)


import numpy as np

def cross_product_matrix(in_vector):
    """
    Return the cross product matrix of a 3d vector

    For more information, see:
    https://en.wikipedia.org/wiki/Cross_product#Conversion_to_matrix_multiplication

    Parameters
    ----------
    in_vector : 3 x 1 numpy matrix
        Input vector
    Returns
    -------
    out_mat = 3 x 3 numpy array
        The cross product matrix of in_vector

    """
    # Alternate implementation for curiosity
    # I = np.identity(3)
    # out_mat = np.zeros((3,3))
    # for row in I:
    #     cross = np.cross(in_vector,row)
    #     outer = np.outer(cross,row)
    #     out_mat += outer
    out_mat = np.array([[0.,-in_vector[2],in_vector[1]],
                        [in_vector[2],0,-in_vector[0]],
                        [-in_vector[1],in_vector[0],0.]])
    return out_mat

def rotation_matrix(axis_vector, angle, degrees = True):
    """
    Return the rotation matrix corresponding to a rotation axis and angle

    For more information, see:
    https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

    Parameters
    ----------
    axis_vector : 3 x 1 numpy array
        A unit vector of the axis of rotation
    angle : float
        Angle of rotation in degrees unless otherwise specified
    degrees : bool (optional)
        Choose between units of degrees of radians. Default True so degrees
    Returns
    -------
    rot_mat : 3 x 3 numpy array
        Rotation matrix

    """
    ang = angle
    if degrees:
        ang = np.radians(ang)
    # the matrix the sum of 3 terms
    cos_id = np.cos(ang) * np.identity(3)
    sin_cross = np.sin(ang) * cross_product_matrix(axis_vector)
    cos_tens = (1-np.cos(ang)) * np.outer(axis_vector,axis_vector)
    # total
    rot_mat = cos_id + sin_cross + cos_tens

    return rot_mat


# mean

# The mean tool computes the arithmetic mean along the specified axis.
# import numpy
# my_array = numpy.array([ [1, 2], [3, 4] ])
# print numpy.mean(my_array, axis = 0)        #Output : [ 2.  3.]
# print numpy.mean(my_array, axis = 1)        #Output : [ 1.5  3.5]
# print numpy.mean(my_array, axis = None)     #Output : 2.5
# print numpy.mean(my_array)                  #Output : 2.5

# var
# The var tool computes the arithmetic variance along the specified axis.
# import numpy
# my_array = numpy.array([ [1, 2], [3, 4] ])
# print numpy.var(my_array, axis = 0)         #Output : [ 1.  1.]
# print numpy.var(my_array, axis = 1)         #Output : [ 0.25  0.25]
# print numpy.var(my_array, axis = None)      #Output : 1.25
# print numpy.var(my_array)                   #Output : 1.25

# By default, the axis is None. Therefore, it computes the variance of the flattened array.

# std
# The std tool computes the arithmetic standard deviation along the specified axis.
# import numpy
# my_array = numpy.array([ [1, 2], [3, 4] ])
# print numpy.std(my_array, axis = 0)         #Output : [ 1.  1.]
# print numpy.std(my_array, axis = 1)         #Output : [ 0.5  0.5]
# print numpy.std(my_array, axis = None)      #Output : 1.11803398875
# print numpy.std(my_array)                   #Output : 1.11803398875

# By default, the axis is None. Therefore, it computes the standard deviation of the flattened array.

# Problem Statement:

# You are given a 2-D array of size X.
# Your task is to find:
# The mean along axis
# The var along axis
# The std along axis

import numpy as np

np.set_printoptions(legacy="1.13")
row, cols = map(int, raw_input().split())

l = []
for i in range(row):
    arr = list(map(float, raw_input().split()))
    l.append(arr)
l = np.array(l).reshape(row, cols)
print(np.array(np.mean(l, axis=1)))
print(np.array(np.var(l, axis=0)))
a = np.array(np.std(l, axis=None))
print(a)

# Input :

# 2 2
# 1 2
# 3 4

# Output :

# [ 1.5  3.5]
# [ 1.  1.]
# 1.11803398875


# the code is the demonstration of the linear regression
# TODO - Implement LARS and proximal gradient descent methods for LASSO

# import the required python libraries
import numpy as np
import numpy.linalg as la
from sklearn import preprocessing

class LinearRegression:

    def fit(self, key, X_train, Y_train, regularization_param=0, num_epochs=10):

        # fit the algorithm provided the training data
        params = [];
        if(key == 'ls'):
            params = self.fit_ls(X_train, Y_train);
        elif(key == 'rr'):
            params = self.fit_rr(X_train, Y_train, regularization_param);
        elif(key == 'lasso'):
            params = self.fit_lasso(X_train, Y_train, regularization_param, num_epochs);
        else:
            raise ValueError('Please provide the correct value of the ley argument');

        return params;

    def fit_ls(self, X_train, Y_train):

        print('------------Using Linear regression with Least squares model-------------------');
        print('Model training data shape:', X_train.shape);
        print('Model training data shape:', Y_train.shape);

        # determine the paramters of the model using linear regression least sqaures
        dd_correlation = np.matmul(np.transpose(X_train), X_train);
        dl_correlation = np.matmul(np.transpose(X_train), Y_train);
        params = np.matmul(la.inv(dd_correlation), dl_correlation);

        return params;

    def fit_rr(self, X_train, Y_train, lambda_param):

        print('----------Using Linear regression with L2 regularization----------------------');
        print('Model training data shape:', X_train.shape);
        print('Model training data shape:', Y_train.shape);

        # determine the paramters of the model using linear regression with L2 regularization
        # note that a close form solution exists for this type of regression.

        tmp = np.matmul(np.transpose(X_train), X_train);
        dd_correlation = np.add(tmp, lambda_param* np.identity(tmp.shape[0]));

        dl_correlation = np.matmul(np.transpose(X_train), Y_train);
        params = np.matmul(la.inv(dd_correlation), dl_correlation);

        return params;

    def fit_lasso(self, X_train, Y_train, lambda_param, num_epochs):

        print('----------Using Linear regression with L1 regularization----------------------');
        print('Model training data shape:', X_train.shape);
        print('Model training data shape:', Y_train.shape);

        learning_rate = 0.00001;
        print('Learning rate:', learning_rate);

        # determine the paramters of the model using linear regression with L1 regularization
        # note that a close form solution does not exists for this type of regression.
        # use the vanilla gradient descent method. Using the vanilla gradient descent gives only
        # approximate sparse solutions(not completely zero)

        X_train = preprocessing.scale(X_train);
        Y_train = preprocessing.scale(Y_train);

        params = np.ones(X_train.shape[1]);

        num_samples = X_train.shape[1];

        for i in range(num_epochs):

            # compuet the loss on the prediction
            rss_loss = Y_train - np.matmul(X_train, params);
            mse = la.norm(rss_loss, ord=2) + lambda_param * la.norm(params, ord=1);
            # print mse;
            # compute the gradient
            # notice the subgradient used in the computation of the gradient of L1 norm
            gradient = (-np.dot(np.transpose(X_train), rss_loss) + \
                        lambda_param * np.sign(params))/num_samples;

            # update the parameters
            params = params - learning_rate * gradient;

        # print params;
        return params;


import numpy as np

""" Determinant of upper triangle or diagnal matrix is product of diagnal element """
a = np.array([(2, 3, 4), (0, 2, 5), (0, 0, 2)])
# array([[2, 3, 4],
#        [0, 2, 5],
#        [0, 0, 2]])

print(np.linalg.det(a))
# 7.999999999999998

""" Determinant of row exhanged matrix will change the sign """
a = np.array([(2, 3, 4), (0, 0, 2), (0, 2, 5)])
print(np.linalg.det(a))
# - 7.999999999999998

""" Determinant of identiy matrix is 1 """
a = np.array([(1, 0, 0), (0, 1, 0), (0, 0, 1)])
print(np.linalg.det(a))
# 1

""" Determinant of zero row contain matrix is 0 """
a = np.array([(2, 3, 4), (0, 0, 0), (9, 2, 5)])
print(np.linalg.det(a))
# 0

""" Determinant of singular (dependent column) matrix is 0 """
a = np.array([( 2, 6,  4 ),
              ( 6, 18, 12),
              ( 9, 27, 5 )])
print(np.linalg.det(a))
# 0

""" Determinant of non-singular (independent column) matrix is non zero """
a = np.array([(2, 6, 4), (6, 7, 12), (9, 9, 5)])
print(np.linalg.det(a))
# 285.99999999999994


# Question 4 Lab 03
# AB Satyaprakash (180123062)

# imports ----------------------------------------------------------------------
from math import factorial
import numpy as np
import sympy as sp
# ------------------------------------------------------------------------------
# functions --------------------------------------------------------------------

def forwardDiff(fArray):
    sz = len(fArray)
    fdArray = [fArray]
    for i in range(1, sz):
        temp = []
        for j in range(sz-i):
            temp.append(fdArray[i-1][j+1]-fdArray[i-1][j])
        fdArray.append(temp)
    return fdArray

def newtonFDPoly(fArray, xArray):
    x0, x1 = xArray[0], xArray[1]
    h = x1-x0
    u = np.array([1/h, -x0/h])
    fdArray = forwardDiff(fArray)
    sz = len(fArray)
    px = np.array([0])

    for i in range(sz):
        term = np.array([1])
        for j in range(i):
            term = np.polymul(term, np.polyadd(u, np.array([-j])))
            term = term/(j+1)
        term = term*fdArray[i][0]
        px = np.polyadd(px, term)
    return px

# ------------------------------------------------------------------------------
# g(x) = sin(x)/(x^2). Calculate g(0.25)
# (a) By direct interpolation
X = [0.1, 0.2, 0.3, 0.4, 0.5]
G = [9.9833, 4.9667, 3.2836, 2.4339, 1.9177]

# Construct a degree 3 polynomial to appproximate g(0.25)
px = newtonFDPoly(G[:4], X[:4])
print('Thus, the approximation of g(0.25) using direct interpolation = P(0.25) = {}'.format(np.polyval(px, 0.25)))
# ((x−0.1)(x−0.2)(x−0.3)(x−0.4)*Δ_4(f0))/4!(0.1)^4 is used to find the error!
Δ4_f0 = forwardDiff(G)[4][0]
x = sp.Symbol('x')
errorExpression = ((x-0.1)*(x-0.2)*(x-0.3)*(x-0.4)*Δ4_f0)/(factorial(4)*(0.1**4))
errorValue = abs(errorExpression.subs(x, 0.25))
print('The error term is at x = 0.25 is', errorValue)

# (b) By first tabulating xg(x) and then forward difference interpolating in that table
for i in range(5):
    G[i] *= X[i]
px = newtonFDPoly(G[:4], X[:4])
print('\n\nThus, the approximation of g(0.25) using interpolation on xg(x) table = P(0.25) = {}'.format(
    np.polyval(px, 0.25)*4))
Δ4_f0 = forwardDiff(G)[4][0]
x = sp.Symbol('x')
errorExpression = ((x-0.1)*(x-0.2)*(x-0.3)*(x-0.4)*Δ4_f0)/(factorial(4)*(0.1**4))
errorValue = abs(errorExpression.subs(x, 0.25)*4)
print('The error term is at x = 0.25 is', errorValue)

# (c) Explain the difference between the results in (i) and (ii) respectively.
print('\nSince the differences in (i) are oscillating and are not decreasing fast, the resulting error in interpolation would be large.')
print('However, the differences in (ii) tend to become smaller in magnitude, we expect more accurate results in this case.')

# Question 4 ends --------------------------------------------------------------

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 3/Code/q4.py


# 数值微分求导数， 数值微分就是用数值方法近似的求解函数的导数的过程
import numpy as np
import matplotlib.pyplot as plt

# f = function, x为input

def numerical_diff(f, x):
    h = 1e-4
    return (f(x + h) - f(x - h)) / (2 * h)

# example 1: f = 0.01x^2 + 0.1x
def function1(x):
    return 0.01 * (x ** 2) + 0.1 * x

# 此函数用于利用数值微分的值作为斜率，重新画出与对应函数的斜线
def tangent_line(f, x):
    d = numerical_diff(f, x) # 计算导数
    print(d)
    y = f(x) - d*x # f(x)与直线的截距
    return lambda t: d*t + y # 类似返回一个直线函数 y = k * x + b

def function(x):
    return 0.02 * x + 0.1

# define 二元函数f(x0, x1) = x0^2 + x1^2
def function2(x):
    return np.sum(x ** 2)

# 当x0 = 3， x1 = 4时， 求funciton2对x0的偏微分
def function2_x0(x0):
    return x0**2 + 4.0 ** 2.0
print('当x0 = 3， x1 = 4时， 求funciton2对x0的偏微分: ', numerical_diff(function2_x0, 3.0))

# 当x0 = 3， x1 = 4时， 求funciton2对x1的偏微分
def function2_x1(x1):
    return 3.0 ** 2.0 + x1 ** 2
print('当x0 = 3， x1 = 4时， 求funciton2对x1的偏微分: ', numerical_diff(function2_x1, 4.0))

x = np.arange(0.0, 20.0, 0.1)
y = function1(x)
tf = tangent_line(function1, 5)
y1 = tf(x)
y2 = function(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x, y)
plt.plot(x, y1)
plt.plot(x, y2)
plt.show()

# 如果通过我们自己计算导数f(x)的导数为0.02*x + 0.1
# 当x in 5： 0.02 * 5 + 0.1 = 0.2
# 当x in 10：0.02 * 10 + 0.1 = 0.3
# compare to this result, discover similarly
print('x in 5的导数：', numerical_diff(function1, 5))

print('x in 10的导数: ', numerical_diff(function1, 10))


#Program to find the eigen values and eigen vectors.
#Developed by: SRIJITH R
#RegisterNumber: 21004191
import numpy as np
A = np.array([[2,-3,0],[2,-5,0],[0,0,3]])
values,vectors=np.linalg.eig(A)
print("Eigen values are {} and Eigen Vectors are {}".format(values,vectors))