import math
import random

from numpy import arange

# def get_i():
#     return math.e ** 1 - math.e ** 0

def method_of_rectangles(func, mim_lim, max_lim, delta):
    def integrate(func, mim_lim, max_lim, n):
        integral = 0.0
        step = (max_lim - mim_lim) / n
        for x in arange(mim_lim, max_lim - step, step):
            integral += step * func(x + step / 2)
        return integral

    d, n = 1, 1
    while math.fabs(d) > delta:
        d = (integrate(func, mim_lim, max_lim, n * 2) - integrate(func, mim_lim, max_lim, n)) / 3
        n *= 2

    a = math.fabs(integrate(func, mim_lim, max_lim, n))
    b = math.fabs(integrate(func, mim_lim, max_lim, n)) + d
    if a > b:
        a, b = b, a
    print('Rectangles:')
    print('\t%s\t%s\t%s' % (n, a, b))

def trapezium_method(func, mim_lim, max_lim, delta):
    def integrate(func, mim_lim, max_lim, n):
        integral = 0.0
        step = (max_lim - mim_lim) / n
        for x in arange(mim_lim, max_lim - step, step):
            integral += step * (func(x) + func(x + step)) / 2
        return integral

    d, n = 1, 1
    while math.fabs(d) > delta:
        d = (integrate(func, mim_lim, max_lim, n * 2) - integrate(func, mim_lim, max_lim, n)) / 3
        n *= 2

    a = math.fabs(integrate(func, mim_lim, max_lim, n))
    b = math.fabs(integrate(func, mim_lim, max_lim, n)) + d
    if a > b:
        a, b = b, a
    print('Trapezium:')
    print('\t%s\t%s\t%s' % (n, a, b))

def simpson_method(func, mim_lim, max_lim, delta):
    def integrate(func, mim_lim, max_lim, n):
        integral = 0.0
        step = (max_lim - mim_lim) / n
        for x in arange(mim_lim + step / 2, max_lim - step / 2, step):
            integral += step / 6 * (func(x - step / 2) + 4 * func(x) + func(x + step / 2))
        return integral

    d, n = 1, 1
    while math.fabs(d) > delta:
        d = (integrate(func, mim_lim, max_lim, n * 2) - integrate(func, mim_lim, max_lim, n)) / 15
        n *= 2

    a = math.fabs(integrate(func, mim_lim, max_lim, n))
    b = math.fabs(integrate(func, mim_lim, max_lim, n)) + d
    if a > b:
        a, b = b, a
    print('Simpson:')
    print('\t%s\t%s\t%s' % (n, a, b))

def monte_karlo_method(func, n):
    in_d, out_d = 0., 0.
    for i in range(n):
        x, y = random.uniform(0, 1), random.uniform(0, 3)
        if y < func(x):
            in_d += 1

    print('M-K:')
    print('\t%s\t%s' % (n, math.fabs(in_d / n * 3)))

print('as')
method_of_rectangles(lambda x: 4 * (x ** 3) - 2 * x, 0.0, 1.0, 0.001)
trapezium_method(lambda x: 4 * (x ** 3) - 2 * x, 0.0, 1.0, 0.001)
simpson_method(lambda x: 4 * (x ** 3) - 2 * x, 0.0, 1.0, 0.001)
# monte_karlo_method(lambda x: math.e ** x, 100)
# print('True value:\n\t%s' % get_i())


from scipy.special import factorial

class TaylorSerApprox(object):
    """
    A simple python representation of the Taylor Series Approximation.
    Taylor Series Approximations are often useful in representing non-linear
    equations as a linear summation of terms relating to those non-linear equations.

    This allows the user to perform an approximation of arbitrary order,
    as long as the function and the derivatives of that function are provided.
    """
    def __init__(self, functions):
        """
        The `functions` argument should be an iterator containing in the first
        position the actual function to be evaluated. The remaining items should
        be the derivatives of the function to be evaluated ascendingly ranked
        based on their order.
        e.g. functions = [function, prime1, prime2, ..., primeN]
        Where function is what should be evaluated, prime# are the derivatives of
        function, and the numbers on prime# correspond to their rank.

        Arguments:
            functions {iterable} -- container of evaluation function and its derivatives
        """
        self.functions = list(functions)
        self.order = len(functions)

    def approximate(self, x, a):
        """
        Use this method to approximate the function supplied as the first
        item in the `functions` argument when instantiating the class at the
        value of `x` (f(x)). Choose `a` such that it can easily be calculated by the
        the function of interest.

        The basic form of the equation is as follows:
        f(x) ~ f(a) + f'(a)(x-a)/1! + f"(a)((x-a)^2)/2! + f'''(a)((x-a)^3)/3! ... etc

        Arguments:
            x {float} -- desired value for function evaluation
            a {float} -- variable used in the approximation to prevent difficult evaluations of your function

        Returns:
            float -- Taylor Series Approximation of f(x)
        """
        fun = self.functions[0]
        initial = fun(a)
        for i, fun in enumerate(self.functions[1:]):
            coef = fun(a)/factorial(i+1)
            right = (x-a)**(i+1)
            initial += coef*right
        return initial

    def __str__(self):
        return f"Taylor Series Approximation of order {self.order} for {self.functions[0].__name__}"

def example(x, a):
    def function(x):
        return 5*x**3 + 6*x**2 - 4*x + 7

    def prime1(x):
        return 15*x**2 + 12*x - 4

    def prime2(x):
        return 30*x + 12

    def prime3(x):
        return 30

    O1 = [function]
    O2 = [function, prime1]
    O3 = [function, prime1, prime2]
    O4 = [function, prime1, prime2, prime3]

    orders = [("O1", O1), ("O2", O2), ("O3", O3), ("O4", O4)]
    actual = function(x)
    for name, funcs in orders:
        tsa = TaylorSerApprox(funcs)
        answer = tsa.approximate(x, a)
        print(tsa)
        print(f"Order {name}: Actual Value = {actual}; Approximation = {answer}")

if __name__ == "__main__":
    x = 0.05
    a = 1
    example(x, a)


import numpy as np

def f(x, normalize=False):
    '''
    Function proportional to target distribution, a sum of Gaussians.
    For testing, set normalize to True, to get target distribution exactly.
    '''
    # Gaussian heights, width parameters, and mean positions respectively:
    a = np.array([10., 3., 1.]).reshape(3, 1)
    b = np.array([ 4., 0.2, 2.]).reshape(3, 1)
    xs = np.array([-4., -1., 5.]).reshape(3, 1)

    if normalize:
        norm = (np.sqrt(np.pi) * (a / np.sqrt(b))).sum()
        a /= norm

    return (a * np.exp(-b * (x - xs)**2)).sum(axis=0)

def g():
    '''Random step vector.'''
    return np.random.uniform(-1,1)

def metropolis_step(x, f=f, g=g):
    '''Perform one full iteration and return new position.'''

    x_proposed = x + g()
    a = min(1, (f(x_proposed) / f(x)).item())

    x_new = np.random.choice([x_proposed, x], p=[a, 1-a])

    return x_new

def metropolis_iterate(x0, num_steps):
    '''Iterate metropolis algorithm for num_steps using iniital position x_0'''

    for n in range(num_steps):
        if n == 0:
            x = x0
        else:
            x = metropolis_step(x)
        yield x

def test_metropolis_iterate(num_steps, xmin, xmax, x0):
    '''
    Calculate error in normalized density histogram of data
    generated by metropolis_iterate() by using
    normalized-root-mean-square-deviation metric.
    '''

    bin_width = 0.25
    bins = np.arange(xmin, xmax + bin_width/2, bin_width)
    centers = np.arange(xmin + bin_width/2, xmax, bin_width)

    true_values = f(centers, normalize=True)
    mean_value = np.mean(true_values - min(true_values))

    x_dat = list(metropolis_iterate(x0, num_steps))
    heights, _ = np.histogram(x_dat, bins=bins, density=True)

    nmsd = np.average((heights - true_values)**2 / mean_value)
    nrmsd = np.sqrt(nmsd)

    return nrmsd

if __name__ == "__main__":
    xmin, xmax = -10, 10
    x0 = np.random.uniform(xmin, xmax)

    num_steps = 50_000

    x_dat = list(metropolis_iterate(x0, 50_000))

    # Write data to file
    output_string = "\n".join(str(x) for x in x_dat)

    with open("output.dat", "w") as out:
        out.write(output_string)
        out.write("\n")

    # Testing
    print(f"Testing with x0 = {x0:5.2f}")
    print(f"{'num_steps':>10s} {'NRMSD':10s}")
    for num_steps in (500, 5_000, 50_000):
        nrmsd = test_metropolis_iterate(num_steps, xmin, xmax, x0)
        print(f"{num_steps:10d} {nrmsd:5.1%}")


"""
Copyright 2013 Steven Diamond

This file is part of CVXPY.

CVXPY is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CVXPY is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with CVXPY.  If not, see <http://www.gnu.org/licenses/>.
"""

#!/usr/bin/env python3
# @author: R. Gowers, S. Al-Izzi, T. Pollington, R. Hill & K. Briggs
# Boyd and Vandenberghe, Convex Optimization, exercise 4.57 page 207

import cvxpy as cvx
import numpy as np

'''
Input parameters
  P: channel transition matrix P_ij(t) = P(output|input) at time t
  n: size of input
  m: size of output
'''

def channel_capacity(n,m,sum_x=1):
  '''
Boyd and Vandenberghe, Convex Optimization, exercise 4.57 page 207
Capacity of a communication channel.

We consider a communication channel, with input x(t)∈{1,..,n} and
output Y(t)∈{1,...,m}, for t=1,2,... .The relation between the
input and output is given statistically:
p_(i,j) = ℙ(Y(t)=i|X(t)=j), i=1,..,m  j=1,...,m
The matrix P ∈ ℝ^(m*n) is called the channel transition matrix, and
the channel is called a discrete memoryless channel. Assuming X has a
probability distribution denoted x ∈ ℝ^n, i.e.,
x_j = ℙ(X=j), j=1,...,n
The mutual information between X and Y is given by
∑(∑(x_j p_(i,j)log_2(p_(i,j)/∑(x_k p_(i,k)))))
Then channel capacity C is given by
C = sup I(X;Y).
With a variable change of y = Px this becomes
I(X;Y)=  c^T x - ∑(y_i log_2 y_i)
where c_j = ∑(p_(i,j)log_2(p_(i,j)))
  '''
  # n is the number of different input values
  # m is the number of different output values
  if n*m == 0:
    print('The range of both input and output values must be greater than zero')
    return 'failed',np.nan,np.nan
  # P is the channel transition matrix
  P = np.ones((m,n))
  # x is probability distribution of the input signal X(t)
  x = cvx.Variable(rows=n,cols=1)
  # y is the probability distribution of the output signal Y(t)
  y = P*x
  # I is the mutual information between x and y
  c = np.sum(P*np.log2(P),axis=0)
  I = c*x + cvx.sum(cvx.entr(y))
  # Channel capacity maximised by maximising the mutual information
  obj = cvx.Minimize(-I)
  constraints = [cvx.sum(x) == sum_x,x >= 0]
  # Form and solve problem
  prob = cvx.Problem(obj,constraints)
  prob.solve()
  if prob.status=='optimal':
    return prob.status,prob.value,x.value
  else:
    return prob.status,np.nan,np.nan

# as an example, let's optimise the channel capacity for two different possible input and output values
if __name__ == '__main__':
  print(channel_capacity.__doc__)
  # print all arrays to have 3 significant figures after the decimal place
  np.set_printoptions(precision=3)
  n = 2
  m = 2
  print('Number of input values=%s'%n)
  print('Number of outputs=%s'%m)
  stat,C,x=channel_capacity(n,m)
  print('Problem status ',stat)
  print('Optimal value of C = %.4g'%(C))
  print('Optimal variable x = \n', x)


import numpy as np
import time

##------------ exact value of the integration -----------

### _--------------Simpson's 3/8 rule -----------------
startTime = time.time()
x = float(input("Enter the value of initial point(try 0.1): "))
y = float(input("Enter the value of final point(try 1.3): "))
n = int(input("number of intervals: "))

def simpson_third(x, y, n):
    h = (y - x) / n

    def fun(x):
        return 5 * x * np.exp(-2 * x)

    i = 0
    sum = 0
    # sum=fun(x)+fun(y)
    while i <= n:
        if i == 0:
            sum = sum + fun(x)
        elif i == n:
            sum = sum + fun(y)
        elif i % 3 == 0:
            sum = sum + 2 * fun(x + h * i)
        else:
            sum += 3 * fun(x + h * i)
        i += 1
    #print("Number of iterations: ", n)
    return (3 * h / 8) * sum

from scipy.integrate import quad

def integrate(p):
    return 5 * p * np.exp(-2 * p)

exact_value = quad(integrate, 0.1, 1.3)
print(" The true value of integration is ", "%.5f" %exact_value[0])

simpson = simpson_third(x, y, n)
# abs_error = (result-exact_value)
#
# while n>np.log((y-x)/abs_error)/np.log(2)-1:
# print(n)
print(" The value of integration is ", "%.5f" % simpson)
abs_err = ( simpson - exact_value[0])
# print(" Error is: " , "%.5f"% abs_err)
print(" error is: ",abs_err)
# print(abs_error)
endTime = time.time()
totalTime = endTime - startTime

