import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from scipy.special import expit
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

# Choosing activation functions for multilayer neural networks

# Logistic function recap

X = np.array([1, 1.4, 2.5]) ## first value must be 1
w = np.array([0.4, 0.3, 0.5])

def net_input(X, w):
    return np.dot(X, w)

def logistic(z):
    return 1.0 / (1.0 + np.exp(-z))

def logistic_activation(X, w):
    z = net_input(X, w)
    return logistic(z)

print('P(y=1|x) = %.3f' % logistic_activation(X, w))

# W : array with shape = (n_output_units, n_hidden_units+1)
# note that the first column are the bias units

W = np.array([[1.1, 1.2, 0.8, 0.4],
              [0.2, 0.4, 1.0, 0.2],
              [0.6, 1.5, 1.2, 0.7]])

# A : data array with shape = (n_hidden_units + 1, n_samples)
# note that the first column of this array must be 1

A = np.array([[1, 0.1, 0.4, 0.6]])
Z = np.dot(W, A[0])
y_probas = logistic(Z)
print('Net Input: \n', Z)

print('Output Units:\n', y_probas)

y_class = np.argmax(Z, axis=0)
print('Predicted class label: %d' % y_class)

# Estimating class probabilities in multiclass classification via the softmax function

def softmax(z):
    return np.exp(z) / np.sum(np.exp(z))

y_probas = softmax(Z)
print('Probabilities:\n', y_probas)

np.sum(y_probas)

Z_tensor = tf.expand_dims(Z, axis=0)
tf.keras.activations.softmax(Z_tensor)

# Broadening the output spectrum using a hyperbolic tangent
def tanh(z):
    e_p = np.exp(z)
    e_m = np.exp(-z)
    return (e_p - e_m) / (e_p + e_m)

z = np.arange(-5, 5, 0.005)
log_act = logistic(z)
tanh_act = tanh(z)
plt.ylim([-1.5, 1.5])
plt.xlabel('Net input $z$')
plt.ylabel('Activation $\phi(z)$')
plt.axhline(1, color='black', linestyle=':')
plt.axhline(0.5, color='black', linestyle=':')
plt.axhline(0, color='black', linestyle=':')
plt.axhline(-0.5, color='black', linestyle=':')
plt.axhline(-1, color='black', linestyle=':')
plt.plot(z, tanh_act,
    linewidth=3, linestyle='--',
    label='Tanh')
plt.plot(z, log_act,
    linewidth=3,
    label='Logistic')
plt.legend(loc='lower right')
plt.tight_layout()
plt.show()

np.tanh(z)
tf.keras.activations.tanh(z)

expit(z)

tf.keras.activations.sigmoid(z)

# Rectified linear unit activation

tf.keras.activations.relu(z)

Chapter 13/04 Choosing activation functions for multilayer neural networks/program.py


import numpy as np

# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print(x)
v = np.array([1, 0, 1])
print(v)
y = np.empty_like(x)   # Create an empty matrix with the same shape as x
print(y)

# Add the vector v to each row of the matrix x with an explicit loop
for i in range(4):
    y[i, :] = x[i, :] + v

# Now y is the following
# [[ 2  2  4]
#  [ 5  5  7]
#  [ 8  8 10]
#  [11 11 13]]
print(y)

vv = np.tile(v, (4, 1))   # Stack 4 copies of v on top of each other
print(vv)                 # Prints "[[1 0 1]
                          #          [1 0 1]
                          #          [1 0 1]
                          #          [1 0 1]]"
yy = x + vv  # Add x and vv elementwise
print(yy)  # Prints "[[ 2  2  4
          #          [ 5  5  7]
          #          [ 8  8 10]
          #          [11 11 13]]"

yyy = x + v  # Add v to each row of x using broadcasting
print(y)  # Prints "[[ 2  2  4]
          #          [ 5  5  7]
          #          [ 8  8 10]
          #          [11 11 13]]"


import numpy as np
from numpy.core._multiarray_umath import ndarray

def Jacobi_nonmatrix(A, b):
    n = len(A)
    x = np.zeros(n)
    y = np.ones(n)
    N = 100
    tmp = np.zeros(n)
    #    for k in range(N):
    while max(abs((y - tmp))) > 0.001:
        for i in range(0, n):
            s1 = sum(A[i][t] * x[t] for t in range(i))
            s2 = sum(A[i][t] * x[t] for t in range(i + 1, n))
            y[i] = (b[i] - s1 - s2) / A[i][i]
        tmp = x.copy()
        x = y.copy()  # You can understand x and y as pointers to a specific array.
        # Direct assignment will cause x and y to point to the same object, causing the x and y values to change
        # together during the iteration.
    return y

def Jacobi_matrix(A, b):
    # Use numpy matrix operation, save time
    x = np.zeros(len(b))
    D = np.diagflat(np.diag(A))
    LU = A - D
    N = 100
    for k in range(N):
        x = np.dot(np.dot(-np.linalg.inv(D), LU), x) + np.dot(np.linalg.inv(D), b)
    #    D = np.diag(A)
    #    LU = A - np.diagflat(D)
    #    N = 100
    #    for k in range(N):
    #       x = (b - np.dot(LU, x))/D
    return x

def Gauss_seidel(A, b):
    n = len(A)
    x = np.zeros(n)
    tmp = np.ones(n)
    #   N = 100
    #   for k in range(N):
    while max(abs((x - tmp))) > 0.001:
        tmp = x.copy()
        for i in range(n):
            x[i] = b[i] / A[i][i] - sum((A[i][j] / A[i][i]) * x[j] for j in range(i)) - sum(
                (A[i][j] / A[i][i]) * x[j] for j in range(i + 1, n))
    return x

# Test case 1, problem 15, problem 17

A = [[5., 2., 1.], [-1., 4., 2.], [2., -5., 10.]]
A = np.array(A)
b = [-12., 10., 1.]
b = np.array(b)
print(Jacobi_nonmatrix(A, b))
print(Jacobi_matrix(A, b))
print(Gauss_seidel(A, b))


import numpy as np
from numpy.random import rand
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from timeit import timeit as ti

#problem 1
def arrmul(A,B):
    new = []
    for i in range(len(A)):
        newrow = []
        for k in range(len(B[0])):
            tot = 0
            for j in range(len(B)):
                tot += A[i][j] * B[j][k]
            newrow.append(tot)
        new.append(newrow)
    return new

def prob1():
    k = 200
    A = [range(i, i+k) for i in range(0, k**2, k)]
    number = 5
    print "problem 1"
    tm = ti("arrmul(A,A)", setup="from __main__ import A, arrmul", number=number)
    print "arrmul(A,A) executed ", number, " times in ", tm, " seconds."
    A = np.array(A)
    number = 5
    tm = ti("np.dot(A,A)", setup="import numpy as np; from __main__ import A", number=number)
    print "np.dot(A,A) executed ", number, " times in ", tm, " seconds."

    #problem 2
def prob2():
    print "problem 2"
    A = rand(1000,1000)
    B = np.empty_like(A)
    for i in xrange(100):
            B[:] = rand(1000,1000)
            A[A<B] = B[A<B]
    np.exp(A, out=A)
    print np.average(np.max(A, axis=1))

def prob3():
    #problem 3
    print "problem 3"
    A = rand(1000,1000)
    number = 100
    tm = ti("A.reshape(A.size)", setup="from __main__ import A", number=number)
    print "A.reshape(A.size) executed ", number, " times in ", tm, " seconds."
    number = 100
    tm = ti("A.flatten()", setup="from __main__ import A", number=number)
    print "A.flatten() executed ", number, " times in ", tm, " seconds."
    number = ti("A.reshape((1,A.size))", setup="from __main__ import A", number=number)
    print "A.reshape((1,A.size)) executed ", number, " times in ", tm, " seconds."

    #part 2 of problem 3
    print "the difference is that np.vstack(A) returns a new array"
    print "while A.T returns a view"
    print "A.T is much faster"
    A = rand(1,1000000)
    number = 500
    tm = ti("np.vstack(A)", setup="import numpy as np; from __main__ import A", number=number)
    print "np.vstack(A) executed ", number, " times in ", tm, " seconds."
    number = 500
    tm = ti("A.T", setup="from __main__ import A", number=number)
    print "A.T executed ", number, " times in ", tm, " seconds."

def laplace(U,tol):
    new = U.copy()
    dif = tol
    while tol<= dif:
        new[1:-1,1:-1] = (U[:-2,1:-1] + U[2:,1:-1] + U[1:-1,:-2] + U[1:-1,2:]) / 4.
        dif = np.max(np.absolute(U-new))
        U[:] = new

def prob4():
    n = 100
    tol=.0001
    U=np.ones((n,n))
    U[:,0] = 100
    U[:,-1] = 100
    U[0] = 0
    U[-1] = 0
    laplace(U, tol)
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X = np.linspace(0,1,n)
    Y = np.linspace(0,1,n)
    X, Y = np.meshgrid(X, Y)
    ax.plot_surface(X, Y, U, rstride=5)
    plt.show()

#problem 5

def broadcast_1():
    """All input arrays have exactly the same shape"""
    a = np.random.rand(4, 5)
    b = np.random.rand(4, 5)
    r = a * b
    print "Case 1: {} * {} = {}".format(a.shape, b.shape, r.shape)

def broadcast_2():
    """All input arrays are of the same dimension and
    the length of corresponding dimensions match or is 1"""

    a = np.random.rand(5, 4, 1, 6)
    b = np.random.rand(5, 4, 1, 1)
    r = a * b
    print "Case 2: {} * {} = {}".format(a.shape, b.shape, r.shape)

def broadcast_3():
    """All input arrays of fewer dimension can have 1
    prepended to their shapes to satisfy the second criteria."""

    a = np.random.rand(1, 6)
    b = np.random.rand(5, 4, 1, 6)
    r = a * b
    print "Case 3: {} * {} = {}".format(a.shape, b.shape, r.shape)

def prob_broadcasting():
    #problem 5
    broadcast_1()
    broadcast_2()
    broadcast_3()


#!/usr/bin/env python
# coding: utf-8
# ## Monte Carlo - Euler Discretization - Part II
# *Suggested Answers follow (usually there are multiple ways to solve a problem in Python).*
# *Euler Discretization – continued.*
# In[ ]:
import numpy as np
import pandas as pd
from pandas_datareader import data as web
from scipy.stats import norm
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
data = pd.read_csv('D:/Python/MSFT_2000.csv', index_col = 'Date')
log_returns = np.log(1 + data.pct_change())
stdev = log_returns.std() * 250 ** 0.5
stdev = stdev.values
r = 0.025
T = 1.0
t_intervals = 250
delta_t = T / t_intervals
iterations = 10000
Z = np.random.standard_normal((t_intervals + 1, iterations))
S = np.zeros_like(Z)
S0 = data.iloc[-1]
S[0] = S0
for t in xrange(1, t_intervals + 1):
    S[t] = S[t-1] * np.exp((r - 0.5 * stdev ** 2) * delta_t + stdev * delta_t ** 0.5 * Z[t])
plt.figure(figsize=(10, 6))
plt.plot(S[:, :10]);
# ******
# Use numpy.maximum to create a vector with as many elements as there are columns in the S matrix.
# In[ ]:
p = np.maximum(S[-1] - 110, 0)
# In[ ]:
p
# In[ ]:
p.shape
# Use the following formula to forecast the price of a stock option.
# $$
# C = \frac{exp(-r \cdot T) \cdot \sum{p_i}}{iterations}
# $$
# In[ ]:
np.sum(p)
# In[ ]:
C = np.exp(-r * T) * np.sum(p) / iterations
C
# Because this pricing model is based on random iterations, you will obtain a different result every time you re-run the code in this document. Expand the “Kernel” list from the Jupyter menu and click on “Restart and run all”/”Restart & run all cells” to verify this is true.


# solutions.py
"""Volume 2 Lab 14: Optimization Packages II (CVXOPT)"""

from cvxopt import matrix, solvers
import numpy as np
from scipy import linalg as la

def prob1():
    """Solve the following convex optimization problem:

    minimize        2x + y + 3z
    subject to      x + 2y          >= 3
                    2x + 10y + 3z   >= 10
                    x               >= 0
                    y               >= 0
                    z               >= 0

    Returns (in order):
        The optimizer x (ndarray)
        The optimal value (sol['primal objective'])
    """

    # Note that 'matrix' initializes by column, not row.
    c = matrix([2., 1., 3.])
    G = matrix(np.array([[-1.,-2.,0.],
                         [-2.,-10.,-3.],
                         [-1.,0.,0.],
                         [0.,-1.,0.],
                         [0.,0.,-1.]]))

    h = matrix([ -3., -10., 0., 0., 0.])
    sol = solvers.lp(c,G,h)
    return np.ravel(sol['x']), sol['primal objective']

    # Answers:
    # np.array([ -1.14760916e-09,   1.50000000e+00,   6.95278285e-11])
    # 1.4999999996249482

