# GoldBach Conjecture
import math

from sympy import Range

MAX = 10 ** 5
primes: list[int] = []

def sieveSundaram() -> None:
    marked = [False] * (int(MAX / 2) + 100)
    for i in Range(1, int((math.sqrt(MAX) - 1) / 2) + 1):
        for j in range((i * (i + 1)) << 1, int(MAX / 2) + 1, 2 * i + 1):
            marked[j] = True

    primes.append(2)

    for i in Range(1, int(MAX / 2) + 1):
        if marked[i] is False:
            primes.append(2 * i + 1)

def findPrimes(n: int) -> None:
    if n <= 2 or n % 2 != 0:
        print("Even Number")
        return
    i: int = 0
    while primes[i] <= n // 2:
        diff = n - primes[i]
        if diff in primes:
            print(primes[i], "+", diff, "=", n)
            return
        i += 1

if __name__ == "__main__":
    sieveSundaram()
    findPrimes(int(input("Enter a number: ")))


import random,math
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

measurements=np.array([4.72, 9.11, 5.57, 7.66, 4.69, 4.86, 6.51, 6.65, 4.77, 0.26, 4.67, 6.37, 5.05, 5.91, 5.54, 2.13, 6.77, 3.84, 4.0, 6.18, 3.54, 5.52, 3.29, 4.62, 4.25, 4.08, 3.37, 4.91, 5.24, 6.85, 3.85, 5.11, 9.41, 5.78, 5.97, 5.87, 1.59, 4.51, 0.72, 6.72, 8.4, 3.94, 8.53, 2.76, 2.62, 6.11, 3.37, 4.01, 7.17, 3.05])
N=10000
def bootstrap(Y,size,mode):

    Ystar = np.empty(size)

    # Bootstraping
    for i in range(size):
        samples = np.random.choice(Y,size=len(Y))
        if mode == 'mean':
            Ystar[i] = np.mean(samples)
        if mode == 'std':
            Ystar[i] = np.std(samples,ddof=1)

    return Ystar

if True:
	bootstrap_sample = bootstrap(measurements,N,'mean')

	CI_95 = np.percentile(bootstrap_sample,[2.5,97.5])

	bins = np.linspace(4, 6, 11)
	# Bootstrap PDF Plot
	plt.hist(bootstrap_sample,density=True,color='green',bins=np.arange(min(measurements), max(measurements) + .1, .1))

	# 95% confidence interval
	plt.axvline(x=np.percentile(bootstrap_sample,[2.5]), ymin=0, ymax=1,label='2.5th percentile',color='black',linestyle='dashed')
	plt.axvline(x=np.percentile(bootstrap_sample,[97.5]), ymin=0, ymax=1,label='97.5th percentile',color='red',linestyle='dashed')
	plt.axvline(x=np.percentile(bootstrap_sample,[50]), ymin=0, ymax=1,label='mean',color='yellow',linestyle='dashed')

	plt.xlabel("$\overline {X}$")
	plt.ylabel("Frequency")
	plt.title(r'Bootstrap : $\overline {X}$'+"= {0:.3f} ; 95 % C.I = [{1:.3f}, {2:.3f}]".format(np.mean(bootstrap_sample),CI_95[0],CI_95[1]))
	plt.legend()
	plt.grid()
	plt.xlim([3.8, 6.2])
	plt.show()

if True:
	bootstrap_sample = bootstrap(measurements,N,'std')

	CI_95 = np.percentile(bootstrap_sample,[2.5,97.5])

	bins = np.linspace(4, 6, 11)
	# Bootstrap PDF Plot
	plt.hist(bootstrap_sample,density=True,color='green',bins=np.arange(min(measurements), max(measurements) + .1, .1))

	# 95% confidence interval
	plt.axvline(x=np.percentile(bootstrap_sample,[2.5]), ymin=0, ymax=1,label='2.5th percentile',color='black',linestyle='dashed')
	plt.axvline(x=np.percentile(bootstrap_sample,[97.5]), ymin=0, ymax=1,label='97.5th percentile',color='red',linestyle='dashed')
	plt.axvline(x=np.percentile(bootstrap_sample,[50]), ymin=0, ymax=1,label='std',color='yellow',linestyle='dashed')

	plt.xlabel("$\overline {\sigma}$")
	plt.ylabel("Frequency")
	plt.title(r'Bootstrap : $\overline {\sigma}$'+"= {0:.3f} ; 95 % C.I = [{1:.3f}, {2:.3f}]".format(np.mean(bootstrap_sample),CI_95[0],CI_95[1]))
	plt.legend()
	plt.grid()
	plt.xlim([1, 2.7])
	plt.show()

if True:
	# Bayesian 1:
	mean, var, std = stats.bayes_mvs(measurements)
	print(mean,'\n',std)

	# Bayesian Model 2:

	import pymc3 as pm

	model = pm.Model()

	if __name__ == '__main__':

	    with model:

		    # Prior
	        mu = pm.Normal('mu',4)
	        sigma = pm.Normal('sigma', 1.5)

		    # Likelihood function.
	        y_obs = pm.Normal('y_obs', mu=mu,sd=sigma, observed=measurements)

		    # Try 330 draws and 3 chains.
	        trace = pm.sample(draws=330, chains=3)

	    pm.traceplot(trace)
	    pm.plot_posterior(trace)

	plt.show()


import numpy as np

arr = np.array([1.1, 1.4, 1.5, 1.6, 1.9, -1.1, -1.4, -1.5, -1.6, -1.9])

# 1.
print(np.trunc(arr))

# 2.
print(np.fix(arr))

# 3.
print(np.around(arr))

# 4.
print(np.floor(arr))

# 5.
print(np.ceil(arr))


"""
    [summary] Chapter 2.
    > poetry run python src/chap2.py
"""

import numpy as np

def AND_nobias(x1: int, x2: int) -> int:
    """
    [summary] AND operator wituout bias term

    Args:
        x1 (int): 1st input for AND
        x2 (int): 2nd input for AND

    Returns:
        int: result of AND operator
    """

    (w1, w2, theta) = (0.5, 0.5, 0.7)
    tmp = w1 * x1 + w2 * x2
    if tmp <= theta:
        return 0
    else:
        return 1

def AND(x1: int, x2: int) -> int:
    """
    [summary] AND operator with bias term

    Args:
        x1 (int): 1st input for AND
        x2 (int): 2nd input for AND

    Returns:
        int: result of AND operator
    """

    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    # w1 * x1 + w2 * x2 + b is condition
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

def NAND(x1: int, x2: int) -> int:
    """
    [summary] NAND Operator

    Args:
        x1 (int): 1st input
        x2 (int): 2md input

    Returns:
        int: output of NAND
    """

    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

def OR(x1: int, x2: int) -> int:
    """
    [summary] OR Operator

    Args:
        x1 (int): 1st input
        x2 (int): 2nd input

    Returns:
        int: output of OR operator
    """
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.1
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

def XOR(x1: int, x2: int) -> int:
    """
    [summary] XOR operator

    Args:
        x1 (int): 1st input
        x2 (int): 2nd input

    Returns:
        int: output of XOR
    """
    return AND(NAND(x1, x2), OR(x1, x2))

if __name__ == "__main__":
    # check AND operator
    assert AND(0, 0) == 0
    assert AND(0, 1) == 0
    assert AND(1, 0) == 0
    assert AND(1, 1) == 1

    # check NAND operator
    assert NAND(0, 0) == 1
    assert NAND(0, 1) == 1
    assert NAND(1, 0) == 1
    assert NAND(1, 1) == 0

    # check OR operator
    assert OR(0, 0) == 0
    assert OR(0, 1) == 1
    assert OR(1, 0) == 1
    assert OR(1, 1) == 1

    # check XOR operator
    assert XOR(0, 0) == 0
    assert XOR(0, 1) == 1
    assert XOR(1, 0) == 1
    assert XOR(1, 1) == 0


import tensorflow as tf
import numpy as np
from tensorflow import keras

'''
    Say hello to the "Hello, World" of machine learning

Consider the following sets of numbers. Can you see the relationship between them ?
X: |-1 |0 |1 |2 |3 |4
Y: |-2 |1 |4 |7 |10|13

The relationship is:  Y = 3X + 1
'''
# Define and compile the neural network
model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])
# Compiled the model
model.compile(optimizer='sgd', loss='mean_squared_error')

'''
optimizer:
    Next, the model uses the optimizer function to make another guess.
    Based on the loss function's result, it tries to minimize the loss.
    At this point, maybe it will come up with something like Y=5X+5.
    While that's still pretty bad, it's closer to the correct result
    (the loss is lower).

loss:
    When the computer is trying to learn that, it makes
    a guess, maybe Y=10X+10. The loss function measures
    the guessed answers against the known correct answers
    and measures how well or badly it did.
'''

# Provide the data, with the relationship is => "Y = 3X + 1"
xs = np.array([-1.0, 0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-2.0, 1.0, 4.0, 7.0, 10.0, 13.0], dtype=float)

# Training the neural network
model.fit(xs, ys, epochs=668)

'''
You have a model that has been trained to learn the relationship between X and Y.
You can use the model.predict method to have it figure out the Y for a previously unknown X.
For example, if X is 10, what do you think Y will be?
'''

print("\n\nPredict: ",model.predict([11.0]))

'''
You might have thought 31, but it ended up being a little over. Why do you think that is?
Neural networks deal with probabilities, so it calculated that there is a very high probability that the relationship between X and Y is Y=3X+1,
but it can't know for sure with only six data points. The result is very close to 31, but not necessarily 31.
As you work with neural networks, you'll see that pattern recurring. You will almost always deal with probabilities
not certainties, and will do a little bit of coding to figure out what the result is based on the probabilities, particularly when it comes to classification.
'''


import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(style='whitegrid', context='notebook')

def least_squares(b0, b1, points):
    total_error = 0
    n = float(len(points))
    for x, y in points:
        total_error += (y - (b1 * x + b0)) ** 2
    return total_error / 2. * n

def step_gradient(b0_current, b1_current, points, learningRate):
    b0_gradient = 0
    b1_gradient = 1
    n = float(len(points))
    for x, y in points:
        b0_gradient += (1 / n) * (y - ((b1_current * x) + b0_current))
        b1_gradient += (1 / n) * x * (y - ((b1_current * x) + b0_current))
    new_b0 = b0_current + (learningRate * b0_gradient)
    new_b1 = b1_current + (learningRate * b1_gradient)
    return [new_b0, new_b1, least_squares(new_b0, new_b1, points)]

def run_gradient_descent(points, starting_b0, starting_b1, learning_rate, num_iterations):
    b0 = starting_b0
    b1 = starting_b1
    slope = []
    intersect = []
    error = []
    for i in range(num_iterations):
        b0, b1, e = step_gradient(b0, b1, np.array(points), learning_rate)
        slope.append(b1)
        intersect.append(b0)
        error.append(e)
    return [b0, b1, e, slope, intersect, error]