print("Total time taken to execute code is= ", totalTime)


import numpy as np

#  Function
def f(x):
    y = 1 /np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2)
    return y

# General Gauss method with three points
def GaussQuad3(a, b, tol):
    # Compute Integral with 3 Point Gauss Method
    def GaussQuad(a, b):
        t = [-pow(3/5, 0.5), 0, pow(3/5, 0.5)]
        x = [0.5*((b-a)*t[0] + b+a), 0.5*((b-a)*t[1] + b+a),
             0.5*((b-a)*t[2] + b+a)]
        I = 0.5 * (b - a) * (f(x[0])/1.8 + f(x[1])/1.125 + f(x[2])/1.8)
        return I

    # We compute the Integral in N Intervals Using Gauss Method so as to Compute the Error
    def GaussQuadn(n):
        h = (b-a)/n
        I = 0.0

        for i in range(n):
            x0 = a + i*h
            x1 = x0 + h
            I += GaussQuad(x0, x1)

        return I

    # We compute the error using analytic computations
    def dGaussQuadn(n):
        h = (b-a)/(2*n)
        I2 = 0.0

        for i in range(2*n):
            x0 = a + i*h
            x1 = x0 + h
            I2 += GaussQuad(x0, x1)

        dI = 32/31 * abs(I2-GaussQuadn(n))

        return dI

    # Min Value
    n = 2
    # General Error
    η = dGaussQuadn(n)/GaussQuadn(n)

    while η > tol:
        n += 1
        η = dGaussQuadn(n)/GaussQuadn(n)

    return GaussQuadn(n), dGaussQuadn(n), η

if __name__ == "__main__":
    print('\nComputed with 3 Point Gauss Method:')
    print(f"\tI ± δI = {'%.8f'%GaussQuad3(-5, 5, 0.001)[0]} ± {GaussQuad3(-5, 5, 0.001)[1]:.8f}")
    print(f'\tThe General Error is η = {GaussQuad3(-5, 5, 0.001)[2]:.5f} < 0.1 %')


import numpy as np

# Create the time axis (seconds)
num_samples = 1001
samples_per_second = 1000
freq_Hz = 0.5
t = np.linspace(0.0, ((num_samples - 1) / samples_per_second), num_samples)
# Create a sine wave, a(t), with a frequency of 1 Hz
a = np.sin((2.0 * np.pi) * freq_Hz * t)
# Create b(t), a (pi / 2.0) phase-shifted replica of a(t)
b_shift = (np.pi / 2.0)
b = np.sin((2.0 * np.pi) * freq_Hz * t + b_shift)

# Cross-correlate the signals, a(t) & b(t)
ab_corr = np.correlate(a, b, "full")
dt = np.linspace(-t[-1], t[-1], (2 * num_samples) - 1)
# Calculate time & phase shifts
t_shift_alt = (1.0 / samples_per_second) * ab_corr.argmax() - t[-1]
t_shift = dt[ab_corr.argmax()]
# Limit phase_shift to [-pi, pi]
phase_shift = ((2.0 * np.pi) * ((t_shift / (1.0 / freq_Hz)) % 1.0)) - np.pi

manual_t_shift = (b_shift / (2.0 * np.pi)) / freq_Hz

# Print out applied & calculated shifts
print("Manual time shift: {}".format(manual_t_shift))
print("Alternate calculated time shift: {}".format(t_shift_alt))
print("Calculated time shift: {}".format(t_shift))
print("Manual phase shift: {}".format(b_shift))
print("Calculated phase shift: {}".format(phase_shift))

import matplotlib.pyplot as plt

plt.figure()
plt.plot(dt, ab_corr)
# plt.plot(t, a)
# plt.plot(t, b)
plt.show()


# Reference Book: Python Data Science Handbook (page:(63-69))
# Date(13 April, 2019) Day-3, Time = 3:25 PM

# Broadcasting is simply a set of rules for applying binary ufuncs(addition,
# subtraction, multiplication, etc.) onarrays of different sizes.

import numpy as np
import matplotlib.pyplot as plt

print('\nIntroducing Broadcasting:')
a = np.array([0, 1, 2])
b = np.array([5, 5, 5])

print(a + b)  # [5,6,7]
print(a + 5)  # [5,6,7]

matrix = np.ones((3, 3))
print(matrix + a)
'''
[[1. 2. 3.]
 [1. 2. 3.]
 [1. 2. 3.]]
'''
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]

print(a + b)
'''
[[0 1 2]
 [1 2 3]
 [2 3 4]]
'''

# @@@@@@@@@@ Broadcasting example 1 @@@@@@@@@@@@@@@@
print('\nBroadcasting Example -1:')
a = np.ones((2, 3))
b = np.arange(3)

print(a.shape, ' ', b.shape)

'''
We see by rule 1 that the array a has fewer dimensions, so we pad it on the left with
ones:
M.shape -> (2, 3)
a.shape -> (1, 3)
By rule 2, we now see that the first dimension disagrees, so we stretch this dimension
to match:
M.shape -> (2, 3)
a.shape -> (2, 3)
The shapes match, and we see that the final shape will be (2, 3) :
'''
print(a + b)

print('\nBroadcasting example - 2:')
a = np.arange(3).reshape((3, 1))
b = np.arange(3)
# Again, we’ll start by writing out the shape of the arrays:
# a.shape = (3, 1)
# b.shape = (3,)

'''
Rule 1 says we must pad the shape of b with ones:
a.shape -> (3, 1)
b.shape -> (1, 3)
And rule 2 tells us that we upgrade each of these ones to match the corresponding
size of the other array:
a.shape -> (3, 3)
b.shape -> (3, 3)
Because the result matches, these shapes are compatible. We can see this here:
'''

print(a + b)

print('\nBroadcasting example - 3:')
a = np.ones((3,2))
b = np.arange(3)

#print(a + b)
#ValueError: operands could not be broadcast together with shapes (3,2) (3,)

#to bypass this value error we can use np.newaxis method
b = b[:,np.newaxis]

print(a + b)

print('\nBroadcasting in Practice: ')
#Centering an array

a = np.random.random((5,3))
print(a)

aMean = a.mean(0)
print(aMean)

aCentered = a - aMean
print(a)
print(aCentered)
print(aCentered.mean(0))

'''
Broadcasting in Practice:
[[0.83862929 0.14317788 0.80506902]
 [0.46464661 0.26061009 0.72977281]
 [0.18953245 0.98315161 0.41065711]
 [0.30367627 0.72104366 0.67134196]
 [0.12079467 0.89210426 0.70943855]]

[0.38345586 0.6000175  0.66525589]

[[0.83862929 0.14317788 0.80506902]
 [0.46464661 0.26061009 0.72977281]
 [0.18953245 0.98315161 0.41065711]
 [0.30367627 0.72104366 0.67134196]
 [0.12079467 0.89210426 0.70943855]]

[[ 0.45517343 -0.45683962  0.13981313]
 [ 0.08119075 -0.33940741  0.06451692]
 [-0.19392341  0.38313411 -0.25459878]
 [-0.07977959  0.12102616  0.00608607]
 [-0.26266118  0.29208676  0.04418266]]

[2.22044605e-17 2.22044605e-17 0.00000000e+00]
'''

#Plotting a two-dimensional function
a = np.linspace(0,5,50)
b = np.linspace(0,5,50)[:, np.newaxis]

z = np.sin(a) ** 10 + np.cos(10 + b * a) * np.cos(a)

fig = plt.figure()

plt.imshow(z, origin='lower', extent=[0,5,0,5],cmap='viridis')
plt.colorbar()
fig.savefig('Plotting a two dimensional function')
print(plt.show())


from sympy import Range

def SieveOfEratosthenes(number: int) -> None:
    """
    SieveOfEratosthenes:
    Primes smaller than or equal to n using Sieve of Eratosthenes.
    Create a boolean array "prime[0..n]" and initialize all entries it as true.
    A value in prime[i] will finally be false if i is not a prime, else true.
    Args:
        number (int): The limit till which the function will find primes.
    """

    prime: list[bool] = [True for _ in Range(number + 1)]
    prime_candidate: int = 2
    while (prime_candidate * prime_candidate <= number):

        # If prime[prime_candidate] is not
        # changed, then it is a prime
        if (prime[prime_candidate] is True):

            # Update all multiples of prime_candidate
            for i in Range(prime_candidate ** 2, number + 1, prime_candidate):
                prime[i] = False
        prime_candidate += 1

    # Print all prime numbers.
    for prime_candidate in Range(2, number + 1):
        if prime[prime_candidate]:
            print(prime_candidate)

if __name__ == '__main__':
    NUMBER = int(input("Enter a limit: "))
    print("Following are the prime numbers smaller than or equal to", NUMBER)
    SieveOfEratosthenes(NUMBER)


import numpy as np
import numpy.linalg as la
from io import StringIO

def print_mat(mat):

    stream = StringIO()
    np.savetxt(stream, mat, fmt="%.3f")
    print( stream.getvalue() )

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

# transition matrix L
L = np.array([
    [0,   1/2, 1/3, 0, 0,   0   ],
    [1/3, 0,   0,   0, 1/2, 0   ],
    [1/3, 1/2, 0,   1, 0,   1/2 ],
    [1/3, 0,   1/3, 0, 1/2, 1/2 ],
    [0,   0,   0,   0, 0,   0   ],
    [0,   0,   1/3, 0, 0,   0   ]
])

# initial vector for r
r = ( np.ones(6) / 6 ) * 100.0

# ------------------------------------
# for r_1

r_1 = np.matmul(L, r)
print_mat(r_1)

# ------------------------------------
# for r_11

r_next = r_1
for _ in range(10):
    r_next = np.matmul(L, r_next)

r_11 = r_next
print_mat(r_11)

# ------------------------------------
# for r_convergence under diff < 0.01

r_cur = r
threshold = 0.01
while True:

    r_next = np.matmul(L, r_cur)

    if la.norm(r_next - r_cur) < threshold:
        # check convergence condition is met or not
        break

    r_cur = r_next

print_mat(r_cur)


# Assignement on Poisson Distribution

import numpy as np
from scipy.stats import poisson

'''
1.Find the probability that atmost 5 defective fuses will be found in a box of 200 fuses if experience shows that 2 per cent of such fuses are defective.
'''
print("Assignment 1")
print("Probability of atmost 5 Defective",poisson.cdf(k=5,mu=200*0.02))
print("\n")

'''
2.The number of accidents in a year attributed to taxi drivers in a city follows a Poisson distribution with mean equal to 3. Out of 1,000 taxi drivers, find approximately the number of drivers with
a)No accidents in a year
b)More than 3 accidents in a year
'''
print("Assignment 2")

print("No of drivers with no accidents in a year",
        poisson.pmf(k=0,mu=3)*1000)
print("No of drivers with more than 3 accidents in a year",
        (1-poisson.cdf(k=3,mu=3))*1000)

print("\n")
'''

3.From the records of 10 Indian Army corps kept over 20 years the following data were obtained showing the number of deaths caused by the horse. Calculate the theoretical Poisson frequencies
No of Deaths:		0	1	2	3	4	Total
Frequency:		109	65	22	3	1	200
'''
print("Assignment 3")
mean_value=(0*109 + 1*65 + 2*22 + 3*3 + 4*1)/200
print("Frequencies of Deaths")
for k in np.arange(4+1):
    print("No of Deaths={}, Frequency={}"\
            .format(k,poisson.pmf(k=k,mu=mean_value)*200))

print("\n")


import numpy as np                     # Import numpy for array manipulation
import matplotlib.pyplot as plt        # Import matplotlib for charts
from utils_nb import plot_vectors      # Function to plot vectors (arrows)
# Create a 2 x 2 matrix
R = np.array([[2, 0],
              [0, -2]])
x = np.array([[1, 1]]) # Create a 1 x 2 matrix
y = np.dot(x, R) # Apply the dot product between x and R
y
plot_vectors([x], axes=[4, 4], fname='transform_x.svg')
plot_vectors([x, y], axes=[4, 4], fname='transformx_and_y.svg')

angle = 100 * (np.pi / 180) #convert degrees to radians

Ro = np.array([[np.cos(angle), -np.sin(angle)],
              [np.sin(angle), np.cos(angle)]])

x2 = np.array([2, 2]).reshape(1, -1) # make it a row vector
y2 = np.dot(x2, Ro)

print('Rotation matrix')
print(Ro)
print('\nRotated vector')
print(y2)

print('\n x2 norm', np.linalg.norm(x2))
print('\n y2 norm', np.linalg.norm(y2))
print('\n Rotation matrix norm', np.linalg.norm(Ro))

plot_vectors([x2, y2], fname='transform_02.svg')

A = np.array([[2, 2],
              [2, 2]])
A_squared = np.square(A)
A_squared

A_Frobenius = np.sqrt(np.sum(A_squared))
A_Frobenius

print('Frobenius norm of the Rotation matrix')
print(np.sqrt(np.sum(Ro * Ro)), '== ', np.linalg.norm(Ro))


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

# # Diffie–Hellman Key Exchange Algorithm