# Problem 2
def l1Min(A, b):
    """Calculate the solution to the optimization problem

        minimize    ||x||_1
        subject to  Ax = b

    Parameters:
        A ((m,n) ndarray)
        b ((m, ) ndarray)

    Returns:
        The optimizer x (ndarray), without any slack variables u
        The optimal value (sol['primal objective'])
    """

    assert A.shape[0] == b.shape[0], "mismatched dimensions"

    n = A.shape[1]
    I = np.eye(n, dtype=np.float)

    '''The optimization problem to solve is:

    minimize                [u]
                      [1 0] [x]

    subject to      [-I  I] [u]     [0]
                    [-I -I] [x]  <= [0]

                            [u]
                    [0   A] [x]  ==  b
    '''

    # Build the matrices for cvxopt (make sure dtype=np.floats)
    c = matrix(np.hstack((np.ones(n), np.zeros(n))).astype(np.float))
    G = matrix(np.vstack((np.hstack((-I, I)),np.hstack((-I, -I)))))
    h = matrix(np.zeros(2*n))
    new_A = matrix(np.hstack((np.zeros_like(A), A)).astype(np.float))
    new_b = matrix(b.astype(np.float))

    # Perform the optimization.
    sol = solvers.lp(c, G, h, new_A, new_b)

    # Flatten out the array and remove the u values.
    return np.ravel(sol['x'])[n:], sol['primal objective']

def prob3():
    """Solve the transportation problem by converting the last equality constraint
    into inequality constraints.

    Returns (in order):
        The optimizer x (ndarray)
        The optimal value (sol['primal objective'])
    """
    c = matrix([4., 7., 6., 8., 8., 9.])
    G = matrix(np.array([[-1.,0.,0.,0.,0.,0.],
                         [0.,-1.,0.,0.,0.,0.],
                         [0.,0.,-1.,0.,0.,0.],
                         [0.,0.,0.,-1.,0.,0.],
                         [0.,0.,0.,0.,-1.,0.],
                         [0.,0.,0.,0.,0.,-1.],
                         [0.,1.,0.,1.,0.,1.],
                         [0.,-1.,0.,-1.,0.,-1.]]))
    h = matrix([0.,0.,0.,0.,0.,0., 8., -8.])
    A = matrix(np.array([[1.,1.,0.,0.,0.,0.],
                         [0.,0.,1.,1.,0.,0.],
                         [0.,0.,0.,0.,1.,1.],
                         [1.,0.,1.,0.,1.,0.]]))
    b = matrix([7.,2.,4.,5.])
    sol = solvers.lp(c,G,h,A,b)
    return np.ravel(sol['x']), sol['primal objective']

    # Answers:
    # np.array([[ 5.00e+00],[ 2.00e+00],[ -7.03e-09],
    #           [ 2.00e+00],[ -5.45e-09],[ 4.00e+00]])
    # 86

def prob4():
    """Find the minimizer and minimum of

    g(x,y,z) = (3/2)x^2 + 2xy + xz + 2y^2 + 2yz + (3/2)z^2 + 3x + z

    Returns (in order):
        The optimizer x (ndarray)
        The optimal value (sol['primal objective'])
    """
    P = matrix(np.array([[3.,2.,1.],
                         [2.,4.,2.],
                         [1.,2.,3.]]))

    q = matrix([3., 0., 1.])
    sol = solvers.qp(P, q)
    return np.ravel(sol['x']), sol['primal objective']

    # Answers:
    # np.array([[-1.50],[ 1.00],[-.5]])
    # -2.5

# Problem 5
def l2Min(A, b):
    """Calculate the solution to the optimization problem

        minimize    ||x||_2
        subject to  Ax = b

    Parameters:
        A ((m,n) ndarray)
        b ((m, ) ndarray)

    Returns:
        The optimizer x (ndarray)
        The optimal value (sol['primal objective'])
    """

    assert A.shape[0] == b.shape[0], "mismatched dimensions"

    n = A.shape[1]
    I = np.eye(n, dtype=np.float)

    '''The optimization problem to solve is:

        minimize              x*x
        subject to          A  =  b
    '''

    # Build the matrices for cvxopt (make sure dtype=np.floats)
    P = matrix(2*I)
    q = matrix(np.zeros(n))
    new_A = matrix(A.astype(np.float))
    new_b = matrix(b.astype(np.float))

    # Perform the optimization.
    sol = solvers.qp(P, q, A=new_A, b=new_b)

    # Flatten out the array and only get the x value.
    return np.ravel(sol['x']), sol['primal objective']

def prob6():
    """Solve the allocation model problem in 'ForestData.npy'.
    Note that the first three rows of the data correspond to the first
    analysis area, the second group of three rows correspond to the second
    analysis area, and so on.

    Returns (in order):
        The optimizer x (ndarray)
        The optimal value (sol['primal objective']*-1000)
    """
    data = np.load('ForestData.npy')

    c = matrix(data[:,3]*-1)

    A = la.block_diag(*[[1.,1.,1.] for _ in xrange(7)])
    b = data[::3,1].copy()

    G = np.vstack((-data[:,4], -data[:,5], -data[:,6], -np.eye(21))) # flip the inequality signs
    h = np.hstack(([-40000., -5., -70.*788.], np.zeros(21)))         # flip the inequality signs

    c = matrix(c)
    A = matrix(A)
    b = matrix(b)
    G = matrix(G)
    h = matrix(h)

    sol = solvers.lp(c,G,h,A,b)
    return np.ravel(sol['x']), sol['primal objective']*-1000.

    # Answers:
    # np.array([[ 1.41e-08],[ 6.76e-08],[ 7.50e+01],[ 9.00e+01],[ 1.28e-07],
    #           [ 2.52e-07],[ 1.40e+02],[ 4.18e-07],[ 5.52e-06],[ 1.04e-08],
    #           [ 8.94e-09],[ 6.00e+01],[ 1.23e-07],[ 1.54e+02],[ 5.80e+01],
    #           [ 3.16e-08],[ 3.58e-08],[ 9.80e+01],[ 1.63e-08],[ 9.12e-09],
    #           [ 1.13e+02]])
    # 322514998.983

''' # Generate the forest data.
forest=np.array([[1,75.,1,503.,310.,0.01,40],
[0,0,2,140,50,0.04,80],
[0,0,3,203,0,0,95],
[2,90.,1,675,198,0.03,55],
[0,0,2,100,46,0.06,60],
[0,0,3,45,0,0,65],
[3,140.,1,630,210,0.04,45],
[0,0,2,105,57,0.07,55],
[0,0,3,40,0,0,60],
[4,60.,1,330,112,0.01,30],
[0,0,2,40,30,0.02,35],
[0,0,3,295,0,0,90],
[5,212.,1,105,40,0.05,60],
[0,0,2,460,32,0.08,60],
[0,0,3,120,0,0,70],
[6,98.,1,490,105,0.02,35],
[0,0,2,55,25,0.03,50],
[0,0,3,180,0,0,75],
[7,113.,1,705,213,0.02,40],
[0,0,2,60,40,0.04,45],
[0,0,3,400,0,0,95]])
np.save('ForestData',forest)
'''

# END OF SOLUTIONS ============================================================

def test(student_module):
    """Test script. Import the student's solutions file as a module.

    10 points for problem 1
    10 points for problem 2
    10 points for problem 3
    10 points for problem 4
    10 points for problem 5
    20 points for problem 6

    Inputs:
        student_module: the imported module for the student's file.

    Returns:
        score (int): the student's score, out of 70.
        feedback (str): a printout of test results for the student.
    """
    tester = _testDriver()
    tester.test_all(student_module)
    return tester.score, tester.feedback

class _testDriver(object):
    """Class for testing a student's work. See test.__doc__ for more info."""

    # Constructor -------------------------------------------------------------
    def __init__(self):
        """Initialize the feedback attribute."""
        self.feedback = ""

    # Main routine -----------------------------------------------------------
    def test_all(self, student_module, total=70):
        """Grade the provided module on each problem and compile feedback."""
        # Reset feedback and score.
        self.feedback = ""
        self.score = 0

        def test_one(problem, number, value):
            """Test a single problem, checking for errors."""
            try:
                self.feedback += "\n\nProblem {} ({} points):".format(
                                                                number, value)
                points = problem(student_module)
                self.score += points
                self.feedback += "\nScore += {}".format(points)
            except BaseException as e:
                self.feedback += "\n{}: {}".format(self._errType(e), e)

        # Grade each problem.
        test_one(self.problem1, 1, 10)  # Problem 1: 10 points.
        test_one(self.problem2, 2, 10)  # Problem 2: 10 points.
        test_one(self.problem3, 3, 10)  # Problem 3: 10 points.
        test_one(self.problem3, 4, 10)  # Problem 4: 10 points.
        test_one(self.problem3, 5, 10)  # Problem 5: 10 points.
        test_one(self.problem4, 6, 20)  # Problem 6: 20 points.

        # Report final score.
        percentage = (100. * self.score) / total
        self.feedback += "\n\nTotal score: {}/{} = {}%".format(
                                    self.score, total, round(percentage, 2))
        if   percentage >=  98: self.feedback += "\n\nExcellent!"
        elif percentage >=  90: self.feedback += "\n\nGreat job!"

        # Add comments (optionally).
        print(self.feedback)
        comments = str(raw_input("Comments: "))
        if len(comments) > 0:
            self.feedback += '\n\n\nComments:\n\t{}'.format(comments)

    # Helper Functions --------------------------------------------------------
    @staticmethod
    def _errType(error):
        """Get just the name of the exception 'error' in string format."""
        if isinstance(error, BaseException):
            return str(type(error)).lstrip("<type 'exceptions.").rstrip("'>")
        else:
            return str(error)

    def _eqTest(self, correct, student, message):
        """Test to see if 'correct' and 'student' are equal.
        Report the given 'message' if they are not.
        """
        if student is None:
            self.feedback += "\nFailed to return a value."
            return 0
        if np.allclose(correct, student, atol=1e-1, rtol=1e-1):
            return 1
        else:
            self.feedback += "\n{}".format(message)
            self.feedback += "\n\tCorrect response:\n{}".format(correct)
            self.feedback += "\n\tStudent response:\n{}".format(student)
            return 0

    # Problems ----------------------------------------------------------------
    def _test_problem(self, s, ans_1, ans_2, stu, index):
        """Test a problem with solutions 'ans' and submission 'stu'."""

        # Attempt to unpack the student's solutions values.
        try:
            stu_1, stu_2 = stu
        except (TypeError, ValueError):
            self.feedback += "\nprob{}() must return 2 values.".format(index)
            raise

        # Test the student's values against the true solution.
        points = 0
        points += 5*self._eqTest(ans_1, stu_1, "Incorrect optimizer")
        points += 5*self._eqTest(ans_2, stu_2, "Incorrect optimial value")
        print("PROBLEM {}: {}".format(index, points))
        return points

    def problem1(self, s):
        """Test prob1(). 10 points."""
        return self._test_problem(s,
                        np.array([[1.54643957],[ 1.20887997],[ 1.89941363]]),
                        10, s.prob1(), 1)

    # np.array([ -1.14760916e-09,   1.50000000e+00,   6.95278285e-11])
    # 1.4999999996249482

    def problem3(self, s):
        """Test prob3(). 10 points."""
        return self._test_problem(s,
                        np.array([[ 5.00e+00],[ 2.00e+00],[ -7.03e-08],
                                  [ 2.00e+00],[ -5.44e-09],[ 4.00e+00]]),
                        86, s.prob3(), 3)

    def problem4(self, s):
        """Test prob4(). 10 points."""
        return self._test_problem(s, np.array([[-1.50],[ 1.00],[-.5]]),
                        -2.5, s.prob4(), 4)

    def problem6(self, s):
        """Test prob6(). 20 points."""
        return 2*self._test_problem(s,
                        np.array([[ 1.41e-08],[ 6.76e-08],[ 7.50e+01],
                                  [ 9.00e+01],[ 1.28e-07],[ 2.52e-07],
                                  [ 1.40e+02],[ 4.18e-07],[ 5.52e-06],
                                  [ 1.04e-08],[ 8.94e-09],[ 6.00e+01],
                                  [ 1.23e-07],[ 1.54e+02],[ 5.80e+01],
                                  [ 3.16e-08],[ 3.58e-08],[ 9.80e+01],
                                  [ 1.63e-08],[ 9.12e-09],[ 1.13e+02]]),
                        322514998.983, s.prob6(), 6)

# END OF FILE =================================================================


import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as plt
from scipy.io import loadmat

def sigmoide(X):
    return 1/(1+np.exp(-X))

def hipotesis(X, Theta):
    return sigmoide(np.dot(X, np.transpose(np.array([Theta]))))

def coste(Theta, X, Y, reg):
    m = np.shape(X)[0]
    H = hipotesis(X, Theta)
    aux = Y*np.log(H + 1e-6) + (1-Y)*np.log(1 - H + 1e-6)
    aux = -aux.sum()/m
    aux2 = np.sum((Theta ** 2))
    aux2 = (reg/(2*m))*aux2
    return aux + aux2

def coste2(Theta,X,Y):
    H = sigmoide(np.matmul(X, Theta))
    return (- 1 / (len(X))) * (np.dot(Y, np.log(H)) + np.dot((1 - Y), np.log(1 - H)))