# This process could prove inefficient when the convergence rate is high, wasting CPU iterations.
# A more clever stop condition would consist of adding an acceptable error value, which would stop the iteration.

iris = sns.load_dataset('iris')
X = iris['petal_width'].tolist()
Y = iris['petal_length'].tolist()
points = np.dstack((X, Y))[0]

learning_rate = 0.0001
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate,
                                                            num_iterations)
plt.figure(figsize=(7, 5))
plt.scatter(X, Y)
xr = np.arange(0, 3.5)
plt.plot(xr, (xr * b1) + b0)
plt.title('Regression, alpha= 0.001, initial values=(0,0), it=1000')
plt.show()
# We are far from what we are looking for, so take a look at errors

plt.figure(figsize=(7, 5))
xr = np.arange(0, 1000)
plt.plot(xr, np.array(error).transpose())
plt.title("Error for 1000 iterations")
plt.show()
# the process seems to be working, but a little slow, we can try to increase the step by a factor of 10 to see if
# it converges quickly

learning_rate = 0.001  # This is the updated value
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate,
                                                            num_iterations)
plt.figure(figsize=(7, 5))
xr = np.arange(0, 1000)
plt.plot(xr, np.array(error).transpose())
plt.title("Error for 1000 iterations, increased step by tenfold")
plt.show()
# This is more alike to what we are looking

plt.figure(figsize=(7, 5))
plt.scatter(X, Y)
xr = np.arange(0, 3.5)
plt.plot(xr, (xr * b1) + b0)
plt.title("Regression, alpha= 0.01, initial values = (0,0), it=1000")
plt.show()

# To go faster we my try this:
# but this end up to a bad move, due to error now tends to infinity
learning_rate = 0.85  # last one was 0.0001
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate,
                                                            num_iterations)
plt.figure(figsize=(7, 5))
xr = np.arange(0, 1000)
plt.plot(xr, np.array(error).transpose())
plt.title('Error for 1000 iterations, big step')

# No trying with semi-random initial parameters
learning_rate = 0.001  # Same as last time
initial_b0 = 0.8  # pseudo random value
initial_b1 = 1.5  # pseudo random value
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate,
                                                            num_iterations)
plt.figure(figsize=(7, 5))
xr = np.arange(0, 1000)
plt.plot(xr, np.array(error).transpose())
plt.title('Error for 1000 iterations, step 0.001, random initial parameter values')
plt.show()
# As you can see, even if you have the same sloppy error rate, the initial error value decreases tenfold
# (from 2e5 to 2e4). Now let's try a final technique to improve the convergence of the parameters based on the
# normalization of the input values.

learning_rate = 0.001  # Same as last time
initial_b0 = 0.8  # pseudo random value
initial_b1 = 1.5  # pseudo random value
num_iterations = 1000
x_mean = np.mean(points[:, 0])
y_mean = np.mean(points[:, 1])
x_std = np.std(points[:, 0])
y_std = np.std(points[:, 1])

X_normalized = (points[:, 0] - x_mean) / x_std
Y_normalized = (points[:, 1] - y_mean) / y_std

plt.figure(figsize=(7, 5))
plt.scatter(X_normalized, Y_normalized)
plt.show()
# Now that we have this set of clean and tidy data, let's try again with the last slow convergence parameters,
# and see what happens to the error minimization speed:

points = np.dstack((X_normalized, Y_normalized))[0]
learning_rate = 0.001  # Same as last time
initial_b0 = 0.8  # pseudo random value
initial_b1 = 1.5  # pseudo random value
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate,
                                                            num_iterations)
plt.figure(figsize=(7, 5))
xr = np.arange(0, 1000)
plt.plot(xr, np.array(error).transpose())
plt.title('Error for 1000 iterations, step 0.001, random initial parameter values, normalized initial values')
plt.show()
# A very good starting point indeed! Just by normalizing the data, we have half the initial error values, and the error
# went down 20% after 1,000 iterations. The only thing we have to remember is to denormalize after we have the results,
# in order to have the initial scale and data center. So, that's all for now on gradient descent. We will be revisiting
# it in the next chapters for new challenges.


# used for manipulating directory paths
import os

# Scientific and vector computation for python
import numpy as np

# Plotting library
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D  # needed to plot 3-D surfaces

def featureNormalize(X):
    """
    Normalizes the features in X. returns a normalized version of X where
    the mean value of each feature is 0 and the standard deviation
    is 1. This is often a good preprocessing step to do when working with
    learning algorithms.

    Parameters
    ----------
    X : array_like
        The dataset of shape (m x n).

    Returns
    -------
    X_norm : array_like
        The normalized dataset of shape (m x n).

    Instructions
    ------------
    First, for each feature dimension, compute the mean of the feature
    and subtract it from the dataset, storing the mean value in mu.
    Next, compute the  standard deviation of each feature and divide
    each feature by it's standard deviation, storing the standard deviation
    in sigma.

    Note that X is a matrix where each column is a feature and each row is
    an example. You needto perform the normalization separately for each feature.

    Hint
    ----
    You might find the 'np.mean' and 'np.std' functions useful.
    """
    # You need to set these values correctly
    X_norm = X.copy()
    mu = np.zeros(X.shape[1])
    sigma = np.zeros(X.shape[1])

    mu = np.mean(X_norm, axis=0)
    X_norm = X_norm - mu

    sigma = np.std(X, axis=0)

    X_norm = X_norm/sigma

    return X_norm, mu, sigma

if __name__ == '__main__':
    data = np.loadtxt(os.path.join("/home/thelichking/Desktop/ml-coursera-python-assignments/Exercise1/Data",
                                   "ex1data2.txt"), delimiter=',')
    X = data[:, :3]  # the first two rows
    y = data[:, 2]  # the actual 3rd row
    m = y.size

    X_norm, mu, sigma = featureNormalize(X)
    print(mu, sigma)


"""
Filename: log_linear_growth_model.py
Authors: John Stachurski, Thomas Sargent

The log linear growth model, wrapped as classes.  For use with the
optgrowth.py module.
"""

import numpy as np
import matplotlib.pyplot as plt
from optgrowth import bellman_operator
from quantecon import compute_fixed_point
from joblib import Memory

memory = Memory(cachedir='./joblib_cache')

@memory.cache
def compute_value_function_cached(grid, beta, alpha, shocks):
    """
    Compute the value function by iterating on the Bellman operator.
    The work is done by QuantEcon's compute_fixed_point function.
    """
    Tw = np.empty(len(grid))
    initial_w = 5 * np.log(grid) - 25

    v_star = compute_fixed_point(bellman_operator,
            initial_w,
            1e-4,  # error_tol
            100,   # max_iter
            True,  # verbose
            5,     # print_skip
            grid,
            beta,
            np.log,
            lambda k: k**alpha,
            shocks,
            Tw=Tw,
            compute_policy=False)
    return v_star

class LogLinearGrowthModel:
    """
    Stores parameters and computes solutions for the basic log utility / Cobb
    Douglas production growth model.  Shocks are lognormal.
    """
    def __init__(self,
            alpha=0.65,         # Productivity parameter
            beta=0.95,          # Discount factor
            mu=1,               # First parameter in lognorm(mu, sigma)
            sigma=0.1,          # Second parameter in lognorm(mu, sigma)
            grid_max=8,
            grid_size=150):

        self.alpha, self.beta, self.mu, self.sigma = alpha, beta, mu, sigma
        self.grid = np.linspace(1e-6, grid_max, grid_size)
        self.shocks = np.exp(mu + sigma * np.random.randn(250))

    def compute_value_function(self, show_plot=False):
        """
        Calls compute_value_function_cached and optionally adds a plot.
        """
        v_star = compute_value_function_cached(self.grid,
                                                   self.beta,
                                                   self.alpha,
                                                   self.shocks)
        if show_plot:
            fig, ax = plt.subplots()
            ax.plot(self.grid, v_star, lw=2, alpha=0.6, label='value function')
            ax.legend(loc='lower right')
            plt.show()

        return v_star

    def compute_greedy(self, w=None, show_plot=False):
        """
        Compute the w-greedy policy on the grid points given w
        (the value of the input function on grid points).  If w is not
        supplied, use the approximate optimal value function.
        """
        if w is None:
            w = self.compute_value_function()

        Tw, sigma = bellman_operator(w,
                                    self.grid,
                                    self.beta,
                                    np.log,
                                    lambda k: k**self.alpha,
                                    self.shocks,
                                    compute_policy=True)

        if show_plot:
            fig, ax = plt.subplots()
            ax.plot(self.grid, sigma, lw=2, alpha=0.6, label='approximate policy function')
            cstar = (1 - self.alpha * self.beta) * self.grid
            ax.plot(self.grid, cstar, lw=2, alpha=0.6, label='true policy function')
            ax.legend(loc='upper left')
            plt.show()

        return sigma


import numpy as np
import matplotlib.pyplot as plt

# y' = u
# u' = -y

"""
y'' + y = 0

y(0) = 0 and y'(0) = 1/pi
"""

def F(y, u, x):
    return -y

a = 0
b = 2 * np.pi
N = 100
h = (b - a) / N

xpoints = np.arange(a, b, h)
ypoints = []
upoints = []

y = 0.0
u = 1.0 / np.pi

for x in xpoints:
    ypoints.append(y)
    upoints.append(u)

    m1 = h * u
    k1 = h * F(y, u, x)  # (x, v, t)

    m2 = h * (u + 0.5 * k1)
    k2 = h * F(y + 0.5 * m1, u + 0.5 * k1, x + 0.5 * h)

    m3 = h * (u + 0.5 * k2)
    k3 = h * F(y + 0.5 * m2, u + 0.5 * k2, x + 0.5 * h)

    m4 = h * (u + k3)
    k4 = h * F(y + m3, u + k3, x + h)

    y += (m1 + 2 * m2 + 2 * m3 + m4) / 6
    u += (k1 + 2 * k2 + 2 * k3 + k4) / 6

plt.plot(xpoints, ypoints)
# plt.xlim([0,4])
# plt.xlim([0,4])
plt.show()


import numpy as np
import scipy, scipy.signal