# ## About
# Diffie–Hellman key exchange is a method of securely exchanging cryptographic keys over a public channel and was one of the first public-key protocols as conceived by Ralph Merkle and named after Whitfield Diffie and Martin Hellman. Diffie–Hellman is one of the earliest practical examples of public key exchange implemented within the field of cryptography.
#
# Diffie-Hellman, uses Asymetric Key cryptography to communicate the Key securely through the media.

# ## Algorithm
#
# 1. Select a random prime number, **q**
# 2. Calculate $\alpha$, such that, $\alpha$ is the **Primitive Root of q** and $\alpha$ > q
# 3. **For User A:**
#     1. Select **Private Key** , **pvtKeya**, such that pvtKeya < q
#     2. Calculate **Public Key**, **pubKeya = $\alpha$pvtKeya mod q**
# 4. **For User B:**
#     1. Select **Private Key** , **pvtKeyb**, such that pvtKeyb < q
#     2. Calculate **Public Key**, **pubKeyb = $\alpha$pvtKeyb mod q**
#
# After the above steps we get a **pair of Private and Public Keys** for each User. These are to be **used** for generating the final **Key**.
#
#
# **User A :** { Private Key of A , Public Key of A } = { **pvtKeya** , **pubKeya**  }
#
# **User B :** { Private Key of B , Public Key of B } = { **pvtKeyb** , **pubKeyb**  }
#
# Generate **Key**, **Ka & Kb**  at both users' end. ***If keys generated***, **match**, the Key Exchange process is **Successful**.
#
# ***Note:*** The Private Key of a user is known only to that user and no other user has the access to it. While the Public Key of all the users are known to all the users in the network.
#
# ![total.jpg](attachment:total.jpg)

# ## Key Generation
#
# For User A, **Ka = pubKeyb pvtKey a  mod q**
#
#
# For User B, **Kb = pubKeya pvtKey b  mod q**
#
#
# If **Ka = Kb** then the **Key Exchange** is **Successful**. And The final secret key is ***K = Ka = Kb***
#
# ![keyGen.jpg](attachment:keyGen.jpg)

# In[13]:

from sympy import *
from termcolor import colored
import random

# ------ Generate q ------ #

q = randprime(0, 250)

# ------ Funtion to Generate ------ #

def generateKey(pubKey, pvtKey):
    return (pubKey**pvtKey)%q

# ------ Funtion to Generate Public Key ------ #

def publicKey(key, pr):
    return (pr**key)%q

# ------ Funtion to Determine primitive roots ------ #

def primitiveRoot(q):
    setOfPR = []
    a = 1
    while a<q:
        rootSet= set()
        reqSet = set()
        for i in range(1, q):
            rootSet.add(int((a**i)%q))
        #print(rootSet)
        for j in range(1, q):
            reqSet.add(j)
        if rootSet == reqSet:
            setOfPR.append(a)
            a += 1
            #break
        else:
            a += 1
    print("Primitive Roots of q = ", setOfPR, end="\n\n")
    return random.choice(setOfPR)

print("q = ", q, end="\n\n")

# -------------- Primitive root of q -------------- #

primRoot = primitiveRoot(q)

print("Selected (at random) Primitive Root of q = ", primRoot, end="\n\n")

# -------------- Name of user A and B -------------- #

a , b = input("Enter the Names of the two users communicating: ").split(" ")

# ------------------ User A Operations ------------------ #

# Private Key of user A
privateKeyOfA = int(input("\nSelect {}'s Private Key (less than q): ".format(a)))

# Public Key of user A
publicKeyOfA = publicKey(privateKeyOfA, primRoot)

# Print Public Key of A
print("Keys of User {}".format(a))
print("--- Private Key : ", privateKeyOfA)
print("--- Public  Key : ", publicKeyOfA)

# ------------------ User B Operations ------------------ #

# Private Key of user B
privateKeyOfB = int(input("\nSelect {}'s Private Key (less than q): ".format(b)))

# Public Key of user B
publicKeyOfB = publicKey(privateKeyOfB, primRoot)

# Print Public Key of B
print("Keys of User {}".format(b))
print("--- Private Key : ", privateKeyOfB)
print("--- Public  Key : ", publicKeyOfB)

# ------------------ Key Generation ------------------ #

print("\nKey Generation")

# Key Generation for A
print("\nGenerate Key for {}".format(a))
keyA = generateKey(publicKeyOfB, int(input("--- Enter {}'s Private Key: ".format(a))))
print("--- Key generated for {} is {}".format(a, keyA))

# Key Generation for B
print("\nGenerate Key for {}".format(b))
keyB = generateKey(publicKeyOfA, int(input("--- Enter {}'s Private Key: ".format(b))))
print("--- Key generated for {} is {}".format(b, keyB))

# ------------------- Key Exchange Verification ------------------- #

if keyA == keyB :
    print(colored("\nKey Exchange Successful!", 'green'))
    print(colored("--- Your Key is {}".format(keyA) , 'green'))
else:
    print(colored("\nKey Exchange Failed!", 'red'))

# #### Tanmoy Sen Gupta
# [tanmoysg.com](http://tanmoysg.com) | +91 9864809029 | tanmoysps@gmail.com

Diffie-Hellman Key Exchange/Diffie-Hellman Key Exchange.py


import numpy as np

def inverse_power_method(A, tol=1e-9, Max_iter=100):
    """ Calculate the minimum eigenvalue and the corresponding eigenvector by inverse power method.

    Args:
        A: ndarray, the matrix to be solved
        tol: double, iteration accuracy
        Max_iter: int, maximum iteration number

    Retruns:
        k: int, interation number
        eig: double. minimum eigenvalue of the matrix A
        eigv: ndarray, corresponding eigenvector
    """
    # initialization
    # iteration number
    k = 0
    # initial eigenvalue
    eig = 0
    # initial eigenvector
    eigv = np.ones(A.shape[0])

    # iteration
    while k < Max_iter and np.linalg.norm(A @ eigv - eig * eigv) >= tol:
        k += 1
        x = np.linalg.solve(A, eigv)
        eig = 1 / x[np.argmax(x)]
        eigv = x * eig

    return k, eig, eigv

def acceleration_inverse_power(A, alpha, tol=1e-9, Max_iter=100):
    """ Calculate the eigenvalue around alpha and corresponding eigenvector by inverse power method.

    Args:
        A: ndarray, the matrix to be solved
        alpha: double, acceleration number
        tol: double, iteration accuracy
        Max_iter: int, maximum iteration number

    Returns:
        k: int, iteration number
        eig: double, eigenvalue around alpha of the matrix A
        eigv: ndarray, corresponding eigenvector
    """
    # initialization
    B = A - alpha * np.eye(A.shape[0])

    # inverse power method
    k, eig, eigv = inverse_power_method(B, tol, Max_iter)

    eig += alpha

    return k, eig, eigv

if __name__ == '__main__':
    A = np.array([[4, -1, 1], [-1, 3, -2], [1, -2, 3]])
    n, eig, eigv = inverse_power_method(A, 1e-6)
    print(f"The eigenvalue is {eig:.7f}, and corresponding eigenvector is {eigv}.")
    print(f"Iteration number is {n}.")
    n, eig, eigv = acceleration_inverse_power(A, 2.5, 1e-6)
    print(f"The eigenvalue around 2.5 is {eig:.7f}, and corresponding eigenvector is {eigv}.")
    print(f"Iteration number is {n}.")


import numpy as np

# Basic demo of
# 1. Given a secuence, compute and apply a Hann Window
# 2. Compute DFT using the decimation in time algorithm => FFT

x = np.array([1, 1, 2, 0, 0, 2, 1, 1])
L = len(x)
N = L - 1

# Hanning window
w = 0.5 * (1 - np.cos(2*np.pi*np.arange(L)/N))

# Windowed signal
xw = x * w

# Wkn
Wkn = np.exp(-1j * 2*np.pi * np.arange(4)/L)
# Patch: Wkn[2] is super ugly!
Wkn[2] = -1j

# Step 1: combine first with second half of the sequence
X1 = np.zeros(L)
X1[0:4] = xw[0:4] + xw[4:]
X1[4:8] = xw[0:4] - xw[4:]

print('Pass 1a', X1)
# Multiply second half by Wkn
X1 = X1 * np.hstack((np.ones_like(Wkn), Wkn))
print('Pass 1b', X1)

# Step 2: 4 elements combos
X2 = np.zeros(L)
X2 = X2 + 1j * 0  # Trick to convert X2 to complex
X2[0:2] = X1[0:2] + X1[2:4]
X2[2:4] = X1[0:2] - X1[2:4]
X2[4:6] = X1[4:6] + X1[6:8]
X2[6:8] = X1[4:6] - X1[6:8]

print('Pass 2a', X2)
# Multiply by Wkn factors
X2 = X2 * np.array([1, 1, Wkn[0], Wkn[2], 1, 1, Wkn[0], Wkn[2]])
print('Pass 2b', X2)

# Step 3: 2 elements combos
X3 = np.zeros_like(X2)  # This directly creates a complex array
X3[0] = X2[0] + X2[1]
X3[1] = X2[0] - X2[1]
X3[2] = X2[2] + X2[3]
X3[3] = X2[2] - X2[3]
X3[4] = X2[4] + X2[5]
X3[5] = X2[4] - X2[5]
X3[6] = X2[6] + X2[7]
X3[7] = X2[6] - X2[7]

# Bit reverse
# binary_repr(...)[::-1] returns the binary number reversed
# int(..., base=2) converts back to decimal
br = [int(np.binary_repr(x, width=3)[::-1], base=2) for x in range(8)]
X3 = X3[br]

# Check result
print('FFT      ', X3)
print('FFT numpy', np.fft.fft(xw))


import scipy as sp

def hilbert(n):
    """Calculate an nxn Hilbert matrix"""

    H = sp.zeros((n,n))

    for i in range(n):
        for j in range(n):
            H[i][j] = 1.0/(i+j+1)

    return H

def cond(matrix):
    """Calculate the condition number of matrix"""

    return sp.linalg.norm(matrix)*sp.linalg.norm(sp.linalg.pinv(matrix))


import sympy as smp
from sympy import *

def prgrm_iter():
    x, k, n, h = smp.symbols ('x k n h')
    f = smp.sin(x)

    print (" ")
    print ("This program comes from the GitHub repository")
    print ("AndreiMurashev/pos.int_diff_formula")
    print (" ")
    print ("The purpose of this program is to check if the formula")
    print ("for the derivative of a certain positive integer order holds")
    print (" ")

    print ("For which positive integer case would you like to check")
    n = int(input("if the formula works?: "))

    d = diff(f, x, n)
    D = smp.limit((smp.Sum(((-1)**k)*(smp.binomial(n,k))*(f.subs(x,x+(n-k)*h)), (k, 0, n)).doit())/(h**n), h, 0)
    print(d," ?=? ", D)
    print (" ")

    if d == D:
        print (d," = ", D)
        print ("[VALID]: The formula holds for the derivative of order", n)

    if d != D:
        print (d," =/= ", D)
        print ("[INVALID]: The formula fails for the derivative of order", n)
        print ("Please screenshot this and email the creator of the GitHub repository")

def prgrm():
    while True:
        prgrm_iter()
        ans = input("Repeat? (y/n): ")
        if ans != "y":
            break

if __name__ == '__main__':
    try:
        prgrm()
    finally:
        input("Done. Press any key to exit: ")


# This script uses the Montecarlo method to find the
# [approximated] area under a curve (definite integral)

import math
import random
import time

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

def montecarlo_iterative_integration(fun, a, b, num_puntos=1000):
    # To keep track of time
    tic = time.time()

    #HALLAMOS M
    M = 0
    for i in np.arange(a, b, (b-a)/num_puntos):
        aux = fun(i)
        if aux > M :
            M = aux
    print("\n__Integral (iterative)__")
    print("Máximum: ", M)

    #QUEDA DEFINIDO EL CUADRADO ENTRE A-B Y 0-M
    #LANZAMOS PUNTOS ALEATORIOS DENTRO DEL CUADRADO

    nDebajo = 0

    for i in range(num_puntos):
        x = random.uniform(a, b)
        y = random.uniform(0, M)

        if fun(x)>y :
            nDebajo+=1

    print("Points under curve: ", nDebajo)
    print("Total points: ", num_puntos)

    I = (nDebajo/num_puntos)*(b-a)*M
    print ("Montecarlo integral: ", I)

    toc = time.time()
    print("time: ", 1000*(toc - tic))

    return 1000*(toc - tic)

def montecarlo_vectorized_integration(fun, a, b, num_puntos=1000):
    # To keep track of time
    tic = time.time()

    # Vectors with uniformly distributed points
    vectorX = np.random.uniform(low = a, high = b, size =(num_puntos))

    vectorizedFun = np.vectorize(fun)
    funResults = vectorizedFun(vectorX)
    M = funResults.max()
    vectorY = np.random.uniform(low = 0, high = M, size =(num_puntos))

    print("\n__Integral (vectorized)__")
    print("Máximum: ", M)

    # Points under the curve
    nDebajo = np.greater(funResults, vectorY).sum()

    print("Points under curve: ", nDebajo)
    print("Total points: ", num_puntos)

    # Value of the integral (Montecarlo approximation)
    I = (nDebajo/num_puntos)*(b-a)*M
    print ("Montecarlo integral: ", I)

    toc = time.time()
    print("Time: ", 1000*(toc - tic))

    return 1000*(toc - tic)