def gradienteRecurs(Theta, X, Y, reg):
    m = np.shape(X)[0]
    grad = np.ravel((1/m)*np.dot(np.transpose(X), (hipotesis(X,Theta) - Y))) #+ (reg/m)*Theta
    grad[0] = (1/m)*np.sum((hipotesis(X,Theta) - Y) * X[:,0:1])
    return grad

def fun(thetas, X, etiq):
    return np.argmax(np.dot(thetas, X)) + 1 == etiq

def oneVsAll(Xp, Yp, num_etiquetas, reg):
    n = np.shape(Xp)[1]
    thetas = np.empty((0,n), float)
    ies = np.arange(1, num_etiquetas + 1)
    for i in ies:
        Y = np.copy(Yp)
        Theta = np.zeros(n)
        tr = np.where(Yp == i)
        fls = np.where(Yp != i)
        X = Xp
        Y[tr[0]] = 1
        Y[fls[0]] = 0
        print(Y)
        result = opt.fmin_tnc(func=coste, x0=Theta, fprime=gradienteRecurs, args=(X, Y, reg))
        thetas = np.vstack((thetas, result[0]))
    return thetas

data = loadmat("ex3data1.mat")
X = data['X']
Y = data['y']
Y = Y.astype(int)
m = np.shape(X)[0]

sample = np.random.choice(X.shape[0],10)
plt.imshow(X[sample,:].reshape(-1,20).T)
plt.axis('off')
plt.savefig('prueba.png')

X = np.hstack([np.ones([m,1]), X])

thetas = oneVsAll(X, Y, 10, 0.1)

aux = [fun(thetas, X[i], Y[i][0]) for i in range(m)]

print("Sol -->", np.sum(aux)/m)

#i = 756

#calculo = np.dot(thetas, X[i])

#print("Sol -->", np.argmax(calculo) + 1, "realmente es ", Y[i])

print("FIN")


import numpy as np

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

for e in L:
    print(e)

for e in A:
    print(e)

L.append(4)
L = L + [5]

L2 = []
for e in L:
    L2.append(e+e)

A+A
2*A

2*L

L3 = []
for e in L:
    L3.append(e*e)

A**2
np.sqrt(A)
np.log(A)
np.exp(A)

a = np.array([1,2])
b = np.array([2,1])

dot = 0
for e,f in zip(a,b):
    dot += e*f

# elementwise multiplication
a*b

# dot product (inner product)
np.sum(a*b)
(a*b).sum()

np.dot(a,b)
a.dot(b)
b.dot(a)
np.inner(a,b)

# magnitude of a vector
amag = np.sqrt((a*a).sum())
amag = np.linalg.norm(a)

# angle between two vectors
cosangle = a.dot(b) / (np.linalg.norm(a)*np.linalg.norm(b))
angle = np.arccos(cosangle)

# matrix
L = [[1,2], [3,4]]
M = np.array(L)

L[0]
L[0][0]

M[0][0]
M[0,0]

M2 = np.matrix(L) # recommended: use np.array instead!!!

A = np.array(M2)
A

# transpose
A.T

# generating arrays and matrices
Z = np.zeros(10)
ZM = np.zeros((10,10))

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

RM = np.random.random((10,10)) # uniformly distributed numbers between 0..1
GM = np.random.randn(10,10) # Gaussian distribution (note: no tuple!)

GM.mean()
GM.var()

# elementwise matrix multiplication:
A = np.array([[1,2],[3,4]])
B = np.array([[5,6],[7,8]])
A*B

# matrix multiplication:
A = np.array([[1,2],[3,4]])
B = np.array([[5,6,7],[7,8,9]])
A.dot(B)

# inverse:
Ainv = np.linalg.inv(A)
Ainv.dot(A)
A.dot(Ainv)

# determinant:
np.linalg.det(A)

# diagonal elements of a matrix:
np.diag(A)

# constructing a diagonal matrix:
np.diag([1,2])

# outer product:
a = np.array([1,2])
b = np.array([3,4])
np.outer(a,b)

# trace (sum of the diagnoal elements):
np.diag(A).sum()
np.trace(A)

# covariance:
X = np.random.randn(100,3)
cov = np.cov(X)
cov.shape
covT = np.cov(X.T)
covT.shape

# eigenvalues, eigenvectors:
np.linalg.eigh(covT) # eigh is for symmetric (A=A^T) and Hermitian (A=A^H) matrices
np.linalg.eig(covT) # eig is for general matrices

# solving a linear system of equations:
A = np.array([[1,2], [3,4]])
b = np.array([1,2])
x = np.linalg.inv(A).dot(b)

np.linalg.solve(A, b)


#Mutlistep method: Adams Bashforth Method
import numpy as np

def dy(x,y):
    dyvalue = x - y**2
    return dyvalue

x0= 0 ##least x value known
xp1 = 0.1
xp2 = 0.2
xp3 = 0.3 #highest x value known

xfinal= 0.3
h = 0.1
n = int(((xfinal - x0)/h)) #if they give limits

y0= 0.854 ## highest i of y value known
ym1 =  0.9145
ym2 = 1
ym3 = 0 ## least y value known

order = 2
##########
x = np.zeros((n+order,1))
y = np.zeros((n+order,1))

i =0

if order == 3:
    p = order -1
    y[p]= y0
    y[p-1]= ym1
    y[p-2]= ym2

    x[p]= xp2
    x[p-1]= xp1
    x[p-2]= x0
    while i<=(n+order):
        print("order3\n")
        y[p+1] = y[p] +(h/12)*(23*dy(x[p],y[p])-16*dy(x[p-1],y[p-1])+5*dy(x[p-2],y[p-2]))
        x[p+1] = x[p] + h
        print("y:"+str(i)+str(y[p+1]))

        p+=1
        i+=1
elif order == 2:
    p = order -1
    y[1]= y0
    y[0]= ym1

    x[1]= xp1
    x[0]= x0
    while i<=(n+order):
        print("order2\n")
        y[p+1] = y[p] +(h/2)*(3*dy(x[p],y[p])-dy(x[p-1],y[p-1]))
        x[p+1] = x[p] + h
        print("y:"+str(i)+str(y[p+1]))

        p+=1
        i+=1
elif order == 4:
    p = order-1
    y[3]= y0
    y[2]= ym1
    y[1]= ym2
    y[0]= ym3

    x[3]= xp3
    x[2]= xp2
    x[1]= xp1
    x[0]= x0
    while i<=(n+order):
        print("order4\n")
        y[p+1] = y[p] +(h/24)*(55*dy(x[p],y[p])-59*dy(x[p-1],y[p-1])+37*dy(x[p-2],y[p-2])-9*dy(x[p-3],y[p-3]))
        x[p+1] = x[p] + h
        print("y:"+str(i)+str(y[p+1]))

        p+=1
        i+=1


import numpy as np
from mlxtend.data import loadlocal_mnist #to convert mnist dataset to np arrays
from pathlib import Path #deal with local and global paths
from matplotlib import pyplot as plt

# If you run the code from pattern_recognition_assignment1 path
data_path = Path.cwd() / 'data'
# If you run the code from code directory
#data_path = Path.cwd() / ".." / "data"/
image_files_path =  str(data_path / 'train-images.idx3-ubyte')
label_files_path =  str(data_path / 'train-labels.idx1-ubyte')

XT, true_labelsT = loadlocal_mnist(
        images_path= image_files_path,
        labels_path= label_files_path)

X = XT.T # X=DxN=784x6000 #Data matrix
true_labels = true_labelsT.T # labels=1x6000

# It is required for reconstruction
mu= np.mean(X,axis=1,keepdims=True) #mu= Dx1

################ Implementation of PCA
def f_PCA(input_data,d):
        # input_data = matrix DxN where D=# dimensions and N=# of samples
        # d = It decides output dimension --> output=dxN
        # output: input_data but dimension is d --> so output size dxN
        # output 2: W matrix --> which is useful for inverse PCA
        # Step 1
        mu= np.mean(input_data,axis=1,keepdims=True) #mu= Dx1
        cov= np.cov(input_data) #cov= DxD

        # Step 2
        input_data_hat = input_data- mu #input_data_hat has zero mean --> shape DxN

        # Step 3
        Evalues, Evectors= np.linalg.eig(cov)

        # Step 4
        # Select the largest d eigenvalues
        # It is sorted, so principal_eigenvalues[0] >  principal_eigenvalues[1] > ... >
        p_evalues_col_indices = Evalues.argsort()[::-1][:d]
        W = Evectors[:,p_evalues_col_indices]

        # Step 5
        y = np.dot(W.T,input_data_hat) #matrix multiplication
        return y, W

################ Selecting a Suitable Dimension d Corresponding to Proportion of Variance (POV) 95\%
cov = np.cov(X) #cov= DxD
eigenvalues, _ = np.linalg.eig(cov)
total_of_all_eigenvalues = np.sum(eigenvalues)
desired_pov = 95/100 #percent
eigenvalues_sorted = -np.sort(-eigenvalues) #ascending order, i.e. first elements is the largest

calculated_pov = 0
epsilon = 0.001
# calculated_pov - desired_pov might be not exactly same, it is good practice to
# put some epsilon value
eigenvalue_sum = 0
d = 0 #counter for while loop, also our d value for PCA algorithm
while abs(calculated_pov - desired_pov) > epsilon:
        eigenvalue_sum += eigenvalues_sorted[d]
        calculated_pov = eigenvalue_sum / total_of_all_eigenvalues
        print("Desired pov is ", desired_pov)
        print("Calculated pov is ", calculated_pov)
        d += 1

# Since python indices start from zero, we need to add 1 after the loop
# to get the real d value
d += 1
print("d value corresponding to ", desired_pov, "POV is ", d)

################ Selecting a Suitable Dimension d Considering Average Mean Square Error (MSE)

# First construct inverse PCA function (PCA Reconstruction)
def f_inv_PCA(input_data,W):
        # input_data = matrix dxN where d=# reduced dimensions and N=# of samples
        # W = size dxD matrix containing all eigenvectors of original data
        # output: original data size dxN, but with zero mean

        reconstructed_data_hat = np.dot(W,input_data)
        return reconstructed_data_hat

# Calculate MSE for the d_values
d_values = np.append(np.insert(np.arange(20,780,20),0,1),784)
MSE_outputs = np.zeros((d_values.shape[0],1))

for i in range(d_values.shape[0]):
        reduced_d, W = f_PCA(X,d_values[i])
        reconstructed_d = f_inv_PCA(reduced_d,W) + mu
        error_square_d = (X-reconstructed_d)**2 #matrix DxN
        features_means_for_each_sample_d = np.mean(error_square_d,axis=0) #vector 1xN
        MSE_outputs[i] = np.mean(features_means_for_each_sample_d) #a scalar

fig1 = plt.figure()
plt.plot(d_values,MSE_outputs)
plt.title("Mean Square Error (MSE) variation with d")
plt.xlabel("d")
plt.ylabel("MSE")

################ Reconstruction of Original Image
number8_data = X[:,true_labels==8] #this contains all data belong to class of number 8
one_sample_number8 = number8_data[0] #just one example of data belong to class of number 8
# one_sample_number8 size --> 784x1

d_values_number8 = np.array([1,10,50,250,784])
# This reconstructed dictionary holds reconstructed image 8 for different d values
# For ex,reconstructed_sample_class8[1] is the reconstructed image 8 for d=10
# I am selecting the first image whose class is 8 with:
# reconstructed_sample[:,true_labels==8][:,0]
reconstructed_sample_class8 = {}
for i in range(d_values_number8.shape[0]):
        #we still need to use all data to train PCA
        reduced_samples, W = f_PCA(X,d_values_number8[i])
        reconstructed_sample = f_inv_PCA(reduced_samples,W) + mu
        reconstructed_sample_class8[i] = reconstructed_sample[:,true_labels==8][:,0]

# Show original image first
original_image_class8 = X[:,true_labels==8][:,0].reshape((28,28))
fig2 = plt.figure()
plt.title("Original Sample from the Class of Number 8")
plt.imshow(original_image_class8)

# Now show reconstructed ones
for i in range(d_values_number8.shape[0]):
        reconstructed_array = reconstructed_sample_class8[i].reshape((28,28))
        plt.figure()
        plt.title("Reconstructed Sample with d=%d" %d_values_number8[i])
        plt.imshow(reconstructed_array)

################ Analyze Variation of Eigenvalues with Reduced Dimension d
# It ask for to display sorted eigenvalues
fig3 = plt.figure()
plt.plot(eigenvalues_sorted)
plt.title("Eigenvalues varying with d value")
plt.xlabel("d")
plt.ylabel("Eigenvalue")

print("Q3 is done. Close the figures to see results of other question")
plt.show()


import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    los_sum = 0
    for i in range(len(y_true)):
        los_sum += ((y_true[i] - y_pred[i]) ** 2)

    return los_sum / len(y_true)

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray,
                            normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    size = y_pred.size
    error_sum = 0

    for i in range(size):
        if y_pred[i] != y_true[i]:
            error_sum += 1

    return error_sum / size if normalize else error_sum

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    accurate_sum = 0
    for i in range(y_true.size):
        if y_true[i] == y_pred[i]:
            accurate_sum += 1

    return accurate_sum / y_true.size

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()