def savitzky_golay( y, window_size, order, deriv = 0 ):
    r"""Smooth (and optionally differentiate) data with a Savitzky-Golay filter.
    The Savitzky-Golay filter removes high frequency noise from data.
    It has the advantage of preserving the original shape and
    features of the signal better than other types of filtering
    approaches, such as moving averages techhniques.
    Parameters
    ----------
    y : array_like, shape (N,)
        the values of the time history of the signal.
    window_size : int
        the length of the window. Must be an odd integer number.
    order : int
        the order of the polynomial used in the filtering.
        Must be less then `window_size` - 1.
    deriv: int
        the order of the derivative to compute (default = 0 means only smoothing)
    Returns
    -------
    ys : ndarray, shape (N)
        the smoothed signal (or it's n-th derivative).
    Notes
    -----
    The Savitzky-Golay is a type of low-pass filter, particularly
    suited for smoothing noisy data. The main idea behind this
    approach is to make for each point a least-square fit with a
    polynomial of high order over a odd-sized window centered at
    the point.
    Examples
    --------
    t = np.linspace(-4, 4, 500)
    y = np.exp( -t**2 ) + np.random.normal(0, 0.05, t.shape)
    ysg = savitzky_golay(y, window_size=31, order=4)
    import matplotlib.pyplot as plt
    plt.plot(t, y, label='Noisy signal')
    plt.plot(t, np.exp(-t**2), 'k', lw=1.5, label='Original signal')
    plt.plot(t, ysg, 'r', label='Filtered signal')
    plt.legend()
    plt.show()
    References
    ----------
    .. [1] A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of
       Data by Simplified Least Squares Procedures. Analytical
       Chemistry, 1964, 36 (8), pp 1627-1639.
    .. [2] Numerical Recipes 3rd Edition: The Art of Scientific Computing
       W.H. Press, S.A. Teukolsky, W.T. Vetterling, B.P. Flannery
       Cambridge University Press ISBN-13: 9780521880688
    """
    try:
        window_size = np.abs( np.int( window_size ) )
        order = np.abs( np.int( order ) )
    except ValueError, msg:
        raise ValueError( "window_size and order have to be of type int" )
    if window_size % 2 != 1 or window_size < 1:
        raise TypeError( "window_size size must be a positive odd number" )
    if window_size < order + 2:
        raise TypeError( "window_size is too small for the polynomials order" )
    order_range = range( order + 1 )
    half_window = ( window_size - 1 ) // 2
    # precompute coefficients
    b = np.mat( [[k ** i for i in order_range] for k in range( -half_window, half_window + 1 )] )
    m = np.linalg.pinv( b ).A[deriv]
    # pad the signal at the extremes with
    # values taken from the signal itself
    firstvals = y[0] - np.abs( y[1:half_window + 1][::-1] - y[0] )
    lastvals = y[-1] + np.abs( y[-half_window - 1:-1][::-1] - y[-1] )
    y = np.concatenate( ( firstvals, y, lastvals ) )
    return np.convolve( m, y, mode = 'valid' )

def savitzky_golay_piecewise( xvals, data, kernel = 11, order = 4 ):
    turnpoint = 0
    last = len( xvals )
    if xvals[1] > xvals[0] : #x is increasing?
        for i in range( 1, last ) : #yes
            if xvals[i] < xvals[i - 1] : #search where x starts to fall
                turnpoint = i
                break
    else: #no, x is decreasing
        for i in range( 1, last ) : #search where it starts to rise
            if xvals[i] > xvals[i - 1] :
                turnpoint = i
                break
    if turnpoint == 0 : #no change in direction of x
        return savitzky_golay( data, kernel, order )
    else:
        #smooth the first piece
        firstpart = savitzky_golay( data[0:turnpoint], kernel, order )
        #recursively smooth the rest
        rest = savitzky_golay_piecewise( xvals[turnpoint:], data[turnpoint:], kernel, order )
        return numpy.concatenate( ( firstpart, rest ) )

def sgolay2d ( z, window_size, order, derivative = None ):
    """
    """
    # number of terms in the polynomial expression
    n_terms = ( order + 1 ) * ( order + 2 ) / 2.0

    if  window_size % 2 == 0:
        raise ValueError( 'window_size must be odd' )

    if window_size ** 2 < n_terms:
        raise ValueError( 'order is too high for the window size' )

    half_size = window_size // 2

    # exponents of the polynomial.
    # p(x,y) = a0 + a1*x + a2*y + a3*x^2 + a4*y^2 + a5*x*y + ...
    # this line gives a list of two item tuple. Each tuple contains
    # the exponents of the k-th term. First element of tuple is for x
    # second element for y.
    # Ex. exps = [(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), ...]
    exps = [ ( k - n, n ) for k in range( order + 1 ) for n in range( k + 1 ) ]

    # coordinates of points
    ind = np.arange( -half_size, half_size + 1, dtype = np.float64 )
    dx = np.repeat( ind, window_size )
    dy = np.tile( ind, [window_size, 1] ).reshape( window_size ** 2, )

    # build matrix of system of equation
    A = np.empty( ( window_size ** 2, len( exps ) ) )
    for i, exp in enumerate( exps ):
        A[:, i] = ( dx ** exp[0] ) * ( dy ** exp[1] )

    # pad input array with appropriate values at the four borders
    new_shape = z.shape[0] + 2 * half_size, z.shape[1] + 2 * half_size
    Z = np.zeros( ( new_shape ) )
    # top band
    band = z[0, :]
    Z[:half_size, half_size:-half_size] = band - np.abs( np.flipud( z[1:half_size + 1, :] ) - band )
    # bottom band
    band = z[-1, :]
    Z[-half_size:, half_size:-half_size] = band + np.abs( np.flipud( z[-half_size - 1:-1, :] ) - band )
    # left band
    band = np.tile( z[:, 0].reshape( -1, 1 ), [1, half_size] )
    Z[half_size:-half_size, :half_size] = band - np.abs( np.fliplr( z[:, 1:half_size + 1] ) - band )
    # right band
    band = np.tile( z[:, -1].reshape( -1, 1 ), [1, half_size] )
    Z[half_size:-half_size, -half_size:] = band + np.abs( np.fliplr( z[:, -half_size - 1:-1] ) - band )
    # central band
    Z[half_size:-half_size, half_size:-half_size] = z

    # top left corner
    band = z[0, 0]
    Z[:half_size, :half_size] = band - np.abs( np.flipud( np.fliplr( z[1:half_size + 1, 1:half_size + 1] ) ) - band )
    # bottom right corner
    band = z[-1, -1]
    Z[-half_size:, -half_size:] = band + np.abs( np.flipud( np.fliplr( z[-half_size - 1:-1, -half_size - 1:-1] ) ) - band )

    # top right corner
    band = Z[half_size, -half_size:]
    Z[:half_size, -half_size:] = band - np.abs( np.flipud( Z[half_size + 1:2 * half_size + 1, -half_size:] ) - band )
    # bottom left corner
    band = Z[-half_size:, half_size].reshape( -1, 1 )
    Z[-half_size:, :half_size] = band - np.abs( np.fliplr( Z[-half_size:, half_size + 1:2 * half_size + 1] ) - band )

    # solve system and convolve
    if derivative == None:
        m = np.linalg.pinv( A )[0].reshape( ( window_size, -1 ) )
        return scipy.signal.fftconvolve( Z, m, mode = 'valid' )
    elif derivative == 'col':
        c = np.linalg.pinv( A )[1].reshape( ( window_size, -1 ) )
        return scipy.signal.fftconvolve( Z, -c, mode = 'valid' )
    elif derivative == 'row':
        r = np.linalg.pinv( A )[2].reshape( ( window_size, -1 ) )
        return scipy.signal.fftconvolve( Z, -r, mode = 'valid' )
    elif derivative == 'both':
        c = np.linalg.pinv( A )[1].reshape( ( window_size, -1 ) )
        r = np.linalg.pinv( A )[2].reshape( ( window_size, -1 ) )
        return scipy.signal.fftconvolve( Z, -r, mode = 'valid' ), scipy.signal.fftconvolve( Z, -c, mode = 'valid' )


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

# In[1]:

import numpy as np
import matplotlib.pyplot as plt

# In[37]:

# Spline3_Coef pseudocode p.271
def Spline3_Coef(n, t, y, z):
    h = np.zeros(n-1)
    b = np.zeros(n-1)
    u = np.zeros(n-1)
    v = np.zeros(n-1)
    for i in range(0, n-1):
        h[i] = t[i+1] - t[i]
        b[i] = (y[i+1] - y[i])/h[i]
    u[1] = 2*(h[0] + h[1])
    v[1] = 6*(b[1] - b[0])
    for i in range(2, n-1):
        u[i] = 2*(h[i] + h[i-1]) - (h[i-1]**2/u[i-1])
        v[i] = 6*(b[i] - b[i-1]) - (h[i-1]*v[i-1]/u[i-1])
    z[n] = 0
    for i in range(n-2, 0, -1):
        z[i] = (v[i] - h[i]*z[i+1])/u[i]
    z[0] = 0
    return z

# In[38]:

# Spline3_Eval pseudocode p.271
def Spline3_Eval(n, t, y, z, x):
    for i in range(n-2, -1, -1):
        if x - t[i] >= 0:
            break
    h = t[i+1] - t[i]
    tmp = z[i]/2 + (x - t[i])*(z[i+1] - z[i])/(6*h)
    tmp = -(h/6)*(z[i+1] + 2*z[i]) + (y[i+1] - y[i])/h + (x - t[i])*tmp
    return y[i] + (x-t[i])*tmp

# In[39]:

X = np.zeros(20)
Y = np.zeros(20)
X = [0.0, 0.6, 1.5, 1.7, 1.9, 2.1, 2.3, 2.6, 2.8, 3.0, 3.6, 4.7, 5.2, 5.7, 5.8, 6.0, 6.4, 6.9, 7.6, 8.0]
Y = [-0.8, -0.34, 0.59, 0.59, 0.23, 0.1, 0.28, 1.03, 1.5, 1.44, 0.74, -0.82, -1.27, -0.92, -0.92, -1.04, -0.79, -0.06, 1.0, 0.0]
Z = np.zeros(21)
Spline3_Coef(20, X, Y, Z)

XS = np.arange(0, 8, 0.01)
YS = np.zeros(len(XS))
for i in range(0, len(XS)):
    YS[i] = Spline3_Eval(19, X, Y, Z, XS[i])
# 6.2 Figure 5.8

# Format plot figure
fig = plt.figure(figsize=(10, 6))
ax1 = fig.add_subplot(111)

# Add f(x) line and points used by Simpson approximation
ax1.scatter(X,Y, label = 'y = S(x)')
ax1.plot(XS,YS, label = 'y = S(x)')
#ax1.scatter(XP,PP)

# Set title of plot
plt.title('f(x) = 1/(1+x^2) from 0 to 1')

# Give x axis label
plt.xlabel('x')
plt.ylabel('f(x)')

# Format the plot
plt.legend(loc='upper right')
plt.grid(True, which='both')
plt.axhline(y=0)
plt.show()

# In[41]:

# Test_Spline3 pseudocode p.272
n = 9
a = 0
b = 1.6875
h = (b - a)/n
t = np.zeros(n+1)
y = np.zeros(n+1)
for i in range(0, n):
    t[i] = a + i*h
    y[i] = np.sin(t[i])