iterativeArray = []
vectorizedArray = []
iValues = []

# Compare both methods (iterative and vectorized) performance when using
# increasingly larger number of points
for i in range(1, 1000000, 100000):
    iterativeArray.append(montecarlo_iterative_integration(math.sin, 0, math.pi, i))
    vectorizedArray.append(montecarlo_vectorized_integration(math.sin, 0, math.pi, i))
    iValues.append(i)

# Show and save the results
plt.figure()
plt.scatter(iValues, iterativeArray, color='red', label="Iterative")
plt.scatter(iValues, vectorizedArray, color='blue', label="Vectorized")
plt.ylabel('Time comparison')
plt.legend()
plt.savefig("time_comparison.png")
plt.show()

print("\nReal integral value: ", integrate.quad(math.sin, 0, math.pi)[0], "\n")


#!python
# -*- coding: utf-8 -*-#
"""
:Title: Univariate Linear Regression.

@author: Bhishan Poudel

@date: Sep 22, 2017

@email: bhishanpdl@gmail.com

The cost function is given by

.. math::

  J(w) = \\frac{1}{2N} \sum_{n=1}^N (h(x_n,w) - t_n)^2

Minimizing the cost function w.r.t. w gives two system of liner equations:

.. math::

    w_0N + w_1 \sum_{n=1}^N x_n = \sum_{n=1}^N t_n \\\\\\\\
    w_0 \sum_{n=1}^N x_n + w_1 \sum_{n=1}^N x_n^2 = \sum_{n=1}^N t_nx_n

We solve these normal equations and find the values w0 and w1.
"""
# Imports
import argparse
import sys
import numpy as np
from matplotlib import pyplot as plt
import numpy.polynomial.polynomial as poly

# checking
# import statsmodels.api as sm # sm 0.8.0 gives FutureWarning

def read_data(infile):
    X,t = np.loadtxt(infile,unpack=True)
    return X,t

#
def train(X, t):
    """Implement univariate linear regression to compute w = [w0, w1].

    I solve system of linear equations from lecture 01

    w0 N      + w1 sum_x  = sum_t

    w0 sum_x  + w1 sum_xx = sum_tx

    """

    # Use system of equations
    N = len(t)
    sum_x = sum(X)
    sum_t = sum(t)

    sum_xsp = sum(X*X)
    sum_tx = sum(X*t)

    w1 = (sum_t * sum_x - N * sum_tx) / (sum_x * sum_x - N * sum_xsp)
    w0 = (sum_t - w1 * sum_x) / N

    w = np.array([w0, w1])

    # checking values using statsmodel library
    # w = sm.OLS(t,sm.add_constant(X)).fit().params
    # [-15682.27021631    115.41845202]

    # params w
    # print('y-intercept bias term w0 = {:.2f}'.format(w[0][0]))
    # print('weight term           w1 = {:.2f}'.format(w[1][0]))

    # plt.scatter(X,t)
    # plt.plot(X, X*w[1] + w[0])
    # plt.show()

    return w

def compute_rmse(X,t,w):
    """Compute RMSE on dataset (X, t).

    Note: cost function J is 1/2 of mean squared error.
    RMSE is square root of mean squared error.

    """
    h = X*w[1] + w[0]
    rmse = np.sqrt(np.mean(( h - t )**2) )

    # debug
    # print('w[0] =', w[0])
    # print('w[1] =', w[1])

    # rmse = np.sqrt(((np.dot(X,w.T)- t)**2).mean())

    return rmse

#
def compute_cost(X, t, w):
    """Compute objective function on dataset (X, t)."""
    h = X*w[1] + w[0]
    J = 1/2 *  np.mean(( h - t )**2)
    return J

def univariate_reg(fh_train, fh_test):
    # Read the training and test data.
    Xtrain, ttrain = read_data(fh_train)
    Xtest, ttest = read_data(fh_test)

    # Train model on training examples.
    w = train(Xtrain, ttrain)

    # train
    E_rms_train_uni = compute_rmse(Xtrain, ttrain, w)
    J_train_uni = compute_cost(Xtrain, ttrain, w)

    # test
    E_rms_test_uni = compute_rmse(Xtest, ttest, w)
    J_test_uni = compute_cost(Xtest, ttest, w)

    return E_rms_train_uni, J_train_uni, E_rms_test_uni, J_test_uni

def myplot(fh_train,fh_test,w):
    # matplotlib customization
    plt.style.use('ggplot')
    fig, ax = plt.subplots()

    # data
    Xtrain, ttrain = read_data(fh_train)
    Xtest, ttest = read_data(fh_test)
    Xhyptest = Xtest * w[1] + w[0]

    # plot with label, title
    ax.scatter(Xtrain,ttrain,color='b',marker='o', label='Univariate Train')
    ax.scatter(Xtest,ttest,c='limegreen', marker='^', label='Univariate Test')
    ax.plot(Xtest,Xhyptest,'r--',label='Best Fit')

    # set xlabel and ylabel to AxisObject
    ax.set_xlabel('Floor Size (Square Feet)')
    ax.set_ylabel('House Price (Dollar)')
    ax.set_title('Univariate Regression')
    ax.legend()
    ax.grid(True)
    plt.tight_layout()
    plt.savefig('images/Univariate.png')
    plt.show()

##=======================================================================
## Main Program
##=======================================================================
def main():
    """Run main function."""
    parser = argparse.ArgumentParser('Univariate Exercise.')
    parser.add_argument('-i', '--input_data_dir',
                        type=str,
                        default='../data/univariate',
                        help='Directory for the univariate houses dataset.')
    FLAGS, unparsed = parser.parse_known_args()

    # Data file paths
    fh_train = FLAGS.input_data_dir + "/train.txt"
    fh_test  = FLAGS.input_data_dir + "/test.txt"

    # Print weight vector
    Xtrain, ttrain = read_data(fh_train)
    w = train(Xtrain, ttrain)
    print('Params Univariate: ', w, '\n')

    # Print RMSE and Cost
    E_rms_train_uni, J_train_uni, E_rms_test_uni, J_test_uni = univariate_reg(fh_train, fh_test)

    print("#"*50)
    print("Univariate Regression")

    # Print cost and RMSE on training data.
    print('E_rms_train Univariate: %0.2e' % E_rms_train_uni)
    print('J_train Univariate: %0.2e' % J_train_uni)

    # Print cost and RMSE on test data.
    print("\n")
    print('E_rms_test Univariate: %0.2e' % E_rms_test_uni)
    print('J_test Univariate: %0.2e' % J_test_uni)

    # Plotting
    myplot(fh_train, fh_test,w)

if __name__ == "__main__":
   import time

   # Beginning time
   program_begin_time = time.time()
   begin_ctime        = time.ctime()

   #  Run the main program
   main()

   # Print the time taken
   program_end_time = time.time()
   end_ctime        = time.ctime()
   seconds          = program_end_time - program_begin_time
   m, s             = divmod(seconds, 60)
   h, m             = divmod(m, 60)
   d, h             = divmod(h, 24)
   print("\n\nBegin time: ", begin_ctime)
   print("End   time: ", end_ctime, "\n")
   print("Time taken: {0: .0f} days, {1: .0f} hours, \
     {2: .0f} minutes, {3: f} seconds.".format(d, h, m, s))


import numpy as np

print("")
print("array0")
print("____")
array0 = np.array(5)
print(array0.shape) # ()
print(array0.ndim) # 0

print("")
print("array1")
print("____")
array1 = np.array([1,2,3,4])
print(array1.shape) # (4, )
print(array1.ndim) # 1

print("")
print("array2")
print("____")
array2 = np.array([[1,2,3,4],[5, 6, 7, 8]])
print(array2.shape) # (2,4)
print(array2.ndim) # 2

print(array2[1,3]) # 8

print("")
print("array5")
print("____")
array5 = np.array([5, 6, 7, 8], ndmin = 5)
print(array5) # [[[[[5 6 7 8]]]]]
print(array5.shape) # (1,1,1,1,4)
print(array5.ndim) # 5
print(array5[0,0,0,0,2]) # 7

print("________________")
print("________________")
print("________________")
print("________________")
print("________________")
print("---RESHAPING----")
print("________________")
print("________________")
print("________________")
print("________________")
print("________________")

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

newarr1 = array.reshape(4,3)

print(array)
# [ 1  2  3  4  5  6  7  8  9 10 11 12]
print(newarr1)
"""[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

"""
print(newarr1.base) # It is a view
# [ 1  2  3  4  5  6  7  8  9 10 11 12]

newarr2 = array.reshape(3,2,2)

print(newarr2)
"""
[[[ 1  2]
  [ 3  4]]

 [[ 5  6]
  [ 7  8]]

 [[ 9 10]
  [11 12]]]
"""

try:
	array.reshape(8)
except Exception as e:
	print("Can not reshape it to 8 dim")

print("________________")
print("________________")
print("Falttening Arrays")
print("________________")
print("________________")

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

newarr1 = array.reshape(-1)

print(newarr1)
# [1 2 3 4 5 6 7 8]


import numpy as np

def f(x):
    return np.exp(-1*x**2)

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

def romberg_algorithm(inte, func, tol, Max_iter=10):
    """ Calculate the integral by Romberg Algorithm

    Args:
        inte: ndarray, the integrand interval
        func: function boject, the integrand function
        tol: double, the interation accurancy
        Max_iter: int, the maximum number of iterations

    Returns:
        (T[-1], m):
            T[-1]: double, the integral value by Romberg algorithm
            m: the number of iterations
    """
    # initial values
    m = 0
    T = [(inte[1] - inte[0]) * np.sum(func(inte)) / 2]
    for i in range(1, Max_iter+1):
        x_list = np.linspace(inte[0], inte[1], 2**i+1)
        T.append(T[i-1]/2 + (x_list[1] - x_list[0]) * np.sum(func(x_list[1::2])))
    T = np.array(T)

    # iteration
    m += 1
    S = (4 * T[1:] - T[:-1]) / (4 - 1)

    # mark whether getting the given iteration accurancy
    flag = 1
    while np.fabs(S[-1] - T[-1] >= tol):
        if m == Max_iter:
            flag = 0
            break
        else:
            m += 1
            T = S
            S = (4**m * T[1:] - T[:-1]) / (4**m - 1)

    if flag:
        return (T[-1], m)
    else:
        reutnr (nan, Max_iter)

if __name__ == '__main__':
    # interval
    inte = np.array([1e-32, 1])
    # the first integral
    value, n = romberg_algorithm(inte, f, 1e-6)
    print(f"The first integral's value is {value:.6f} by Romberg algorithm after {n} iterations")
    # the second integral
    value, n = romberg_algorithm(inte, g, 1e-6)
    print(f"The second integral's value is {value:.6f} by Romberg algorithm after {n} iterations")


import numpy as np

def tanh(value, derivative=False):
    """
    Tanh(x) function / derivative
    :param value: ndarray -> value to activate
    :param derivative: bool -> compute derivative
    :return: ndarray -> activated ndarray
    """
    if derivative:
        return 1.0 - (value ** 2.0)
    return np.tanh(value)

def sigmoid(value, derivative=False):
    """
    Sigmoid(x) function / derivative
    :param value: ndarray -> value to activate
    :param derivative: bool -> compute derivative
    :return: ndarray -> activated ndarray
    """
    if derivative:
        return value * (1.0 - value)
    return 1.0 / (1.0 + np.exp(-value))

def softplus(value, derivative=False):
    """
    Softplus(x) function / derivative
    :param value: ndarray -> value to activate
    :param derivative: bool -> compute derivative
    :return: ndarray -> activated ndarray
    """
    if derivative:
        return 1.0 / (1.0 + np.exp(-value))
    return np.log(1.0 + np.exp(value))

def softmax(value, derivative=False):
    """
    Softmax(x) function / derivative
    :param value: ndarray -> value to activate
    :param derivative: bool -> compute derivative
    :return: ndarray -> activated ndarray
    """
    if derivative:
        s = value.reshape(-1, 1)
        return np.diagflat(s) - np.dot(s, s.T)
    return np.exp(value) / np.sum(np.exp(value))

def stable_softmax(value, derivative=False):
    """
    Softmax(x) function / derivative (softmax - except compute value in a numerically stable way)
    :param value: ndarray -> value to activate
    :param derivative: bool -> compute derivative
    :return: ndarray -> activated ndarray
    """
    if derivative:
        s = value.reshape(-1, 1)
        return np.diagflat(s) - np.dot(s, s.T)
    shift_x = value - np.max(value)
    return np.exp(shift_x)/np.sum(np.exp(shift_x))


'''
    This script tries to use a random number generator
    that samples a value from a uniform distribution
    between (0,1) to estimate the value of pi.

    The intuition behind the function created is that the
    ratio of the areas of a circle and a square having a
    length equal to the circles diameter is pi/4.

    So if we are able to estimate the number of times a point in
    2D space falls within a circle versus outside the circle bounded
    by a square out of a number of trials, we will be able to estimate pi
'''

import numpy as np

def estimate_pi(n_trials):
    #counter variable to estimate the probability
    ctr = 0
    for i in range(n_trials):
        #sample a 2d coordinate
        x = np.random.random()
        y = np.random.random()
        #count the number of times the coordinate falls within the circle radius
        if (x**2+y**2) < 1:
           ctr+=1
    #multiply by 4 to get pi since the probability is equal to pi/4
    return 4*(ctr/n_trials)