if __name__ == '__main__':
    y_true = np.array([279000, 432000, 326000, 333000, 437400, 555950])
    y_pred = np.array(
        [199000.37562541, 452589.25533196, 345267.48129011, 345856.57131275,
         563867.1347574, 395102.94362135])

    print(mean_square_error(y_true, y_pred))


import numpy as np
from math import *
import sympy as sp

# Global variables
NMAX = 100
ERROR = 100
TOLERANCE = 0.00001

def newtonRaphson(x0,  error, test, f, df):
    ni = 0
    if not test:
        print("# iter\t\tx\t\t\tf(x)\t\t\terror\t\t\t")
        print("{0:d}\t\t{1:8.6f}\t\t{2:8.6f}\t\t{3:8.6f}".format(
            ni, x0, f(x0), error))

    root_solution = 0
    # Ejecucion del programa
    while error > TOLERANCE and ni < NMAX:
        x1 = x0 - (f(x0) / df(x0))
        ni += 1
        error = abs(x1 - x0)
        if not test:
            print("{0:d}\t\t{1:8.6f}\t\t{2:8.4f}\t\t{3:8.6f}".format(
                ni, x0, x1, error))
        x0 = x1
        root_solution = x1

    if test:
        return root_solution
    else:
        print("The value of the root is: ", root_solution)

def f_test_1(x):
    return (x**3) - (x**2) + 2

def df_test_1(x):
    return (3*x**2) - (2*x)

def f_test_2(x):
    return (2 * x**3) - (2 * x) - 5

def df_test_2(x):
    return (6 * x**2) - 2

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

def df_test_3(x):
    return 1 + np.sin(x)

if __name__ == "__main__":
    x0 = -20
    newtonRaphson(x0, ERROR, False, f_test_1, df_test_1)

# Tests
def test_1():
    x0 = -20
    assert newtonRaphson(x0, ERROR, True,
                         f_test_1, df_test_1) == -1.000000000000011

def test_2():
    x0 = 2
    assert newtonRaphson(x0, ERROR, True,
                         f_test_2, df_test_2) == 1.6005985449336209

def test_3():
    x0 = 0
    assert newtonRaphson(x0, ERROR, True, f_test_3,
                         df_test_3) == 0.7390851332151607


"""
Demonstration of Python Scientific computing.

Uses Newton Raphson's method as a vehicle.
"""

import matplotlib.pyplot as plt
import scipy as sp

def myfunc(x):
    """
    Generic function.

    Parameters
    ----------
    x: float or array_like
        The value or array at which to calculate the function

    Examples
    --------

    Execution on a float

    >>> from newton_raphson import *
    >>> myfunc(3.0)
        11.0

    Execution on a SciPy array_like

    >>> a = np.linspace(0,10,10)
    >>> myfunc(a)
        array([  -4.        ,   -0.54320988,    5.38271605,   13.77777778,
             24.64197531,   37.97530864,   53.77777778,   72.04938272,
             92.79012346,  116.        ])
    """
    return x**2+2*x-4

def newton_raphson_plot(function, x0=0, dx=1e-10, eps=1e-10):
    """
    Solve for a root of a function using Newton Raphson's method.

    Also plots the process.

    Parameters
    ----------
    function : string
        String with name of function to be solved for *function(x) = 0*
    x0       : float
        Initial guess for *x* near *function(x) = 0*
    dx       : float
        Delta x used for finite difference calculation of slope
    eps      : float
        Absolute value of *function(x)* which is considered zero.

    Examples
    --------
    >>> from newton_raphson import *
    >>> def myfunc(x):
    ...     return x**2+2*x-4
    >>> function_name = 'myfunc'
    >>> newton_raphson_plot(function_name, x0=2)
        (1.23606797..., ...)
    """
    deltax = 2 * eps
    count = 0
    x = x0
    y = np.linspace(1, 6, 200)
    plt.plot(y, globals()[function](y))
    plt.ylabel('$f(x)$')
    plt.xlabel('$x$')
    plt.title('Newton Raphson search for solution to $f(x)=0$.')
    plt.grid(True)
    plt.plot(np.array([x0, x0]), np.array([globals()[function](x0), 0]), 'r')
    plt.plot(np.array([x0]), np.array([globals()[function](x0)]), 'r*')
    while abs(globals()[function](x)) > eps and count < 50:
        count += 1
        plt.plot(np.array([x, x]), np.array([globals()[function](x), 0]), 'r')
        plt.plot(np.array([x]), np.array([globals()[function](x)]), 'r*')
        f = globals()[function](x)
        f2 = globals()[function](x + dx)
        dfdx = (f2 - f) / dx
        deltax = -f / dfdx
        x = x + deltax
        xr = np.linspace(x, x - deltax, 200)
        y = xr * dfdx - x * dfdx
        plt.plot(xr, y, 'y')  # Current point
    return x, deltax

def newton_raphson(function, x0=0, dx=1e-10, eps=1e-10):
    """
    Solve for a root of a function using Newton Raphson's method.

    Parameters
    ----------
    function : string
        String with name of function to be solved for *function(x) = 0*
    x0       : float
        Initial guess for *x* near *function(x) = 0*
    dx       : float
        Delta x used for finite difference calculation of slope
    eps      : float
        Absolute value of *function(x)* which is considered zero.

    Examples
    --------
    >>> from newton_raphson import *
    >>> def myfunc(x):
    ...     return x**2+2*x-4
    >>> function_name = 'myfunc'
    >>> newton_raphson(function_name, x0=2)
        (1.2360679..., ...)
    """
    deltax = 2*eps
    count = 0
    x = x0
    # loop until it converges, but no more than 50 times
    while abs(deltax) > eps and count < 50:
        count += 1 # I can add 1 to the variable *count*. Neat Python shortcut.
        # This is a comment
        # The next line is "Matlab style" and *bad*
        #f = eval(function + '('+ str(x) + ')')
        f = globals()[function](x)  #We explain later.
        #f2 = eval(function + '('+ str(x+dx) + ')')
        f2 = globals()[function](x+dx)
        dfdx = (f2-f)/dx
        deltax = -f/dfdx
        x = x + deltax
    return x, deltax


"""
This is a simple python program to generate pseudo random numbers between 0 and 1 using Linear Congruent method
"""

import numpy as np
import matplotlib.pyplot as plt

def rand_no_gen(seed, a, c, m, no_of_randnos):
    """
    This function generates a numpy array of random numbers (positive integers) using Linear congruent method

    If r_i is initial random number, the next random number in the sequence is given by
    r_i+1 = (a*r_i + c) % m
    The random numbers generated will start repeating after m numbers
    Parameters
    ----------
    seed : int
        The starting 'seed' for the random no. generator
    a : int
        The multiplying parameter for the random no. generator
    c : int
        The adding parameter for the random no. generator
    m : int
        m determines how many number of random nos can be generated. Repetition starts after m entries
    no_of_randnos : int
        Total no of random numbers (including seed) required

    Returns
    -------
    randno_array : ndarray
        A numpy array of the random nos generated

    """
    randno_array = np.zeros(no_of_randnos)
    randno_array[0] = seed
    for i in range(no_of_randnos - 1):
        randno_array[i + 1] = (a * randno_array[i] + c) % m
    return randno_array

def random_generator_test(array_of_random_numbers):
    if len(array_of_random_numbers) % 2 != 0:
        mod_array = array_of_random_numbers[0:-1]
    else:
        mod_array = array_of_random_numbers
    x_values = []
    y_values = []

    for i in range(len(mod_array)):
        if i % 2 == 0:
            x_values.append(mod_array[i])
        else:
            y_values.append(mod_array[i])

    fig, ax = plt.subplots()
    ax.scatter(x_values, y_values)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_title('Correlations of random nos')
    ax.grid(True)
    plt.show()

def main():
    print('## A simple python script to generate random numbers ##')
    no_of_randnos = int(input('Enter the number of random numbers that you want: '))
    seed = int(input('Enter seed value: '))
    a = int(input("Enter 'a' parameter for the generator: "))
    c = int(input("Enter 'c' parameter for the generator: "))
    m = int(input("Enter 'm' parameter for the generator: "))
    rand_array = rand_no_gen(seed, a, c, m, no_of_randnos)
    print(f'The generated random numbers are {rand_array}')
    random_generator_test(rand_array)

if __name__ == '__main__':
    main()


from scipy import stats
import matplotlib.pyplot as plt
import numpy as np
import jax.scipy.stats as jstats
from jax import grad

def main():

    # various beta distribution shapes
    x_vals = np.linspace(0.0, 1.0, 100)
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=0.5, b=0.5), label=f'a={0.5}, b={0.5}')
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=1.0, b=1.0), label=f'a={1.0}, b={1.0}')
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=10.0, b=10.0), label=f'a={10.0}, b={10.0}')
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=5.0, b=20.0), label=f'a={5.0}, b={20.0}')
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=20.0, b=5.0), label=f'a={20.0}, b={5.0}')
    plt.ylabel('Beta PDF')
    plt.xlabel('Action')
    plt.legend()
    plt.show()

    # action distribution
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=2.0, b=2.0), label=f'a={2.0}, b={2.0}')
    plt.axvline(0.1, color='red')
    plt.legend()
    plt.xlabel('Action')
    plt.ylabel('Beta PDF')
    plt.show()

    # beta distribution at x=0.1 for varying values of a
    a_vals = np.linspace(0.1, 2.0, 100)
    pdf_at_x = [stats.beta.pdf(x=0.1, a=a, b=2.0) for a in a_vals]
    plt.plot(a_vals, pdf_at_x)
    plt.xlabel('a')
    plt.ylabel('Beta PDF @ x=0.1, b=2.0')
    plt.xlim((0.1, 2.0))
    plt.show()

    # modified beta distribution to increase the density at x=0.1
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=2.0, b=2.0), label=f'a={2.0}, b={2.0}')
    plt.plot(x_vals, stats.beta.pdf(x_vals, a=0.6, b=2.0), label=f'a={0.6}, b={2.0}')
    plt.axvline(0.1, color='red')
    plt.legend()
    plt.xlabel('Action')
    plt.ylabel('Beta PDF')
    plt.show()

    # 1. Define the function for which we want a gradient.
    def jax_beta_pdf(
            x: float,
            a: float,
            b: float
    ):
        return jstats.beta.pdf(x=x, a=a, b=b, loc=0.0, scale=1.0)

    # 2. Ask JAX for the gradient with respect to the second argument (shape parameter a).
    jax_beta_pdf_grad = grad(jax_beta_pdf, argnums=1)

    # 3. Calculate the gradient that we want.
    print(f'{jax_beta_pdf_grad(0.1, 2.0, b=2.0)}')

if __name__ == '__main__':
    main()

import numpy,from scipy,import jax,from jax


import numpy as np

def angular_distance(lam1, lam2, phi1, phi2):
    """
    Angular distance between points at lon-lat coordinates
    (lam1, phi1) and (lam2, phi2) in degrees.

    See https://en.wikipedia.org/wiki/Great-circle_distance
    """
    return (
        np.arccos(
            np.sin(phi1 * np.pi / 180) * np.sin(phi2 * np.pi / 180)
            + np.cos(phi1 * np.pi / 180)
            * np.cos(phi2 * np.pi / 180)
            * np.cos((lam2 - lam1) * np.pi / 180)
        )
        * 180
        / np.pi
    )

def fS_samp(r, N=100000):
    """
    Fractional spot coverage area computed by sampling a bunch
    of points on the sphere and computing the fraction that are
    inside a spot of radius `r`.

    See https://mathworld.wolfram.com/SpherePointPicking.html
    """
    u = np.random.random(N)
    v = np.random.random(N)
    lam = 360 * u - 180
    phi = np.arccos(2 * v - 1) * 180 / np.pi - 90
    delta_angle = angular_distance(0, lam, 0, phi)
    return np.array(
        [np.count_nonzero(delta_angle <= r_) / N for r_ in np.atleast_1d(r)]
    )

def fS_exact(r):
    """
    Analytical expression for the fractional spot coverage.

    """
    return 0.5 * (1 - np.cos(r * np.pi / 180))

def test_fS():
    r = np.linspace(0, 45, 100)
    assert np.allclose(fS_samp(r), fS_exact(r), atol=1e-2)


import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import os
import pyprobml_utils as pml

data_dir = "../data"
img = matplotlib.image.imread(os.path.join(data_dir, "clown.png"))

def rgb2gray(rgb):
    return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])

X = rgb2gray(img)

r = np.linalg.matrix_rank(X)
print(r)

U, sigma, V = np.linalg.svd(X, full_matrices=True)
ranks = [1, 2, 5, 10, 20, r]
R = len(ranks)

for i in range(R):
    k = ranks[i]
    #x_hat = np.matrix(U[:, :k]) * np.diag(sigma[:k]) * np.matrix(V[:k, :])
    x_hat = np.dot(np.dot(U[:, :k], np.diag(sigma[:k])), V[:k, :])
    plt.imshow(x_hat, cmap='gray')
    plt.title("rank {}".format(k))
    plt.axis("off")
    pml.savefig("svdImageDemoClown{}.pdf".format(k))
    plt.show()

k = 100
plt.plot(np.log(sigma[:k]), 'r-', linewidth=4, label="Original")
plt.ylabel(r"$log(\sigma_i)$")
plt.xlabel("i")