z = np.zeros(n+1)
Spline3_Coef(n, t, y, z)
temp = 0
for j in range(0, 4*n):
    x = a + j*h/4
    e = np.abs(np.sin(x) - Spline3_Eval(n, t, y, z, x))
    if j == 19:
        print("j =", j, "\nx =", x, "\ne =", e)
    if e > temp:
        temp = e
    #print(j)
print("j =", j, "\nx =", x, "\ne =", e)
#Book solution j = 19, x = 0.890625, and d = 0.930 x 10^-5.

# In[56]:

def f(x):
    return (x**2 + 1)**(-1)
n = 41
t = np.linspace(-5, 5, n)
y = f(t)
print(t)
print(y)
z = np.zeros(n+1)
Spline3_Coef(n, t, y, z)
XS = np.linspace(0, 5, 101)
YS = np.zeros(len(XS))
for i in range(0, len(XS)):
    YS[i] = Spline3_Eval(40, t, y, z, XS[i])

# Format plot figure
fig = plt.figure(figsize=(10, 6))
ax1 = fig.add_subplot(111)

# Add f(x) line and points used by Simpson approximation
ax1.scatter(t,y, label = 'f(x) = (x^2 + 1)^-1')
ax1.plot(XS,YS, label = 'S(x)')
#ax1.scatter(XP,PP)

# Set title of plot
plt.title('f(x) = 1/(1+x^2) from 0 to 1')

# Give x axis label
plt.xlabel('x')
plt.ylabel('f(x)')

# Format the plot
plt.legend(loc='upper right')
plt.grid(True, which='both')
plt.axhline(y=0)
plt.show()

Y = f(XS)
Dif = YS - Y
print(Dif)
print("Max difference: ", max(Dif), "\nAverage difference: ", np.average(Dif), "\nStandard Deviation: ", np.std(Dif))

# In[ ]:


import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    exp_L = np.exp(L)
    return exp_L / np.sum(exp_L)

# 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.array(Y)
    P = np.array(P)
    yp = Y * np.log(P)
    return - np.sum(yp + (1 - Y) * np.log(1 - P))


"""
This Set is related to Matrix Programs in Python, Questions from geeks of Geeks
"""
L1 = [[2 ,1, 1],
 [5, 5, 5],
 [3, 3, 8]]
from math import sqrt
import numpy as np
from numpy.core.fromnumeric import transpose
"""
1) Basic program to add two Matrices
"""
#Method 1, Using numpy
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = matrix1 + matrix2
print("First matrix: \n",matrix1)
print("Second Matrix: \n",matrix2)
print("Matrix1 + Matrix2: \n",matrix3)
#Time taken:  0.005991697311401367

#Method 2. Using Loop
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = np.zeros((3,3))
for i in range(len(matrix1)):
    for j in range(len(matrix2)):
        matrix3[i][j] = matrix1[i][j] + matrix2[i][j]
print(matrix3)
#Time taken:  0.0031545162200927734

#Method 3, Using list comprehension
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = [matrix1[i][j] + matrix2[i][j] for i in range(len(matrix1)) for j in range(len(matrix2))]
print(np.reshape(matrix3,(3,3)))
#Time taken:  0.0009968280792236328

#Method 4, using zip() and sum
# matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
# matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
# matrix3 = [map(sum,zip(*t)) for t in zip(matrix1,matrix2)]
# print(matrix3)

"""
2) Basic program to multiply two matrices
"""
#Method 1, Using numpy
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = matrix1 * matrix2
print("First matrix: \n",matrix1)
print("Second Matrix: \n",matrix2)
print("Matrix1 * Matrix2: \n",matrix3)
#Time taken:  0.005980491638183594

#Method 2. Using Loop
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = np.zeros((3,3))
for i in range(len(matrix1)):
    for j in range(len(matrix2)):
        matrix3[i][j] = matrix1[i][j] * matrix2[i][j]
print(matrix3)
#Time taken:  0.0049860477447509766

#Method 3, Using list comprehension
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = [matrix1[i][j] * matrix2[i][j] for i in range(len(matrix1)) for j in range(len(matrix2))]
print(np.reshape(matrix3,(3,3)))
#Time taken:  0.0042514801025390625

"""
3) Basic program for Matrix Product
"""
#Method 1, same thing has been done in BasicsProgramSet2 Program 8 for Lists in different ways
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
print(matrix1)
result = np.prod(matrix1)
print(result)
#Time taken:  0.003002643585205078

#Method 2, try with list comprehension and function
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
def MatrixProd(x):
    ans = 1
    for i in x:
        ans *= i
    return ans
result = MatrixProd([i for j in matrix1 for i in j])
print(result)
#Time taken:  0.005983591079711914
#Note: for both the programs product can be zero if matrix randomly chooses 0 as any elements, don't get confused

"""
4) Adding and Subtracting Matrices in Python
"""
#Method 1, This can be done same as Program 1 of this Set, for other ways look for Program 1
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = matrix1 + matrix2
matrix4 = matrix1 - matrix2
print("First matrix: \n",matrix1)
print("Second Matrix: \n",matrix2)
print("Matrix1 + Matrix2: \n",matrix3)
print("Matrix1 - Matrix2: \n",matrix4)
#Time taken:  0.00698089599609375

#Method 2, using add() and subtract() of numpy
matrix1 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix2 = np.random.randint(9, size=(3,3)) #Matrix can be given directly too I have created randomly
matrix3 = np.add(matrix1,matrix2)
matrix4 = np.subtract(matrix1,matrix2)
print(matrix3)
print(matrix4)
#Time taken:  0.002694845199584961

"""
5) Basic Transpose a matrix in Single line in Python
"""
#Method 1 , using transpose from numpy
transpose1 = transpose(L1)
print(transpose1)
#Time taken:  0.003000020980834961

#Method 2, list comprehension
transpose2 = [[L1[i][j] for i in range(len(L1))] for j in range(len(L1[0]))]
for i in transpose2:
    print(i)
#Time taken:  0.0030078887939453125

#Method 3, Using Zip
transpose3 = zip(*L1)
for i in transpose3:
    print(i)
#Time taken:  0.0019838809967041016

"""
6) Basic Matrix creation of n*n
"""
n = int(input("Enter value of N(Must be perfefct square): "))
matrix1 = [list(range(1 + n*i, 1+n*(i+1))) for i in range(n)]
print(matrix1)
#Time taken:  0.6289224624633789

"""
7) Basic program to Get Kth Column of Matrix for n*n matrix
"""
#Method 1, List Comprehension
kth = int(input("Give Column number to select Options(0,1,2): "))
lst = [i[kth] for i in L1]
print(lst)

#Method 2, using zip
kth = int(input("Give Column number to select Options(0,1,2): "))
#In Python 2, zip returned a list. In Python 3, zip returns an iterable object.
#If we subscript zip then TypeError: 'zip' object is not subscriptable will come
#So, [kth] is used after creating list
lst = list(zip(*L1))[kth]
print(lst)

"""
8) Basic program to Vertical Concatenation in Matrix
"""
j1 = [['anant', 'good'], ['arun', 'great']]

#Method 1 , using zip and list comprehension
j3 = [x + y for x,y in zip(*j1)]
print(j3)
#Time taken:  0.0009992122650146484


import numpy as np
from numpy.core.numeric import ones
from numpy.lib.function_base import append
from numpy.linalg import inv
from sklearn.datasets import load_boston as load
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import scale
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error

class LeastSquares:

    def __init__(self, fit_intercept=True):
        self.__fit_intercept = fit_intercept
        self.__weights = []

    def __add_intercept(self, data):
        intercept = ones((len(data), 1))
        return append(intercept, data, axis=1)

    def fit(self, x_train, y_train):
        if self.__fit_intercept:
            X = self.__add_intercept(x_train)
            self.__weights = inv(X.T @ X) @ X.T @ y_train
        else:
            self.__weights = inv(x_train.T @ x_train) @ x_train.T @ y_train

    def predict(self, x_test):
        if self.__fit_intercept:
            X = self.__add_intercept(x_test)
            predictions = X @ self.__weights
            return predictions
        return x_test @ self.__weights

    def mse(self, y_test, predictions):
        return ((y_test - predictions) ** 2).mean()

    def mae(self, y_test, predictions):
        return abs(y_test - predictions).mean()

x_train, x_test, y_train, y_test = train_test_split(scale(load().data), load().target, test_size=0.1, shuffle=True)

model = LeastSquares()
model.fit(x_train, y_train)
predictions = model.predict(x_test)
print("My LinearRegression MAE:", model.mae(y_test, predictions))

# scikit-learn way->
reg = LinearRegression(fit_intercept=True, normalize=False)
reg.fit(x_train, y_train)
predictions = reg.predict(x_test)
print("Scikit LinearRegression MAE:", mean_absolute_error(y_test, predictions))


import numpy as np
import pandas as pd
import scipy.stats as spss
import plotly.express as px
import seaborn as sns

'''
Central Limit Theorem

The central limit theorem states that the sampling distribution of the mean of any independent, random variable will be normal or nearly normal, if the sample size is large enough.

How large is "large enough"? The answer depends on two factors.

    Requirements for accuracy. The more closely the sampling distribution needs to resemble a normal distribution, the more sample points will be required.
    The shape of the underlying population. The more closely the original population resembles a normal distribution, the fewer sample points will be required.

In practice, some statisticians say that a sample size of 30 is large enough when the population distribution is roughly bell-shaped. Others recommend a sample size of at least 40. But if the original population is distinctly not normal (e.g., is badly skewed, has multiple peaks, and/or has outliers), researchers like the sample size to be even larger.
'''

'''
Continuous Distributions:
- Uniform distribution
- Normal Distribution, also known as Gaussian distribution
- Standard Normal Distribution - case of normal distribution where loc or mean = 0 and scale or sd = 1
- Gamma distribution - exponential, chi-squared, erlang distributions are special cases of the gamma distribution
- Erlang distribution - special form of Gamma distribution when a is an integer ?
- Exponential distribution - special form of Gamma distribution with a=1
- Lognormal - not covered
- Chi-Squared - not covered
- Weibull - not covered
- t Distribution - not covered
- F Distribution - not covered

Discrete Distributions:
- Poisson distribution is a limiting case of a binomial distribution under the following conditions: n tends to infinity, p tends to zero and np is finite
- Binomial Distribution
- Negative Binomial - not covered
- Bernoulli Distribution is a special case of the binomial distribution where a single trial is conducted n=1
- Geometric - not covered
'''

# uniform distribution
# generate an array of random variables using scipy
# size specifies number of random variates, loc corresponds to mean, scale corresponds to standard deviation
rv_array = spss.uniform.rvs(size=10000, loc = 10, scale=20)

# we can directly plot the data from the array
px.histogram(rv_array) # plotted using plotly express
sns.histplot(rv_array, kde=True) # plotted using seaborn