import numpy as np

# Write a function that takes as input two lists Y, P,
# and returns the float corresponding to their cross-entropy.

def cross_entropy(Y, P):
    Y = np.float_(Y)
    P = np.float_(P)
    return -1 * np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))

Y = [1, 1, 0]
P = [0.8, 0.7, 0.1]
result = cross_entropy(Y, P)

print(
    "The result of the above Y[1,1,0] and P[0.8, 0.7, 0.1] should be 0.69, and the calculated was "+str(result))

Y = [0, 0, 1]
P = [0.8, 0.7, 0.1]
result = cross_entropy(Y, P)

print(
    "The result of the above Y[0, 0, 1] and P[0.8, 0.7, 0.1] should be 5.12, and the calculated was "+str(result))


import sympy as sym
import numpy as np
import matplotlib.pyplot as plt

# For all printing methods from Sympy
sym.init_printing()

def show_example_number(index):
    print("\n".join(("\n********", f"Example {index}", "********\n")))

# %%
#
# Ex 1 : dy/dt = -5 y(t) with y(0) = 1
# Plot solution from 0 to 20 with a step of 0.4
#
show_example_number(1)

# Variables
t = sym.symbols("t")
y = sym.Function("y")

# Initial conditions
ics = {y(0): 1}

# Build equation
left_hand = sym.Derivative(y(t), t)
right_hand = -5 * y(t)
eq = sym.Eq(left_hand, right_hand)
print("Diff equation :", sym.pretty(eq), sep="\n")

# Solve equation dsolve(eq, func)
sol = sym.dsolve(eq, y(t), ics=ics)
print("\nSolution :", sym.pretty(sol), sep="\n")

# Lambdify : Transform into function, from string to numpy function
fun_y = sym.lambdify(t, sol.rhs, modules=["numpy"])

a = 0
b = 20
h = 0.4
t = np.arange(a, b + h, h)
y = fun_y(t)

# Plot(y, t)
plt.plot(t, y, color="b")
plt.xlim(a, b)
plt.show()

# %%
#
# Ex 2 : f'(x) = x + (f(x) / 5) with f(0) = -3
#
show_example_number(2)

x = sym.var("x")
f = sym.Function("f")

# Equation, then solving
eq2 = sym.Eq(sym.Derivative(f(x), x), x + f(x) / 5)
ics = {f(0): -3}
sol2 = sym.dsolve(eq2, f(x), ics=ics).rhs
print("Solution :", sym.pretty(sym.simplify(sol2)), sep="\n")

# %%
#
# Ex 3 : f'(x) - f(x) = 0 with f(0) = A
#
show_example_number(3)

# Variables and initial condition
f_3 = sym.Function("f_3")
x_3 = sym.var("x_3")
A = sym.var("A")
ics = {f_3(0): A}

# Not using sym.Eq, simply using an expression, assumed to be equal to 0
sol3 = sym.dsolve(f_3(x_3).diff(x_3) - f_3(x_3), f_3(x_3), ics=ics)

print("Solution :", sym.pretty(sol3), sep="\n")


#Solutions to Policy Function Iteration Lab

import numpy as np
import scipy as sp
from scipy import sparse
from scipy.sparse import linalg
import math
from matplotlib import pyplot as plt
from scipy import linalg as la

def u(x):
    return np.sqrt(x).flatten()

def policyIter(beta, N, Wmax=1.):
    """
    Solve the infinite horizon cake eating problem using policy function iteration.
    Inputs:
        beta -- float, the discount factor
        N -- integer, size of discrete approximation of cake
        Wmax -- total amount of cake available
    Returns:
        values -- converged value function (Numpy array of length N)
        psi -- converged policy function (Numpy array of length N)
    """
    W = np.linspace(0,Wmax,N) #state space vector
    I = sparse.identity(N, format='csr')

    #precompute u(W-W') for all possible inputs
    actions = np.tile(W, N).reshape((N,N)).T
    actions = actions - actions.T
    actions[actions<0] = 0
    rewards = np.sqrt(actions)
    rewards[np.triu_indices(N, k=1)] = -1e10 #pre-computed reward function

    psi_ind = np.arange(N)
    rows = np.arange(0,N)
    tol = 1.
    while tol >= 1e-9:
        columns = psi_ind
        data = np.ones(N)
        Q = sparse.coo_matrix((data,(rows,columns)),shape=(N,N))
        Q = Q.tocsr()
        values = linalg.spsolve(I-beta*Q, u(W-W[psi_ind])).reshape(1,N)
        psi_ind1 = np.argmax(rewards + beta*values, axis=1)
        tol = math.sqrt(((W[psi_ind] - W[psi_ind1])**2).sum())
        psi_ind = psi_ind1
    return values.flatten(), W[psi_ind]

def modPolicyIter(beta, N, Wmax=1., m=15):
    """
    Solve the infinite horizon cake eating problem using modified policy function iteration.
    Inputs:
        beta -- float, the discount factor
        N -- integer, size of discrete approximation of cake
        Wmax -- total amount of cake available
    Returns:
        values -- converged value function (Numpy array of length N)
        psi -- converged policy function (Numpy array of length N)
    """
    W = np.linspace(0,Wmax,N) #state space vector

    #precompute u(W-W') for all possible inputs
    actions = np.tile(W, N).reshape((N,N)).T
    actions = actions - actions.T
    actions[actions<0] = 0
    rewards = np.sqrt(actions)
    rewards[np.triu_indices(N, k=1)] = -1e10 #pre-computed reward function

    psi_ind = np.arange(N)
    values = np.zeros(N)
    tol = 1.
    while tol >= 1e-9:
        for i in xrange(m):
            values = u(W - W[psi_ind]) + beta*values[psi_ind]
        psi_ind1 = np.argmax(rewards + beta*values.reshape(1,N), axis=1)
        tol = math.sqrt(((W[psi_ind] - W[psi_ind1])**2).sum())
        psi_ind = psi_ind1
    return values.flatten(), W[psi_ind]


import math
import numpy as np

from hybridimages.convolution import convolve

def createHybridImage(lowImage: np.ndarray, lowSigma: float, highImage: np.ndarray, highSigma:float) -> np.ndarray:
    """
    Create hybrid images by combining a low-pass and high-pass filtered pair.

    :param lowImage: the image to low-pass filter (either greyscale shape=(rows, cols) or colour shape=(rows,cols,channels))
    :type numpy.ndarray

    :param lowSigma: the standard deviation of the Gaussian used for low-pass filtering lowImage
    :type numpy.ndarray

    :param highSigma: the standard deviation of the Gaussian used for low-pass filtering highImage before subtraction to create the high-pass filtered image
    :type float

    :returns returns the hybrid image created
        by low-pass filtering lowImage with a Gaussian of s.d. lowSigma and combining it with a high-pass image created by subtracting highImage from highImage
        convolved with a Gaussian of s.d. highSigma. The resultant image has the same size as the input images.
    :rtype numpy.ndarray
    """
    print(f'Attempting to merge {lowImage.shape} low frequency image with {highImage.shape} high frequency image')

    low_sigma_kernel = makeGaussianKernel(lowSigma)
    print(f'Applying convolution...')
    low_pass_image = convolve(lowImage, low_sigma_kernel)
    print(f'Low-pass filter successfully applied')
    high_sigma_kernel = makeGaussianKernel(highSigma)
    print(f'Applying convolution...')
    low_pass_of_highimage = convolve(highImage, high_sigma_kernel)

    print(f'Acquiring High-pass filtered image...')
    high_pass_img = highImage - low_pass_of_highimage

    # visualise high_pass image by adding 128 as high freq image is 0 mean with negative values
    # show_image(high_pass_img + 128, grey=False)

    hybrid_img = low_pass_image + high_pass_img
    print(f'Hybrid image computed successfully')

    return hybrid_img

def makeGaussianKernel(sigma: float) -> np.ndarray:
    """
    Use this function to create a 2D Gaussian kernel with standard deviation sigma.
    The kernel values should sum to 1.0, and the size should be floor(8*sigma+1) or
    floor(8*sigma+1)+1 (whichever is odd).
    """
    print('Initiating Gaussian kernel with sigma:', sigma)
    size = np.floor(8 * sigma + 1).astype(int)

    # ensure size remains odd
    if size % 2 == 0:
        size += 1
    print(f'Gaussian kernel dimensions: {size} x {size}')

    # create range between -1 and +1
    ax = np.linspace( -(size-1) / 2, (size-1) / 2, size)
    # fill a size x size grid with above range
    x, y = np.meshgrid(ax,ax)

    # construct kernel using Gauss
    K = (1/(2 * math.pi * sigma**2)) * np.exp(-((x**2 + y**2)/(2 * sigma**2)))
    return K


# import linear matrix equation solving method or function
from numpy.linalg import solve
from numpy import array
from numpy import allclose
from numpy import dot

"""
    Solve(a, b): Take two arguments a = Co-efficient matrix
                                    b = dependent variable values

    Return: Solution of the system ax = b, returned shape is
            identical to b

    Raises: LinAlgError: if a is a singular or not square

    ** Broadcasting rules apply.
    ** a must be square and of full-rank, i.e., all rows (or, equivalently, columns) must be           linearly independent; if either is not true, use lstsq for the least-squares best “solution”    of the system/equation.
"""

# Solve the system of equations 3 * x0 + x1 = 9 and x0 + 2 * x1 = 8
a = array([[3, 1], [1, 2]])
b = array([9, 8])

x = solve(a, b)

print(x)

# Check the solution is okay or not!!!!!
flag = allclose(dot(a, x), b)

if flag:
    print("Solution is correct.")
else:
    print("Solution is not correct.")

# Ref: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.solve.html


'''
The sum of the squares of the first ten natural numbers is,
1**2 + 2**2 + ... + 10**2 == 385

The square of the sum of the first ten natural numbers is,
(1 + 2 + ... + 10)**2 == 3025

Hence the difference between the sum of the squares of the
first ten natural numbers and the square of the sum is:
3025 - 385 = 2640

Find the difference between the sum of the squares of the
first one hundred natural numbers and the square of the sum.
'''

import numpy as np

# brute force
def sum_square_difference(n):
  first_n = np.arange(1, n+1)
  sum_square = np.dot(first_n, first_n)
  square_sum = np.sum(first_n) ** 2
  return square_sum - sum_square

%timeit sum_square_difference(100)

# using euler and pyramidal numbers
def sum_square_difference_2(n):
  square_sum = ((n+1) * n / 2)**2
  sum_square = n * (n+1) * (2*n+1) / 6
  return square_sum - sum_square

%timeit sum_square_difference_2(100)


'''
Q: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
Find the sum of all the primes below two million.
'''

'''
final answer: 142913828922
'''

import numpy as np

# init constants
upper_bound = 2000000

def list_of_primes(n):
    # https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Pseudocode
    numbers = [True for i in range(n)]
    numbers[0] = numbers[1] = False

    for p in range(1, int(n ** 0.5) + 1):
        if (numbers[p] == True):
            for i in range(p ** 2, n, p):
                numbers[i] = False

    primes = []
    for p in range(len(numbers)):
        if numbers[p]:
            primes.append(p)

    return np.array(primes,
                    dtype=object)  # sum is too large so need to set as object (https://stackoverflow.com/a/22664510)

if __name__ == '__main__':
    print("The sum of all the primes below two million is " + str(list_of_primes(upper_bound).sum()))


# -*- coding: utf-8 -*-
"""HILL_CIPHER.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/14sYOs_tSma_mGwx3ulAB48srtODas8DD
"""

import numpy as np
import sympy
import string
import random

# Define variables
dimension = 3 # Your N
key = np.matrix([[17,17,5], [21,18,21], [2,2,19]]) # Your key
message = 'paymoremoney' # Your message

print("Plain Text: "+message)
print("Key Matrix: ")
print(key)
# Generate the alphabet
alphabet = string.ascii_lowercase

# Encrypted message
encryptedMessage = ""

# Group message in vectors and generate crypted message
for index, i in enumerate(message): #PAYMOREMONEY
    values = []
    # Make bloc of N values
    if index % dimension == 0:
        for j in range(0, dimension):
            if(index + j < len(message)):
                values.append([alphabet.index(message[index + j])])
                # print(f'if:{values}')
            else:
                values.append([random.randint(0,25)])
                # print(f'else:{values}')
        # Generate vectors and work with them
        vector = np.matrix(values)
        vector = key * vector
        vector %= 26
        for j in range(0, dimension):
            encryptedMessage += alphabet[vector.item(j)]

# Show the result
print("Encrypted message is: "+ encryptedMessage.upper())

#DECRYPTION

def modulo_multiplicative_inverse(A, M):

    # This will iterate from 0 to M-1
    for i in range(0, M):
        # If we have our multiplicative inverse then return it
        if (A*i) % M == 1:
            return i
    # If we didn't find the multiplicative inverse in the loop above
    # then it doesn't exist for A under M
    return -1

matrix= sympy.Matrix([[17,17,5], [21,18,21], [2,2,19]])
adj=(matrix.adjugate()%26) #TO FIND ADJOINT OF KEY MATRIX

mat=np.matrix([[17,17,5], [21,18,21], [2,2,19]])