# Compare this to a random shuffled version of the image
x2 = np.random.permutation(X)
# so we convert to a 1d vector, permute, and convert back
x1d = X.ravel()
np.random.shuffle(x1d) # inplace
x2 = x1d.reshape(X.shape)
U, sigma2, V = np.linalg.svd(x2, full_matrices = False)
plt.plot(np.log(sigma2[:k]), 'b', linewidth=4, label="Randomized")
plt.legend()
pml.savefig("svdImageDemoClownSigmaScrambled.pdf")
plt.show()


import numpy as np
import matplotlib.pyplot as plt

"""
Generates a Geometric Brownian Motion timeseries.
Args:
    S0: the first term in the timeseries
    mu: the drift parameter of the GBM
    sigma: the volatility parameter of the GBM
    num_periods: the number of periods to model
    increments_per_period: the number of increments in the timeseries per period
"""
def gbm(S0, mu, sigma, num_periods, increments_per_period):
    T = num_periods
    dt = 1/increments_per_period
    N = num_periods*increments_per_period
    t = np.linspace(0, T, N)
    W = np.random.standard_normal(size = N)
    W = np.cumsum(W)*np.sqrt(dt) # standard brownian motion
    X = (mu-0.5*sigma**2)*t + sigma*W
    S = S0*np.exp(X) # geometric brownian motion
    return S

"""
Generates a cyclical timeseries, each cycle modeled independently by a Geometric Brownian Motion.
This is a useful function for simulating macroeconomic variables like GDP.
Args:
    S0: the first term in the timeseries
    mu_boom: the drift parameter of the GBM in a boom cycle (positive)
    mu_bust: the drift parameter of the GBM in a bust cycle (negative)
    sigma: the volatility parameter of the GBM
    cycle_lengths: an array of integers specifying the length of each cycle (number of periods)
    increments_per_period: the number of increments in the timeseries per period

cycle_lengths could be [2,4,3,4] for example, specifying four cycles with lenghts 2, 4, 3 and 4 periods respectively.
Cycles alternate between boom and bust. The first cycle is always a boom.
"""
def gbm_cyclical(S0, mu_boom, mu_bust, sigma, cycle_lengths, increments_per_period):
    cycle_lengths = np.array(cycle_lengths)
    cycle_lengths *= increments_per_period

    dt = 1
    mu_boom = mu_boom/increments_per_period
    mu_bust = mu_bust/increments_per_period
    sigma = np.sqrt(sigma**2/increments_per_period)

    cycles = np.array([[]])
    for i, cycle_length in enumerate(cycle_lengths):
        cycle_mu = mu_boom if i%2 == 0 else mu_bust
        cycle = np.exp((cycle_mu-sigma**2/2)*dt)*np.exp(sigma*np.random.normal(0, np.sqrt(dt), (1,cycle_length)))
        cycles = np.concatenate((cycles, cycle), axis=1)

    return S0*cycles.cumprod()

if __name__ == '__main__':
    # demonstrates the usage of gbm and gbm_cyclical
    np.random.seed(0)
    t = range(0, 520)
    S = gbm(1, 0.05, 0.5, 10, 52)
    S_cyclical = gbm_cyclical(1, 0.34, -0.2, 0.3, [2,3,5], 52)
    plt.plot(t, S, S_cyclical)
    plt.show()


"""
base.py
=======

Fundamental components for a simple python number theory library.

License Info
============

(c) David Lowry-Duda 2018 <davidlowryduda@davidlowryduda.com>

This is available under the MIT License. See
<https://opensource.org/licenses/MIT> for a copy of the license,
or see the home github repo
<https://github.com/davidlowryduda/pynt>.
"""
from typing import List, Tuple, Union
from itertools import product as cartesian_product
import numpy

def gcd(num1: int, num2: int) -> int:
    """
    Returns the greatest common divisor of `num1` and `num2`.

    Examples:
    >>> gcd(12, 30)
    6
    >>> gcd(0, 0)
    0
    >>> gcd(-1001, 26)
    13
    """
    if num1 == 0:
        return num2
    if num2 == 0:
        return num1
    if num1 < 0:
        num1 = -num1
    if num2 < 0:
        num2 = -num2
    # This is the Euclidean algorithm
    while num2 != 0:
        num1, num2 = num2, num1 % num2
    return num1

def smallest_prime_divisor(num: int, bound: Union[int, None] = None) -> int:
    """
    Returns the smallest prime divisor of the input `num` if that divisor is
    at most `bound`. If none are found, this returns `num`.

    Input:
        num: a positive integer
        bound: an optional bound on the size of the primes to check. If not
               given, then it defaults to `num`.

    Output:
        The smallest prime divisor of `num`, or `num` itself if that divisor is
        at least as large as `bound`.

    Raises:
        ValueError: if num < 1.

    Examples:
    >>> smallest_prime_divisor(15)
    3
    >>> smallest_prime_divisor(1001)
    7
    """
    if num < 1:
        raise ValueError("A positive integer is expected.")
    if num == 1:
        return num
    for prime in [2, 3, 5]:
        if num % prime == 0:
            return prime
    if bound is None:
        bound = num
    # Possible prime locations mod 2*3*5=30
    diffs = [6, 4, 2, 4, 2, 4, 6, 2]
    cand = 7
    i = 1
    while cand <= bound and cand*cand <= num:
        if num % cand == 0:
            return cand
        cand += diffs[i]
        i = (i + 1) % 8
    return num

