# Import
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import axes3d
from warmUpExercise import warmUpExercise
from computeCost import computeCost
from gradientDescent import gradientDescent
from plotData import plotData

# Machine Learning Online Class - Exercise 1: Linear Regression

#  Instructions
#  ------------
#
#  This file contains code that helps you get started on the
#  linear exercise. You will need to complete the following modules
#  in this exericse:
#
#     warmUpExercise.py
#     plotData.py
#     gradientDescent.py
#     computeCost.py
#     gradientDescentMulti.py
#     computeCostMulti.py
#     featureNormalize.py
#     normalEqn.py
#
#  For this exercise, you will not need to change any code in this file,
#  or any other files other than those mentioned above.
#
# x refers to the population size in 10,000s
# y refers to the profit in $10,000s

# ==================== Part 1: Basic Function ====================
# Complete warmUpExercise.py
print('\n -------------------------- \n')
print('Running warmUpExercise ...')
print('5x5 Identity Matrix:')
warmup = warmUpExercise()
print(warmup)

# ======================= Part 2: Plotting =======================

# Read data using pandas
path = 'ex1data1.txt'
data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
data.head()

# Résumé des données
data.describe()

# set X (training data) and y (target variable)
nbCol = data.shape[1]
X = data.iloc[:,0:nbCol-1]
y = data.iloc[:,nbCol-1:nbCol]

# convert from data frames to numpy arrays
X = np.array(X.values)
y = np.array(y.values)

# Plot Data
# Note: You have to complete the code in plotData.py
plotData(X,y)

# =================== Part 3: Gradient descent ===================
m = X.shape[0]

# Add intercept term to X
#X = np.concatenate((np.ones((m, 1)), X), axis=1)
X = np.column_stack((np.ones((m, 1)), X)) # works fine too

# initialize theta
theta = np.array([[0.,0.]]).T

# compute and display initial cost
# Note: You have to complete the code in computeCost.py
J = computeCost(X, y, theta)
print('\n -------------------------- \n')
print('cost: %0.4f ' % J)
print('Expected cost value (approx) 32.07')

# further testing of the cost function
J = computeCost(X, y, np.array([[-1, 2]]).T)
print('\n -------------------------- \n')
print('With theta = [-1 ; 2] Cost computed = %f' %J)
print('Expected cost value (approx) 54.24')

# compute Descent gradient
# initialize variables for learning rate and iterations
alpha = 0.01
iters = 1500

# perform gradient descent to "fit" the model parameters
# Note: You have to complete the code in gradientDescent.py
theta, cost_history, theta_history = gradientDescent(X, y, theta, alpha, iters)

# print theta to screen
print('\n -------------------------- \n')
print('Theta found by gradient descent: ')
print('%s %s' % (theta[0,0], theta[1,0]))
print('Expected theta values (approx)')
print(' -3.6303  1.1664')

# Checking the convergence
# Evolution du coût
fig= plt.figure(figsize=(12,8))
ax = plt.gca()
ax.plot(np.arange(iters), cost_history, color="blue", linewidth=2.0, linestyle="-")
ax.set_xlabel('iteration number')
ax.set_ylabel(r'Cost J($\theta$)')
ax.set_title('Error vs. Training Epoch (number of iters)')
ax.grid()
ax.set_xlim([-20,1600])
ax.set_ylim([4,7])

# Checking the goodness-of-fit
# Fit: calcul de la droite de régression
x = np.linspace(data.Population.min(), data.Population.max(), 100)
f = theta[0, 0] + (theta[1, 0] * x)

# Plot the linear fit
fig = plt.figure(figsize=(12,8))
ax = plt.gca()
ax.plot(x, f, 'r', label='Linear regression: h(x) = %0.2f + %0.2fx'%(theta[0,0],theta[1,0]))
ax.scatter(data.Population, data.Profit, label='Training Data')
ax.legend(loc=2)
ax.set_xlabel('Population')
ax.set_ylabel('Profit')
ax.set_title('Predicted Profit vs. Population Size')
ax.grid()
fig.show()

# Predict values for population sizes of 35,000 and 70,000
predict1 = np.array([[1, 3.5]]).dot(theta)
predict2 = np.array([[1, 7]]).dot(theta)
#predict1 = np.array([[1, 3.5]])@theta
#predict2 = np.array([[1, 7]])@theta

print('\n -------------------------- \n')
print('For population = 35,000, we predict a profit of {:.4f}'.format(predict1[0,0]*10000))
print('For population = 70,000, we predict a profit of {:.4f}'.format(predict2[0,0]*10000))

# ============= Part 4: Visualizing J(theta_0, theta_1) =============
print('\n -------------------------- \n')
print('Visualizing J(theta_0, theta_1) ...')

# Create grid coordinates for plotting
theta0 = np.linspace(-10, 10, 100)
theta1 = np.linspace(-1, 4, 100)
theta0, theta1 = np.meshgrid(theta0, theta1, indexing='xy')

Z = np.zeros((theta0.shape[0],theta1.shape[0]))

# Calculate Z-values (Cost) based on grid of coefficients
for (i,j),v in np.ndenumerate(Z):
    t = np.array([[theta0[i,j], theta1[i,j]]]).T
    Z[i,j] = computeCost(X,y, t)

fig = plt.figure(figsize=(15,6))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection='3d')

# Left plot
CS = ax1.contour(theta0, theta1, Z, np.geomspace(Z.min(),Z.max(),10), cmap=plt.cm.jet)
plt.clabel(CS, inline=1, fontsize=10)
ax1.scatter(theta_history[0,:],theta_history[1,:], c='r')
ax1.grid()

# Right plot
ax2.plot_surface(theta0, theta1, Z, rstride=1, cstride=1, alpha=0.6, cmap=plt.cm.jet, linewidth=0, antialiased=True)
ax2.set_zlabel('Cost')
ax2.set_zlim(Z.min(),Z.max())
ax2.view_init(elev=15, azim=230)
ax2.grid()

# settings common to both plots
for ax in fig.axes:
    ax.set_xlabel(r'$\theta_0$', fontsize=17)
    ax.set_ylabel(r'$\theta_1$', fontsize=17)

plt.show()


"""
Binning
-------

"""

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from IPython.display import display
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import OneHotEncoder

X, y = mglearn.datasets.make_wave(n_samples=100)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
reg = DecisionTreeRegressor(min_samples_split=3).fit(X, y)
plt.plot(line, reg.predict(line), label="Decision tree")
reg = LinearRegression().fit(X, y)
plt.plot(line, reg.predict(line), label="Linear regression")
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.legend(loc='Best')
plt.show()
# Comparing decision tree with linear regresssion
# Decision trees can model the data much more closely

bins = np.linspace(-3, 3, 11)
print 'bins: {}'.format(bins)
# Separate the linear space into 10 bins where
# the feature values range from -3 to -2.4, -2.4 to -1.8, etc

which_bin = np.digitize(X, bins=bins)
print '\nData points:\n{}'.format(X[:5])
print '\nBin membership:\n{}'.format(which_bin[:5])
# np.digitize encoded the continuous input feature
# into a discrete feature representing which bin the
# each point belongs to

encoder = OneHotEncoder(sparse=False).fit(which_bin)
X_binned = encoder.transform(which_bin)
print X_binned[:5]
print 'X_binned.shape: {}'.format(X_binned.shape)
# One hot encoder does the same thing as pandas.get_dummies (see categoricalvars.py)
# except it only works on integer categorical variables
# It separates the categorical feature into multiple binary features

line_binned = encoder.transform(np.digitize(line, bins=bins))
reg = LinearRegression().fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='Linear regression binned')
reg = DecisionTreeRegressor(min_samples_split=3).fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='Decision tree binned')
plt.plot(X[:,0], y, 'o', c='k')
plt.vlines(bins, -3, 3, linewidth=1, alpha=.2)
plt.legend(loc='best')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.show()
# Now the two lines are right on top of each other
# Binning data seems to help linear models make better predictions
# but it made the decision tree perform worse
# Decision trees in general can learn what bins to put data in
# based on multiple features

X_combined = np.hstack([X, X_binned])
print X_combined.shape
# One way to improve the linear model after binning is to add
# slopes back to the bins using np.hstack

reg = LinearRegression().fit(X_combined, y)
line_combined = np.hstack([line, line_binned])
plt.plot(line, reg.predict(line_combined), label='Linear regression combined')
for b in bins:
  plt.plot([b, b], [-3, 3], ':', c='k', linewidth=1)
plt.legend(loc='best')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.plot(X[:,0], y, 'o', c='k')
plt.show()
# The linear regression learned to separate the
# data into each bin and it learned a shared slope
# across all bins. This is less preferable than
# finding a separate slope across all bins

X_product = np.hstack([X_binned, X * X_binned])
print X_product.shape
# We can achieve separate slopes for each bin by
# taking a product of each bin indicator with
# the original input feature
# Each point has 20 features now, 10 indicate
# which bin its in and the other 10 are the corresponding
# products with the original input feature

reg = LinearRegression().fit(X_product, y)
line_product = np.hstack([line_binned, line * line_binned])
plt.plot(line, reg.predict(line_product), label='linear regression product')
for b in bins:
  plt.plot([b, b], [-3, 3], ':', c='k', linewidth=1)
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.legend(loc='best')
plt.show()
# This time, each bin has its own slope


import numpy as np
import sys

class PartyNN(object):

    def __init__(self, learning_rate=0.1):
        self.weights_0_1 = np.random.normal(0.0, 2 ** -0.5, (2, 3))
        self.weights_1_2 = np.random.normal(0.0, 1, (1, 2))
        self.signoid_mapper = np.vectorize(self.sigmoid)
        self.learning_rate = np.array([learning_rate])

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def predict(self, inputs):
        inputs_1 = np.dot(self.weights_0_1, inputs)
        outputs_1 = self.signoid_mapper(inputs_1)

        inputs_2 = np.dot(self.weights_1_2, outputs_1)
        outputs_2 = self.signoid_mapper(inputs_2)
        return outputs_2

    def train(self, inputs, expected_predict):
        inputs_1 = np.dot(self.weights_0_1, inputs)
        outputs_1 = self.signoid_mapper(inputs_1)

        inputs_2 = np.dot(self.weights_1_2, outputs_1)
        outputs_2 = self.signoid_mapper(inputs_2)
        actual_predict = outputs_2[0]

        error_layer_2 = np.array([actual_predict - expected_predict])
        gradient_layer_2 = actual_predict * (1 - actual_predict)
        weights_delta_layer_2 = error_layer_2 * gradient_layer_2
        self.weights_1_2 -= (np.dot(weights_delta_layer_2,
                                    outputs_1.reshape(1, len(outputs_1)))) * self.learning_rate

        error_layer_1 = weights_delta_layer_2 * self.weights_1_2
        gradient_layer_1 = outputs_1 * (1 - outputs_1)
        weights_delta_layer_1 = error_layer_1 * gradient_layer_1
        self.weights_0_1 -= np.dot(inputs.reshape(len(inputs), 1),
                                   weights_delta_layer_1).T * self.learning_rate

def MSE(y, Y):
    return np.mean((y-Y)**2)

train = [
    ([0, 0, 0], 0),
    ([0, 0, 1], 1),
    ([0, 1, 0], 0),
    ([0, 1, 1], 0),
    ([1, 0, 0], 1),
    ([1, 0, 1], 1),
    ([1, 1, 0], 0),
    ([1, 1, 1], 1),
]