det=(round(np.linalg.det(mat))%26) #TO FIND DETERMINENT

# print(det)
# print(adj)

mult_inverse=modulo_multiplicative_inverse(det, 26)

# print(mult_inverse)

inv_m=(mult_inverse*adj)%26
print("inverse of Key Matrix: ")
print(inv_m)

decryptedMessage=""

for index, i in enumerate(encryptedMessage):
    values = []
    if index % dimension == 0:
        for j in range(0, dimension):
            if(index + j < len(encryptedMessage)):
                values.append([alphabet.index(encryptedMessage[index + j])])
            else:
                values.append([random.randint(0,25)])
        vector = np.matrix(values)
        vector = inv_m * vector
        vector %= 26
        for j in range(0, dimension):
            decryptedMessage += alphabet[vector[j]]

# Show the result
print("Decrypted Message: "+ decryptedMessage)


# GRADED FUNCTION
import numpy as np
import numpy.linalg as la

verySmallNumber = 1e-14 # That's 1×10⁻¹⁴ = 0.00000000000001

# Our first function will perform the Gram-Schmidt procedure for 4 basis vectors.
# We'll take this list of vectors as the columns of a matrix, A.
# We'll then go through the vectors one at a time and set them to be orthogonal
# to all the vectors that came before it. Before normalising.
# Follow the instructions inside the function at each comment.
# You will be told where to add code to complete the function.
def gsBasis4(A) :
    B = np.array(A, dtype=np.float_) # Make B as a copy of A, since we're going to alter it's values.
    # The zeroth column is easy, since it has no other vectors to make it normal to.
    # All that needs to be done is to normalise it. I.e. divide by its modulus, or norm.
    B[:, 0] = B[:, 0] / la.norm(B[:, 0])
    # For the first column, we need to subtract any overlap with our new zeroth vector.
    B[:, 1] = B[:, 1] - B[:, 1] @ B[:, 0] * B[:, 0]
    # If there's anything left after that subtraction, then B[:, 1] is linearly independant of B[:, 0]
    # If this is the case, we can normalise it. Otherwise we'll set that vector to zero.
    if la.norm(B[:, 1]) > verySmallNumber :
        B[:, 1] = B[:, 1] / la.norm(B[:, 1])
    else :
        B[:, 1] = np.zeros_like(B[:, 1])
    B[:,2] = B[:,2] - B[:, 2] @ B[:, 0] * B[:, 0]
    B[:,2] = B[:,2] - B[:, 2] @ B[:, 1] * B[:, 1]
    if la.norm(B[:,2]) > verySmallNumber :
        B[:, 2] = B[:, 2] / la.norm(B[:, 2])
    else :
        B[:, 2] = np.zeros_like(B[:, 2])
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 0] * B[:, 0]
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 1] * B[:, 1]
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 2] * B[:, 2]
    if la.norm(B[:,3]) > verySmallNumber :
        B[:, 3] = B[:, 3] / la.norm(B[:, 3])
    else :
        B[:, 3] = np.zeros_like(B[:, 3])

    # Now we need to repeat the process for column 2.
    # Insert two lines of code, the first to subtract the overlap with the zeroth vector,
    # and the second to subtract the overlap with the first.

    # Again we'll need to normalise our new vector.
    # Copy and adapt the normalisation fragment from above to column 2.

    # Finally, column three:
    # Insert code to subtract the overlap with the first three vectors.

    # Now normalise if possible

    # Finally, we return the result:
    return B

# The second part of this exercise will generalise the procedure.
# Previously, we could only have four vectors, and there was a lot of repeating in the code.
# We'll use a for-loop here to iterate the process for each vector.
def gsBasis(A) :
    B = np.array(A, dtype=np.float_) # Make B as a copy of A, since we're going to alter it's values.
    # Loop over all vectors, starting with zero, label them with i
    for i in range(B.shape[1]) :
        # Inside that loop, loop over all previous vectors, j, to subtract.
        for j in range(i) :
            # Complete the code to subtract the overlap with previous vectors.
            # you'll need the current vector B[:, i] and a previous vector B[:, j]
            B[:, i] = B[:,i] - B[:,i] @ B[:,j] * B[:,j]
        # Next insert code to do the normalisation test for B[:, i]
        if la.norm(B[:, i]) > verySmallNumber :
            B[:, i] = B[:, i] / la.norm(B[:, i])
        else :
            B[:, i] = np.zeros_like(B[:, i])

    # Finally, we return the result:
    return B

# This function uses the Gram-schmidt process to calculate the dimension
# spanned by a list of vectors.
# Since each vector is normalised to one, or is zero,
# the sum of all the norms will be the dimension.
def dimensions(A) :
    return np.sum(la.norm(gsBasis(A), axis=0))


# Graphical Solutions
## Introduction to Linear Programming

#Import some required packages.
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Graphical solution is limited to linear programming models containing only two decision variables (can be used with three variables but only with great difficulty).

Graphical methods provide a picture of how a solution for a linear programming problem is obtained.

## Product mix problem - Farmers Fields
Problem: How much of each brand to purchase to minimize total cost of fertilizer given following data ?

Product resource requirements and unit profit:
Two brands of fertilizer available – Super-gro, Crop-quick.
Field requires at least 16 pounds of nitrogen and 24 pounds of phosphate.
Super-gro costs $6 per bag, Crop-quick $3 per bag.

Decision Variables:

$x_{1}$ = number of bags of Super-gro

$x_{2}$ = number of bags of Crop-quick

Cost (Z) minimization

Z = 6$x_{1}$ + 3$x_{2}$

Nitrogen Constraint

2$x_{1}$ + 4$x_{2}$ >= 16

Phosphate Constraint Check

4$x_{1}$ + 3$x_{2}$ >= 24

Non-negativitiy Constraint

$x_{1}$ > 0

$x_{2}$ > 0

#Create an Array X2 from 0 to 60, and it should have a length of 61.
x2 = np.linspace(0, 14, 15)

#This is the same as starting your Excel Spreadsheet with incrementing X2
x2

#Nitrogen Constraint
# 2x1  + 4x2  >= 16
# x1= 8-2x2
c1 =  8 - 2*x2
c1

#Clay (Physicial Resource) Constraint Check
#4x1 + 3x2 <= 120
#4x1  + 3 x2  >= 24
c2  = (24 - 3*x2)/4
c2

#Calculate the maximim value of X1 you can make per the 2 different constraints.

ct = np.maximum(c1,c2)
ct

ct=ct[0:9] #removes negative values
ct

#Calculate the minimum cost from ct
#Z = 6 x1  + 3 x2
cost = 6*ct+3*x2[0:9]#Shape of array must be the same.
cost

# Make plot for the Nitrogen Constraint

plt.plot(c1, x2, label=r'2$x_{1}$ + 4$x_{2}$ >= 16')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(x2,c1, color='grey', alpha=0.5)

#Graph Phosphate Constraint Check

plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ >= 24')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(c2, x2, color='grey', alpha=0.5)

# Make plot for the combined constraints.
plt.plot(c1, x2, label=r'2$x_{1}$ + 4$x_{2}$ >= 16')
plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ >= 24')
#plt.plot(ct, x2, label=r'min(x$x_{1}$)')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(ct, x2,  color='grey', alpha=0.5)

Our solution must be in in lies somewhere in the grey feasible region in the graph above. However, according to the fundamental theorum of Linear programming we know it is at a vertex.

"In mathematical optimization, the fundamental theorem of linear programming states, in a weak formulation, that the maxima and minima of a linear function over a convex polygonal region occur at the region's corners. Further, if an extreme value occurs at two corners, then it must also occur everywhere on the line segment between them."