# primesfrom2to(n) from
# https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n
def primes(limit):
    """
    Returns the numpy array of primes up to (and not including) `limit`.

    Examples:
    >>> primes(10)
    array([2, 3, 5, 7])
    """
    sieve = numpy.ones(limit // 3 + (limit % 6 == 2), dtype=numpy.bool)
    for i in range(1, int(limit ** 0.5) // 3 + 1):
        if sieve[i]:
            k = (3 * i + 1) | 1
            sieve[k * k // 3::2 * k] = False
            sieve[k * (k - 2 * (i & 1) + 4) // 3:: 2 * k] = False
    return numpy.r_[2, 3, ((3 * numpy.nonzero(sieve)[0][1:] + 1) | 1)]

def factor(num: int) -> List[Tuple[int, int]]:
    """
    Returns the factorization of `num` as a list of tuples of the form (p, e)
    where `p` is a prime and `e` is the exponent of that prime in the
    factorization.

    Input:
        num: an integer to factor

    Output:
        a list of tuples (p, e), sorted by the size of p.

    Examples:
    >>> factor(100)
    [(2, 2), (5, 2)]
    >>> factor(-7007)
    [(7, 2), (11, 1), (13, 1)]
    >>> factor(1)
    []
    """
    if num in (-1, 0, 1):
        return []
    if num < 0:
        num = -num
    factors = []
    while num != 1:
        prime = smallest_prime_divisor(num)
        exp = 1
        num = num // prime
        while num % prime == 0:
            exp += 1
            num = num // prime
        factors.append((prime, exp))
    return factors

def factors(num: int) -> List[int]:
    """
    Returns the list of factors of an integer.

    Examples:
    >>> factors(6)
    [1, 2, 3, 6]
    >>> factors(30)
    [1, 2, 3, 5, 6, 10, 15, 30]
    """
    factorization = factor(num)
    primes_, exps = zip(*factorization)
    exp_choices = cartesian_product(*[range(exp+1) for exp in exps])
    ret = []
    for exp_choice in exp_choices:
        val = 1
        for prime, exp in zip(primes_, exp_choice):
            val *= (prime**exp)
        ret.append(val)
    return sorted(ret)


# log_posterior_probs function was implemented in October 2021 by
# Pascal Lieberherr, Zador Pataki, Timo Schönegg

import numpy
from scipy.stats import laplace, norm, t
import scipy
import math
import numpy as np
from scipy.special import logsumexp

VARIANCE = 2.0

normal_scale = math.sqrt(VARIANCE)
student_t_df = (2 * VARIANCE) / (VARIANCE - 1)
laplace_scale = VARIANCE / 2

HYPOTHESIS_SPACE = [norm(loc=0.0, scale=math.sqrt(VARIANCE)),
                    laplace(loc=0.0, scale=laplace_scale),
                    t(df=student_t_df)]

PRIOR_PROBS = np.array([0.35, 0.25, 0.4])

def generate_sample(n_samples, seed=None):
    """ data generating process of the Bayesian model """
    random_state = np.random.RandomState(seed)
    hypothesis_idx = np.random.choice(3, p=PRIOR_PROBS)
    dist = HYPOTHESIS_SPACE[hypothesis_idx]
    return dist.rvs(n_samples, random_state=random_state)

""" Solution """

from scipy.special import logsumexp

def log_posterior_probs(x):
    """
    Computes the log posterior probabilities for the three hypotheses, given the data x

    Args:
        x (np.ndarray): one-dimensional numpy array containing the training data
    Returns:
        log_posterior_probs (np.ndarray): a numpy array of size 3, containing the Bayesian log-posterior probabilities
                                          corresponding to the three hypotheses
    """
    assert x.ndim == 1

    # p1 = sum(log p(xi | H1)) + log(p(H1)) - log(prod(p(x1|H1))*p(H1)+prod(p(x2|H2))*p(H2)+prod(p(x3|H3))*p(H3))
    # p1 ~ sum(log p(xi | H1)) + log(p(H1)) - LSE(log(prod(p(x1|H1))*p(H1)), prod(p(x2|H2))*p(H2), prod(p(x3|H3))*p(H3))   # https://en.wikipedia.org/wiki/LogSumExp
    # p1 ~ sum(log p(xi | H1)) + log(p(H1)) - LSE(sum(log(p(x1|H1))) + log(p(H1)), ...)

    log_p_h1 = np.log(PRIOR_PROBS[0])
    log_p_h2 = np.log(PRIOR_PROBS[1])
    log_p_h3 = np.log(PRIOR_PROBS[2])

    sum_log_p_x_given_h1 = 0
    sum_log_p_x_given_h2 = 0
    sum_log_p_x_given_h3 = 0

    for xi in np.nditer(x):
        sum_log_p_x_given_h1 += np.log(norm.pdf(xi, loc=0.0, scale=math.sqrt(VARIANCE)))
        sum_log_p_x_given_h2 += np.log(laplace.pdf(xi, loc=0.0, scale=laplace_scale))
        sum_log_p_x_given_h3 += np.log(t.pdf(xi, df=student_t_df))

    lse_123 = logsumexp(np.array([sum_log_p_x_given_h1 + log_p_h1, sum_log_p_x_given_h2 + log_p_h2, sum_log_p_x_given_h3 + log_p_h3]))

    log_p_1 = sum_log_p_x_given_h1 + log_p_h1 - lse_123
    log_p_2 = sum_log_p_x_given_h2 + log_p_h2 - lse_123
    log_p_3 = sum_log_p_x_given_h3 + log_p_h3 - lse_123

    log_p = np.array([log_p_1, log_p_2, log_p_3])

    assert log_p.shape == (3,)
    return log_p

def posterior_probs(x):
    return np.exp(log_posterior_probs(x))

""" """

def main():
    """ sample from Laplace dist """
    dist = HYPOTHESIS_SPACE[1]
    x = dist.rvs(1000, random_state=28)

    print("Posterior probs for 1 sample from Laplacian")
    p = posterior_probs(x[:1])
    print("Normal: %.4f , Laplace: %.4f, Student-t: %.4f\n" % tuple(p))

    print("Posterior probs for 100 samples from Laplacian")
    p = posterior_probs(x[:50])
    print("Normal: %.4f , Laplace: %.4f, Student-t: %.4f\n" % tuple(p))

    print("Posterior probs for 1000 samples from Laplacian")
    p = posterior_probs(x[:1000])
    print("Normal: %.4f , Laplace: %.4f, Student-t: %.4f\n" % tuple(p))

    print("Posterior for 100 samples from the Bayesian data generating process")
    x = generate_sample(n_samples=100)
    p = posterior_probs(x)

    print("Normal: %.4f , Laplace: %.4f, Student-t: %.4f\n" % tuple(p))

if __name__ == "__main__":
    main()


# # Exponential functions and logarithms

import math
import numpy as np

# ## Exponential functions

# What is **e**? It is simply a number (known as Euler's number):

math.e

# **e** is a significant number, because it is the base rate of growth shared by all continually growing processes.
#
# For example, if I have **10 dollars**, and it grows 100% in 1 year (compounding continuously), I end up with **10\*e^1 dollars**:

# 100% growth for 1 year
10 * np.exp(1)

# 100% growth for 2 years
10 * np.exp(2)

# Side note: When e is raised to a power, it is known as **the exponential function**. Technically, any number can be the base, and it would still be known as **an exponential function** (such as 2^5). But in our context, the base of the exponential function is assumed to be e.
#
# Anyway, what if I only have 20% growth instead of 100% growth?

# 20% growth for 1 year
10 * np.exp(0.20)

# 20% growth for 2 years
10 * np.exp(0.20 * 2)

# ## Logarithms

# What is the **(natural) logarithm**? It gives you the time needed to reach a certain level of growth. For example, if I want growth by a factor of 2.718, it will take me 1 unit of time (assuming a 100% growth rate):

# time needed to grow 1 unit to 2.718 units
np.log(2.718)

# If I want growth by a factor of 7.389, it will take me 2 units of time:

# time needed to grow 1 unit to 7.389 units
np.log(7.389)

# If I want growth by a factor of 1, it will take me 0 units of time:

# time needed to grow 1 unit to 1 unit
np.log(1)

# If I want growth by a factor of 0.5, it will take me -0.693 units of time (which is like looking back in time):

# time needed to grow 1 unit to 0.5 units
np.log(0.5)

# ## Connecting the concepts

# As you can see, the exponential function and the natural logarithm are **inverses** of one another:

np.log(np.exp(5))

np.exp(np.log(5))


# @Time: 2022/4/13 21:18
# @Author: chang liu
# @Email: chang_liu_tamu@gmail.com
# @File:3.10.Performing_Matrix_and_Linear_algebra_calculations.py

import numpy as np
from numpy import linalg as nlg

m = np.arange(9).reshape((3, 3))
print(m)
print(m.T)
# print(nlg.inv(m))

v = np.arange(2, 5).reshape(3, 1)
print(v)

print(m * v)
print(m @ v)

print(nlg.det(m))
print(nlg.eigvals(m))

# nlg.solve(m, v) #solve linear systems


import numpy as np
from linear_programming.simplex_algorithm import SimplexAlgorithm

# object  min Z = -3 * x1 + x2 + x3
# s.t.
#       x1 - 2 * x2 +     x3 <= -11
#  -4 * x1 +     x2 + 2 * x3 >= 3
#   2 * x1 -              x3 = -1
#   x_i >= 0 (i = 1,2,3)

# A = np.array([[1, -2, 1], [-4, 1, 2], [2, 0, -1]])
# B = np.array([11, 3, -1])
# Z = np.array([-3, 1, 1])  # min
# restrict = ['<=', '>=', '=']
# mode = 'min'

# A = np.array([[-1, 2], [0, 1]])
# B = np.array([2, 3])
# Z = np.array([1, 2]) # max
# restrict = ['<=', '<=']
# mode = 'max'

# A = np.array([[1, 2, 2, 1, 0], [3, 4, 1, 0, 1]])
# B = np.array([8, 7])
# Z = np.array([5, 2, 3, -1, 1])
# restrict = ['=', '=']
# mode = 'max'

# A = np.array([[-2, 2, 1, 0], [3, 1, 0, 1]])
# B = np.array([4, 6])
# Z = np.array([3, 1, 1, 1])
# restrict = ['=', '=']
# mode = 'min'

# A = np.array([[4, 5], [5, 2]])
# B = np.array([100, 80])
# Z = np.array([10, 5])    # max
# restrict = ['<=', '<=']
# mode = 'max'

A = np.array([[1, 0], [1, 2], [0, 1]])
B = np.array([5, 10, 4])
Z = np.array([1, 3])    # max
restrict = ['<=', '<=', '<=']
mode = 'max'

if __name__ == '__main__':
    x, optimal = SimplexAlgorithm(A, B, Z, mode, restrict).run()
    print('最优解', x)
    print('目标函数最优值', optimal[0])


# This program and its computation instruction was written by Andre Christoga Pramaditya (drepram.com) with the student ID (2006570006).
# Program dan instruksi komputasi ini ditulis oleh Andre Christoga Pramaditya (drepram.com) dengan Nomor Pokok Mahasiswa (2006570006).

import numpy as np
import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings("ignore") # insert programmer laziness joke here

size_ten = 10
size_hundred = 100
size_thousand = 1000
size_million = 1000000

# Case 1 on the task said to retrieve n=10 random numbers from the uniform distribution [1,10]

# Remembering the set notation of [low, high), where low is included and high is excluded.
# We could check the validity of our computation with the functions that we would show below (via console logging)

case_1_histogram = plt.figure(1, figsize=(7,7))

case_1_sample_ten = np.random.uniform(1,10,10)
print("For we are supposed to retrieve random numbers from [1,10], it begs the question as to whether the intervals are actually included in our computation.\n")
print("Does the random distribution of (low=1,high=10,size=10) includes samples from numbers that is less or equal to 10?", np.all(case_1_sample_ten <= 10))
print("Does the random distribution of (low=1,high=10,size=10) includes samples from numbers that is greater or equal to 1?", np.all(case_1_sample_ten >= 1))
print("\nAs the questions above has been answered, we will proceed to show the graph of other uniform distributions with differing number of sample size.")

case_1_sample_hundred = np.random.uniform(1,10,100)
case_1_sample_thousand = np.random.uniform(1,10,1000)
case_1_sample_million = np.random.uniform(1,10,1000000)

plt.hist(case_1_sample_ten, 15, density=True)
plt.hist(case_1_sample_hundred, 15, density=True)
plt.hist(case_1_sample_thousand, 15, density=True)
plt.hist(case_1_sample_million, 15, density=True)

case_1_histogram.suptitle('Uniform Distribution Case 1 Combined Histogram', fontsize=14, fontweight='bold')
case_1_axes_title = case_1_histogram.add_subplot(111)
case_1_histogram.subplots_adjust(top=0.85)
case_1_axes_title.set_title('retrieve n=10 random numbers from the uniform distribution [1,10] \nwith n iterating from 10,100,1000,1000000')
case_1_histogram.show()

print("\n---\nNow on to the second case.\n")

case_2_histogram = plt.figure(2, figsize=(7,7))

case_2_sample_ten = np.random.uniform(25,100,10)
print("For we are supposed to retrieve random numbers from [25,100], it begs the question as to whether the intervals are actually included in our computation.\n")
print("Does the random distribution of (low=25,high=100,size=10) includes samples from numbers that is less or equal to 25?", np.all(case_2_sample_ten <= 25))
print("Does the random distribution of (low=25,high=100,size=10) includes samples from numbers that is greater or equal to 10?", np.all(case_2_sample_ten >= 10))
print("\nAs the questions above has been answered, we will proceed to show the graph of other uniform distributions with differing number of sample size.")

case_2_sample_hundred = np.random.uniform(25,100,100)
case_2_sample_thousand = np.random.uniform(25,100,1000)
case_2_sample_million = np.random.uniform(25,100,1000000)

plt.hist(case_2_sample_ten, 15, density=True)
plt.hist(case_2_sample_hundred, 15, density=True)
plt.hist(case_2_sample_thousand, 15, density=True)
plt.hist(case_2_sample_million, 15, density=True)

case_2_histogram.suptitle('Uniform Distribution Case 2 Combined Histogram', fontsize=14, fontweight='bold')
case_2_axes_title = case_2_histogram.add_subplot(111)
case_2_histogram.subplots_adjust(top=0.85)
case_2_axes_title.set_title('retrieve n=10 random numbers from the uniform distribution [25,100] \nwith n iterating from 10,100,1000,1000000')
case_2_histogram.show()

print("\n---\nNow that the combined histograms of both cases has been shown, we will show the individual graphs.\n")

case_1_each, case_1_each_axes = plt.subplots(2, 2, figsize=(10,10))
case_1_each.suptitle('Uniform Distribution Case 1 Histogram of Every Sample Size', fontsize=14, fontweight='bold')

case_1_each_axes[0, 0].hist(case_1_sample_ten, 15, density=True)
case_1_each_axes[0, 0].set_title('low=1,high=10,size=10')
case_1_each_axes[1, 0].hist(case_1_sample_hundred, 15, density=True)
case_1_each_axes[1, 0].set_title('low=1,high=10,size=100')
case_1_each_axes[0, 1].hist(case_1_sample_thousand, 15, density=True)
case_1_each_axes[0, 1].set_title('low=1,high=10,size=1000')
case_1_each_axes[1, 1].hist(case_1_sample_million, 15, density=True)
case_1_each_axes[1, 1].set_title('low=1,high=10,size=1000000')

case_2_each, case_2_each_axes = plt.subplots(2, 2, figsize=(10,10))
case_2_each.suptitle('Uniform Distribution Case 2 Histogram of Every Sample Size', fontsize=14, fontweight='bold')

case_2_each_axes[0, 0].hist(case_2_sample_ten, 15, density=True)
case_2_each_axes[0, 0].set_title('low=25,high=100,size=10')
case_2_each_axes[1, 0].hist(case_2_sample_hundred, 15, density=True)
case_2_each_axes[1, 0].set_title('low=25,high=100,size=100')
case_2_each_axes[0, 1].hist(case_2_sample_thousand, 15, density=True)
case_2_each_axes[0, 1].set_title('low=25,high=100,size=1000')
case_2_each_axes[1, 1].hist(case_2_sample_million, 15, density=True)
case_2_each_axes[1, 1].set_title('low=25,high=100,size=1000000')

plt.show()


import networkx as nx
import numpy as np
from scipy.sparse.linalg import eigs
from sklearn.cluster import KMeans

# ############# Task 5

# Perform spectral clustering to partition graph G into k clusters
def spectral_clustering(G, k):
    n = G.number_of_nodes()
    A = nx.adjacency_matrix(G)
    D = np.zeros((n, n))
    for i, node in enumerate(G.nodes()):
        D[i, i] = G.degree(node)

    L = D - A
    eigvals, eigvecs = eigs(L, k=100, which='SR')
    eigvals = eigvals.real
    eigvecs = eigvecs.real

    km = KMeans(n_clusters=k)
    km.fit(eigvecs)

    clustering = dict()

    for i, node in enumerate(G.nodes()):
        clustering[node] = km.labels_[i]

    return clustering

# ############# Task 6

G = nx.read_edgelist('CA-HepTh.txt', comments='#',
                     delimiter='\t', create_using=nx.Graph())

largest_cc = max(nx.connected_components(G), key=len)
gcc = G.subgraph(largest_cc)

clustering = spectral_clustering(gcc, 50)

# ############# Task 7

# Compute modularity value from graph G based on clustering

def modularity(G, clustering):

    modularity = 0
    clusters = set(clustering.values())
    m = G.number_of_edges()

    for cluster in clusters:
        nodes_in_cluster = [node for node in G.nodes()
                            if clustering[node] == cluster]
        subG = G.subgraph(nodes_in_cluster)
        l_c = subG.number_of_edges()

        d_c = 0
        for node in nodes_in_cluster:
            d_c += G.degree(node)

        modularity += (l_c/m) - (d_c/(2*m))**2

    return modularity

# ############# Task 8

print("Modularity Spectral Clustering: ", modularity(gcc, clustering))

# Implement random clustering

k = 50
r_clustering = dict()

for node in gcc.nodes:
    r_clustering[node] = np.random.randint(0, k)

print("Modularity Random Clustering: ", modularity(gcc, r_clustering))


import math as m
from scipy.misc import derivative

# Here we simply give the input needed as The Following:
# ( Equation ) First Specify if the input is an equation ( 1 ) or only a single application of the rule ( 0 )
# ( Xi ) is the initial point
# If The Equation was set to "0" you need to provide The Values First Derivative ( dr ) And Second Derivative ( Ddr ). if not selected keep 0
# If The Equation was set to "1" you need to provide The Equation in the function ( F ) if not selected keep 0
# ( n ) is the number of Iterations needed

Equation = 1

Xi = 8.6

dr = 1.2

Ddr = 5

def F(x): return -0.25*x**4+1.1*x**3-1.75*x**2+2*x

n = 5

print("\n-----------------------------------------------------------------")
print(" Iteration |   Xi    |   F(Xi)  |   F(x)\'  |  F(x)\"    |  Error "
      "\n-----------------------------------------------------------------")
Error = 100
XiPlus1 = 0
for i in range (n):
    if Equation == 1:
        print("\t{0:1d}  | {1:.5f} | {2:.5f}  | {3:.5f} | {4:.5f} |  {5:.4f}%\n"
              .format(i, Xi, F(Xi), derivative(F, Xi, dx=1e-6), derivative(F, Xi, dx=1e-6, n = 2 ), Error))
        XiPlus1 = Xi - derivative(F, Xi, dx=1e-6)/derivative(F, Xi, dx=1e-6, n = 2 )
        Error = abs(((XiPlus1 - Xi)/XiPlus1) *100)
    if Equation == 0:
        print("\t{0:1d}  | {1:.5f} |  F(Xi)  | {2:.5f} | {3:.5f} |  {4:.4f} %\n"
              .format(i, Xi, dr2,Ddr2, Error))
        XiPlus1 = Xi - (dr/Ddr)
        Error = abs(((XiPlus1 - Xi)/XiPlus1) *100)
        if i == 2 : break
    Xi = XiPlus1

# Finally displaying the solution as well as the percentge of error after each iteration


import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-1,3,100)
y=x**2-2*x+1

fig = plt.figure()
axdef = fig.add_subplot(1, 1, 1)
axdef.spines['left'].set_position('center')
axdef.spines['bottom'].set_position('zero')
axdef.spines['right'].set_color('none')
axdef.spines['top'].set_color('none')
axdef.xaxis.set_ticks_position('bottom')
axdef.yaxis.set_ticks_position('left')

plt.plot(x,y, 'r')
plt.show()

Gradf = lambda x: 2*x-2

ActualX = 3
LearningRate = 0.01
PrecisionValue = 0.000001
PreviousStepSize = 1
MaxIteration = 10000
IterationCounter = 0

while PreviousStepSize > PrecisionValue and IterationCounter < MaxIteration:
    PreviousX = ActualX
    ActualX = ActualX - LearningRate * Gradf(PreviousX)
    PreviousStepSize = abs(ActualX - PreviousX)
    IterationCounter = IterationCounter+1
    print("Number of iterations = ",IterationCounter,"\nActual value of x  is = ",ActualX)

print("X value of f(x) minimum = ", ActualX)


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, pprint )
import matplotlib.pyplot as plt
import numpy as np

# If the age of a randomly selected driver in a fatal car crash is a random variable with a probability density function given by

x = symbols( 'x' )
F = 0.01912 * exp( -0.00321 * x )

# What does the petal distribution look like?

g_xlim = [ -1, 90 ]

lam_p = lambdify( x, F, np )
x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_p( x_vals )
plt.plot( x_vals, y_vals )

x_min, x_max = 18, 79

plt.hlines( y = 0, xmin = g_xlim[0], xmax = g_xlim[1], color = 'Black', zorder = 1 )

plt.vlines( x = x_min, ymin = 0, ymax = F.subs( { x: x_min } ), color = 'Black', zorder = 1 )
plt.vlines( x = x_max, ymin = 0, ymax = F.subs( { x: x_max } ), color = 'Red', zorder = 1 )

# The probability that the driver's age is between 24 and 45 is

a, b = 24, 45
bounds = np.arange( a, b, 1/25., dtype=float)

for n in bounds:
	y = F.subs( { x: n } )
	plt.vlines( x = n, ymin = 0, ymax = y, color = 'Teal', zorder = 1, alpha = .4 )

area = integrate( F, ( x, a, b ) ).evalf()
total_area = integrate( F, ( x, x_min, x_max ) ).evalf()

area_pct = round( ( area / total_area ), 2 )

plt.title( 'Area: {0}'.format( area_pct ) )
plt.show()

# The probability that the driver's age is greater than or equal to 33

a, b = 32, x_max
bounds = np.arange( a, b, 1/25., dtype=float)

for n in bounds:
	y = F.subs( { x: n } )
	plt.vlines( x = n, ymin = 0, ymax = y, color = 'Teal', zorder = 1, alpha = .4 )

area = integrate( F, ( x, a, b ) ).evalf()
total_area = integrate( F, ( x, x_min, x_max ) ).evalf()

area_pct = round( ( area / total_area ), 2 )

plt.title( 'Area: {0}'.format( area_pct ) )
plt.show()

# The probability that the driver is less than or equal to 28.

a, b = x_min, 21
bounds = np.arange( a, b, 1/25., dtype=float)

for n in bounds:
	y = F.subs( { x: n } )
	plt.vlines( x = n, ymin = 0, ymax = y, color = 'Teal', zorder = 1, alpha = .4 )

area = integrate( F, ( x, a, b ) ).evalf()
total_area = integrate( F, ( x, x_min, x_max ) ).evalf()

area_pct = round( ( area / total_area ), 2 )

plt.title( 'Area: {0}'.format( area_pct ) )
plt.show()

# Find the cumulative distribution function for this random variable.
a, b = x_min, x
cdf = integrate( F, ( x, a, b ) )

pprint( cdf )

round( 5.95638629283489, 5 )

#  Use the answer to part D to find the probability that a randomly selected driver in a fatal crash is at most 24 years old.

ans = cdf.subs( { x: 23 } ).evalf()
round( ans, 2 )


__doc__ = """ Fits a set of 2D data points (x,y) to a circle

See https://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html
From https://dtcenter.org/met/users/docs/write_ups/circle_fit.pdf
For more complicated shapes, see https://arxiv.org/pdf/cs/0301001.pdf
"""

import numpy as np
from scipy.optimize import least_squares
from scipy.stats import linregress

def fit_circle_to_data(in_data, verbose=False):
    """
    Wrapper around _fit_circle_impl that takes care of unwrapping shapes and so on...
    Returns
    -------

    """
    slope, intercept, r_value, p_value, std_err = linregress(in_data[0], in_data[1])

    if verbose:
        print("R-square of linear fit : {}".format(r_value ** 2))
        print("std-error of linear fit : {}".format(std_err))

    if r_value ** 2 < 0.98 and std_err > 1e-3:
        xc, yc, r, opt = circle_fit_impl(in_data)

        if verbose:
            print("Number of function evaluations : {}".format(opt.nfev))
            print("Number of Jacobian evaluations : {}".format(opt.njev))
            print("Stddev. residuals at optimal : {}".format(np.std(opt.fun)))

        return xc, yc, r
    else:
        # It's almost a straight line, so return all infinitirys
        return np.inf, np.inf, np.inf

def circle_fit_impl(x):
    """(2,n) array as input"""

    def distance_from_center(xc, yc):
        """calculate the distance of each 2D points from the center (xc, yc)"""
        return np.sqrt((x[0] - xc) ** 2 + (x[1] - yc) ** 2)

    def objective(c):
        """calculates the objective : algebraic distance between the data points
        and the mean circle centered at c=(xc, yc)

        Maps from R^2 -> R^n where n is the number of points to map
        """
        Ri = distance_from_center(*c)
        return Ri - Ri.mean()

    def jacobian_objective(c):
        """Jacobian of above objective function
        By definition from scipy J_{ij} = \\partial f_{i}/ \\partial x_{j}
        Hence J is a (n,2) matrix below

        Parameters
        ----------
        c

        Returns
        -------

        """
        xc, yc = c
        df2b_dc = np.empty((x.shape[-1], c.shape[0]))

        r_i = distance_from_center(xc, yc)
        df2b_dc[..., 0] = (xc - x[0]) / r_i  # dR/dxc
        df2b_dc[..., 1] = (yc - x[1]) / r_i  # dR/dyc
        df2b_dc -= np.mean(df2b_dc, axis=0)

        return df2b_dc

    # estimate center via mean of data
    center_estimate = np.mean(x, axis=-1)
    optimum_results = least_squares(
        objective, center_estimate, jac=jacobian_objective, method="lm"
    )

    center_optimized = optimum_results.x
    radius_samples = distance_from_center(*center_optimized)
    radius = np.mean(radius_samples)
    return (*center_optimized, radius, optimum_results)


import math

import numpy as np
import pytest

from skspatial.measurement import area_triangle
from skspatial.measurement import volume_tetrahedron

@pytest.mark.parametrize(
    ("array_a", "array_b", "array_c", "area_expected"),
    [
        ([0, 0], [1, 0], [0, 1], 0.5),
        ([0, 0], [1, 1], [2, 0], 1),
        ([0, 0], [1, 10], [2, 0], 10),
        ([0, 0], [1, 0], [2, 0], 0),
        ([0, 0], [-5, -2], [5, 2], 0),
        ([1, 0, 0], [0, 1, 0], [0, 0, 1], math.sin(np.pi / 3)),
        ([2, 0, 0], [0, 2, 0], [0, 0, 2], 4 * math.sin(np.pi / 3)),
    ],
)
def test_area_triangle(array_a, array_b, array_c, area_expected):

    area = area_triangle(array_a, array_b, array_c)
    assert math.isclose(area, area_expected)

@pytest.mark.parametrize(
    ("array_a", "array_b", "array_c", "array_d", "volume_expected"),
    [
        ([0, 0], [2, 0], [1, 1], [10, -7], 0),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 1], 1 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, -1], 1 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 2], 2 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 3], 1),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [-56, 10, 3], 1),
        ([0, 1, 1], [0, 1, 5], [0, -5, 7], [0, 5, 2], 0),
    ],
)
def test_volume_tetrahedron(array_a, array_b, array_c, array_d, volume_expected):

    volume = volume_tetrahedron(array_a, array_b, array_c, array_d)
    assert math.isclose(volume, volume_expected)