# or we can convert array into a dataframe and then plot the data frame
rv_df = pd.DataFrame(rv_array, columns=['value_of_random_variable'])
px.histogram(rv_df, x='value_of_random_variable', nbins=20) # plotted using plotly express
sns.histplot(data=rv_df, x='value_of_random_variable', kde=True) # plotted using seaborn

# Normal Distribution, also known as Gaussian distribution
# normal distribution has a bell-shaped density curve described by its mean and standard deviation.
# The density curve is symmetrical, centered about its mean, with its spread determined by its standard deviation
# showing that data near the mean are more frequent in occurrence than data far from the mean.
'''
Normal distribution is a limiting case of Poisson distribution with the parameter lambda tends to infinity

Since normal distribution is a limiting case of Poisson distribution, it is also
another limiting form of binomial distribution under the following conditions:
- The number of trials is indefinitely large, n tends to infinity
- Both p and q are not indefinitely small.
'''
rv_array = spss.norm.rvs(size=10000,loc=10,scale=100)  # size specifies number of random variates, loc corresponds to mean, scale corresponds to standard deviation
sns.histplot(rv_array, kde=True)

# we can add x and y labels, change the number of bins, color of bars, etc.
# with distplot we can supply additional arguments for adjusting width of bars, transparency, etc.
ax = sns.distplot(rv_array, bins=100, kde=True, color='cornflowerblue', hist_kws={"linewidth": 15,'alpha':1})
ax.set(xlabel='Normal Distribution', ylabel='Frequency')

# Standard Normal Distribution
# Is a special case of the normal distribution where mean = 0 and sd = 1
# z-score (aka, a standard score) indicates how many standard deviations an element is from the mean
# z = ( value of element - population mean ) / population sd
# if z score = 0 then the element = mean, if < 0 then element < mean and if > 0 then element is > mean
rv_array = spss.norm.rvs(size=10000,loc=0,scale=1)  # case of normal distribution where loc or mean = 0 and scale or sd = 1
sns.histplot(rv_array, kde=True)

# Gamma distribution
# gamma distribution is a two-parameter family of continuous probability distributions
# it is used rarely in its raw form
# exponential, chi-squared, erlang distributions are special cases of the gamma distribution
rv_array = spss.gamma.rvs(a=5, size=10000) # size specifies number of random variates, a is the shape parameter
sns.distplot(rv_array, kde=True)

# Erlang distribution - special form of Gamma distribution when a is an integer
# Exponential distribution - special form of Gamma distribution with a=1

# Exponential distribution
# Exponential distribution - special form of Gamma distribution with a=1
# exponential distribution describes the time between events in a Poisson point process,
# i.e., a process in which events occur continuously and independently at a constant average rate
rv_array = spss.expon.rvs(scale=1,loc=0,size=1000) # size specifies number of random variates, loc corresponds to mean, scale corresponds to standard deviation
sns.distplot(rv_array, kde=True)

# Poisson Distribution
'''
Poisson random variable is typically used to model the number of times an event happened in a time interval.
For example, the number of users visited on a website in an interval can be thought of a Poisson process.
Poisson distribution is described in terms of the rate (μ) at which the events happen.
An event can occur 0, 1, 2, ... times in an interval.
The average number of events in an interval is designated λ (lambda). Lambda is the event rate, also called the rate parameter.
The probability of observing k events in an interval is given by the equation:
P(k events in interval) = e^(-lambda) * (lambda^k / k!)
'''
'''
poisson distribution is a limiting case of a binomial distribution under the following conditions:
- The number of trials is indefinitely large or n tends to infinity
- The probability of success for each trial is same and indefinitely small or p tends to zero
- np = lambda, is finite.
'''
rv_array = spss.poisson.rvs(mu=3, size=10000) # size specifies number of random variates, loc corresponds to mean, scale corresponds to standard deviation
sns.distplot(rv_array, kde=False)

# Binomial Distribution
# distribution where only two outcomes are possible, such as success or failure, gain or loss, win or lose
# and where the probability of success and failure is same for all the trials.
# The outcomes need not be equally likely, and each trial is independent of each other.
'''
f(k;n,p) = nCk * (p^k) * ((1-p)^(n-k))
nCk = (n)! / ((k)! * (n-k)!)
n=total number of trials
p=probability of success in each trial
'''
rv_array = spss.binom.rvs(n=10,p=0.8,size=10000) # n = number of trials, p = probability of success, size = number of times to repeat the trials
sns.distplot(rv_array, kde=False)

# Bernoulli Distribution is a special case of the binomial distribution where a single trial is conducted n=1
'''
Bernoulli distribution has only two possible outcomes, 1 (success) and 0 (failure),
and a single trial, for example, a coin toss.
So the random variable X which has a Bernoulli distribution can take value 1 with the probability of success, p,
and the value 0 with the probability of failure, q or 1-p.
The probabilities of success and failure need not be equally likely.
Probability mass function of Bernoulli distribution:
f(k;p) = (p^k) * ((1-p)^(1-k))

Bernoulli distribution is a special case of the binomial distribution where a single trial is conducted (n=1)
'''
rv_array = spss.bernoulli.rvs(size=10000,p=0.6) # p = probability of success, size = number of times to repeat the trial
sns.distplot(rv_array, kde=False)


import math
import sympy

b = 8
n = 21
temp = math.pow(b,n-1)
mod = temp%n

'''
if n is prime and math.pow(b,n-1) = 1 mod n then n is base b probable prime
if n is composite and math.pow(b,n-1) = 1 mode n then n is base b pseudo prime
'''

if sympy.isprime(n) and mod == 1 :
	print(f"{n} is a base {b} probable prime")
elif sympy.isprime(n) == False and mod == 1 :
	print(f"{n} is a base {b} pseudo prime")


import sympy

def transfer_function_lpi():
    print()
    I, d, gp, gi, c,  s = sympy.symbols('I d gp gi c s')

    A = sympy.Matrix([[-(d + gp)/I, 0], [-1, 0]])
    B = sympy.Matrix([[gp/I], [1]])
    C = sympy.Matrix([[1, 0]])
    Id = sympy.Matrix([[1,0], [0,1]])
    Phi = (s*Id - A).inv()
    H = (C*Phi*B)[0,0]
    H = sympy.simplify(H)
    print('P Controller')
    print('-----------------')
    print(H)
    print()

    A = sympy.Matrix([[-(d + gp)/I, gi/I], [-1, 0]])
    B = sympy.Matrix([[gp/I], [1]])
    C = sympy.Matrix([[1, 0]])
    Id = sympy.Matrix([[1,0], [0,1]])
    Phi = (s*Id - A).inv()
    H = (C*Phi*B)[0,0]
    H = sympy.simplify(H)
    num, den = sympy.fraction(H)
    den = sympy.collect(den,'s')
    print('PI Controller')
    print('-----------------')
    print(num/den)
    print()

    A = sympy.Matrix([[-(d + gp)/I, gi/I], [-1, -c]])
    B = sympy.Matrix([[gp/I], [1]])
    C = sympy.Matrix([[1, 0]])
    Id = sympy.Matrix([[1,0], [0,1]])
    Phi = (s*Id - A).inv()
    H = (C*Phi*B)[0,0]
    H = sympy.simplify(H)
    num, den = sympy.fraction(H)
    num = sympy.expand(num)
    num = sympy.collect(num,s)
    den = sympy.collect(den,s)
    print('LPI Controller')
    print('-----------------')
    print(num/den)

def transfer_function_2x2():
    print()
    a11, a12, a21, a22, b1, b2, s = sympy.symbols('a11 a12 a21 a22 b1 b2 s')
    A = sympy.Matrix([[a11, a12], [a21, a22]])
    B = sympy.Matrix([[b1], [b2]])
    C = sympy.Matrix([[1, 0]])
    Id = sympy.Matrix([[1,0], [0,1]])
    Phi = (s*Id - A).inv()
    H = (C*Phi*B)[0,0]
    H = sympy.simplify(H)
    num, den = sympy.fraction(H)
    num = sympy.expand(num)
    num = sympy.collect(num,s)
    den = sympy.collect(den,'s')
    print('General 2D system')
    print('-----------------')
    print(num/den)


"""
Tests for the measure module
"""

import star
import numpy as np
import math
import pytest

def test_calculate_distance():
	r1 = np.array([0, 0, 0])
	r2 = np.array([0, 1, 0])

	expected_distance = 1

	calculated_distance = star.calculate_distance(r1, r2)

	assert expected_distance == calculated_distance
	##assert 1 == 2

def test_calculate_angle():
	r1 = np.array([0, 0, -1])
	r2 = np.array([0, 0, 0])
	r3 = np.array([1, 0, 0])

	expected_angle = math.pi/2

	calculated_angle = star.calculate_angle(r1, r2, r3)

	assert pytest.approx(expected_angle) == calculated_angle
	##assert 1 == 2

@pytest.mark.parametrize("p1, p2, p3, expected_angle",[
(np.array([np.sqrt(2)/2,np.sqrt(2)/2,0]),np.array([0,0,0]),np.array([1,0,0]),math.pi/4),
(np.array([0,0,-1]),np.array([0,1,0]),np.array([1,0,0]),math.pi/3)
])
def test_calculate_angle_many(p1, p2, p3, expected_angle):
	calculated_value = star.calculate_angle(p1,p2,p3)
	assert pytest.approx(expected_angle) == calculated_value


import statsmodels.api as sm
import statsmodels.formula.api as smf
from scipy import stats
import numpy as np
import emcee
from matplotlib import pyplot as plt
import seaborn as sns
from scipy import integrate

X = [0.0, 0.03, 0.05, 0.07, 0.1, 0.12, 0.15, 0.17, 0.2, 0.23, 0.25, 0.28, 0.3, 0.33, 0.35, 0.38, 0.4, 0.42, 0.45, 0.47, 0.5, 0.53, 0.55, 0.57, 0.6, 0.62, 0.65, 0.68, 0.7, 0.72, 0.75, 0.78, 0.8, 0.82, 0.85, 0.88, 0.9, 0.93, 0.95, 0.97]
Y = [3.15, 3.01, 3.29, 3.61, 3.13, 3.18, 3.77, 3.58, 3.26, 3.61, 3.36, 3.41, 3.67, 3.08, 3.18, 3.58, 3.5, 3.94, 3.63, 3.53, 4.44, 3.98, 4.12, 3.72, 4.04, 4.28, 3.95, 4.46, 4.22, 4.36, 4.32, 5.11, 4.6, 4.33, 4.95, 4.38, 4.86, 4.26, 4.5, 5.01]
X=np.array(X)
Y=np.array(Y)

# Data Exploration:

slope, intercept, r_value, p_value, slope_std_error = stats.linregress(X,Y)
predict_y = slope * X + intercept