- [Wikipedia](https://en.wikipedia.org/wiki/Fundamental_theorem_of_linear_programming)

#This returns the index position of the maximum value
min_value = np.argmin(cost)
min_value

#Calculate The min cost that is made.
profit_answer=cost[min_value]
profit_answer

# Verify all constraints are integers
x2_answer = x2[min_value]
x2_answer

# Verify all constraints are integers
ct_answer = ct[min_value]
ct_answer


from typing import Union, Dict, Callable, Tuple

import numpy as np

def sigmoid(X: Union[float, np.array]) -> Union[float, np.array]:
    """
    Sigmoid activation function σ(X) = 1 / /1 + e^(-x))
    :param X: A scalar or numpy array of any size.
    :return: σ(X) = 1 / (1 + e^(-x))
    """
    return 1 / (1 + np.exp(-X))

def sigmoid_prime(X: Union[float, np.array]):
    """
    Derivative of the sigmoid function σ'(X) = σ(X) * (1 - σ(X))
    :param X: A scalar or numpy array of any size
    :return: σ'(X) = σ(X) * (1 - σ(X))
    """
    s = sigmoid(X)
    sigma_prime = s * (1 - s)
    return sigma_prime

def relu(x: Union[float, np.array]) -> Union[float, np.array]:
    """
    ReLU activation function implementation relu(X) = max(0, X)
    :param x: A scalar or numpy array of any size
    :return: relu(X) = max(0, x)
    """
    return np.maximum(0, x)

def relu_prime(X: Union[float, np.array]):
    """
    Derivative of the relu function relu'(X) = 0 if x <= 0, 1 if x > 0
    :param X: A scalar or numpy array of any size
    :return: σ'(X) = σ(X) * (1 - σ(X))
    """
    relu_prime = X > 0
    return relu_prime

def tanh(X: Union[float, np.array]) -> Union[float, np.array]:
    """
    tanh activation function tanh(X) = (e^x - e^(-x)) / (e^x + e^(-x))
    :param X: A scalar or numpy array of any size
    :return: tanh(X) = (e^X - e^(-X)) / (e^X + e^(-X))
    """
    e_x = np.exp(X)
    e_minus_x = np.exp(-X)
    return (e_x - e_minus_x) / (e_x + e_minus_x)

def tanh_prime(X: Union[float, np.array]):
    """
    Derivative of the tanh function tanh'(X) = 1 - (tanh(X))^2
    :param X: A scalar or numpy array of any size
    :return: tanh'(X) = 1 - (tanh(X))^2
    """
    t = tanh(X)
    tanh_prime = 1 - t * t
    return tanh_prime

# The dictionary of activation functions, to be able to retrieve them by name
activation_functions: Dict[str, Tuple[Callable, Callable]] = {
    sigmoid.__name__: (sigmoid, sigmoid_prime),
    relu.__name__: (relu, relu_prime),
    tanh.__name__: (tanh, tanh_prime)
}

class UnknownActivationFunctionName(Exception):
    def __init__(self, activation_function_name: str):
        message = "Unknown activation function name {}".format(activation_function_name)
        super().__init__(message)

def get_activation_function(activation_function_name: str):
    """
    Given the name of an activation function, retrieve the corresponding function and its derivative
    :param cost_function_name: the name of the cost function
    :return: the corresponding activation function and its derivative
    """
    try:
        return activation_functions[activation_function_name]
    except KeyError:
        raise UnknownActivationFunctionName(activation_function_name)


# inner
# The inner tool returns the inner product of two arrays.

# import numpy
# A = numpy.array([0, 1])
# B = numpy.array([3, 4])
# print numpy.inner(A, B)     #Output : 4

# outer
# The outer tool returns the outer product of two arrays.

# import numpy
# A = numpy.array([0, 1])
# B = numpy.array([3, 4])
# print numpy.outer(A, B)     #Output : [[0 0]
#                             #          [3 4]]

# Task :
# You are given two arrays:
# and .
# Your task is to compute their inner and outer product.

import numpy as np

a = list(map(int, raw_input().split()))
b = list(map(int, raw_input().split()))
a = np.array(a)
b = np.array(b)

print(np.inner(a, b))
print(np.outer(a, b))

# Sample Input
# 0 1
# 2 3

# Sample Output
# 3
# [[0 0]
#  [2 3]]


from __future__ import annotations

from typing import Iterable, Union

import numpy as np

Vector = Union[Iterable[float], Iterable[int], np.ndarray]
VectorOut = Union[np.float64, int, float]

def euclidean_distance(vector_1: Vector, vector_2: Vector) -> VectorOut:
    """
    Calculate the distance between the two endpoints of two vectors.
    A vector is defined as a list, tuple, or numpy 1D array.
    >>> euclidean_distance((0, 0), (2, 2))
    2.8284271247461903
    >>> euclidean_distance(np.array([0, 0, 0]), np.array([2, 2, 2]))
    3.4641016151377544
    >>> euclidean_distance(np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8]))
    8.0
    >>> euclidean_distance([1, 2, 3, 4], [5, 6, 7, 8])
    8.0
    """
    return np.sqrt(np.sum((np.asarray(vector_1) - np.asarray(vector_2)) ** 2))

def euclidean_distance_no_np(vector_1: Vector, vector_2: Vector) -> VectorOut:
    """
    Calculate the distance between the two endpoints of two vectors without numpy.
    A vector is defined as a list, tuple, or numpy 1D array.
    >>> euclidean_distance_no_np((0, 0), (2, 2))
    2.8284271247461903
    >>> euclidean_distance_no_np([1, 2, 3, 4], [5, 6, 7, 8])
    8.0
    """
    return sum((v1 - v2) ** 2 for v1, v2 in zip(vector_1, vector_2)) ** (1 / 2)

if __name__ == "__main__":

    def benchmark() -> None:
        """
        Benchmarks
        """
        from timeit import timeit

        print("Without Numpy")
        print(
            timeit(
                "euclidean_distance_no_np([1, 2, 3], [4, 5, 6])",
                number=10000,
                globals=globals(),
            )
        )
        print("With Numpy")
        print(
            timeit(
                "euclidean_distance([1, 2, 3], [4, 5, 6])",
                number=10000,
                globals=globals(),
            )
        )

    benchmark()


import numpy as np

def pseudo(A):
    # Check if matrix A is injective.
    if np.linalg.det(A.T @ A) < 1e-9:
        print("the given matrix is not injective.")
        return

    # Return pseudo-inverse (See lecture notes page 155)
    print("the given matrix is injective.")
    return np.linalg.inv(A.T @ A) @ A.T

# A = np.array([[1, 2, 0], [2, 4, 0], [1, 0, 3]]) # not injective
A = np.array([[1, 2, 0], [1, 4, 2], [1, 0, 3]]) # injective
print(pseudo(A))

A = np.array([[1, 0], [2, 1], [0, 2]])
b = np.array([1, 0, 1]).reshape((3, 1))
x = pseudo(A) @ b
print("\nOwn implementation:")
print(x)

print("\nNumpy implementation:")
print(np.linalg.lstsq(a = A, b = b, rcond=None)[0])


import numpy as np
import sys

try:
    n=int(sys.argv[1])
except IndexError:
    n=100
except ValueError:
    try:
        n=int(sys.argv[2])
    except IndexError:
        n=100

silent="-s" in sys.argv

N=4

base=list(range(N))

def init():
    '''
    Function that initializes monty hall problem simulation.

    returns a list with randomly ordered door's content.
    '''
    l=[]
    for i in range(N-1):
        l.append("goat")
    l.append("car")
    return np.random.permutation(l)

def first_choice():
    '''
    Emulates the initial choice.
    '''
    return np.random.choice(base)

def reveal_goat(doors_list,initial_choice):
    '''
    In answer to the initial choice, the machine reveals a door (index of) where is a goat, randomly.
    '''
    possible_indexes=[]
    for i in base:
        if( i!=initial_choice and doors_list[i]=="goat" ):
            # It is not fair to reveal the first goat. Here are considered all scenarios.
            possible_indexes.append(i)
    return np.random.choice(possible_indexes)

def second_choice(change,initial_choice,goat_index):
    '''
    Emulates the second choice, according to the rules of the game.
    '''
    if(change):
        choices=[]
        for i in base:
            if( i!=goat_index and i!=initial_choice ):
                choices.append(i)
        return np.random.choice(choices)
    else:
        return initial_choice

def MontyHall(change_arg):
    '''
    Simulates a Monty Hall game, with the changing choice given by the boolean "change".

    Returns True if win the car, False if win a goat.
    '''
    doors=init()
    n1=first_choice()
    goat=reveal_goat(doors,n1)
    n2=second_choice(change_arg,n1,goat)
    if doors[n2]=="goat":
        return False
    if doors[n2]=="car":
        return True

# Set up of several simulations

# Change option, n games

CH_win=0
CH_lose=0

for i in range(n):
    result=MontyHall(True)
    if(result):
        CH_win+=1
    else:
        CH_lose+=1

# Non-change option, n games

N_win=0
N_lose=0

for i in range(n):
    result=MontyHall(False)
    if(result):
        N_win+=1
    else:
        N_lose+=1

# Print the results:

if silent:
    print(round(N_win/n*100,3),round(CH_win/n*100,3))
else:
    print("There was a ",round(N_win/n*100,3),"%  of probabilities to win if one chooses not to change the selected door")
    print("There was a ",round(CH_win/n*100,3),"%  of probabilities to win if one chooses to change the selected door")


# -*- coding: utf-8 -*-
"""
Created on Fri Sep 17 02:09:53 2021

@author: Mahfuz_Shazol
"""

import numpy as np
import torch as th

A=np.array([[25,2],
            [5,4]])

A_trace=np.trace(A)
print(A_trace)

# Tr(A)=Tr(A.T)
result1=np.trace(A)
print(result1)
result2=np.trace(A.T)
print(result2)
print('Tr(A)=Tr(A.T)   Ans:',result1==result2)

#Calculate Frobenius norm AF=(Tr(A A.T))**(1/2)
A_p=th.tensor([
    [-1,2],
    [3,-2],
    [5,7],
    ])

calculated_frobenius_norm=(th.trace(th.matmul(th.as_tensor(A),th.as_tensor(A.T))))**(1/2)
print('calculated_frobenius_norm  Ans:',calculated_frobenius_norm)

norm_result=np.linalg.norm(A)
print(norm_result)


#!/usr/bin/python

import os
import numpy as np
import math

print "This script solves the excercises propossed in the Linear Algebra & 2D Geometry Lectures"

# --------------------------------------------------------------------------------------------------
# Length and Normalized Vector
v = np.array([4, 8, -4])
len_v = math.sqrt(pow(v[0], 2) + pow(v[1], 2) + pow(v[2], 2))
print "Length of v=" + str(v) + " is = " + str(len_v)

v_norm = v / len_v

print "Normalized vector" + str(v_norm) + ",length of normalized v =" + str(np.linalg.norm(v_norm))

# --------------------------------------------------------------------------------------------------
# Scalar and cross product

x1 = np.array([2, -4, 1])
x2 = np.array([2, 1, -2])
dot_product = x1[0] * x2[0] + x1[1] * x2[1] + x1[2] * x2[2]
print "The scalar product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(dot_product)

dot_product = np.dot(x1, x2)
print "The scalar product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(dot_product)

cross_product = np.cross(x1, x2)
print "The cross product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(cross_product)

# --------------------------------------------------------------------------------------------------
# Matrix Algebra
M1 = np.matrix([[1, 2], [3, 4]])
M2 = np.matrix([[5, 4], [3, 2]])
print "M1= " + str(M1)
print " + M2= " + str(M2)
print "=" + str(M1 + M2)

M1 = np.matrix([[-1, 0], [0, 1]])
M2 = np.matrix([[-2, 1], [-1, 2]])
print "M1= " + str(M1)
print " - M2= " + str(M2)
print "=" + str(M1 - M2)

# --------------------------------------------------------------------------------------------------
# Scalar Matrix Multiplication
M1 = np.matrix([[1, -2], [2, -1]])
print "2.5 * M1= " + str(2.5 * M1)

# --------------------------------------------------------------------------------------------------
# Matrix Vector Multiplication
M1 = np.matrix([[1, 0], [0, 1]])
x1 = np.array([1, 2])
print "x1 * M1= " + str(M1.dot(x1))

M1 = np.matrix([[1, -1], [-1, 1]])
x1 = np.array([5, 6])
print "x1 * M1= " + str(M1.dot(x1))

# --------------------------------------------------------------------------------------------------
# Matrix Multiplication
M1 = np.matrix([[1, 0], [0, -1]])
M2 = np.matrix([[1, -2], [2, -1]])
print "M1 * M2= " + str(M1 * M2)

M1 = np.matrix([[1, 2], [3, 4]])
M2 = np.matrix([[4, 3], [2, 1]])
print "M1 * M2= " + str(M1 * M2)


import read_data as RD
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

X = RD.read_data()
print('X = ',X.shape)
X_mean = np.reshape(np.sum(X,1)/X.shape[1],[ X.shape[0],1])
X = X-X_mean
print('X_centerred = ',X.shape)
[U,S,V] = np.linalg.svd(X, full_matrices=False)
print('U = ',U.shape)
print('S = ',S.shape)
print('V = ',V.shape)

N = 12#number of eigen images
Eig_im = U[:,0:N]
plt.figure(figsize=(10,10))
for i in range(0,N):
	plt.subplot(int(np.sqrt(N)),int(np.ceil(N/int(np.sqrt(N)))),i+1)
	im = np.reshape(Eig_im[:,i],[64,64])
	plt.imshow(im,cmap=plt.cm.gray, interpolation='none')
	plt.title('Eigen Image = '+str(i+1))

plt.savefig('Eigen_Images.png')
plt.savefig('Eigen_Images.tif')

Y = np.matmul(np.transpose(U),X)
print('Y = ',Y.shape)
plt.figure(figsize=(10,10))
Np = 10#Number of projection coefficients to plot
Ni = 4#Number of images
images = ['a','b','c','d']
for i in range(0,Ni):
	plt.plot(np.arange(1,Np+1),Y[0:Np,i],label='Image = '+images[i])
plt.xlabel('Eigenvectors',fontsize=20)
plt.xticks(weight = 'bold',fontsize=15)
plt.ylabel('Magnitude of the projection coefficient',fontsize=20)
plt.yticks(weight = 'bold',fontsize=15)
plt.legend(fontsize=20)
plt.savefig('Projection_Coefficients.png')
plt.savefig('Projection_Coefficients.tif')

#Image synthesis
ind = 0#index of the image to synthesize
m = [1, 5, 10, 15, 20, 30]
plt.figure(figsize=(10,15))
for i in range(0,len(m)):
	X_hat = np.reshape(np.matmul(U[:,0:m[i]],Y[0:m[i],ind]),[X.shape[0],1])
	print(X_hat.shape)
	print(X_mean.shape)
	X_hat += X_mean
	plt.subplot(3,2,i+1)
	im = np.reshape(X_hat,[64,64])
	plt.imshow(im,cmap=plt.cm.gray, interpolation='none')
	plt.title('m = '+str(m[i]),fontsize=20)
	plt.xticks(weight = 'bold',fontsize=15)
	plt.yticks(weight = 'bold',fontsize=15)
	#img_out = Image.fromarray(im.astype(np.uint8))
	#img_out.save('Im_reconstruction_'+str(m[i])+'.tif')
plt.savefig('Im_reconstruction.png')
plt.savefig('Im_reconstruction.tif')


from __future__ import print_function, division

import numpy as np
import scipy.linalg as la

def simple_lu(A):
    R = A.copy()
    L = np.eye(A.shape[0])
    for i in range(A.shape[0] - 1):
        factors = R[i + 1:, i] / R[i, i]
        R[i+1:, :] -= factors[:, None] * R[i, :]
        L[i+1:, i] = factors
    return L, R

A = np.array([[1.1, 2.1, 3.1],
              [2.2, 3.2, 1.2],
              [3.3, 1.3, 2.3]])

print("Simple LR without pivotisierung")
L, R = simple_lu(A)
print("A:\n", A)
print("L:\n", L)
print("R:\n", R)
print("L*R:\n", L.dot(R))

def lu(A):
    R = A.copy()
    L = np.eye(A.shape[0])
    p_rows = np.arange(A.shape[0])
    for i in range(A.shape[0] - 1):
        # pivot wahl
        pivot_row = i + np.argmax(np.abs(R[i:, i]))
        p_rows[i], p_rows[pivot_row] = p_rows[pivot_row], p_rows[i]
        R[i, :], R[pivot_row, :] = R[pivot_row, :], R[i, :].copy()
        # eliminate
        factors = R[i + 1:, i] / R[i, i]
        R[i + 1:, :] -= factors[:, None] * R[i, :]
        L[i + 1:, i] = factors
    P = np.zeros(A.shape)
    for i, p in enumerate(p_rows):
        P[i, p] = 1
    return P, L, R

print("Simple LR with pivotisierung")
P, L, R = lu(A)
print("A:\n", A)
print("L:\n", L)
print("R:\n", R)
print("P:\n", P)
print("L*R - P*A:\n", L.dot(R) - P.dot(A))

def backsubstitution(upper_triangular_matrix, rhs):
    sol = np.zeros(rhs.shape[0])
    for i in range(rhs.shape[0] - 1, -1, -1):
        sol[i] = (rhs[i] - np.sum(upper_triangular_matrix[i, i + 1:]*sol[i + 1:]))/upper_triangular_matrix[i, i]
    return sol

def forwardsubstituition(lower_triangular_matrix, rhs):
    sol = np.zeros(rhs.shape[0])
    for i in range(rhs.shape[0]):
        sol[i] = (rhs[i] - np.sum(lower_triangular_matrix[i, :i]*sol[:i]))/lower_triangular_matrix[i, i]
    return sol

def lu_solve(P, L, R, b):
    # Ax = b
    # PA = LR
    # P^-1*L*Rx = b
    # LRx = Pb
    # y = Rx
    # Ly = Pb
    # Rx = y
    y = forwardsubstituition(L, P.dot(b))
    x = backsubstitution(R, y)
    return x

b = np.array([1,2,3])
x = lu_solve(P, L, R, b)
print("b:\n", b)
print("x with Ax = b:\n", x)
print("Ax - b:\n", A.dot(x) - b)


import numpy as cp

class Sigmoid():

    '''

    A Sigmoid function is a mathematical function which has a characteristic S-shaped curve.
    Normally used to refer specifically to the logistic function, also called the logistic
    sigmoid function.sigmoid functions have the property that they map the entire number line
    into a small range such as between 0 and 1,so one use of a sigmoid function is to convert
    a real value into one that can be interpreted as a probability.

    The logistic sigmoid function is defined as follows:

        S(x) =   _____1_______ where exp is exponential
                ( 1 + exp(-x))

     With the help of Sigmoid activation function, we are able to reduce the loss during
     the time of training because it eliminates the gradient problem in machine learning
     model while training.

    Parameters
    ----------
    x : cp.array
        Array of ouputs of deeplearning layer.

    Returns
    -------
    cp.array
        Array after sigmoid function applied .

    '''

    def __call__(self, x: cp.array) -> cp.array:

        return 1 / (1 + cp.exp(-x))

    def gradient(self, x: cp.array) -> cp.array:
        '''

        Parameters
        ----------
        x : cp.array
             Array of ouputs of deeplearning layer..

        Returns
        -------
        cp.array
            Array after derivatives of sigmoid applied .

        '''
        return self.__call__(x) * (1 - self.__call__(x))

tutorial/7. Deep Learning/Activation Function/Sigmoid.py


'''
    Vector length formula for three-dimensional vector
    --------------------------------------------------
    In the case of the spatial problem the length of the vector a = {ax ; ay ; az}
    can be found using the   following formula:

    |a| = √ax2 + ay2 + az2

    Examples of plane tasks
    Example 1. Find the length of the vector a = {2; 4}.
    Solution: |a| = √2^2 + 4^2 = √4 + 16 = √20 = 2√5.

    Example 2. Find the length of the vector a = {3; -4}.
    Solution: |a| = √3^2 + (-4)^2 = √9 + 16 = √25 = 5.

    Examples of spatial tasks
    Example 3. Find the length of the vector a = {2; 4; 4}.
    Solution: |a| = √2^2 + 4^2 + 4^2 = √4 + 16 + 16 = √36 = 6.

    Example 4. Find the length of the vector a = {-1; 0; -3}.
    Solution: |a| = √(-1)^2 + 0^2 + (-3)^2 = √1 + 0 + 9 = √10.
'''

import numpy as np

a = np.array([2,4,4]) # 3 dimension

total = 0

for i in range(len(a)):
    total += a[i] ** 2

print(np.sqrt(total))
# or
print(np.linalg.norm(a))

a = np.array([-1, 0, -3, -4, 10])

total = 0

for i in range(len(a)):
    total += a[i] ** 2

print(np.sqrt(total))
#or
print(np.linalg.norm(a))


# Least square method of univariate linear regression.
# Solution/weight coeffecients: y = mx + c -> m = sum((Mean-deviation of Xi) * (Mean-deviation of Yi)) / sum((Mean-deviation of Xi) ** 2)
#                                             c = Mean-Y - m * Mean-X
# https://www.amherst.edu/system/files/media/1287/SLR_Leastsquares.pdf

import numpy as np
import matplotlib.pyplot as plt

class LinearRegression:
    def __init__(self):
        self._X = None
        self._y = None
        self._beta0 = 0.
        self._beta1 = 0.
        self._correlation = 0.

    def _mean(self, arr):
        return np.mean(arr)

    def _diff(self, arr, num):
        return np.subtract(arr, num)

    def fit(self, X, y):
        self._X = X
        self._y = y
        self._compute_correlation()

        mean_x = self._mean(self._X)
        mean_y = self._mean(self._y)
        mean_deviation_x = self._diff(self._X, mean_x)
        mean_deviation_y = self._diff(self._y, mean_y)

        self._beta1 = np.sum(np.multiply(mean_deviation_x, mean_deviation_y)) / np.sum(mean_deviation_x ** 2)
        self._beta0 = mean_y - self._beta1 * mean_x

        train_pred = self.predict(X)
        return self._rmse_error(y, train_pred)

    def _rmse_error(self, y, y_pred):
        return np.sqrt(np.mean(np.subtract(y, y_pred) ** 2))

    def predict(self, input):
        return np.add(self._beta0, np.dot(self._beta1, input))

    def weight_coefficients(self, decimal=4):
        return round(self._beta0, decimal), round(self._beta1, decimal)

    def _compute_correlation(self):
        sum_x = np.sum(self._X)
        sum_y = np.sum(self._y)
        sum_xy = np.sum(np.multiply(self._X, self._y))
        sum_xsquare = np.sum(np.multiply(self._X, self._X))
        sum_ysquare = np.sum(np.multiply(self._y, self._y))
        n = len(self._X)
        self._correlation = (n * sum_xy - sum_x * sum_y) / np.sqrt(((n * sum_xsquare - sum_x ** 2) * (n * sum_ysquare - sum_y ** 2)))

    def correlation(self, decimal=4):
        return round(self._correlation, decimal)

    def plot_regression(self, X, y):
        plt.scatter(X, y, color="red", marker="x", s=20)

        y_pred = self._beta0 + np.multiply(self._beta1, X)

        plt.plot(X, y_pred, color="green")

        plt.xlabel('X')
        plt.ylabel('y')

        plt.show()

def main():
    linear_reg = LinearRegression()
    X = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    y = [6, 8, 9, 11, 13, 16, 17, 19, 20, 24]

    rmse_error = linear_reg.fit(X, y)
    weights = linear_reg.weight_coefficients()
    print 'Weight coefficients for y = mx + c: m = {}, c = {}\n'.format(weights[1], weights[0])
    print 'Correlation between X and y: {}\n'.format(linear_reg.correlation())
    print 'Root mean-squared error for training: {}\n'.format(rmse_error)

    test_y = [3, 5, 10]
    print 'Prediction of {}: {}'.format(test_y, (linear_reg.predict(test_y)))
    linear_reg.plot_regression(X, y)

if __name__ == '__main__':
    main()


# ***Question (1)***
#
# Part (a)
#
# Simultaneous equations for the loops:
# 80*i1-50*i2-30*i3 = 240
# 50*i1-100*i2-10*i3-25*i4 = 0
# -30*i1-10*i2+65*i3-20*i4 = 0
# -25*i2-20*i3-100*i4 = 0

# Part (b)

import scipy as sp
import numpy as np
import gaussElimin as gE

A = sp.array([[80.0,-50.0,-30.0,0.0],[-50.0,100.0,-10.0,-25.0],[-30.0,-10.0,65.0,-20.0],[0.0,-25.0,-20.0,100.0]]) # array of coefficients in simultaneous equations
b = sp.array([240.0,0.0,0.0,0.0]) # results for all the simultaneous equations

print "For the first array, plug in all of the coefficients of the simultaneous equations derived from the circuit diagrams. This array, A:"
print A

print "For the second array, plug in all the results of the simultaneous equations derived from the circuit diagram. This array, b:"
print b

x = gE.gaussElimin(A,b)

print "Using the Gaussian elimination module, we can solve:"
print x

# Part (c)

from scipy import linalg

P, L, U = linalg.lu(A) # computes LU factorisation explicitly where P is the permutation matrix, L is the lower triangular matrix, and U is the upper triangular matrix

print "We can use an LU (Lower-Upper) decomposition to decompose our resultant matrix into an upper part and a lower part. The lower part:"
print L

print "And the upper part:"
print U

# Part (d)

print "Next, use the LU decomposition to solve Ax = b"

y1 = gE.gaussElimin(L,b) # lower triangular matrix and b

x1 = gE.gaussElimin(U,y1) # upper triangular matrix and the gE of lower and b
print x1

print "Now we want to use our LU decomposition to prove that we can go the other direction and get an answer from it too."
print "Using a new array where the voltage across is 120 V instead of 240 V (as the range is 0 V to +120 V instead of -120 V to +120 V:"

c = sp.array([240.0,0.0,0.0,0.0]) # new b array where the -120 V is = 0 V

y2 = gE.gaussElimin(L,c) # same process as with y1 and x1

x2 = gE.gaussElimin(U,y2)

print x2

ExamPrep/Shit Comp/SciComp Exam 2016/Assignments 15-16/A1Q1 LU decomp Gauss Elim.py


"""
Created on Aug  30

@author: tsquire

Description: Solving the knapsack problem (see below) using dynamic programming

max 3x1 + x2 + 6x3
s.t 4x1 + 5x2 + 10x3 <= 20
"""

import numpy as np

c = np.array([3,1,6])
a= np.array([4,5,10])
d= 20
n= c.size

def KnapSackSolve(costs,weights,cap,varNum):
    valMat = np.zeros([varNum,cap+1])
    decMat = np.zeros([varNum,cap+1])
    for i in range(varNum-1,-1,-1):
        for j in range(cap+1):
            range2Check = int(j/weights[i])
            maxVal = 0
            maxPos = -1
            for k in range(range2Check+1):
                if i == varNum-1:
                    val = k*costs[i]
                else:
                    val = k*costs[i]+valMat[i+1,j-k*weights[i]]
                if val >= maxVal:
                    maxVal = val
                    maxPos = k
            valMat[i,j] = maxVal
            decMat[i,j] = maxPos
    return valMat,decMat

def EvalSolu(valMat,decMat,costs,weights,cap,varNum):
    x = np.zeros(varNum)
    currentCap = cap
    totalCost = 0
    for i in range(varNum):
        x[i] = decMat[i,currentCap]
        totalCost += x[i]*costs[i]
        currentCap -= int(x[i]*weights[i])
    return x,totalCost

valMat,decMat = KnapSackSolve(c,a,d,n)
solu,totalCost = EvalSolu(valMat,decMat,c,a,d,n)


"""Halton low discrepancy sequence.

This snippet implements the Halton sequence following the generalization of
a sequence of *Van der Corput* in n-dimensions.

---------------------------

MIT License

Copyright (c) 2017 Pamphile Tupui ROY

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np
import tensorflow as tf

def primes_from_2_to(n):

	sieve = np.ones(n // 3 + (n % 6 == 2), dtype=np.bool)
	for i in range(1, int(n ** 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 np.r_[2, 3, ((3 * np.nonzero(sieve)[0][1:] + 1) | 1)]

def van_der_corput(n_sample, base=2):

	sequence = []
	for i in range(n_sample):
		n_th_number, denom = 0., 1.
		while i > 0:
			i, remainder = divmod(i, base)
			denom *= base
			n_th_number += remainder / denom
		sequence.append(n_th_number)

	return sequence

def halton(dim, n_sample):

	big_number = 10
	while 'Not enought primes':
		base = primes_from_2_to(big_number)[:dim]
		if len(base) == dim:
			break
		big_number += 1000

	sample = [van_der_corput(n_sample + 1, dim) for dim in base]
	sample = np.stack(sample, axis=-1)[1:]

	return sample

def halton_batch(batch_size, dim, n_sample, extent=1.5, z_offset=0.):

	if z_offset == 0:
		return tf.cast(tf.constant([halton(dim, n_sample) for _ in range(batch_size)]), tf.float32) * (extent / 0.5)
	else:
		arr = np.array([halton(dim, n_sample) for _ in range(batch_size)], dtype=np.float32) * (extent / 0.5)
		arr[:, :, 2] = z_offset
		arr[:, :, :2] = arr[:, :, :2] - np.mean(arr[:, :, :2], axis=1).reshape(arr.shape[0], -1, 2)
		return tf.constant(arr)


import numpy as np
import matplotlib.pyplot as plt
from tictoc import *
from plot import plot
#from RFA import secant

def secant(f,x0,x1,TOL,NMAX):
    #INPUT : f , x0 , x1 , TOL , NMAX
    # f : function / polynomial
    # x0 : first initial point/guess
    # x1 : second initial point/guess
    # TOL : tolerance value
    # NMAX : maximum number of iterations

    #Approximates root using Secant Method

    #Print Header
    print('--------------------------------------------------------------------------')
    print("iter \t\t\t x2 \t\t\t\t f(x2)")
    print('--------------------------------------------------------------------------')

    for i in range(NMAX):
        if f(x0) == f(x1):
            print('DIVIDE BY ZERO ENCOUNTERED!')
            exit()
        #improved guess
        x2 = x0 - (x1-x0)*f(x0)/(f(x1)-f(x0))
        N.append(1+i)
        f_x.append(f(x2))
        #print values after each iteration step
        print("%d \t\t %15.12f \t\t %15.12f" %(1+i,x2,f(x2)))
        x0 = x1
        x1 = x2

        #executing the required precision
        if abs(f(x2)) < TOL:
            print('--------------------------------------------------------------------------')
            print('Root Found : ',x2)
            break
    #limiting number of iterations
    if i == NMAX -1:
        print("MAX NUMBER OF ITERATIONS REACHED!")
        print('Approximaiton to the Root after max iterations is : '+str(c))
        exit()

f1 = lambda x: x**3 - 3*(x**2) - x + 9
f2 = lambda x: np.exp(x)*(x**3 - 3*(x**2) - x + 9)
x0 = -2
x1 = 5

N=[]
f_x=[]
tic()
secant(f1,x0,x1,1.e-10,1000)
toc()
plot(N,f_x,'b',1)

N=[]
f_x=[]
tic()
secant(f1,x0-0.5,x1+0.5,1.e-10,1000)
toc()
plot(N,f_x,'r',1)
plt.legend(['(x0,x1)=(-2,2)','(x0,x1)=(-2.5,5.5)'])
plt.show()

N=[]
f_x=[]
tic()
secant(f2,x0,x1,1.e-10,1000)
toc()
plot(N,f_x,'b',2)

N=[]
f_x=[]
tic()
secant(f2,x0-0.5,x1+0.5,1.e-10,1000)
toc()
plot(N,f_x,'r',2)
plt.legend(['(x0,x1)=(-2,2)','(x0,x1)=(-2.5,5.5)'])
plt.show()