import numpy as np

def universe_maker(setsList):
    '''
    This alorithm determines the universe of elements contain on the list of sets 'setList'.  To do that it uses a double loop that adds the elements to 'universe' set.  Since 'universe' is a set only unique elements are store.

    Arg:
        setsList: is a list of all the sets

    Out:
        universe:  is a set of all the elements

    '''
    universe = set(e for s in setsList for e in s)

    return universe

def set_cover_greedy(universe, setsList):
   '''
   This alorithm for the set cover problem uses the greedy alorithm.  The rule that follows is choosing the set that contains the largest number of uncovered elements.
   This alorithm is a ln n –approximation.

   Arg:
        universe:  is a set of all the elements
        setsList: is a list of all the sets

   Out:
        cover: is the list of sets that covers the hole universe.
        listCover: is the list that indicates what set (number) was use to forn the cover.
   '''

   if universe_maker(setsList) != universe:
        raise ValueError('There is a discrepancy between the universe input and the universe constructed from the list of sets.')

   covered = set()
   cover = []
   listCover = []
   while covered != universe:
        subSet = max(setsList, key=lambda s: len(s - covered))
        cover.append(subSet)
        listCover.append(setsList.index(subSet))
        covered |= subSet

   return cover, listCover

def set_cover_weighted_greedy(universe, setsList, weightList, normalize = True):
    '''
    This alorithm for the set cover problem uses the greedy alorithm.  The rule that follows is choosing the set that is most efficent at adding new elements.  The efficency is deffiend as the new elements to be added weighted by their cost.
    This alorithm is a ln n –approximation.

    Arg:
        universe:  is a set of all the elements
        setsList: is a list of all the sets

   Out:
        cover: is the list of sets that covers the hole universe.
        listCover: is the list that indicates what set (number) was use to forn the cover.
   '''

    if universe_maker(setsList) != universe:
        raise ValueError('There is a discrepancy between the universe input and the universe constructed from the list of sets.')

    if len(setsList) != len(weightList):
        raise ValueError('The two list have different lenghts')

    covered = set()
    cover = []
    listCover = []

    #The weights are normalized, limits numerical errors.
    if normalize == True:
        weightList = weightList/np.mean(weightList)

    while covered != universe:
        #setWeightStep is the list of cost given what is alrady present in covered. When a set does not gives value (no new items) it assings a 0.
        setWeightStep = list(map(lambda x, y: len(x -covered)/y if len(x -covered) != 0 else 0, setsList, weightList))
        #It will give the first element on the setWeightStep that is the most efficent. It is likely that more than one index will have the min same value.  In such cases a secondary cost function could be use to separate them. s
        index = setWeightStep.index(max(setWeightStep))
        cover.append(setsList[index])
        listCover.append(index)
        covered |= setsList[index]

    return cover, listCover


# 1次元ガウス分布の作図

# 利用するライブラリ
import numpy as np
from scipy.stats import norm # 1次元ガウス分布
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

#%%

### 確率密度の計算

# 平均を指定
mu = 1.0

# 標準偏差を指定
sigma = 2.5

# 確率変数の値を指定
x = 1.0

# 定義式により確率密度を計算
C = 1.0 / np.sqrt(2.0 * np.pi * sigma**2)
dens = C * np.exp(-0.5 * (x - mu)**2 / sigma**2)
print(dens)

# 対数をとった定義式により確率密度を計算
log_C = -0.5 * np.log(2.0 * np.pi) - np.log(sigma)
log_dens = log_C - 0.5 * (x - mu)**2 / sigma**2
dens = np.exp(log_dens)
print(dens, log_dens)

# ガウス分布の関数により確率密度を計算
dens = norm.pdf(x=x, loc=mu, scale=sigma)
print(dens)

# ガウス分布の対数をとった関数により確率密度を計算
log_dens = norm.logpdf(x=x, loc=mu, scale=sigma)
dens = np.exp(log_dens)
print(dens, log_dens)

#%%

### 統計量の計算

# 平均を指定
mu = 1.0

# 標準偏差を指定
sigma = 2.5

# 計算式により平均を計算
E_x = mu
print(E_x)

# 計算式により分散を計算
V_x = sigma**2
print(V_x)

# 関数により平均を計算
E_x = norm.mean(loc=mu)
print(E_x)

# 関数により分散を計算
V_x = norm.var(scale=sigma)
print(V_x)

#%%

### 分布の可視化

## 分布の計算

# 平均を指定
mu = 0.0

# 標準偏差を指定
sigma = 1.0

# 作図用のxの点を作成
x_vals = np.linspace(start=mu - sigma*4.0, stop=mu + sigma*4.0, num=250)

# ガウス分布を計算
density = norm.pdf(x=x_vals, loc=mu, scale=sigma)

#%%

## 分布の作図

# ガウス分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(x_vals, density, color='#00A968') # 折れ線グラフ
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.show() # 描画

#%%

## 統計量を重ねた分布の作図

# ガウス分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(x_vals, density, color='#00A968') # 分布
plt.vlines(x=mu, ymin=0.0, ymax=np.max(density), color='orange', linestyle='--', label='$\mu$') # 平均
plt.vlines(x=mu - sigma, ymin=0.0, ymax=np.max(density), color='orange', linestyle=':', label='$\mu \pm \\sigma$') # 平均 - 標準偏差
plt.vlines(x=mu + sigma, ymin=0.0, ymax=np.max(density), color='orange', linestyle=':') # 平均 + 標準偏差
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.legend() # 凡例
plt.show() # 描画