sns.set(rc={'figure.figsize':(8, 8)})
ax = sns.regplot(x=X, y=Y, line_kws={'label':'$y=%3.4s*x+%3.4s$'%(slope, intercept)});
sns.regplot(x=X, y=Y, fit_reg=False, ax=ax);
sns.regplot(x=X, y=predict_y,scatter=False, ax=ax);
ax.legend()
plt.xlabel('X')
plt.ylabel('Y')
plt.title('OLS Fit')
plt.show()

# 12.3.1 - two different approaches:
if True:
	ts=1.96
	# Method 1: Scipy stats package
	result = stats.linregress(X,Y)
	slope,intercept = result.slope,result.intercept
	slope_95,intercept_95 = ts*result.stderr,ts*result.intercept_stderr

	m='Slope = {:.2f} 95% CI: [{:.2f},{:.2f}]'.format(slope,slope-slope_95,slope+slope_95)
	b ='Intercept = {:.2f} 95% CI: [{:.2f},{:.2f}]'.format(intercept,intercept-intercept_95,intercept+intercept_95)
	print(m,'\n',b)

	# Method 2: Statsmodels
	x=sm.add_constant(X)
	mod = sm.OLS(Y, x)
	res = mod.fit()
	print(res.summary())

#12.3.2
# Reference: 'https://statswithr.github.io/book/introduction-to-bayesian-regression.html'

sigma = 0.3

def gaussian(x,mu,s=sigma):
    v = (1/(s*np.sqrt((2*np.pi))))*np.exp(-0.5*((x-mu)/s)**2)
    return v

# a,b will be constrained to {-5,5} anyway
def prior(c):
    if abs(c) > 5:
        return 0
    return 1

a = np.linspace(-5,5,1000)
b = np.linspace(-5,5,1000)

def likelihood(a,b):
    l=1
    for i in range(len(X)):
        l*=gaussian(Y[i],(b+a*X[i]))
    return l

# Most readings ignore the evidence since it's a constant
# and often times not easy to compute. It makes sense since
# all we need is the propability maximum/peak.
#'https://www.countbayesie.com/blog/2019/6/12/logistic-regression-from-bayes-theorem'

def evidence():

    evd = lambda b, a: np.product([gaussian(Y[i],(b+a*X[i])) for i in range(len(X))])
    # using scipy.integrate.dblquad() method

    P_D = integrate.dblquad(evd, -5,5 , lambda a: -5, lambda a: 5)
    return P_D[0]

P_D=evidence()

matrix = []

for i in range(len(a)):
    row=[]
    for j in range(len(b)):
        Posterior= likelihood(a[i],b[j])/P_D
        row.append(Posterior)
    matrix.append(row)
    row=[]

m = plt.pcolor(a, b, matrix,cmap=plt.get_cmap('viridis', 20))
cb = plt.colorbar(m)
plt.ylabel('Intercept: b')
plt.xlabel('Slope: a')
cb.ax.set_title(r'$p((a,b)|\mathcal{D})$')
plt.xlim([2, 4])
plt.ylim([1,3])
plt.title('12.3.2: P((a,b)|D) Heatmap')
plt.show()

# 3-4: Plot Posteriors and 95% CIs:
# Reference: 'https://www.programcreek.com/python/example/103021/emcee.EnsembleSampler'

# Log works best with emcee

def log_likelihood(xs, data):
    a,b = xs
    x, y = data
    reg = (a*x + b)-y

    return stats.norm.logpdf(reg).sum()

ndim = 2  # num of params
nwalk = 50  # num of walks
p0 = np.random.uniform(low=-5, high=5, size=(nwalk, ndim))  # initial position
sampler = emcee.EnsembleSampler(nwalk, ndim, log_likelihood, args=[(X, Y)])
state = sampler.run_mcmc(p0, 1000)
chain = sampler.chain[:, 100:, :] # discard first 100
flat_chain = chain.reshape((-1, ndim))

samples = sampler.get_chain()

fig, axes = plt.subplots(ndim, figsize=(10, 7), sharex=True)

labels = ["m", "b"]
for i in range(ndim):
    ax = axes[i]
    ax.plot(samples[:, :, i], "k", alpha=0.3)
    ax.set_xlim(0, len(samples))
    ax.set_ylabel(labels[i])
    ax.yaxis.set_label_coords(-0.1, 0.5)

axes[-1].set_xlabel("step number")

import corner

flat_samples = sampler.get_chain(discard=100, thin=15, flat=True)
fig = corner.corner(
    flat_samples, labels=labels,plot_density=True,fill_contours=True,color='green',quantiles=[0.05,0.5,0.95],show_titles=True
);

for i in range(ndim):
    mcmc = np.percentile(flat_samples[:, i], [5, 50, 95])
    q = np.diff(mcmc)
    param = "{3} = {0:.3f} 95% CI [{1:.3f},{2:.3f}]"
    param = param.format(mcmc[1], mcmc[1]-q[0],mcmc[1]+ q[1], labels[i])
    print(param)

plt.show()

import numpy,from scipy,import statsmodels


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

# # Machine Learning using Multivariate Linear Regression
#
# Estimating the profit from a city basing on its population size.

# In[1]:

import pandas as pd
# import pandas_profiling
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from pylab import meshgrid, cm
from mpl_toolkits.mplot3d import axes3d, Axes3D
from matplotlib.ticker import LinearLocator, FormatStrFormatter

sns.set(style="whitegrid")

# In[2]:

print('Plotting Data ...\n')
data = pd.read_csv('ex1data2.txt', names = ['size', 'qty_bedrooms', 'price'])
data = data.astype('float64')
print(data.head())

# In[3]:

print('Profiling Data')
data.info()
# pandas_profiling.ProfileReport(data)

# In[4]:

x = np.array(data[['size', 'qty_bedrooms']]) # training set
y = data['price'] # labels
m = len(y) # training size

# In[5]:

"""
Normalizes the features in X
returns a normalized version of X where the mean value of each feature is 0 and the standard deviation is 1.
This is often a good preprocessing step to do when working with learning algorithms.

First, for each feature dimension, compute the mean of the feature and subtract it from the dataset,
storing the mean value in mu. Next, compute the  standard deviation of each feature and divide
each feature by it's standard deviation, storing the standard deviation in sigma.
"""
def featureNormalize(X):
    X_norm = np.zeros(np.shape(X))
    qty_features = np.shape(X)[1]
    mu = np.zeros(qty_features)
    sigma = np.zeros(qty_features)

    for i in range(qty_features):
        mu[i] = np.mean(X[:,i])
        X_norm[:,i] = X[:,i] - mu[i]

        # by default np.std calculate the population std, here we want sample std (as done in Octave)
        sigma[i] = np.std(X_norm[:,i], ddof=1) # default: ddof=0 (N - 0) will predict 293092.21273075533
        X_norm[:,i] = X_norm[:,i] / sigma[i]
    return X_norm, mu, sigma

# In[6]:

# Tessting Feature Normalization
X_ = np.array([
    [2104, 3],
    [1600, 3],
    [2400, 3],
    [1416, 2],
    [3000, 4]
])
X_n, mu_, sigma_ = featureNormalize(X_)
print("X Norm", X_n)
print("mu", mu_) # [2104.    3.]
print("sigma", sigma_) # [635.96226303   0.70710678]

# In[7]:

# Scale features and set them to zero mean
print('Normalizing Features ...\n')

X, mu, sigma = featureNormalize(x)

print('Normalized data')
X[:5]

# Add a column of ones to X to facilitate the manipulation.
#
# Each row is a input with the following format:
#
# $X[0] = [ x_0, x_1, x_2 ]$ where $x_0 = 1$

# In[8]:

# Add a column of ones to X to facilitate the manipulation
X = np.column_stack((np.ones(m), X))
X[:5]

# ## ================ Part 2: Gradient Descent ================

# ### Hypothesis Function
# Function that defines our linear model.
#
# Definition:
#
# $h_\theta(x) = \theta_0 + \theta_1 * x_1 + \theta_2 * x_2$
#
# Vectorial form:
#
# $h_\theta(x) = \theta^{T} * x$
#
# where:
# $x = [x_0, x_1, x_2]$; $x_0 = 1$ and $\theta = [\theta_0, \theta_1, \theta_2]$

# In[9]:

def hypothesisMulti(X, theta):
    # return [np.dot(xi, theta) for xi in X]
    return X.dot(theta)

# ### Compute cost for linear regression
# `computeCost` computes the cost of using theta as the parameter for linear regression to fit the data points in X and y.
#
# Function cost:
#
# $ J(\theta) = \frac{1}{2m} \sum_{i=0}^{m} (h_\theta(x^{(i)}) - y^{(i)})^{2} $

# In[10]:

"""
Inputs:
X = [
  [ x0, x1, x2 ]
]
y = [
  [ ]
]
theta = [ theta_0, theta_1, theta_2 ]
"""
def computeCostMulti(X, y, theta):
    m = len(y)
    h_theta = hypothesisMulti(X, theta)
    # j = (1 / (2 * m)) * ((h_theta - y) ** 2).sum()
    j = (1 / (2 * m)) * (h_theta - y).T.dot(h_theta - y)
    return j

# ### Running Gradient Descent
# We use gradient descent to find the parameters values $\theta$ that **minimize** $J$.
# In each iteration we calculate a $\theta'$ where $J(\theta') < J(\theta)$.
#
# This $\theta'$ defined by $\theta' = \theta - \alpha * \nabla h_\theta$, where $\nabla h_\theta$ is the amount we need change to *minimize* $J(\theta)$ and $\alpha$ is the step we will take.
#
# If $\alpha$ is too large we could ending increasing $J$, it need to be small enough to converge quickly to the points the $J(\theta)$ is minimum.
#
#
# Step to update each parameter:
#
# $\theta_j := \theta_j - \alpha * \frac{\partial J}{\partial \theta_j} $
#
# Where:
#
# $\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} [( h_\theta(x^{(i)}) - y^{(i)})$ when $j = 0$ (bacause it is the bias - doesn't have a feature).
#
# $\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} [( h_\theta(x^{(i)}) - y^{(i)}) * x^{(i)}]$ when $j > 1$.
#
#
# Metrix form:
#
# $ \frac{\partial J}{\partial \theta_j} = \frac{1}{m} = X^{T} ( h_\theta(x^{(i)}) - y^{(i)}) $
#
#
# `gradientDescent(X, y, theta, alpha, num_iters)` performs gradient descent to learn $\theta$ parameters.
#
# It return the an array with $\theta$ containing the values found by taking num_iters gradient steps with learning rate alpha.
#
# Also it return a array with the history of $J(\theta)$ to be plotted.

# In[11]:

def gradientDescentMulti(X, y, theta, alpha, num_iters):
    m = len(y)
    J_history = np.zeros(num_iters)

    for i in range(num_iters):
        h_theta = hypothesisMulti(X, theta)
        nabla = (1 / m) * (h_theta - y).T.dot(X);

        theta = theta - alpha * nabla.T

        # Save the cost J in every iteration
        J_history[i] = computeCostMulti(X, y, theta);
    return theta, J_history