epochs = 5000
learning_rate = 0.05

network = PartyNN(learning_rate=learning_rate)

for e in range(epochs):
    inputs_ = []
    correct_predictions = []
    for input_stat, correct_predict in train:
        network.train(np.array(input_stat), correct_predict)
        inputs_.append(np.array(input_stat))
        correct_predictions.append(np.array(correct_predict))

    train_loss = MSE(network.predict(np.array(inputs_).T),
                     np.array(correct_predictions))
    sys.stdout.write("\rProgress: {}, Training loss: {}".format(
        str(100 * e/float(epochs))[:4], str(train_loss)[:5]))

for input_stat, correct_predict in train:
    print("For input: {} the prediction is: {}, expected: {}".format(
        str(input_stat),
        str(network.predict(np.array(input_stat)) > .5),
        str(correct_predict == 1)
    ))


def f(x):
    ret = x
    for _ in range(1, 4):
        ret = 4 * ret * (1 - ret)
    return ret

# Symbolic
import sympy  # noqa: E402

symbolic_x = sympy.symbols("x")
symbolic_f = f(symbolic_x)
symbolic_fprime = symbolic_f.diff(symbolic_x)
print(symbolic_fprime)
print(f"f'(x=2): {symbolic_fprime.subs(symbolic_x, 2)}")

# Nuermic
def df_numeric(x, delta=1e-8):
    return (f(x + delta) - f(x)) / delta

print(f"f'(x=2): {df_numeric(x=2)}")

# Automatic
import jax  # noqa: E402

print(f"f'(x=2): {jax.grad(f)(2.0)}")
print(f"f''(x=2): {jax.grad(jax.grad(f))(2.0)}")
print(f"f'''(x=2): {jax.grad(jax.grad(jax.grad(f)))(2.0)}")


import numpy as np

training_inputs = np.array([[0,0,0,0,0,0,0,0,0,0],
                            [0,1,0,0,1,1,0,1,0,0],
                            [1,0,1,1,0,1,1,1,1,1],
                            [1,0,1,0,1,0,1,1,0,0],
                            [1,0,0,0,1,1,0,0,1,1],
                            [1,0,0,1,0,1,0,1,1,1],
                            [0,1,1,0,1,1,0,1,0,0],
                            [1,0,1,0,0,1,0,0,0,1],
                            [1,1,0,1,0,0,1,1,0,0]])

training_outputs = np.array([[0,0,1,1,0,0,1,1,0]]).T

print (training_inputs)
print (training_outputs)

def nonlin(x, deriv=False):

    if deriv==True:
        return x*(1-x)

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

np.random.seed(1)

synaptic_weight=2*np.random.random((10,1))-1

for iter in range(20000):

    gettrain=nonlin(np.dot(training_inputs,synaptic_weight))

    training_error = training_outputs - gettrain

    #multiply how much we missed with the slope of the sigmoid(nonlinear)

    train_delta= training_error * nonlin(gettrain,True)

    synaptic_weight += np.dot(training_inputs.T,train_delta)

test_array= np.array([1,1,0,1,1,1,1,1,1,1])

print (nonlin(np.dot(test_array,synaptic_weight)))


import numpy as np
from sklearn.metrics import mean_squared_error
from numpy.linalg import norm
import matplotlib.pyplot as plt

class RBF:
    def __init__(self, num_nodes, sigma, mus=None):
        """ Radial Basis Functions in a Network
         Args:
              num_nodes (int): number of nodes (RBFs) in the network
        """

        self.num_nodes = num_nodes
        self.weights = None
        self.sigma = sigma
        self.mus = mus # None

    def _kernel_function(self, mu, x, sigma):
        return np.exp(-((np.linalg.norm(np.array(x)- np.array(mu))) ** 2) / (2 * sigma ** 2)) # numpy.linalg.norm: euclidean dist

    def _compute_interpolation_matrix(self, x):
        """Calculates the interpolation matrix using the kernel_function
        Args:
            x(ndarray): data points

        """
        matrix = np.zeros((len(x), self.num_nodes))
        for n in range(len(x)):
            for m in range(self.num_nodes):
                matrix[n, m] = self._kernel_function(self.mus[m], x[n], self.sigma)
        return matrix

    def _select_mus(self, x):
        """ Randomly selecting mus"""

        if len(x) < self.num_nodes:
            print("Warning: Number of nodes shouldn't be bigger than the input")

        # if mus not manually initialized, create random mus
        if self.mus is None: ######## eventuell if statement wieder löschen
            idx = np.random.choice(len(x), self.num_nodes) # out of all training samples choose randomly self.num_nodes mus
            self.mus = x[idx]

    def fit(self, x, y):
        """ Fit weights according to the least square rule
        Args:
            x (ndarray): training data points
            y (ndarray): training targets
        """
        self._select_mus(x)
        #print("self.mus",self.mus)
        phi_matrix = self._compute_interpolation_matrix(x)
        self.weights = np.dot(np.linalg.pinv(phi_matrix), y) # w = phi^(-1) * f, from equation (5); np.linalg.pinv: inverse

    def fit_delta(self, x, y, learning_rate=1e-3, num_epochs=10, error_scores=False):
        """ Fit weights according to the delta rule
        Args:
            x (ndarray): training data points
            y (ndarray): training targets
        """

        self._select_mus(x)
        #print("self.mus",self.mus)
        self.weights = np.random.normal(0, 0.3, self.num_nodes)
        errors = []
        for n in range(num_epochs):
            for i in range(len(x)):
                phi_matrix = self._compute_interpolation_matrix(np.array([x[i]]))
                delta_weights = learning_rate * (y[i] - self.predict([x[i]])) * phi_matrix
                self.weights += delta_weights.reshape(self.weights.shape[0])
            if error_scores:
                mse = mean_squared_error(y,self.predict(x))
                errors.append(mse)

        if error_scores:
            return errors

    def predict(self, x):
        """
        # Arguments
            X: test data
        # Input shape
            (num_test_samples, input_shape)
        """
        if self.weights is None:
            raise ValueError("Radial Basis Function Network is not yet fitted")

        phi_matrix = self._compute_interpolation_matrix(x)
        oputput = np.dot(phi_matrix, self.weights)
        return oputput

def cl_competitive(num_nodes, input_space, iterations=20, lr=0.1, num_winners=1):
    input_space = input_space.reshape(len(input_space), -1)
    idx = np.random.choice(len(input_space), num_nodes)
    nodes = input_space[idx] # nodes is shape (num_nodes, 1)
    for i in range(iterations):
        np.random.shuffle(input_space)
        for x in input_space:
            distances = norm(nodes - np.array([x]), axis = 1)
            winners = distances.argsort()[:num_winners]
            for winner in winners:
                nodes[winner] += lr * (x - nodes[winner])

    return nodes

class SOM():
    def __init__(self, num_nodes=100):
        self.num_nodes = num_nodes

    def fit(self, x, batch_size=1, epochs=20, neighbours_init=50, lr=0.2, two_d = None):
        """
        Args:
            neighbours_init (int): number of nodes which are adjusted per sample for the first epoch
            x (pandas.DataFrame): (32x84) pandas.DataFrame with 32 animals (rows) and 84 attributes (columns)
        """

        self.weights = self.initialize_weights(x)
        #print("self.weights: ",self.weights)
        num_neighbours = neighbours_init

        for e in range(epochs):
            for sample in x: # one animal at a time
                rest_lower = None
                rest_upper = None
                winners = []

                #Calculate Winner
                dist = norm(sample - self.weights, axis=1)
                winner = dist.argsort()[:1] # find row of weight matrix with shortest distance to sample
                #print("winner",winner)

                #Calculate neighbourhood
                if two_d is None:
                    upper_bound = winner + int((num_neighbours + 1)/2)
                    if upper_bound >= self.num_nodes: # either upper_bound > num_nodes or lower_bound < 0, but XOR
                        rest_lower = upper_bound - self.num_nodes
                        winners.extend(np.arange(0, rest_lower).tolist())
                        upper_bound = self.num_nodes
                    lower_bound = winner - int((num_neighbours + 1)/2)
                    if lower_bound < 0:
                        rest_upper = self.num_nodes + lower_bound
                        winners.extend(np.arange(rest_upper, self.num_nodes).tolist())
                        lower_bound = 0
                    winners.extend(np.arange(lower_bound, upper_bound).tolist())
                else: # two-dimensional case: get surrounding neighbours
                    winners.extend(winner)

                    if winner == 0:
                        winners.extend(self.get_neighbourhood(winner, up=False, down=True, left=False, right=True))
                    elif winner == 9:
                        winners.extend(self.get_neighbourhood(winner, up=False, down=True, left=True, right=False))
                    elif winner == 90:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=False, left=False, right=True))
                    elif winner == 99:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=False, left=True, right=False))
                    elif 1 <= winner <= 8:
                        winners.extend(self.get_neighbourhood(winner, up=False, down=True, left=True, right=True))
                    elif 91 <= winner <= 98:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=False, left=True, right=True))
                    elif (winner%10) == 0:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=True, left=False, right=True))
                    elif (winner%10) == 9:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=True, left=True, right=False))
                    else:
                        winners.extend(self.get_neighbourhood(winner, up=True, down=True, left=True, right=True))

                #print("winners:",winners)

                for weight_idx in winners:
                    self.weights[weight_idx] += lr * (sample - self.weights[weight_idx])
            num_neighbours -= (neighbours_init/epochs)
        print("self.weights: ",self.weights.shape)

    def initialize_weights(self, x):
        """Initialized weight matrix with random numbers between zero and one"""
        return abs(np.random.randn(self.num_nodes,x.shape[1])) # (100x84)

    def predict(self, x):
        """Calculate winner nodes for every data point"""
        pos = []
        for sample in x:
            dist = norm(sample - self.weights, axis=1)
            pos.append(dist.argsort()[:1])
        return pos

    def get_neighbourhood(self, x, up=False, down=False, left=False, right=False):
        add_winners = []

        if up == True:
            add_winners.extend(x-10)
        if down == True:
            add_winners.extend(x+10)
        if left == True:
            add_winners.extend(x-1)
        if right == True:
            add_winners.extend(x+1)

        return add_winners


import numpy as np
import math

def sum_squared_error( outputs, targets, derivative=False ):
    if derivative:
        return outputs - targets
    else:
        return 0.5 * np.sum( np.power(outputs - targets,2) )
#end cost function