#%%

### パラメータと分布の形状の関係

## 平均の影響

# 平均として利用する値を指定
mu_vals = np.arange(start=-5.0, stop=5.0, step=0.1)
print(len(mu_vals)) # フレーム数

# 標準偏差を指定
sigma = 1.0

# 作図用のxの点を作成
x_vals = np.linspace(start=np.median(mu_vals) - sigma*4.0, stop=np.median(mu_vals) + sigma*4.0, num=250)

# y軸(確率密度)の最大値を設定
dens_max = np.max(norm.pdf(x=x_vals, loc=0.0, scale=sigma)) + 0.05

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# 作図処理を関数として定義
def update(i):
    # 前フレームのグラフを初期化
    plt.cla()

    # i番目の平均パラメータを取得
    mu = mu_vals[i]

    # ガウス分布を計算
    dens = norm.pdf(x=x_vals, loc=mu, scale=sigma)

    # ガウス分布を作図
    plt.plot(x_vals, dens, color='#00A968') # 折れ線グラフ
    plt.xlabel('x') # x軸ラベル
    plt.ylabel('density') # y軸ラベル
    plt.title('$\mu=' + str(np.round(mu, 1)) + ', \sigma=' + str(sigma) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.01, ymax=dens_max) # y軸の表示範囲

# gif画像を作成
anime_dens = FuncAnimation(fig, update, frames=len(mu_vals), interval=100)

# gif画像を保存
anime_dens.save('ProbabilityDistribution/Gaussian_dens_mu.gif')

#%%

## 標準偏差の影響

# 標準偏差として利用する値を指定
sigma_vals = np.arange(start=1.0, stop=10.1, step=0.1)
print(len(sigma_vals)) # フレーム数

# 平均を指定
mu = 0.0

# 作図用のxの点を作成
x_vals = np.linspace(start=mu - np.max(sigma_vals)*2.0, stop=mu + np.max(sigma_vals)*2.0, num=250)

# y軸(確率密度)の最大値を設定
dens_max = np.max(norm.pdf(x=x_vals, loc=mu, scale=np.min(sigma_vals))) + 0.05

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# 作図処理を関数として定義
def update(i):
    # 前フレームのグラフを初期化
    plt.cla()

    # i番目の標準偏差パラメータを取得
    sigma = sigma_vals[i]

    # ガウス分布を計算
    dens = norm.pdf(x=x_vals, loc=mu, scale=sigma)

    # ガウス分布を作図
    plt.plot(x_vals, dens, color='#00A968') # 折れ線グラフ
    plt.xlabel('x') # x軸ラベル
    plt.ylabel('density') # y軸ラベル
    plt.title('$\mu=' + str(mu) + ', \sigma=' + str(np.round(sigma, 1)) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.01, ymax=dens_max) # y軸の表示範囲

# gif画像を作成
anime_dens = FuncAnimation(fig, update, frames=len(sigma_vals), interval=100)

# gif画像を保存
anime_dens.save('ProbabilityDistribution/Gaussian_dens_sigma.gif')

#%%

### 乱数の生成

## 乱数の可視化

# 平均を指定
mu = 1.0

# 標準偏差を指定
sigma = 2.5

# データ数を指定
N = 1000

# ガウス分布に従う乱数を生成
x_n = np.random.normal(loc=mu, scale=sigma, size=N)

# 作図用のxの点を作成
x_vals = np.linspace(mu - sigma*4.0, mu + sigma*4.0, num=250)

# ガウス分布を計算
density = norm.pdf(x=x_vals, loc=mu, scale=sigma)

#%%

## 乱数の可視化

# サンプルのヒストグラムを作成
plt.figure(figsize=(12, 9)) # 図の設定
plt.hist(x=x_n, bins=50, range=(x_vals.min(), x_vals.max()), color='#00A968') # ヒストグラム
plt.xlabel('x') # x軸ラベル
plt.ylabel('frequency') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) + ', N=' + str(N) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.show() # 描画

# サンプルのヒストグラムを作成
plt.figure(figsize=(12, 9)) # 図の設定
plt.hist(x=x_n, bins=50, range=(x_vals.min(), x_vals.max()), density=True, color='#00A968') # ヒストグラム
plt.plot(x_vals, density, color='green', linestyle='--') # 元の分布
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) + ', N=' + str(N) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.show() # 描画

#%%

## アニメーションによる可視化:(頻度)

# フレーム数を指定
N_frame = 100

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# y軸(頻度)の最大値を設定
freq_max = np.max(
    np.histogram(a=x_n[:N_frame], bins=30, range=(x_vals.min(), x_vals.max()))[0],
) + 1.0

# 作図処理を関数として定義
def update(n):
    # 前フレームのグラフを初期化
    plt.cla()

    # サンプルのヒストグラムを作成
    plt.hist(x=x_n[:(n+1)], bins=50, range=(x_vals.min(), x_vals.max()), color='#00A968', zorder=1) # ヒストグラム
    plt.scatter(x=x_n[n], y=0.0, color='orange', s=100, zorder=2) # サンプル
    plt.xlabel('x') # x軸ラベル
    plt.ylabel('freqency') # y軸ラベル
    plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
    plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) +
              ', N=' + str(n + 1) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.5, ymax=freq_max) # y軸の表示範囲

# gif画像を作成
anime_freq = FuncAnimation(fig, update, frames=N_frame, interval=100)

# gif画像を保存
anime_freq.save('ProbabilityDistribution/Gaussian_freq.gif')

#%%

## アニメーションによる可視化:(密度)

# フレーム数を指定
N_frame = 100

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# 作図処理を関数として定義
def update(n):
    # 前フレームのグラフを初期化
    plt.cla()

    # サンプルのヒストグラムを作成
    plt.hist(x=x_n[:(n+1)], bins=50, range=(x_vals.min(), x_vals.max()), density=True, color='#00A968', zorder=1) # ヒストグラム
    plt.plot(x_vals, density, color='green', linestyle='--', zorder=2) # 元の分布
    plt.scatter(x=x_n[n], y=0.0, color='orange', s=100, zorder=3) # サンプル
    plt.xlabel('x') # x軸ラベル
    plt.ylabel('density') # y軸ラベル
    plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
    plt.title('$\mu=' + str(mu) + ', \sigma=' + str(sigma) +
              ', N=' + str(n + 1) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.01, ymax=density.max() + 0.1) # y軸の表示範囲

# gif画像を作成
anime_ｐrop = FuncAnimation(fig, update, frames=N_frame, interval=100)

# gif画像を保存
anime_prop.save('ProbabilityDistribution/Gaussian_prop.gif')

#%%

### 分布の生成

## パラメータの生成

# 超パラメータを指定
mu_prior = 1.0
sigma_prior = 2.5

# サンプルサイズを指定
N = 10

# 1次元ガウス分布の平均パラメータを生成
mu_n = np.random.normal(loc=mu_prior, scale=sigma_prior, size=N)

# 標準偏差パラメータを指定
sigma = 1.0

# 平均パラメータを計算
E_mu = mu_prior

# 作図用のxの点を作成
x_vals = np.linspace(E_mu - sigma*5.0, E_mu + sigma*5.0, num=250)

# 平均パラメータの期待値による1次元ガウス分布の確率密度を計算
E_dens = norm.pdf(x=x_vals, loc=E_mu, scale=sigma)

#%%

## 分布の作図

# サンプルによる分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(x_vals, E_dens, color='blue', linestyle='--', label='$E[\mu]=' + str(E_mu) + '$') # 期待値による分布
for n in range(N):
    tmp_dens = norm.pdf(x=x_vals, loc=mu_n[n], scale=sigma)
    plt.plot(x_vals, tmp_dens, alpha=0.5, label='$\mu=' + str(np.round(mu_n[n], 2)) + '$') # サンプルによる分布
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$\mu_{pri}=' + str(mu_prior) + ', \sigma_{pri}=' + str(sigma_prior) +
          ', \sigma=' + str(sigma) + ', N=' + str(N) + '$', loc='left') # タイトル
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show() # 描画

#%%


import numpy as np
import cv2 as cv
import scipy.signal as sp
import matplotlib.pyplot as plt

def gauss(dim, sigma):
    # Takes the dimensions of the desired gauss filter and the sigma and returns the filter
    # Dim must be odd
    # Filter has been scaled to add to 1 to preserve image brightness
    ret = np.zeros(shape=(dim, dim))
    start = -(dim // 2)
    for i in range(0, dim):
        for j in range(0, dim):
            ret[i][j] = (1 / (2 * np.pi * (np.square(sigma)))) * (
                np.power(np.e, (-(np.square(start + i) + np.square(start + j)) / (2 * np.square(sigma)))))
    return ret * 1/(np.sum(ret))

def laplace_gauss(dim, sigma):
    # Takes the dimensions of the desired Laplacian of Gaussian filter and returns the filter
    # Dim must be odd
    # Source of LoG function: http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html
    ret = np.zeros(shape=(dim, dim))
    start = -(dim // 2)
    for i in range(0, dim):
        for j in range(0, dim):
            ret[i][j] = (np.square(start + i) + np.square(start + j) - 2*np.square(sigma))/(np.power(sigma, 4)) * \
                        (np.power(np.e, (-(np.square(start + i) + np.square(start + j))/(2*np.square(sigma)))))
    return ret

def detect_zero(img):
    # Takes in the Laplacian filtered image and detects the zero crossings
    # Algorithm sources:
    # https://stackoverflow.com/questions/22050199/python-implementation-of-the-laplacian-of-gaussian-edge-detection
    # https://theailearner.com/tag/zero-crossings/
    # Code was written by me after looking at these algorithms, and my algorithm uses parts of both
    thresh = np.absolute(img).mean()
    height = len(img)
    width = len(img[0])
    ret = np.zeros(shape=(height, width))
    for i in range(1, height-1):
        for j in range(1, width-1):
            patch = img[i-1:i+2, j-1:j+2]
            minp = patch.min()
            maxp = patch.max()
            p = img[i][j]
            if p < 0 < maxp and maxp - minp > thresh:
                ret[i][j] = np.abs(p) + maxp if np.abs(p) + maxp > thresh else 0
            elif minp < 0 < p and maxp - minp > thresh:
                ret[i][j] = p + np.abs(minp) if p + np.abs(minp) > thresh else 0
    return ret.astype(np.uint8)

def plot_array(kernel, title):
    # Plot a 2D kernel as a colour map
    # Source of function:
    # https://matplotlib.org/3.1.1/gallery/images_contours_and_fields/image_annotated_heatmap.html
    fig, ax = plt.subplots()
    im = ax.imshow(kernel)

    for i in range(len(kernel)):
        for j in range(len(kernel)):
            text = ax.text(j, i, round(kernel[i, j], 2),
                           ha="center", va="center", color="w")
    ax.set_title(title)
    fig.tight_layout()
    plt.show()

def convolve(img, kernel):
    # Convolve and image with the given kernel
    return sp.convolve2d(img.astype(float), kernel)

def convolve_display(img, kernel, title):
    # Perform the convolution and display the image
    cv.imshow(title, convolve(img, kernel).astype(np.uint8))

if __name__ == '__main__':
    paolina = cv.cvtColor(cv.imread('images/Paolina.jpg'), cv.COLOR_BGR2GRAY)
    dog = cv.cvtColor(cv.imread('images/dog.png'), cv.COLOR_BGR2GRAY)
    # Image source for dog picture:
    # https://www.google.com/url?sa=i&url=http%3A%2F%2Fpets.university%2Fi-love-dogs%2F&psig=AOvVaw2-Xgkpnat2Z2c3wkaX4pNv&ust=1590948869508000&source=images&cd=vfe&ved=0CAIQjRxqFwoTCKikmMeY3OkCFQAAAAAdAAAAABAW

    # Question 4
    # convolve_display(paolina, gauss(5, 1), "Gaussian sigma=1")
    # convolve_display(paolina, gauss(11, 2), "Gaussian sigma=2")
    plot_array(gauss(5, 1), "Gaussian 5x5, sigma=1")
    plot_array(gauss(11, 2), "Gaussian 11x11, sigma=2")

    # Question 5
    log1 = laplace_gauss(7, 1)
    log2 = laplace_gauss(13, 1.5)
    log3 = laplace_gauss(21, 3)
    plot_array(log1, "Laplacian of Gaussian 7x7, sigma=1")
    plot_array(log2, "Laplacian of Gaussian 13x13, sigma=1.5")

    # Question 6
    paolina_log1 = convolve(paolina, log1)
    paolina_log3 = convolve(paolina, log3)
    dog_log1 = convolve(dog, log1)
    dog_log3 = convolve(dog, log3)

    # Question 7
    t = detect_zero(paolina_log1)
    cv.imshow("Paolina", paolina)
    cv.imshow("dog", dog)
    cv.imshow("Paolina with sigma=1", detect_zero(paolina_log1))
    cv.imshow("Paolina with sigma=3", detect_zero(paolina_log3))
    cv.imshow("Dog with sigma=1", detect_zero(dog_log1))
    cv.imshow("Dog with sigma=3", detect_zero(dog_log3))

    k = cv.waitKey(0)