# In[12]:

# Testing gradientDescentMulti
theta, J_history = gradientDescentMulti(X, y, np.zeros(3), 0.3, 50)
print(theta)
# [340412.65345227 110572.96193087  -6591.38592285]

# After that, try running gradient descent with different values of alpha and see which one gives you the best result.

# In[13]:

# computeCostMulti(np.array([X[0]]), np.array([y[0]]), np.zeros(3)) # should print 79960005000.0
num_iters = 50; # with alpha = 0.01 we should rise the # of iterations
alphas = [0.01, 1, 0.1, 0.3]
colors = ['b', 'r', 'y', 'g'];

# In[14]:

# To plot the J(theta) using different alphas
fig, ax = plt.subplots()
iterations = range(num_iters)

print('Running gradient descent ...\n')
for alpha, color in zip(alphas, colors):
    theta = np.zeros(3) # reset the theta to the current alpha

    theta, J_history = gradientDescentMulti(X, y, theta, alpha, num_iters)
    # print('alpha ', alpha, ' found theta ', theta)
    plt.plot(iterations, J_history, color=color, label='alpha %.2f' % alpha)

plt.legend(loc='best', ncol=2)

# In[15]:

# Display gradient descent's result
print('Theta computed from gradient descent:');
print(theta);

# In[16]:

# Estimate the price of a 1650 sq-ft, 3 br house
predict = [1650, 3]
# normalize and add the x_0 = 1
predict_norm = np.column_stack((np.ones(1), [((predict - mu) / sigma)]))

# In[17]:

price = hypothesisMulti(predict_norm, theta)[0] # 293094.09812
print("Predicted price: ", price)

# ### ================ Part 3: Normal Equations ================

# In[18]:

# Load again as we don't need to apply feature normalization
x = np.array(data[['size', 'qty_bedrooms']]) # training set
y = data['price'] # labels
m = len(y) # training size

X = np.column_stack((np.ones(m), x))

# Computes the closed-form solution to linear regression using the normal equations.
#
# $ inv(X^{T} * X) * X^{T} * y $

# In[19]:

def normalEqn(X, y):
    xxInv = np.linalg.inv(np.matmul(X.transpose(), X))
    return np.matmul(np.matmul(xxInv, X.transpose()), y)

# In[20]:

print('Solving with normal equations...')
theta = normalEqn(X, y)
print(theta)

# In[21]:

# Estimate the price of a 1650 sq-ft, 3 br house
predict = np.column_stack((np.ones(1), [[1650, 3]]))
price = hypothesisMulti(predict, theta)[0] # 293081.46
print("Predicted price: ", price)

# In[ ]:

Machine Learning - Andrew Ng/assignments/machine-learning-ex1/ex1_python/Machine_Learning_Multivariate_Linear_Regression.py


#!/usr/bin/env python3
"""
Clustering Module
"""
import numpy as np

def variance(X, C):
    """
    Calculates the total intra-cluster variance for a data set:

    X is a numpy.ndarray of shape (n, d) containing the data set
    C is a numpy.ndarray of shape (k, d) containing the centroid means for
    each cluster
    You are not allowed to use any loops

    Returns: var, or None on failure
        var is the total variance
    """
    if not isinstance(X, np.ndarray) or len(X.shape) != 2:
        return None
    if not isinstance(C, np.ndarray) or len(C.shape) != 2:
        return None
    if X.shape[1] != C.shape[1]:
        return None
    if C.shape[0] > X.shape[0]:
        return None

    dtcs = np.sqrt(((X - C[:, np.newaxis])**2).sum(axis=-1))
    mind = np.min(dtcs, axis=0)
    return np.sum(mind ** 2)


###GRPC init

# test()
###Generate DATA

#=>get x,y, label,center
###Apply kMeans

##=>
###send to server
#Prep data

#create stub

#receive Data

#prepare for Checking

###Check data

##Plot Results
from typing import Tuple
import numpy as np

from sklearn.cluster import DBSCAN,KMeans
from sklearn import metrics
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from typing import List, Tuple,Dict
from sklearn.utils.validation import _num_samples

##settings
centers:List[Tuple[float,float]] = [[1, 1], [2, 1], [1, -1]]
numSamples:int=100
seed:int=0
# #############################################################################
# Generate sample data
points, clusterLabels ,clusterCenters= make_blobs(n_samples=numSamples, centers=centers, cluster_std=0.4,
                            random_state=seed,return_centers=True)
# X = StandardScaler().fit_transform(X)
base_test_solution:np.ndarray=np.hstack((points,clusterLabels.reshape(numSamples,1)))
###############
#compute kmeans
k_means=KMeans(n_clusters=4, random_state=0).fit_predict(points)
k_means_test_solution:np.ndarray=np.hstack((points,k_means.reshape(numSamples,1)))

# # #############################################################################
# # Compute DBSCAN
db_test = DBSCAN(eps=0.8, min_samples=30).fit(points)

dbScan_test_solution:np.ndarray=np.hstack((points,db_test.labels_.reshape(numSamples,1)))

# # Number of clusters in labels, ignoring noise if present.
n_clusters_ = len(set(db_test.labels_)) - (1 if -1 in db_test.labels_ else 0)
n_noise_ = list(db_test.labels_).count(-1)

# #############################################################################
# Plot result
import matplotlib.pyplot as plt
plt.figure(1)
plt.title(label=f"k_means")
plt.scatter(points[:, 0], points[:, 1], c=k_means)
# # Black removed and is used for noise instead.
plt.figure(2)
plt.title(label=f"dbScan numClusters={n_clusters_}")
plt.scatter(points[:, 0], points[:, 1], c=db_test.labels_)
plt.show()

# unique_labels = set(labels)
# colors = [plt.cm.Spectral(each)
#           for each in np.linspace(0, 1, len(unique_labels))]
# for k, col in zip(unique_labels, colors):
#     if k == -1:
#         # Black used for noise.
#         col = [0, 0, 0, 1]

#     class_member_mask = (labels == k)

#     xy = points[class_member_mask & core_samples_mask]
#     plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
#              markeredgecolor='k', markersize=14)

#     xy = points[class_member_mask & ~core_samples_mask]
#     plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
#              markeredgecolor='k', markersize=6)

# plt.title('Estimated number of clusters: %d' % n_clusters_)


"""
Optimizer Module
"""

import numpy as np

def derivative(f, x, epsilon = 1e-10):
    """
    Calculate derivative of f at x

    ...

    Parameters
    ---
    f : Function to calculate derivative for
    x : Value at which to calculate derivative
    epsilon(optional): Adjustable precision

    Returns
    ---
    value: Derivative of f at x
    """

    x_ = x + epsilon
    value = (f(x_) - f(x)) / epsilon

    return value

def partial_derivative(f, x, i, epsilon = 1e-10):
    """
    Calculate partial derivative of f at xi

    ...

    Parameters
    ---
    f: Vector function
    x: Vector input
    i: index of x
    epsilon(optional): Adjustable precision

    Returns
    ---
    value: Partial Derivative of f at xi
    """
    x_ = np.copy(x).astype(np.float64)
    x_[i] = x_[i] + epsilon
    value = (f(x_) - f(x)) / epsilon

    return value

def jacobian(f, x, epsilon = 1e-10):
    """
    Calculate Jacobian of f wrt x

    ...

    Parameters
    ---
    f: Vector function
    x: Vector input
    epsilon(optional): Adjustable precision

    Returns
    ---
    value: Jacobian of f at x
    """
    f_ = f(x)
    value = np.zeros((len(f_), len(x)))

    for i in range(len(x)):
        f_ = partial_derivative(f, x, i, epsilon)
        value[:,i] = f_

    return value

def newton_method(f, x_init = 0, epsilon = 1e-10):
    """
    Newton Raphson Optimizer

    ...

    Parameters
    ---
    f: Function to calculate root for
    x_init(optional) : initial value of x
    epsilon(optional): Adjustable precision

    Returns
    ---
    x: Value of root
    """
    prev_value = x_init + 2 * epsilon
    value = x_init

    iterations = 0
    while abs(prev_value - value) > epsilon:
        prev_value = value

        f_dash = derivative(f, value)
        value = value - f(value) / f_dash

        iterations += 1

    print(f"Newton Method converged in {iterations} iterations")

    return value

def newton_method_vector(f, x_init, epsilon = 1e-10, max_iterations = 1000):
    """
    Newton Raphson Optimizer for Vector Functions

    ...

    Parameters
    ---
    f: Function to calculate root for
    x_init : initial value of x
    epsilon(optional): Adjustable precision
    max_iterations(optional): Number of iterations to run

    Returns
    ---
    x: Value of root
    """
    prev_value = x_init + 2 * epsilon
    value = x_init

    iterations = 0
    while np.any(np.abs(prev_value - value) > epsilon):
        prev_value = value

        j = jacobian(f, value)
        value = value - np.dot(np.linalg.pinv(j), f(value))

        iterations += 1

    print(f"Newton Method converged in {iterations} iterations")

    return value


import numpy as np

def cut_rod(p,n):
    if n==0:
        return 0
    q=-np.inf
    for i in range(1,n+1):
        q=max(q,p[i-1]+cut_rod(p,n-i))
    return q

def memoized_cut_rod(p,n):
    r=np.ones(n+1)*-np.inf
    r[0]=0
    return memoized_cut_rod_aux(p,n,r)

def memoized_cut_rod_aux(p,n,r):
    if r[n]>=0:
        return r[n]
    if n==0:
        q=0
    else:
        q=-np.inf
        for i in range(1,n+1):
            q=max(q,p[i-1]+memoized_cut_rod_aux(p,n-i,r))
    r[n]=q
    return q

def bottom_up_cut_rod(p,n):
    r = np.ones(n+1)*-np.inf
    r[0]=0
    for i in range(1,n+1):
        q=-np.inf
        for j in range(1,i+1):
            q=max(q,p[j-1]+r[i-j])
        r[i]=q
    return r

if __name__=="__main__":
    p=[1,5,8,9,10,17,17,20,24,30,31,31,33,34,37,37,37,38]
    r0 = cut_rod(p,len(p))
    print(r0)
    r1=memoized_cut_rod(p,len(p))
    print(r1)
    r2 = bottom_up_cut_rod(p,len(p))
    print(r2)


# https://en.wikipedia.org/wiki/Test_functions_for_optimization

import numpy as np

def ackley(x):
    x = np.array(x)
    return -20 * np.exp(-0.2 * np.sqrt(0.5 * (x[0] ** 2 + x[1] ** 2))) - np.exp(
        0.5 * (np.cos(2 * np.pi * x[0]) + np.cos(2 * np.pi * x[1]))) + np.e + 20