def hellinger_distance( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    root_difference = np.sqrt( outputs ) - np.sqrt( targets )

    if derivative:
        return root_difference/( np.sqrt(2) * np.sqrt( outputs ))
    else:
        return np.sum( np.power(root_difference, 2 )) / math.sqrt( 2 )
#end cost function

def cross_entropy_cost( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    if derivative:
        return (outputs - targets) / (outputs * (1 - outputs))
    else:
        return np.mean(-np.sum(targets * np.log( outputs ) + (1 - targets) * np.log(1 - outputs), axis=1))
#end cost function

def softmax_cross_entropy_cost( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    if derivative:
        return outputs - targets
    else:
        return np.mean(-np.sum(targets * np.log( outputs ), axis=1))
#end cost function

def exponential_cost( outputs, targets, derivative=False, tau = 2.0 ):
    diff = outputs - targets
    cost = tau * np.exp( np.sum( diff**2 ) / tau )

    if derivative:
        return 2/tau * diff * cost

    return np.sum(cost) - tau
#end cost function


import numpy as np
from bisect import bisect_right

def list_prime(target: int) -> np.ndarray:
    smallest_odd_multiple_of_n_geq_m: int = lambda n, m : m + (n-m) % (2*n)
    primes = np.array([2, 3, 5, 7], dtype=np.uint64)
    index_segment_max = 3
    extend = 7
    segment_min, segment_max = 9, 49

    while segment_max < target:
        is_prime = np.full(shape=(segment_max-segment_min)//2, fill_value=True, dtype=bool)
        for p in primes[1:index_segment_max]:
            p = int(p)
            is_prime[(smallest_odd_multiple_of_n_geq_m(p, segment_min) - segment_min) // 2 :: p] = False
        new_primes = np.arange(segment_min, segment_max, 2, dtype=np.uint64)[is_prime]
        old_len = len(primes)
        primes.resize(old_len + len(new_primes))
        primes[old_len:] = new_primes
        index_segment_max += extend
        segment_min, segment_max = segment_max, int(primes[index_segment_max]) ** 2

    is_prime = np.full(shape=(target-segment_min+3)//2, fill_value=True, dtype=bool)
    for p in primes[1:bisect_right(primes, target**.5)]:
        p = int(p)
        is_prime[(smallest_odd_multiple_of_n_geq_m(p, segment_min) - segment_min) // 2 :: p] = False
    new_primes = np.arange(segment_min, target+2, 2, dtype=np.uint64)[is_prime]
    old_len = len(primes)
    primes.resize(old_len + len(new_primes))
    primes[old_len:] = new_primes

    return primes

def main():
    from time import time
    target = 10**9
    start = time()
    out = list_prime(target)
    elapsed = time() - start
    print(len(out))
    print('Done in: ', elapsed, 'seconds')

if __name__ == '__main__':
    main()


from scipy.stats import linregress

def linear_regression(cases, air_qualities):
    '''takes a list of cases and list of air_qualities as inputs. Returns the results
    of linear regression analysis performed by scipy.stats'''
    return linregress(cases, air_qualities)

def check_if_entry_exists(state, city, from_date, to_date):
    ''' Takes state, city, from_date, to_date, as inputs for linear regression analysis.
    Tries to open results.txt and checks if there's already results for the inputs. Returns a
    boolean to avoid entering duplicate data'''
    location = f"Location: {city}, {state}\n"
    start_date = f"Start Date: {from_date}\n"
    end_date = f"End Date: {to_date}\n"
    try:
        results_file = open("results.txt", "r")
    except:
        return False

    lines = results_file.readlines()
    return location in lines and start_date in lines and end_date in lines

def write_stats_to_file(data, state, city):
    '''Takes data, state, and city where data is a dictionary of the cases, air qualities,
    and dates for the given state. First checks if there is already an entry in results.txt
    for these parameters and if not, performs linear regression analysis and records results.'''
    from_date = data["Date"][0]
    to_date = data["Date"][-1]
    if check_if_entry_exists(state, city, from_date, to_date):
        return

    with open("results.txt", "a") as results_file:
        slope, y_intercept, corr_coef, p_value, std_error = \
            linear_regression(data["COVID-19 Cases"], data["Air Pollution (PM2.5)"])
        coef_of_det = corr_coef ** 2

        results_file.write("==========================\n")
        results_file.write("Linear Regression Analysis\n")
        results_file.write(f"Location: {city}, {state}\n")
        results_file.write(f"Start Date: {from_date}\n")
        results_file.write(f"End Date: {to_date}\n")
        results_file.write(
            f"Equation: Air Pollution = {slope} * COVID-19 Cases + {y_intercept}\n"
        )
        results_file.write(f"Pearson's Correlation Coefficient (R): {corr_coef}\n")
        results_file.write(f"Coefficient of Determination (R^2): {coef_of_det}\n")
        results_file.write(f"P-Value: {p_value}\n")
        results_file.write(f"Standard Error: {std_error}\n")

        results_file.write("==========================\n")
        results_file.write("\n")

def clear_results_file():
    ''' If the user chooses to do so, clears the contents of results.txt.'''
    open("results.txt", "w").close()


""" ReLU Layer """

import numpy as np
def Relu(x):
	y=np.copy(x)
	y[x<0]=0
	return y
def Relu_deriv(x):
	y=np.copy(x)
	y[x<0]=0
	y[x>0]=1
	return y
class ReLULayer():
	def __init__(self):
		"""
		Applies the rectified linear unit function element-wise: relu(x) = max(x, 0)
		"""
		self.trainable = False # no parameters

	def forward(self, Input, **kwargs):

		############################################################################
	    # TODO: Put your code here
		# Apply ReLU activation function to Input, and return results.
		self.Input=Input
		return Relu(Input)
	    ############################################################################

	def backward(self, delta):

		############################################################################
	    # TODO: Put your code here
		# Calculate the gradient using the later layer's gradient: delta

		return np.multiply(delta,Relu_deriv(self.Input))
	    ############################################################################


import numpy as np

def binomialCoefficient(n, k):
    if k < 0 or k > n:   return 0
    if k == 0 or k == n: return 1

    k = min(k, n - k)  # Take advantage of geometry

    k_range = np.arange(k)
    c = np.prod((n - k_range) / (k_range + 1))

    return c

def bernstein(i, n, t):
    return binomialCoefficient(n, i) * (t**i) * ((1 - t)**(n - i))

def catmull(ps, t):
    # https://github.com/ppy/osu-framework/blob/050a0b8639c9bd723100288a53923547ce87d487/osu.Framework/Utils/PathApproximator.cs#L449
    t2 = t * t
    t3 = t2 * t
    return 0.5 * (
        2 * ps[1]
        + (ps[2] - ps[0]) * t
        + (2 * ps[0] - 5 * ps[1] + 4 * ps[2] - ps[3]) * t2
        + (ps[3] - 3 * ps[2] + 3 * ps[1] - ps[0]) * t3
    )

def bound(min_val, max_val, value):
    return min(max(value, min_val), max_val)

def frange(start, stop, step):
    curr = start
    while curr < stop:
        yield curr
        curr += step

def binary_search(values, value):
    low = 0
    high = len(values)
    while low < high:
        mid = (low + high) // 2
        if values[mid] < value:
            low = mid + 1
        elif values[mid] > value:
            high = mid
        else:
            return mid
    return low

def value_to_percent(min_val, max_val, value):
    return 1.0 - ((max_val - bound(min_val, value, max_val)) / (max_val - min_val))

# Returns a triangle wave function
def triangle(val, amplitude):
    return np.abs((np.fmod(val + (amplitude / 2.0), amplitude)) - (amplitude / 2.0))

# Linear interpolation
# Percent: 0.0 -> 1.0
def lerp(low, high, percent):
    return low * (1.0 - percent) + high*percent

def dist(a, b):
    dx = a[0] - b[0]
    dy = a[1] - b[1]
    return (dx*dx + dy*dy)**0.5

def intersect(a, ta, b, tb, precision):
    des = tb[0]*ta[1] - tb[1]*ta[0]
    if abs(des) < precision: return None

    u = ((b[1] - a[1])*ta[0] + (a[0] - b[0])*ta[1]) / des
    return b + tb*u


# John Mourad (jmm5kg)
# Canny Edge Detector
# Import some libraries
import numpy, skimage.io, pylab, math, sys, skimage.color
import scipy.ndimage.filters, scipy.signal, scipy.misc

def canny_edges(A):
    # Convert image to float
    A = skimage.img_as_float(A)
    A = skimage.color.rgb2gray(A)

    # Build Gaussian filter
    gauss_dim = 15
    gauss_row = scipy.signal.gaussian(gauss_dim, 1)
    gauss_filter = numpy.array([[gauss_row[x] * gauss_row[y] for x in range(gauss_dim)] for y in range(gauss_dim)])

    # Convolve our image with the Gaussian filter
    C = scipy.ndimage.filters.convolve(A, gauss_filter)

    # Compute the gradient
    gradient = numpy.gradient(C)
    x_gradient = gradient[0]
    y_gradient = gradient[1]

    height = gradient[0].shape[0]
    width = gradient[0].shape[1]

    # Initialize magnitude and direction arrays
    magnitudes = numpy.array([[0.0 for w in range(width)] for h in range(height)])
    directions = numpy.array([[0.0 for w in range(width)] for h in range(height)])

    # PI constant
    pi = math.pi

    # Compute magnitude and direction of gradient
    for i in range(height):
        for j in range(width):
            mag = math.sqrt((x_gradient[i][j] ** 2) + (y_gradient[i][j] ** 2))
            direction = math.atan2(y_gradient[i][j], x_gradient[i][j])
            if direction < 0:
                direction = direction + pi
            magnitudes[i][j] = mag
            directions[i][j] = direction

    # Categorize direction of gradient
    for i in range(height):
        for j in range(width):
            if 0 <= directions[i][j] < (pi / 8):
                directions[i][j] = 0
            if (pi / 8) <= directions[i][j] < (3 * pi / 8):
                directions[i][j] = (pi / 4)
            if (3 * pi / 8) <= directions[i][j] < (5 * pi / 8):
                directions[i][j] = (pi / 2)
            if (5 * pi / 8) <= directions[i][j] < (7 * pi / 8):
                directions[i][j] = (3 * pi / 4)
            if (7 * pi / 8) <= directions[i][j] <= pi:
                directions[i][j] = pi

    # Create copy of magnitudes for thinned image
    thinned_image = numpy.copy(magnitudes)

    # Non-maximum suppression
    for i in range(1, height - 1):
        for j in range(1, width - 1):
            if directions[i][j] == 0 or directions[i][j] == pi:
                if magnitudes[i][j] < magnitudes[i - 1][j] or magnitudes[i][j] < magnitudes[i + 1][j]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (pi / 4):
                if magnitudes[i][j] < magnitudes[i - 1][j - 1] or magnitudes[i][j] < magnitudes[i + 1][j + 1]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (pi / 2):
                if magnitudes[i][j] < magnitudes[i][j - 1] or magnitudes[i][j] < magnitudes[i][j + 1]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (3 * pi / 4):
                if magnitudes[i][j] < magnitudes[i - 1][j + 1] or magnitudes[i][j] < magnitudes[i + 1][j - 1]:
                    thinned_image[i][j] = 0

    # Establish high and low thresholds
    # flower.jpg thresholds
    t_low = 0.1
    t_high = 0.5

    # building.jpg thresholds
    # t_low = 0.2
    # t_high = 0.5

    # Initialize status array
    status = numpy.array([["" for w in range(width)] for h in range(height)])

    # Initialize array to hold strong edge pixels
    strong = []

    # Hysteresis thresholding
    for i in range(height):
        for j in range(width):
            if thinned_image[i][j] < t_low:
                status[i][j] = "N"  # not strong edge
                thinned_image[i][j] = 0
            if thinned_image[i][j] > t_high:
                status[i][j] = "S"  # strong edge
                thinned_image[i][j] = 1
                strong.append((i, j))
            if t_low <= thinned_image[i][j] <= t_high:
                status[i][j] = "W"  # weak edge

    # Iterative DFS for connected neighbor algorithm
    visited = {}

    while len(strong) > 0:
        v = strong.pop()  # pop from stack

        if v not in visited:
            visited[v] = 1  # mark as visited

            # if a weak edge, mark as strong
            if status[v[0]][v[1]] == "W":
                status[v[0]][v[1]] = "S"
                thinned_image[v[0]][v[1]] = 1

            # visit bottom row
            if 0 <= v[0] + 1 < height and 0 <= v[1] + 1 < width:
                if (v[0] + 1, v[1] + 1) not in visited:
                    strong.append((v[0] + 1, v[1] + 1))

            if 0 <= v[0] + 1 < height and 0 <= v[1] < width:
                if (v[0] + 1, v[1]) not in visited:
                    strong.append((v[0] + 1, v[1]))

            if 0 <= v[0] + 1 < height and 0 <= v[1] - 1 < width:
                if (v[0] + 1, v[1] - 1) not in visited:
                    strong.append((v[0] + 1, v[1] - 1))

            # visit middle row
            if 0 <= v[0] < height and 0 <= v[1] + 1 < width:
                if (v[0], v[1] + 1) not in visited:
                    strong.append((v[0], v[1] + 1))

            if 0 <= v[0] < height and 0 <= v[1] - 1 < width:
                if (v[0], v[1] - 1) not in visited:
                    strong.append((v[0], v[1] - 1))

            # visit top row
            if 0 <= v[0] - 1 < height and 0 <= v[1] + 1 < width:
                if (v[0] - 1, v[1] + 1) not in visited:
                    strong.append((v[0] - 1, v[1] + 1))

            if 0 <= v[0] - 1 < height and 0 <= v[1] < width:
                if (v[0] - 1, v[1]) not in visited:
                    strong.append((v[0] - 1, v[1]))

            if 0 <= v[0] - 1 < height and 0 <= v[1] - 1 < width:
                if (v[0] - 1, v[1] - 1) not in visited:
                    strong.append((v[0] - 1, v[1] - 1))

    # Mark any remaining weak edges as no edges
    for i in range(height):
        for j in range(width):
            if status[i][j] == "W":
                status[i][j] = "N"
                thinned_image[i][j] = 0

    return thinned_image

if __name__ == "__main__":
    img_file = sys.argv[1]
    save_file = sys.argv[2]

    #  Load an image
    A = skimage.io.imread(img_file, True)

    thinned_image = canny_edges(A)
    # Show the result
    # pylab.imshow(thinned_image, cmap="gray")
    # pylab.show()

    scipy.misc.imsave(save_file, thinned_image)


import numpy as np
import csv
import sys

#Avoid log0=-inf
def c_log(x):
	return np.log10(x + 0.0000001)

def read_network(filepath):
	nt = []
	with open(filepath) as csvfile:
		csvreader = csv.reader(csvfile, delimiter = ",")

		for row in csvreader:
			row_int = [int(s) for s in row]
			nt.append(row_int)

	nt = np.array(nt, dtype = int)

	return nt

#Initialize chromosome population
def initialize_pop(N, S, k):
	pop = np.random.randint(low = 0, high = k, size = (N, S))
	return pop

#Calculate AIC for a particular grouping (ch)
#p_ij = l_ij/S_iS_j
def calc_AIC(nt, ch, k, S):
	k_dict = {i:[] for i in range(k)}
	for i in range(S):
		k_dict[ch[i]].append(i)

	loglikehood = 0.
	for i in range(k):
		for j in range(k):
			nodes_i, nodes_j = k_dict[i], k_dict[j]
			s_i, s_j = len(nodes_i), len(nodes_j)
			l_ij = sum([nt[x, y] for x in nodes_i for y in nodes_j])
			p_ij = l_ij / (s_i * s_j + 0.0000001)
			loglikehood += (l_ij * c_log(p_ij) + (s_i * s_j - l_ij) * (c_log(1. - p_ij)))

	AIC = 2. * k * k + 2. * S - (2. * loglikehood)

	return AIC

def locally_optimize(pop, nt, k, S, N):
	#Iterate to locally optimize each chromosome
	for i in range(N):
		ch = pop[i]
		best_score = calc_AIC(nt, ch, k, S)
		best_neighbor = ch.copy()
		cont = True
		cycles = 0

		#Hill climbing: search for the best neighbor
		while cont:
			cycles += 1
			starter_best_score = best_score
			starter_best_neighbor = best_neighbor
			for position in range(S):
				for group in range(k):
					#try every possible neighbor differing by this position
					if group != ch[position]:
						temp_ch = starter_best_neighbor.copy()
						temp_ch[position] = group
						temp_score = calc_AIC(nt, temp_ch, k, S)
						if temp_score < best_score:
							best_score = temp_score
							best_neighbor = temp_ch.copy()
			if starter_best_score == best_score or cycles > 1000:
				cont = False

		pop[i] = best_neighbor.copy()

	return pop

def simulate_pop_one_gen(pop, nt, N, S, M, k):
	#Hill climbing
	pop = locally_optimize(pop, nt, k, S, N)

	#Reproduction
	AICs = np.array([calc_AIC(nt, ch, k, S) for ch in pop])

	fitnesses = 1. / AICs
	wbar = np.mean(fitnesses)
	freqs = (1. / N) * (fitnesses / wbar)
	new_nums = np.random.multinomial(N, pvals = freqs)

	#Mutation
	new_pop = np.array([pop[i] for i in range(N) for j in range(new_nums[i])])
	mutations = np.random.binomial(n = 1, p = M, size = (N, S))
	mutations_index = np.where(mutations == 1)
	targets = np.random.randint(low = 0, high = k, size = len(mutations_index[0]))

	for i in range(len(targets)):
		new_pop[mutations_index[0][i], mutations_index[1][i]] = targets[i]

	return new_pop

def simulate_pop(pop, nt, N, S, M, k, MAX_GEN):
	cont = True
	gens = 0

	while cont:
		gens += 1
		pop = simulate_pop_one_gen(pop, nt, N, S, M, k)
		print(gens, flush = True)
		neighbor_equal = [np.array_equal(pop[i], pop[i + 1]) for i in range(N - 1)]
		if all(neighbor_equal) or gens > MAX_GEN:
			cont = False
			print(gens, flush = True)

	score = calc_AIC(nt, pop[0], k, S)
	grouping = pop[0]

	return score, grouping

def main():
	SEED = 1024
	MAX_GEN = 1000
	N = 1000 		#size of the chromosome's population
	M = 3. 	        #expected number of mutations in each generation
	MIN_K = 1       #minimum number of groups
	MAX_K = 6       #maximum number of groups
	S = 0 			#initialize num of nodes in the network

	if len(sys.argv) > 1:
		NETWORK_PATH = sys.argv[1]
	else:
		NETWORK_PATH = "tests/adj_m_small.csv"
	BEST_SCORES = np.ones(MAX_K) * np.inf
	BEST_GROUPING = []

	######################SCRATCH TESTS######################
	TEST = True
	if TEST:
		N = 20
		MIN_K = 5
		MAX_K = 15
		MAX_GEN = 100
		M = 3.

	#########################################################

	print("N = {}".format(N), flush = True)
	np.random.seed(SEED)

	#Read network
	nt = read_network(NETWORK_PATH) #adj matrix; np arr
	S = len(nt)
	MAX_K = min(MAX_K, S) #cannot have more groups than nodes
	assert(S > 0)
	M = M / (N * S)

	#Grouping
	for k in range(MIN_K, MAX_K + 1):
		pop = initialize_pop(N, S, k)
		best_score, best_group = simulate_pop(pop, nt, N, S, M, k, MAX_GEN)
		print("k =", k, "AIC =", best_score, "grouping:", best_group, flush = True)

if __name__ == "__main__":
	main()


import numpy as np
from Models.SPDImage import *
from PIL import Image
import numba
from scipy.optimize import minimize

class HistogramModelDriver:

    @staticmethod
    @numba.jit(nopython=True)
    def histogram(image: np.ndarray, pixel_colors_count: int) -> (np.ndarray, str):
        h = [0] * pixel_colors_count
        for x in range(image.shape[0]):
            for y in range(image.shape[1]):
                i = int(image[x, y])
                h[i] = h[i] + 1
        return np.array(h), '_hist'

    @staticmethod
    @numba.jit(nopython=True)
    def cdf(histogram: np.ndarray) -> (np.ndarray, str):
        h = [0] * len(histogram)
        for x in range(len(histogram)):
            for y in range(x + 1):
                h[x] = h[x] + histogram[y]

        return np.array(h), '_cdf'

    @staticmethod
    def inverse_cdf(cdf: np.ndarray):
        x_array = np.linspace(0, len(cdf) - 1, len(cdf))
        y_array = cdf

        max_x = x_array[len(cdf) - 1]
        max_y = y_array[len(cdf) - 1]

        new_x_array = y_array.astype(np.float64)
        new_y_array = x_array

        new_y_array *= int(max_y / max_x)
        new_x_array /= int(max_y / max_x)

        for i in range(len(new_x_array)):
            new_x_array[i] = round(new_x_array[i])

        return [new_x_array.astype(np.int64), new_y_array]

    @staticmethod
    @numba.jit(nopython=True)
    def equalize_numba(image: np.ndarray, cdf_x: np.ndarray, cdf_y: np.ndarray) -> np.ndarray:
        new_image = image
        cdf_min = np.min(cdf_y)

        for x in range(image.shape[0]):
            for y in range(image.shape[1]):
                idx = int(cdf_x[new_image[x, y]])
                new_image[x, y] = round(
                    (cdf_y[idx] - cdf_min) * 255.0 / (image.shape[0] * image.shape[1] - 1)
                )

        return new_image

    @staticmethod
    def equalize_image(image: SPDImage, max_type_colors_count=-1, is_inverse_cdf=False):
        if max_type_colors_count < 0:
            max_type_colors_count = int(image.max_type_colors_count())

        histogram = np.array(HistogramModelDriver.histogram(
            np.array(image.modified_image),
            max_type_colors_count
        )[0])

        cdf_y = np.array(HistogramModelDriver.cdf(histogram)[0])
        cdf_x = np.linspace(0, len(cdf_y) - 1, len(cdf_y))

        if is_inverse_cdf:
            cdf = HistogramModelDriver.inverse_cdf(cdf_y)
            cdf_y = cdf[1]
            cdf_x = cdf[0]

        cdf_x = np.array(cdf_x)
        cdf_y = np.array(cdf_y)

        image_equalized = HistogramModelDriver.equalize_numba(np.array(image.modified_image), cdf_x, cdf_y)

        image.update(np.abs(image_equalized), '_cdf_normalize')


""".. include:: ../_notebooks/01-shannon.rst"""

from typing import Optional
import numpy as np

########################################################################
class Shannon:
    """Shannon entropy"""

    # ----------------------------------------------------------------------
    def __new__(self, data: np.ndarray, bins: Optional[int] = 16, base: Optional[int] = 2, conditional: Optional[int] = None) -> float:
        """Calculate the shannon entropy for `n` variables.

        If `n` is higher than 1, then the joint entropy is calculated.
        """
        # Data must be nxm
        if len(data.shape) == 1:
            data = data.reshape(1, -1)

        dist = self.dist(data, bins)

        if not conditional is None:
            return np.sum(- dist * np.log(dist) / np.log(base)) - (self.__new__(self, data[conditional], bins, base))
        else:
            return np.sum(- dist * np.log(dist) / np.log(base))

    # ----------------------------------------------------------------------
    @classmethod
    def dist(cls, x: np.ndarray, bins: int) -> np.ndarray:
        """Calculate the joint probability of `x`."""

        jointProbs, edges = np.histogramdd(x.T, bins=bins, normed=True)
        jointProbs /= jointProbs.sum()
        dist = jointProbs[jointProbs != 0]

        return dist


import numpy as np

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

def sigmoid2(x):
    try:
        iter(x)
    except TypeError:
        return sigmoid(x)
    else:
        ret = []
        for item in x:
            if item >= 0:
                z = np.exp(-item)
                ret.append(1 / (1 + z))
            else:
                z = np.exp(item)
                ret.append(z / (1 + z))
        return np.array(ret)


import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm

def get_random_coef(size=10):
    return np.random.normal(size=size)*np.random.binomial(n=1, p=0.3, size=size)

def make_random_fractal(imgsize=1000, max_iter=200, verbose=False):
    coef = get_random_coef()
    return coef, make_fractal(coef, imgsize=imgsize, max_iter=max_iter, verbose=verbose)

"""
This is a macro that will print out strings you need for f(z) and df(z)
if you want to change the power of the polynomial. You will also want to change the size variable in
get_random_coef
"""
def print_f_df(power):
    f = ""
    df = ""
    for i in range(power):
        f += "coef[{}]".format(i)+"*z"*i + "+"
        if i>0:
            df += "{}*coef[{}]".format(i, i)+"*z"*(i-1) + "+"
    print(f[:-1])
    print(df[:-1])

def make_fractal(coef, imgsize=1000, max_iter=200, verbose=False):
    if verbose:
        print("Creating fractal for ", coef)
    def f(z):
        return coef[0]+coef[1]*z+coef[2]*z*z+coef[3]*z*z*z+coef[4]*z*z*z*z+coef[5]*z*z*z*z*z+coef[6]*z*z*z*z*z*z+coef[7]*z*z*z*z*z*z*z+coef[8]*z*z*z*z*z*z*z*z+coef[9]*z*z*z*z*z*z*z*z*z
    def df(z):
        return 1*coef[1]+2*coef[2]*z+3*coef[3]*z*z+4*coef[4]*z*z*z+5*coef[5]*z*z*z*z+6*coef[6]*z*z*z*z*z+7*coef[7]*z*z*z*z*z*z+8*coef[8]*z*z*z*z*z*z*z+9*coef[9]*z*z*z*z*z*z*z*z

    y, x = np.ogrid[1: -1: imgsize*2j, -1: 1: imgsize*2j]
    z = x +y*1j

    img = np.zeros(z.shape)
    it = range(max_iter)
    if verbose:
        it = tqdm(range(max_iter))
    for i in it:
        z = z - f(z)/df(z)
        ind = np.logical_and(np.abs(f(z)) < 1e-4, img==0)
        img[ind] = i

    return img

def save(img, outfile, cmap="gist_yarg", imgsize=1000):
    fig = plt.figure(figsize=(imgsize/100.0, imgsize/100.0), dpi=100)
    ax = fig.add_axes([0, 0, 1, 1], aspect=1)
    ax.axis('off')
    ax.imshow(img, cmap=cmap)
    fig.savefig(outfile)
    plt.close(fig)


# Starting code for HW 2. Add to this!
import numpy as np
import scipy.linalg as linalg

tol = 1e-12 # tolerance

# Function definitions:
f1 = lambda x: np.cos(x[0] + x[1]) - x[1]**2
f2 = lambda x: np.sin(x[0] - x[1]) - x[0]*x[1]
f = lambda x: (f1(x), f2(x))

f1d1 = lambda x : -np.sin(x[0] + x[1])
f1d2 = lambda x : -np.sin(x[0] + x[1]) - 2*x[1]
f2d1 = lambda x:  np.cos(x[0] - x[1]) - x[1]
f2d2 = lambda x: -np.cos(x[0] - x[1]) - x[0]

xk = np.array([1,1]) # Initial guess x_k

k = [0]
residuals = [np.abs(f(xk))]

itnum = 0 # iteration counter
success = True # Assume success unless we fail!

while linalg.norm(f(xk))>tol:
    A = np.array([[f1d1(xk), f1d2(xk)],[f2d1(xk), f2d2(xk)]])
    xk = xk - np.matmul(linalg.inv(A), f(xk))

    print(f"x_{itnum} = {xk}")
    itnum += 1
    k.append(itnum)
    residuals.append(linalg.norm(f(xk)))

    if itnum>10000:
        print('Something is wrong, too many iterations, let\'s break out of this loop...!')
        success = False
        break

if success:
    # Plotting using matplotlib, imported at the top of the code.
    print()
    print(f"Done in {itnum} iterations")
    print(f"x1 = {xk[0]}")
    print(f"x2 = {xk[1]}")


#!/usr/bin/python
# -*- coding:utf-8 -*-
# @author  : east
# @time    : 2019/10/7 0:23
# @file    : gauss1d.py
# @project : fem
# software : PyCharm

import numpy as np

"""
Data from Wikipedia [Gaussian quadrature](https://en.wikipedia.org/wiki/Gaussian_quadrature).
"""

n = [1, 2, 3, 4, 5]
name = ['gauss1', 'gauss2', 'gauss3', 'gauss4', 'gauss5']

gauss1 = np.array([[0], [2]])

gauss2 = np.array([[-1/np.sqrt(3), 1/np.sqrt(3)], [1, 1]], dtype=np.float64)

gauss3 = np.array([[-np.sqrt(3/5), 0, np.sqrt(3/5)],
                   [5 / 9, 8 / 9, 5 / 9]], dtype=np.float64)

gauss4 = np.array([[-np.sqrt(3/7+2/7*np.sqrt(6/5)), -np.sqrt(3/7-2/7*np.sqrt(6/5)),
                    np.sqrt(3/7-2/7*np.sqrt(6/5)), np.sqrt(3/7-2/7*np.sqrt(6/5))],
                   [(18-np.sqrt(30))/36, (18+np.sqrt(30))/36,
                    (18+np.sqrt(30))/36, (18-np.sqrt(30))/36]], dtype=np.float64)

gauss5 = np.array([[-np.sqrt(5+2*np.sqrt(10/7))/3, -np.sqrt(5-2*np.sqrt(10/7))/3,
                    0, np.sqrt(5-2*np.sqrt(10/7))/3, np.sqrt(5+2*np.sqrt(10/7))/3],
                   [(322-13*np.sqrt(70))/900, (322+13*np.sqrt(70))/900, 128/225,
                    (322+13*np.sqrt(70))/900, (322-13*np.sqrt(70))/900]], dtype=np.float64)

for k, v in enumerate([gauss1, gauss2, gauss3, gauss4, gauss5]):
    np.save(name[k], v)


#!/usr/bin/env python
# Wenchang Yang (wenchang@princeton.edu)
# Tue Jun 15 22:10:57 EDT 2021
if __name__ == '__main__':
    from misc.timer import Timer
    tt = Timer(f'start {__file__}')
#import sys, os.path, os, glob
import xarray as xr, numpy as np, pandas as pd
from numba import njit,guvectorize
from scipy.stats import t as stu
#import matplotlib.pyplot as plt
#more imports
#
if __name__ == '__main__':
    tt.check('end import')
#
#start from here
@njit
def mycorr(x, y):
    xm = np.mean(x)
    ym = np.mean(y)
    xxm = np.mean(x*x)
    yym = np.mean(y*y)
    xym = np.mean(x*y)
    return (xym - xm*ym)/np.sqrt( (xxm - xm*xm)*(yym - ym*ym) )

@guvectorize(["f8[:],f8[:], b1[:],f8[:],f8[:],f8[:],i8[:],f8[:],f8[:],f8[:]"],
    "(n),(n),()->(),(),(),(),(),(),()", nopython=True, target='parallel')
def linregress_core(y, x, ess_on, slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr):#, predict_stderr):
    """calculate parameters associated with linear relationship between x and y.
    see https://en.wikipedia.org/wiki/Simple_linear_regression"""
    xm = np.mean(x)
    ym = np.mean(y)
    xxm = np.mean(x*x)
    yym = np.mean(y*y)
    xym = np.mean(x*y)
    s2x = xxm - xm*xm
    s2y = yym - ym*ym
    sxy = xym - xm*ym
    #slope
    slope[0] = sxy/s2x
    #intercept
    intercept[0] = ym - slope[0]*xm
    e = np.zeros(x.shape)
    for ii in range(x.size):
        e[ii] = y[ii] - intercept[0] - slope[0]*x[ii]
    eem = np.mean(e*e)
    #correlation
    r[0] = slope[0]*np.sqrt(s2x/s2y)
    N = x.size
    #effective sample size considered
    if ess_on[0]:
        s = 1.0
        for tao in range(1, N//2+1):
            #lag-1 correlation coefficient; here we use np.corrcoef
            #r1x = np.corrcoef(x[:-tao], x[tao:])[0,1]
            #r1y = np.corrcoef(y[:-tao], y[tao:])[0,1]
            #use mycorr is faster than np.corrcoef
            r1x = mycorr(x[:-tao], x[tao:])
            r1y = mycorr(y[:-tao], y[tao:])
            if r1x < 0 or r1y <0:
                break
            s = s + 2*(1 - tao/N)*r1x*r1y
        Ne = int(N/s)
        dof[0] = Ne - 2#degree of freedom in t-test
    else:
        dof[0] = N - 2
    #tvalue
    tvalue[0] = r[0]*np.sqrt( dof[0]/(1 - r[0]*r[0]) )
    #standard error of slope
    slope_stderr[0] = np.sqrt( eem/s2x/dof[0] )
    #standard error of intercept
    intercept_stderr[0] = slope_stderr[0] * np.sqrt( xxm )
    #standard error of the prediction
    #for ii in range(x.size):
    #    predict_stderr[ii] = np.sqrt( eem/dof[0]*( 1.0 + (x[ii] - xm)**2/s2x ) )

    #return slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr, predict_stderr

def linregress_np(y, x, ess_on=False, alpha=0.05):
    """wrap around linregress_core and use scipy.stats.t.cdf to calculate pvalue and t_alpha"""
    x_ = x.astype('float64')
    y_ = y.astype('float64')
    #slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr, predict_stderr = linregress_core(x_, y_, ess_on)
    slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr = linregress_core(y_, x_, ess_on)
    pvalue = 2*stu.cdf(-np.abs(tvalue), dof)
    t_alpha = stu.ppf(1-alpha/2, dof)

    return slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr, pvalue, t_alpha

def linregress(da_y, da_x, dim=None, ess_on=False, alpha=0.05, predict_stderr_on=None, predicted_on=None):
    """xarray-version linregress_np (accept xr.DataArray as input)"""
    if dim is None:
        dim = [d for d in da_y.dims if d in da_x.dims][0]

    #automatically control whether to output predicted values; output when input arrays have lower dimensions
    if predicted_on is None:
        if da_y.ndim > 2 or da_x.ndim > 2:
            predicted_on = False
        else:
            predicted_on = True
    #automatically control whether to output predict_stderr; output when input arrays have lower dimensions
    if predict_stderr_on is None:
        if da_y.ndim > 2 or da_x.ndim > 2:
            predict_stderr_on = False
        else:
            predict_stderr_on = True

    #use xr.apply_ufunc to accept dataarray as input
    slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr, pvalue, t_alpha = xr.apply_ufunc(
        linregress_np,
        da_y, da_x,
        input_core_dims=[[dim], [dim]],
        output_core_dims=[[], [], [], [], [], [], [], [], []],
        dask='allowed', kwargs={'ess_on': ess_on, 'alpha': alpha})

    #add some attrs
    slope.attrs['long_name'] = 'slope of the linear regression'
    intercept.attrs['long_name'] = 'intercept of the linear regression'
    r.attrs['long_name'] = 'correlation coefficient'
    dof.attrs['long_name'] = 'degree of freedom of the t distribution'
    tvalue.attrs['long_name'] = 't-value in the t-test'
    slope_stderr.attrs['long_name'] = 'standard error of the estimated slope'
    intercept_stderr.attrs['long_name'] = 'standard error of the estimated intercept'
    pvalue.attrs['long_name'] = 'p-value'
    t_alpha.attrs['long_name'] = 't value corresponding to the significance level alpha'

    #result as a dataset
    ds = xr.Dataset(dict(
        slope=slope, intercept=intercept, r=r,
        dof=dof, tvalue=tvalue,
        slope_stderr=slope_stderr, intercetp_stderr=intercept_stderr,
        pvalue=pvalue, t_alpha=t_alpha))

    if predicted_on:
        predicted = da_x * slope + intercept
        predicted.attrs['long_name'] = 'predicted values by the linear regression model'
        ds['predicted'] = predicted
    if predict_stderr_on:
        e = da_y - slope*da_x - intercept
        eem = (e*e).mean(dim)
        xm = da_x.mean(dim)
        s2x = (da_x*da_x).mean(dim)
        predict_stderr = np.sqrt( eem/dof*( 1.0 + (da_x - xm)**2/s2x ) )
        predict_stderr.attrs['long_name'] = 'standard error of the predicted value'
        ds['predict_stderr'] = predict_stderr

    return ds

if __name__ == '__main__':
    #test
    from wyconfig import * #my plot settings
    from xaddon.xlib.linregress import linregress as linregress_da
    import xarray as xr
    from xdata import ds
    x = ds.nino34.values
    y = ds.iod.values

    slope, intercept, r, dof, tvalue, slope_stderr, intercept_stderr, pvalue, t_alpha = linregress_np(y, x)
    ds_ = linregress(ds.iod, ds.nino34)
    print(f'{slope = }; {intercept = }; {r = }; {dof = }; {tvalue = }; {slope_stderr = }; {intercept_stderr = }; {pvalue = }; {t_alpha = }')

    #print(linregress_da(xr.DataArray(y), xr.DataArray(x)))
    print('\n scipy.stats.linregress result')
    print(linregress_da(ds.iod, ds.nino34))
    print('\n linregress result with ess_on=False')
    print(linregress(ds.iod, ds.nino34, ess_on=False))
    print('\n linregress result with ess_on=True')
    print(linregress(ds.iod, ds.nino34, ess_on=True))

    da_y = ds.sst.pipe(lambda x: x.groupby('time.month') - x.groupby('time.month').mean('time') )
    da_x = ds.nino34
    tt.check('start linregress')
    ds_x = linregress(da_y, da_x, predict_stderr_on=True)
    tt.check('start linregress_da')
    ds_da = linregress_da(da_y, da_x)
    tt.check('end linregress_da')

    plt.scatter(x, y, color='none', edgecolor='C0')
    x_ = np.sort(x)
    iis = np.argsort(x)
    predict = x_*slope + intercept
    spread = ds_.predict_stderr[iis]*t_alpha
    plt.fill_between(x_, predict-spread, predict + spread, alpha=0.5, color='C0')
    plt.plot(x_, predict, color='C0', ls='--')

    tt.check(f'**Done**')
    plt.show()


import numpy as np

def compute_q10_correction(q10, T1, T2):
    """Compute the Q10 temperature coefficient.

    As explained in [1]_, the time course of voltage clamp recordings are
    strongly affected by temperature: the rates of activation and inactivation
    increase with increasing temperature. The :math:`Q_{10}` temperature
    coefficient, a measure of the increase in rate for a  10 :math:`^{\circ}C`
    temperature change, is a correction factor used in HH-style models to
    quantify this temperature dependence.

    In HH-style models, the adjustment due to temperature can be achieved by
    decreasing the time constants by a factor :math:`Q_{10}^{(T_2 - T_1)/10}`,
    where the temperatures :math:`T_1 < T_2`. The temperature unit must be
    either the Celsius or the Kelvin. Note that :math:`T_1` and :math:`T_2`
    must have the same unit, and do not need to be exactly 10 degrees apart.

    Parameters
    ----------
    q10 : :obj:`float`
        The :math:`Q_{10}` temperature coefficient.
    T1 : :obj:`float`
        Temperature at which the first rate is recorded.
    T2 : :obj:`float`
        Temperature at which the second rate is recorded.

    Returns
    -------
    correction : :obj:`float`
        Correction factor due to temperature.

    References
    ----------
    .. [1] D. Sterratt, B. Graham, A. Gillies, D. Willshaw,
           "Principles of Computational Modelling in Neuroscience",
           Cambridge University Press, 2011.
    """

    # that the test below allows T1 = T2 is intentional; the function should
    # accomendate for no correction, i.e. a correction factor equal to 1.
    if T1 > T2:
        msg = ("T2 must be greater than or equal to T1")
        raise ValueError(msg)
    return q10**((T2 - T1) / 10)

def vtrap(x, y):
    """Traps for zero in denominator of rate eqns.

    HH-style model rate equations often contain expressions that are
    equivalent to

    .. math::
        x / (\exp(u) - 1),

    where :math:`u = x / y`. From Taylor series approximation, one can find that
    the above expression is approximated by

    .. math::
        y (1 - u / 2)

    if :math:`u << 1`.

    This approximation ensures that indeterminate cases are handled properly.

    Parameters
    ----------
    x : :obj:`float` or :term:`ndarray`
        Numerator in the exponential function argument.
    y : :obj:`float` or :term:`ndarray`
        Denominator in the exponential function argument.

    Returns
    -------
    vtrap : :obj:`float` or :term:`ndarray`
        The result of the evaluation.

    References
    ----------
    Inspired by the ``vtrap`` function in the NEURON simulator;
    github.com/neuronsimulator/nrn/blob/master/src/nrnoc/hh.mod
    """

    u = x / y
    return np.where(u < 1e-6, y * (1 - u / 2), x / (np.exp(u) - 1))


# -*- coding: utf-8 -*-
"""
Euler - Cromer algorithm for solving 2nd order ODEs like y''(x) = f(x,t,y(x))
And 1D oscillator modelling as the demo
@author: ssklykov
"""
# %% Import section
import numpy as np
import matplotlib.pyplot as plt
from DynamicSystem import dynamicSys

# %% Algorithm implementation (in general form - using 1st, 2nd order derivatives)
def eulerCromer(f: float, y1stDerivPrevious: float, yPrevious: float, h: float) -> tuple:
    """Implementation is kept general - it consumes only the values calculated outside of it"""
    y2ndDeriv = f  # y''(x..) = f(*args); a  = f/m - this is the 2nd derivative!!! (acceleration)
    y1stDeriv = y1stDerivPrevious + h*y2ndDeriv  # y'(n) = y'(n-1) + h*y''; this is speed or velocity!
    y = yPrevious + h*y1stDeriv
    return (y1stDeriv, y)

# %% Testing values and their probing
gamma = 0.015; radius = 5; mass = 1; amplitude = 8.8
xStart = -2*radius; xFinish = 2*radius
initSpeed = 1.6
nTimePoints = int(40000); hTime = 0.0025
sys1 = dynamicSys("1st example"); sysForce1 = sys1.getForce

# %% Getting force profile
nX = 1000; hX = (xFinish - xStart)/nX
nX += 1
f = np.zeros(nX, dtype=float); x = np.zeros(nX, dtype=float)
for i in range(nX):
    x[i] = xStart + i*hX
    gammaPureExternalForce = 0.0
    f[i] = sysForce1(amplitude, x[i], radius, mass, gammaPureExternalForce, initSpeed)

plt.figure(); plt.plot(x,f,linewidth=2); plt.xlabel("x coordinates"); plt.title("External force profile f(x)")

# %% Testing algorithm for solving 1D oscillatory form
xCoord = np.zeros(nTimePoints+1, dtype='float'); speeds = np.zeros(nTimePoints+1, dtype='float')
timePoints = np.zeros(nTimePoints+1, dtype='float')
xCoord[0] = xStart; speeds[0] = initSpeed; timePoints[0] = 0
# Get modelled values - x coordinates, speed along x
for i in range(1, nTimePoints+1):
    forceCalibrated = sysForce1(amplitude, xCoord[i-1], radius, mass, gamma, speeds[i-1])
    (speeds[i], xCoord[i]) = eulerCromer(forceCalibrated, speeds[i-1], xCoord[i-1], hTime)
    timePoints[i] = timePoints[i-1] + hTime
# Plotting dynamic pictures for x and v
plt.figure(); plt.plot(timePoints,xCoord,'r',linewidth=2); plt.xlabel("time"); plt.ylabel("x"); plt.title("x(t)")
plt.figure(); plt.plot(timePoints,speeds,'g',linewidth=2); plt.xlabel("time"); plt.ylabel("v"); plt.title("v(t)")
plt.figure(); plt.plot(xCoord,speeds,'m',linewidth=1); plt.xlabel("x"); plt.ylabel("v")
plt.title("speed versus x coordinate")


import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import sys
from sklearn import linear_model
class Problem1Base():
    def __init__(self):
        pass

    def create_dataset(self):
        n_samples = 30
        self.x_point = np.linspace(0,20,n_samples)
        self.y_point = 3.7 * self.x_point + 14 + 4 * np.random.randn(n_samples)
        plt.plot(self.x_point, self.y_point,'o')
        plt.savefig("lesson5.png")

class Problem1SK(Problem1Base):
    def __init__(self):
        pass

    def compute_regression(self):
        linreg = linear_model.LinearRegression()
        self.x_point = np.reshape(self.x_point,(len(self.x_point),1))
        linreg.fit(self.x_point,self.y_point,self.y_point)
        return linreg.coef_,linreg.intercept_

class Problem1TF(Problem1Base):
    def __init__(self):
        self.graph = tf.Graph()
        self.RANDOM_SEED = 42
        tf.set_random_seed(self.RANDOM_SEED)
        np.random.seed(self.RANDOM_SEED)

    def build(self):
        self.X = tf.placeholder(tf.float32)
        self.Y = tf.placeholder(tf.float32)
        self.LR = tf.placeholder(tf.float32)
        self._slope = tf.Variable(tf.random_uniform([1],-1.0,1.0))
        self._intercept = tf.Variable(tf.zeros([1]))
        self._response = self._slope*self.X + self._intercept
        self._cost = tf.reduce_mean(tf.square(self._response - self.Y))
        self._optimizer = tf.train.GradientDescentOptimizer(self.LR).minimize(self._cost)

    @property
    def cost(self):
        return self._cost
    @property
    def optimizer(self):
        return self._optimizer
    @property
    def slope(self):
        return self._slope
    @property
    def intercept(self):
        return self._intercept

    def compute_regression(self):
        init = tf.global_variables_initializer()
        with tf.Session() as session:
            session.run(init)
            for epoch in range(50000):
    #           for (_x,_y) in zip(self.x_point,self.y_point):
    #               session.run(self._optimizer,feed_dict = {self.LR: 0.0001,self.X : _x, self.Y : _y})
                session.run(self._optimizer,feed_dict = {self.LR: 0.0001,self.X : self.x_point, self.Y : self.y_point})
                if (epoch % 500) == 0:
                    c = session.run(self._cost, feed_dict = {self.LR: 0.0001,self.X : self.x_point, self.Y : self.y_point})
                    print("Epoch", epoch , ": cost =", c, "slope =", session.run(self._slope), "intercept =", session.run(self._intercept))
            print("Slope = ",session.run(self._slope))
            print("Intercept = ",session.run(self._intercept))

def main():
    problem1 = Problem1SK()
    problem1.create_dataset()
    print(problem1.compute_regression())

    problem1 = Problem1TF()
    problem1.create_dataset()
    problem1.build()
    print(problem1.compute_regression())

if __name__ == "__main__":
    main()


import numpy as np
from lib.functions import *
from lib.linear_regression import *

def test_numpy_operator():
    """
    The functions uses numpy's @ operator, this test ensures that @ works
    as intended!
    """
    A = np.array([1, 2, -1, 3, 0, 4]).reshape(3, 2)
    B = np.array([-1, -3, 2, 0]).reshape(2, 2)
    numerical = A @ B
    # hand-calculated result:
    analytical = np.array([3, -3, 7, 3, 8, 0]).reshape(3, 2)
    msg = 'Error, there is something wrong with numpy\'s "@" operator'
    assert np.array_equal(numerical, analytical), msg

def test_ridge_regression():
    """
    Test a simple linear function f(x) = 4x + 2, and checks that Ridge
    regression obtains the coefficients 4 and 2 (+/- 0.1).
    """
    x = np.linspace(-2,2,1000)
    X = np.column_stack((np.ones(1000), x))
    y = 4*x + 2 + np.random.normal(0,0.2,1000)
    LinReg = LinearRegression(X, y)
    LinReg.ridge_fit()
    intercept, slope = LinReg.beta
    msg = "Error, a simple Ridge regression did not correctly fit the data:\n"
    msg += f"\t\tOriginal: f(x) = 4.0x + 2.0,\n"
    msg += f"\t\tObtained: g(x) = {slope:2.1f}x + {intercept:2.1f}."
    assert np.abs(intercept - 2) < 0.1 and np.abs(slope - 4) < 0.1, msg

def test_franke_noise():
    """
    Test that the noise and mean in generate_franke_data() actually
    Returns whats inteded.
    """
    X, y_noise = generate_franke_data(N=10000, noise=0.5)
    y_func = franke_function(X[:,0], X[:,1])
    noise = np.std(y_noise - y_func)
    mean = np.mean(y_noise - y_func)
    msg = "Error, generate franke data did not create noise as intended.\n"
    msg += f"\t\tExpected mean=0, std=0.5, Obtained mean={mean:1.1f}, std={noise:1.1f}."
    assert np.abs(noise - 0.5) < 0.1 and np.abs(mean) < 0.1, msg

def tests():
    test_numpy_operator()
    test_ridge_regression()
    test_franke_noise()
    print("All test functions complete.\n")


'''
POLYNOMIAL CALCULUS

INPUT N                     {Degree of P(x)}
INPUT A(0), A(1)....A(N)    {Coefficients of P(x)}
INPUT C                     {Constant of integration}
INPUT X                     {Indepedent Variable}
INPUT x                     {List of coefficients of the equation}
'''

import numpy
import copy
#   x:@list coefficients of x  such that x2-x-2 would be [1,-1,-2] and x2-1 would be [1,0,-1]
#   note: it is not p of x as in p(4)
def p(x):
    p = numpy.poly1d(x)
    return p

'''
Evaluating p(x)
'''
def eval_polynomial(x, X):
    equation = p(x)
    A = equation.c      #list of coefficients
    B = copy.copy(A)
    index = len(A)
    counter = len(A) - 1

    B[counter] = A[0] # B(N):=A(N)
    print('b({0}) = {1}'.format(counter, A[0]))
    counter = counter - 1

    for N in range(1,index):
        B[counter] = A[N] + B[counter + 1] * X
        print('b({0}) = {1} +({2})*{3} = {4}'.format(counter, A[N], B[counter + 1], X, B[counter]))
        counter = counter - 1

'''
Evaluating p(x) derivate
'''
def eval_polynomial_derivative(x, X):
    equation = p(x)
    A = equation.c #list of coefficients
    D = copy.copy(A)
    index = len(A) - 1 # -1 to ensure loop stops when K=1
    counter = len(A) - 1

    D[counter - 1] = counter * A[0]  # D(N-1) := N * A(N)
    print("d({0}) = {1} * {2} = {3}".format(counter - 1, counter, A[0], D[counter - 1]));
    counter = counter - 1

    for N in range(1,index): # For K = N-1  DOWNTO 1
        D[counter - 1] = (counter) * A[N] + D[counter] * X # DO D(K-1):= K * A(K) + D(K) * X
        print("d({0}) = {1} * {2} + {3} * {4} = {5}".format(counter - 1, counter, A[N], D[counter], X, D[counter - 1]));

        counter = counter - 1

'''
Algorithm to evaluate I(x)

'''
def eval_polynomial_integral(x,X):
    equation = p(x)
    A = equation.c
    I = copy.copy(x)
    index = len(A)
    counter = len(A) - 1

    I.insert((counter + 1),(A[0] /(counter + 1))) # I(N+1):= A(N)/(N+1)
    print("I({0}) = {1}/{2} = {3}".format(counter + 1, A[0], counter + 1, I[counter + 1]))

    for N in range(1, index): #For K=N DOWNTO 1
        I[counter] =( A[N] / counter ) + I[counter + 1] * X # DO I(K):= A(K-1)/K + I(K+1)*X
        print("I({0}) = {1}/{2} + {3} * {4} = {5}".format(counter, A[N], counter, I[counter + 1], X, I[counter]))
        counter = counter - 1

    #   I(0):= C + I(1)*X
    I[0] = 0 + I[1] * X
    print("I(0) = 0 + {0} * {1} = {2}".format(I[1], X, I[0]))
    print('\n')

    return I[0];

'''
Definite Integral of p(x) taken over two points
upper       {Upper limit}
lower       {Lower limit}
'''
def eval_polynomial_integral_limits(x,upper, lower):
    print('I({0}) - I({1}) = {2}'.format(upper, lower, eval_polynomial_integral(x, upper) - eval_polynomial_integral(x,lower)))

print(eval_polynomial([-0.02, 0.2, -0.4,1.28], 4))
print(eval_polynomial_derivative([-0.02, 0.2, -0.4,1.28], 4))
print(eval_polynomial_integral([-0.02, 0.2, -0.4,1.28], 4))
(eval_polynomial_integral_limits([-0.02, 0.2, -0.4,1.28], 4, 1))


import numpy as np
import scipy
import sympy
from numpy import linalg as lg
from numpy.linalg import solve
from numpy.linalg import eig
from scipy.integrate import quad

# Question 1

# The Eigenvalue is 6

# Question 2

# v is not an eigenvector of A

# Question 3

'''
A.
The basis is:
[ 0]
[-4]

B.
The basis is:
[4]
[4]
'''

# Question 4
'''
a.
False. The condition that Axequalslambdax for some scalar lambda is not sufficient to determine if x is an eigenvector of A. The vector x must be nonzero.

b.
False. There may be linearly independent eigenvectors that both correspond to the same eigenvalue.

c.
True. A steady-state vector for a stochastic matrix is actually an eigenvector because it satisfies the equation Axequalsx.

d.
False. If the matrix is a triangular matrix, the values on the main diagonal are eigenvalues. Otherwise, the main diagonal may or may not contain eigenvalues.

e.
True. An eigenspace of A corresponding to the eigenvalue lambda is the null space of the matrix (AminuslambdaI).
'''

# Question 5
'''
Attempt 1
AD =
FOIL:
(-3 - lambda)(-1 - lambda)
lambda ^2 + 4lambda + 3

AD - BC:
Characteristic polynomial:
lambda ^2 + 4lambda + 1

Set the characteristic polynomial equal to 0 and solve to find the eigenvalues:
lambda ^2 + 4lambda + 1 = 0

Attempt 2
Foil AD =
(5 - lambda)(-1 - lambda)
lambda ^2 - 4 lambda - 5

AD - BC:
lambda ^2 - 4 lambda - 3

Use the quadratic equation to find the roots:
2 + sqrt(7), 2 - sqrt(7)
'''

# Question 6
'''
D1 = (1 - lambda)(2 - lambda)(-lambda)
D2 = 0
D3 = -21

U1 = 0
U2 = -14(1 - lambda)
U3 = 0

D1 + D2 + D3 - U1 - U2 - U3
(1 - lambda)(2 - lambda)(-lambda) + 0 +(-21) - 0 - (-14(1 - lambda)) - 0

(1 - lambda)(2 - lambda)(-lambda) - 21 + 14 - 14lambda
(2 - lambda - 2lambda + lambda^2)(-lambda) - 14lambda - 7
-lambda^3 +3lambda^2- 16lambda -7 # Correct
'''

# Question 7
# The real eigenvalues are just the coefficients of the diagonal
# 0, 2, 2, 9, 9

# Question 8
# Attempt 1
'''
pinv = np.array([[-5, 3],[2, -1]])
print(pinv)

p = np.linalg.inv(pinv)
print(p)

d = np.array([[1,0],[0,3]])
d4 = d ** 4
print(d4)

#Multiply p and D^4
pd4 = np.array([[1,3],[2,405]])
print(pd4)

# Multiply by pinv
final = pd4 * pinv
print(final)
'''

# Attempt 2
p = np.array([[1,2],[3,5]])
print(p)

pinv = np.linalg.inv(p)
print(pinv)

d = np.array([[1,0],[0,2]])
print(d)

d4 = d ** 4
print(d4)

mult = np.dot(p, d4)
print(mult)

a4 = np.dot(mult, pinv)
print(a4) # Correct

# Question 9
'''
p = np.array([[7,0],[3,1]])
'''

# Question 10
# The matrix is just the coefficients

# Question 11
# Attempt 1
# First find the eigenvalues
# Foil:
'''
(-1 - lambda)(6 - lambda) - (-12)
lambda^2 - 5lambda - 18

# Attempt 2
(6 - lambda) (1 - lambda) - (-6)
lambda^2 - 7lambda + 12

Factor:
(lambda - 3)(lambda -4)

# The second vector is the inverse of the bd diagonal
B = np.array([[1, -1], [-3, 2]])

# This is the answer from factoring the polynomial
TB = np.array([[3, 0], [0,4]])

import numpy,from numpy,import scipy,from scipy,import sympy


#numpy solution

import numpy
def primesfrom2to(n):
    """ Input n>=6, Returns a array of primes, 2 <= p < n """
    sieve = numpy.ones(n/3 + (n%6==2), dtype=numpy.bool)
    for i in xrange(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 numpy.r_[2,3,((3*numpy.nonzero(sieve)[0][1:]+1)|1)]

#pure-python solution

def primes1(n):
    """ Returns  a list of primes < n """
    sieve = [True] * (n/2)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
    return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]

def primes2(n):
    """ Input n>=6, Returns a list of primes, 2 <= p < n """
    n, correction = n-n%6+6, 2-(n%6>1)
    sieve = [True] * (n/3)
    for i in xrange(1,int(n**0.5)/3+1):
      if sieve[i]:
        k=3*i+1|1
        sieve[      k*k/3      ::2*k] = [False] * ((n/6-k*k/6-1)/k+1)
        sieve[k*(k-2*(i&1)+4)/3::2*k] = [False] * ((n/6-k*(k-2*(i&1)+4)/6-1)/k+1)
    return [2,3] + [3*i+1|1 for i in xrange(1,n/3-correction) if sieve[i]]


from sympy import sin, cos, pi
from sympy.vector.coordsysrect import CoordSys3D
from sympy.vector.parametricregion import ParametricRegion
from sympy.testing.pytest import raises
from sympy.abc import a, b, r, t, z, theta, phi

C = CoordSys3D('C')

def test_parametricregion():

    point = ParametricRegion((), (3, 4), {})
    assert point.definition == (3, 4)
    assert point.parameters == ()
    assert point.limits == {}

    # line x = y
    line_xy = ParametricRegion(C, (C.y), limits={C.y: (-3, 3)})
    assert line_xy .definition == (C.y,)
    assert line_xy.parameters == (C.x, C.y, C.z)

    # line y = z
    line_yz = ParametricRegion((t), (C.x,t,t), limits={t: (1, 2)})
    assert line_yz.definition == (C.x,t,t)
    assert line_yz.parameters == (t,)

    p1 = ParametricRegion((a, b), (9*a, -16*b), limits={a: (0, 2), b: (-1, 5)})
    assert p1.definition == (9*a, -16*b)
    assert p1.parameters == (a, b)
    assert p1.limits == {a: (0, 2), b: (-1, 5)}

    p2 = ParametricRegion(t, (t, t**3))
    assert p2.parameters == (t,)
    assert p2.limits == {}

    circle = ParametricRegion((r, theta), (r*cos(theta), r*sin(theta)), {theta: (0, 2*pi)})
    assert circle.definition == (r*cos(theta), r*sin(theta))

    halfdisc = ParametricRegion((r, theta), (r*cos(theta), r* sin(theta)), {r: (-2, 2), theta: (0, pi)})
    assert halfdisc.definition == (r*cos(theta), r*sin(theta))
    assert halfdisc.parameters == (r, theta)
    assert halfdisc.limits == {r: (-2, 2), theta: (0, pi)}

    ellipse = ParametricRegion(t, (a*cos(t), b*sin(t)), {t: (0, 8)})
    assert ellipse.parameters == (t,)
    assert ellipse.limits == {t: (0, 8)}

    cylinder = ParametricRegion((r, theta, z), (cos(theta), r*sin(theta), C.z), {theta: (0, 2*pi), z: (0, 4)})
    assert cylinder.parameters == (r, theta, z)

    sphere = ParametricRegion((r, theta, phi), (r*sin(phi)*cos(theta),r*sin(phi)*sin(theta), r*cos(phi)),
                            {theta: ( 0, 2*pi), phi: ( 0, pi)})
    assert sphere.definition == (r*sin(phi)*cos(theta),r*sin(phi)*sin(theta), r*cos(phi))
    assert sphere.parameters == (r, theta, phi)

    raises(ValueError, lambda: ParametricRegion((t), (a*t**2, 2*a*t), {a:  (-2, 2)}))
    raises(ValueError, lambda: ParametricRegion((a, b), (a**2, sin(b)), {a: (2, 4, 6)}))


from scipy.stats import norm
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style
# style.use('fivethirtyeight')
np.random.seed(0)

# from https://www.python-course.eu/expectation_maximization_and_gaussian_mixture_models.php
# also see https://towardsdatascience.com/implement-expectation-maximization-em-algorithm-in-python-from-scratch-f1278d1b9137

datenpunkte_pro_cluster = 20
clusterbereich = np.linspace(-5, 5, num=datenpunkte_pro_cluster)

number_clusters_to_generate = 3
clusters = []
for i in range(number_clusters_to_generate):
    # Datenpunkte, jeweils auf der x-Achse verschoben
    cluster = clusterbereich * np.random.rand(datenpunkte_pro_cluster) + (i - 1) * 18
    clusters.append(cluster)
# Die Beispieldaten X bestehen aus der Summe aler Cluster
X = np.stack(clusters).flatten()

def plot_setup(X):
    number_examples = len(X)

    # Eine Figure zum darstellen der Daten erstellen
    fig = plt.figure(figsize=(10, 10))
    ax0 = fig.add_subplot(111)
    ax0.set_xlim([-33, 33])
    ax0.set_ylim([-0.02, 0.4])

    # Die Datenpunkte plotten
    rgb_color = np.array([0.0, 0.0, 1.0])
    for i in range(number_examples):
        ax0.scatter(X[i], 0.0, color=rgb_color, s=100)

    ax0.set_xlabel("x")
    ax0.set_yticks([])
    ax0.set_yticks([], minor=True)
    ax0.xaxis.label.set_size(30)
    ax0.tick_params(axis='both', which='both', labelsize=20)

    plt.savefig("em1d_0.png")
    plt.close()

def plot_data_and_gaussians(p, mu, var, X, iter):
    number_gaussians = len(mu)
    number_examples = len(p)

    # Eine Figure zum darstellen der Daten erstellen
    fig = plt.figure(figsize=(10, 10))
    ax0 = fig.add_subplot(111)
    ax0.set_ylim([-0.02, 0.4])

    # Die Datenpunkte plotten
    rgb_color = np.zeros(shape=(3))
    for i in range(number_examples):
        # Die Farbe der Datenpunkte entspricht der Verteilung der Wahrscheinlichkeiten p(x | mu, sigma)
        # (wie wahrscheinlich welcher Gauss den Datenpunkt erzeugt und nur für die ersten drei).
        for j in range(min(number_gaussians, 3)):
            rgb_color[j] = p[i][j]

        # Normieren der Farben ist notwendig für die Darstellung
        rgb_color = rgb_color / np.linalg.norm(rgb_color, ord=1)

        # Plotten der Datenpunkte
        ax0.scatter(X[i], 0.0, color=rgb_color, s=100)

    # Ploten der Gaussverteilungen
    plotting_grid = np.linspace(-30, 30, num=300)
    colors = ['r', 'g', 'b', 'm']
    color = colors[0]
    for i in range(number_gaussians):
        gaussian = norm(loc=mu[i], scale=var[i])
        gaussian_pdf = gaussian.pdf(plotting_grid).flatten()

        if i < len(colors):
            color = colors[i]
        ax0.plot(plotting_grid, gaussian_pdf, c=color, linewidth=3.0)

    ax0.set_xlabel("x")
    ax0.set_yticks([])
    ax0.set_yticks([], minor=True)
    ax0.xaxis.label.set_size(30)
    ax0.tick_params(axis='both', which='both', labelsize=20)

    plt.savefig("em1d_" + str(iter + 1) + ".png")
    plt.close()

# y(z) = pi N(x, mu, Sigma) / sum(N(x, mu, Sigma))
# y = f(pi, x, mu, Sigma)
# y = f(mu, var, pi, x)
# y = p
def e_step(mu, var, pi, X):
    """ Calculates probabilities p for every datapoint how likely it has been generated by every gaussian.

    Args:
    mu: Means of the Gaussian distributions.
    var: Variances of the Gaussian distributions.
    pi: Scaling factors ("height") for the Gaussian distributions.
    X: The 1D data in the format X[index_of_example], with every x = X[i] being a scalar

    Returns:
    The probabilities p
    """

    number_gaussians = len(mu)
    number_examples = len(X)
    p = np.zeros((number_examples, number_gaussians))

    # Probability for each datapoint x_i to belong to gaussian g
    gaussians = []
    for i in range(number_gaussians):
        gaussians.append(norm(loc=mu[i], scale=np.sqrt(var[i])))

    for i, gaussian, this_pi in zip(range(number_gaussians), gaussians, pi):
        p[:, i] = this_pi * gaussian.pdf(X)  # Write the probability that x belongs to gaussian i in column i.

    # Normalize the probabilities such that each row of r sums to 1 and weight it by mu_c == the fraction of points belonging to cluster c
    for i in range(len(p)):
        p[i] = p[i] / (np.sum(pi) * np.sum(p, axis=1)[i])

    return p

# N_k = total_p_of_gaussians[i]
def m_step(p, pi, mu, X):
    """M-Step"""
    number_gaussians = len(mu)
    number_examples = len(X)
    x = X.reshape(number_examples, 1)

    total_p = np.sum(p)

    """calculate "contribution weights" of gaussians"""
    total_p_of_gaussians = []
    for c in range(number_gaussians):
        p_this_gaussian_for_all_data = np.sum(p[:, c])
        total_p_of_gaussians.append(p_this_gaussian_for_all_data)

    """update the fraction of contributions (pi) from each cluster"""
    for i in range(number_gaussians):
        pi[i] = (total_p_of_gaussians[i] / total_p)

    """update means mu for each cluster """
    mu = np.sum(p * x, axis=0) / total_p_of_gaussians

    """update variances var"""
    var_c = []
    for i in range(number_gaussians):
        ps_this_gaussian = np.array(p[:, i])
        ps_this_gaussian = ps_this_gaussian.reshape(ps_this_gaussian.shape[0], 1)

        distances_examples_to_cluster = x - mu[i]
        distances_squared = distances_examples_to_cluster * distances_examples_to_cluster

        sigma_this_cluster = (1.0 / total_p_of_gaussians[i]) * np.sum(ps_this_gaussian * distances_squared)

        var_c.append(sigma_this_cluster)

    return pi, mu, var_c

def em(X, iterations: int = 10):
    mu = [-8, 8, 5]
    pi = [1/3, 1/3, 1/3]
    var = [5, 3, 1]

    plot_setup(X=X)
    for iter in range(iterations):
        print("Iteration ", iter, " of ", iterations)
        p = e_step(mu=mu, var=var, pi=pi, X=X)
        plot_data_and_gaussians(p=p, mu=mu, var=var, X=X, iter=iter)
        pi, mu, var = m_step(p=p, pi=pi, mu=mu, X=X)

em(X=X, iterations=14)


# -*- coding: utf-8 -*-
"""
Created on Fri May  8 10:45:02 2020

@author: perip
"""

import numpy as np

#Dadas as seguintes séries temporais
s1 = np.array((168, 398, 451, 337, 186, 232, 262, 349, 189, 204, 220, 220, 207, 239, 259, 258, 242, 331, 251, 323, 106, 1055, 170))
s2 = np.array((168, 400, 451, 300, 186, 200, 262, 349, 189, 204, 220, 220, 207, 239, 259, 258, 242, 331, 251, 180, 106, 1055, 200))

#calcular a distância euclidiana entre as séries
dist = np.linalg.norm(s1-s2)
print("Distância euclidiana entre S1 e S2 : ", dist)

#calcular a série temporal com os valores médios entre s1 e s2
medi = ((s1 + s2) / 2)
print("Vals. Média: ", medi)

#calcular a série temporal com os valores máximos de cada instante entre s1 e s2
maxi = np.maximum(s1, s2)
print("Vals. Máx: ", maxi)

#calcular a série temporal com os valores mínimos de cada instante entre s1 e s2
mini = np.minimum(s1, s2)
print("Vals. Min: ", mini)