def beale(x):
    x = np.array(x)
    return (1.5 - x[0] + x[0] * x[1]) ** 2 + (2.25 - x[0] + x[0] * x[1] ** 2) ** 2 + (
            2.625 - x[0] + x[0] * x[1] ** 3) ** 2

def booth(x):
    y = x[1]
    x = x[0]
    return (x + 2 * y - 7) ** 2 + (2 * x + y - 5) ** 2

def buking(x):
    y = x[1]
    x = x[0]
    return 100 * np.sqrt(np.abs(y - 0.01 * x ** 2)) + 0.01 * np.abs(x + 10)

def matyas(x):
    y = x[1]
    x = x[0]
    return 0.26 * (x ** 2 + y ** 2) - 0.48 * x * y

def levi(x):
    y = x[1]
    x = x[0]
    return np.sin(3 * np.pi * x) ** 2 + (x - 1) ** 2 * (1 + np.sin(3 * np.pi * y) ** 2) + (y - 1) ** 2 * (
            1 + np.sin(2 * np.pi * y) ** 2)

def himmelblau(x):
    y = x[1]
    x = x[0]
    return (x ** 2 + y - 11) ** 2 + (x + y ** 2 - 7) ** 2

def three_hump_camel(x):
    y = x[1]
    x = x[0]
    return 2 * x ** 2 - 1.05 * x ** 4 + (x ** 6 / 6) + x * y + y ** 2

def easom(x):
    y = x[1]
    x = x[0]
    return -np.cos(x) * np.cos(y) * np.exp(-((x - np.pi) ** 2 + (y - np.pi) ** 2))

def cross_in_tray(x):
    y = x[1]
    x = x[0]
    return -0.0001 * (np.abs(np.sin(x) * np.sin(y) * np.exp(np.abs(100 - (np.sqrt(x ** 2 + y ** 2) / np.pi)))) + 1) ** (
        0.1)

def eggholder(x):
    y = x[1]
    x = x[0]
    return -(y + 47) * np.sin(np.sqrt(np.abs(x / 2 + (y + 47)))) - x * np.sin(np.sqrt(np.abs(x - (y + 47))))

def hoelder_table(x):
    y = x[1]
    x = x[0]
    return -np.abs(np.sin(x) * np.cos(y) * np.exp(np.abs(1 - (np.sqrt(x ** 2 + y ** 2) / np.pi))))

def mccormick(x):
    y = x[1]
    x = x[0]
    return np.sin(x + y) + (x - y) ** 2 - 1.5 * x + 2.5 * y + 1

def schaffern2(x):
    y = x[1]
    x = x[0]
    return 0.5 + (np.sin(x ** 2 - y ** 2) ** 2 - 0.5) / ((1 + 0.001 * (x ** 2 + y ** 2)) ** 2)

def schaffern4(x):
    y = x[1]
    x = x[0]
    return 0.5 + (np.cos(np.sin(np.abs(x ** 2 - y ** 2))) ** 2 - 0.5) / (1 + 0.001 * (x ** 2 + y ** 2)) ** 2

def goldstein_price(x):
    y = x[1]
    x = x[0]
    firstTerm = (1 + (x + y + 1) ** 2 * (19 - 14 * x + 3 * x ** 2 - 14 * y + 6 * x * y + 3 * y ** 2))
    secondTerm = (30 + (2 * x - 3 * y) ** 2 * (18 - 32 * x + 12 * x ** 2 + 48 * y - 36 * x * y + 27 * y ** 2))
    return firstTerm * secondTerm

ackley_bounds = [(-5., 5.), (-5., 5.)]
ackley_ySol = 0.
ackley_xSol = np.array([[0., 0.]])

beale_bounds = [(-4.5, 4.5), (-4.5, 4.5)]
beale_ySol = 0.0
beale_xSol = np.array([[3., 0.5]])

booth_bounds = [(-10., 10.), (-10., 10.)]
booth_ySol = 0.
booth_xSol = np.array([[1., 3.]])

buking_bounds = [(-15., 5.), (-3., 3.)]
buking_ySol = 0.
buking_xSol = np.array([[-10., 1.]])

matyas_bounds = [(-10., 10.), (-10., 10.)]
matyas_ySol = 0.
matyas_xSol = np.array([[0., 0.]])

levi_bounds = [(-10., 10.), (-10., 10.)]
levi_ySol = 0.
levi_xSol = np.array([[1.0, 1.0]])

himmelblau_bounds = [(-5., 5.), (-5., 5.)]
himmelblau_ySol = 0.
himmelblau_xSol = np.array([[3., 2.], [-2.805118, 3.131312],
                            [-3.779310, -3.283186], [3.584428, -1.848126]])

three_hump_camel_bounds = [(-5., 5.), (-5., 5.)]
three_hump_camel_ySol = 0.
three_hump_camel_xSol = np.array([[0., 0.]])

easom_bounds = [(-100., 100.), (-100., 100.)]
easom_ySol = -1.
easom_xSol = np.array([[np.pi, np.pi]])

cross_in_tray_bounds = [(-10., 10.), (-10., 10.)]
cross_in_tray_ySol = -2.06261
cross_in_tray_xSol = np.array([[1.34941, -1.34941], [1.34941, 1.34941],
                               [-1.34941, 1.34941], [-1.34941, -1.34941]])

eggholder_bounds = [(-512., 512.), (-512., 512.)]
eggholder_ySol = -959.6407
eggholder_xSol = np.array([[512., 404.2319]])

hoelder_table_bounds = [(-10., 10.), (-10., 10.)]
hoelder_table_ySol = -19.2085
hoelder_table_xSol = np.array([[8.05502, 9.66459], [-8.05502, 9.66459],
                               [8.05502, -9.66459], [-8.05502, -9.66459]])

mccormick_bounds = [(-1.5, 4.), (-3., 4.)]
mccormick_ySol = -1.9133
mccormick_xSol = np.array([[-0.54719, -1.54719]])

schaffern2_bounds = [(-100., 100.), (-100., 100.)]
schaffern2_ySol = 0.
schaffern2_xSol = np.array([[0., 0.]])

schaffern4_bounds = [(-100., 100.), (-100., 100.)]
schaffern4_ySol = 0.292579
schaffern4_xSol = np.array([[0, 1.25313], [0, -1.25313]])

goldstein_price_bounds = [(-2., 2.), (-2., 2.)]
goldstein_price_ySol = 3.
goldstein_price_xSol = np.array([[0., -1.]])

all_functions_string = ['ackley', 'beale', 'booth', 'buking', 'matyas', 'levi', 'himmelblau', 'three_hump_camel',
                        'easom', 'cross_in_tray', 'eggholder', 'hoelder_table', 'mccormick', 'schaffern2', 'schaffern4',
                        'goldstein_price']

all_functions = [eval(function_string) for function_string in all_functions_string]

all_bounds = [eval(function_string + '_bounds') for function_string in all_functions_string]

all_xSol = [eval(function_string + '_xSol') for function_string in all_functions_string]

all_ySol = [eval(function_string + '_ySol') for function_string in all_functions_string]


filename = "VehiclesItaly.txt"

X = []
y = []
with open(filename, 'r') as f:
    for line in f.readlines():
        xt, yt = [float(i) for i in line.split(',')]
        X.append(xt)
        y.append(yt)

# Train/test split
num_training = int(0.8 * len(X))
num_test = len(X) - num_training

import numpy as np

# Training data
X_train = np.array(X[:num_training]).reshape((num_training,1))
y_train = np.array(y[:num_training])

# Test data
X_test = np.array(X[num_training:]).reshape((num_test,1))
y_test = np.array(y[num_training:])

# Create linear regression object
from sklearn import linear_model

linear_regressor = linear_model.LinearRegression()

# Train the model using the training sets
linear_regressor.fit(X_train, y_train)

# Predict the output
y_train_pred = linear_regressor.predict(X_train)

# Plot outputs
import matplotlib.pyplot as plt

plt.figure()
plt.scatter(X_train, y_train, color='green')
plt.plot(X_train, y_train_pred, color='black', linewidth=4)
plt.title('Training data')
plt.show()

y_test_pred = linear_regressor.predict(X_test)
plt.figure()
plt.scatter(X_test, y_test, color='green')
plt.plot(X_test, y_test_pred, color='black', linewidth=4)
plt.title('Test data')
plt.show()

# Measure performance
import sklearn.metrics as sm

print("Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred), 2))
print("Mean squared error =", round(sm.mean_squared_error(y_test, y_test_pred), 2))
print("Median absolute error =", round(sm.median_absolute_error(y_test, y_test_pred), 2))
print("Explain variance score =", round(sm.explained_variance_score(y_test, y_test_pred), 2))
print("R2 score =", round(sm.r2_score(y_test, y_test_pred), 2))

# Model persistence
import pickle

output_model_file = "3_model_linear_regr.pkl"

with open(output_model_file, 'wb') as f:
    pickle.dump(linear_regressor, f)

with open(output_model_file, 'rb') as f:
    model_linregr = pickle.load(f)

y_test_pred_new = model_linregr.predict(X_test)
print("New mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred_new), 2))

ridge_regressor = linear_model.Ridge(alpha=0.01, fit_intercept=True, max_iter=10000)

ridge_regressor.fit(X_train, y_train)
y_test_pred_ridge = ridge_regressor.predict(X_test)
print( "Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred_ridge), 2))
print( "Mean squared error =", round(sm.mean_squared_error(y_test, y_test_pred_ridge), 2))
print( "Median absolute error =", round(sm.median_absolute_error(y_test, y_test_pred_ridge), 2))
print( "Explain variance score =", round(sm.explained_variance_score(y_test, y_test_pred_ridge), 2))
print( "R2 score =", round(sm.r2_score(y_test, y_test_pred_ridge), 2))


"""

    This module creates the Hilbert matrix system.
    Marina von Steinkirch, spring 2013.

"""

import numpy as npy

def createHilbert(N):

    # generating H^N
    H = npy.zeros((N,N), dtype=npy.float64)

    for i in range(1,N+1):
        for j in range(1,N+1):
            Hij = 1.0/(i + j -1.0)
            H[i-1,j-1] = Hij

    # generating x^n
    xt = npy.zeros((N), dtype=npy.float64)

    for i in range(0,N):
            xt[i] = i

    x = npy.transpose(xt)

    return H, x


# -*- coding: utf-8 -*-
"""
Created on Wed May 27 22:00:41 2020

@author: PRAVEEN KUMAR -1
"""
import numpy as np

def sigmoid(Z):
    return 1/(1+np.exp(-Z))

def tanh(Z):
    return (np.exp(Z) - np.exp(-Z))/(np.exp(Z) + np.exp(-Z))

def relu(Z):
    return np.max(0,Z)

def leaky_relu(Z):
    return np.max(0.01*Z,Z)