# Logistic Regression

import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets

In this section we will construct binary and multiclass logistic regression models. We will try our binary model on the {doc}`breast cancer </content/appendix/data>` dataset and the multiclass model on the {doc}`wine </content/appendix/data>` dataset.

## Binary Logistic Regression

# import data
cancer = datasets.load_breast_cancer()
X = cancer['data']
y = cancer['target']

Let's first define some helper functions: the logistic function and a standardization function, equivalent to `scikit-learn`'s `StandardScaler`

def logistic(z):
    return (1 + np.exp(-z))**(-1)

def standard_scaler(X):
    mean = X.mean(0)
    sd = X.std(0)
    return (X - mean)/sd

The binary logistic regression class is defined below. First, it (optionally) standardizes and adds an intercept term. Then it estimates $\boldsymbol{\beta}$ with gradient descent, using the gradient of the negative log-likelihood derived in the {doc}`concept section </content/c3/s1/logistic_regression>`,

$$
\frac{\partial \mathcal{L}(\boldsymbol{\beta})}{\partial \boldsymbol{\beta}} = \frac{\partial - \log L (\boldsymbol{\beta})}{\partial \boldsymbol{\beta}} = -\mathbf{X}^\top (\mathbf{y} - \mathbf{p}).
$$

class BinaryLogisticRegression:

    def fit(self, X, y, n_iter, lr, standardize = True, has_intercept = False):

        ### Record Info ###
        if standardize:
            X = standard_scaler(X)
        if not has_intercept:
            ones = np.ones(X.shape[0]).reshape(-1, 1)
            X = np.concatenate((ones, X), axis = 1)
        self.X = X
        self.N, self.D = X.shape
        self.y = y
        self.n_iter = n_iter
        self.lr = lr

        ### Calculate Beta ###
        beta = np.random.randn(self.D)
        for i in range(n_iter):
            p = logistic(np.dot(self.X, beta)) # vector of probabilities
            gradient = -np.dot(self.X.T, (self.y-p)) # gradient
            beta -= self.lr*gradient

        ### Return Values ###
        self.beta = beta
        self.p = logistic(np.dot(self.X, self.beta))
        self.yhat = self.p.round()

The following instantiates and fits our logistic regression model, then assesses the in-sample accuracy. Note here that we predict observations to be from class 1 if we estimate $P(Y_n = 1)$ to be above 0.5, though this is not required.

binary_model = BinaryLogisticRegression()
binary_model.fit(X, y, n_iter = 10**4, lr = 0.0001)
print('In-sample accuracy: '  + str(np.mean(binary_model.yhat == binary_model.y)))

Finally, the graph below shows a distribution of the *estimated* $P(Y_n = 1)$ based on each observation's *true* class. This demonstrates that our model is quite confident of its predictions.

fig, ax = plt.subplots()
sns.distplot(binary_model.p[binary_model.yhat == 0], kde = False, bins = 8, label = 'Class 0', color = 'cornflowerblue')
sns.distplot(binary_model.p[binary_model.yhat == 1], kde = False, bins = 8, label = 'Class 1', color = 'darkblue')
ax.legend(loc = 9, bbox_to_anchor = (0,0,1.59,.9))
ax.set_xlabel(r'Estimated $P(Y_n = 1)$', size = 14)
ax.set_title(r'Estimated $P(Y_n = 1)$ by True Class', size = 16)
sns.despine()

## Multiclass Logistic Regression

# import data
wine = datasets.load_wine()
X = wine['data']
y = wine['target']

Before fitting our multiclass logistic regression model, let's again define some helper functions. The first (which we don't actually use) shows a simple implementation of the softmax function. The second applies the softmax function to each row of a matrix. An example of this is shown for the matrix

$$
\mathbf{Z} = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}.
$$

The third function returns the $I$ matrix discussed in the concept section, whose $(n, k)^\text{th}$ element is a 1 if the $n^\text{th}$ observation belongs to the $k^\text{th}$ class and a 0 otherwise. An example is shown for

$$
\mathbf{y} = \begin{bmatrix} 0 & 0 & 1 & 1 & 2 \end{bmatrix}^\top.
$$

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

def softmax_byrow(Z):
    return (np.exp(Z)/(np.exp(Z).sum(1)[:,None]))

def make_I_matrix(y):
    I = np.zeros(shape = (len(y), len(np.unique(y))), dtype = int)
    for j, target in enumerate(np.unique(y)):
        I[:,j] = (y == target)
    return I

Z_test = np.array([[1, 1],
              [0,1]])
print('Softmax for Z:\n', softmax_byrow(Z_test).round(2))

y_test = np.array([0,0,1,1,2])
print('I matrix of [0,0,1,1,2]:\n', make_I_matrix(y_test), end = '\n\n')

The multiclass logistic regression model is constructed below. After standardizing and adding an intercept, we estimate $\hat{\mathbf{B}}$ through gradient descent. Again, we use the gradient discussed in the {doc}`concept section </content/c3/s1/logistic_regression>`,

$$
\frac{\partial \mathcal{L}(\mathbf{B})}{\partial \mathbf{B}} = \frac{\partial -\log L (\mathbf{B})}{\partial \mathbf{B}} = \mathbf{X}^\top (\mathbf{I} - \mathbf{P}).
$$

class MulticlassLogisticRegression:

    def fit(self, X, y, n_iter, lr, standardize = True, has_intercept = False):

        ### Record Info ###
        if standardize:
            X = standard_scaler(X)
        if not has_intercept:
            ones = np.ones(X.shape[0]).reshape(-1, 1)
            X = np.concatenate((ones, X), axis = 1)
        self.X = X
        self.N, self.D = X.shape
        self.y = y
        self.K = len(np.unique(y))
        self.n_iter = n_iter
        self.lr = lr

        ### Fit B ###
        B = np.random.randn(self.D*self.K).reshape((self.D, self.K))
        self.I = make_I_matrix(self.y)
        for i in range(n_iter):
            Z = np.dot(self.X, B)
            P = softmax_byrow(Z)
            gradient = np.dot(self.X.T, self.I - P)
            B += lr*gradient

        ### Return Values ###
        self.B = B
        self.Z = np.dot(self.X, B)
        self.P = softmax_byrow(self.Z)
        self.yhat = self.P.argmax(1)

The multiclass model is instantiated and fit below. The `yhat` value returns the class with the greatest estimated probability. We are again able to classify all observations correctly.

multiclass_model = MulticlassLogisticRegression()
multiclass_model.fit(X, y, 10**4, 0.0001)
print('In-sample accuracy: '  + str(np.mean(multiclass_model.yhat == y)))

The plots show the distribution of our estimates of the probability that each observation belongs to the class it actually belongs to. E.g. for observations of class 1, we plot $P(y_n = 1)$. The fact that most counts are close to 1 shows that again our model is confident in its predictions.

fig, ax = plt.subplots(1, 3, figsize = (17, 5))
for i, y in enumerate(np.unique(y)):
    sns.distplot(multiclass_model.P[multiclass_model.y == y, i],
                 hist_kws=dict(edgecolor="darkblue"),
                 color = 'cornflowerblue',
                 bins = 15,
                 kde = False,
                 ax = ax[i]);
    ax[i].set_xlabel(xlabel = fr'$P(y = {y})$', size = 14)
    ax[i].set_title('Histogram for Observations in Class '+ str(y), size = 16)
sns.despine()


# Question 6 Lab 03
# AB Satyaprakash (180123062)

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

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

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

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

# ------------------------------------------------------------------------------
# The following data are given for a polynomial P(x) of unknown degree.
# P(0) = 4, P(1) = 9, P(2) = 15, P(3) = 18
# Determine the coefficient of x^3 in P(x), if all fourth-order forward differences are 1.

print('As we can simply observe, if all 4th order forward differences are 1, this means')
print('All 5th order forward differences will be 0, in other words degree of polynmial is 4')
X = [0, 1, 2, 3]
P = [4, 9, 15, 18]

px = newtonFDPoly(P, X)
# Since we also have informatio about the 4th order FD, we can add 1 more term to P(x)
term = np.polymul([1, 0], np.polymul([1, -1], np.polymul([1, -2], [1, -3])))
term = (term*1)/factorial(4)
px = np.polyadd(px, term)
print('The polynmial is thus given as \n{}'.format(np.poly1d(px)))
print('Clearly, the coefficient of x^3 is {} or {}'.format(px[1], '-11/12'))

# Question 6 ends --------------------------------------------------------------

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


import numpy as np

def create_dataset(func, x_range, n_samples, mu, sigma, equispaced=True):
    """Creates ``n_samples`` samples using function ``func``
    and adding noise from Gaussian with mean ``mu`` and
    variance ``sigma``.
    """
    if equispaced:
        xs = np.linspace(x_range[0], x_range[1], n_samples)
    else:
        xs = np.random.uniform(x_range[0], x_range[1], size=n_samples)
    ts = func(xs) + np.random.normal(mu, sigma, n_samples)
    return xs, ts

def augment_dataset(xs, deg):
    """Augments monodimentional list of samples.
    """
    assert(xs.ndim == 1)
    xs = np.expand_dims(xs, 1)
    xs_augmented = np.ones(xs.shape)
    for i in range(1, deg+1):
        xs_augmented = np.hstack((xs_augmented, xs**i))
    return xs_augmented

def polynomial(x, coeffs):
    """ Returns a polynomial for ``x`` values for the ``coeffs`` provided.

    The coefficients must be in ascending order (``x**0`` to ``x**deg``).
    """
    y = 0
    for i in range(len(coeffs)):
        y += coeffs[i]*x**i
    return y


from app.utils.methods import BaseMethod
from sympy import Function, Symbol, diff
from sympy.parsing.sympy_parser import parse_expr

class Newton(BaseMethod):

    def __init__(self, function, x0, tolerance, iterations):
        self.function = function
        self.x0 = x0
        self.tol = tolerance
        self.iterations = int(iterations)
        self.array = []

    def run(self):
        f = parse_expr(self.function)
        x = Symbol("x")
        df = diff(f, x)
        fx = f.subs(x, self.x0)
        dfx = f.subs(x, self.x0)
        error = self.tol + 1
        self.array.append(["0", float(self.x0), float(fx), float(dfx), "0"])
        for i in range(1, self.iterations):
            if fx == 0 or error <= self.tol or dfx == 0:
                break

            x1 = self.x0 - fx / dfx
            fx = f.subs(x, x1)
            dfx = df.subs(x, x1)
            error = abs(x1 - self.x0)
            rel_error = error
            self.x0 = x1

            self.array.append([str(i), float(self.x0), float(fx), float(dfx), float(rel_error)])

        if fx == 0:
            return {
                "method_status": "success",
                "result": f"{float(self.x0)} is a root",
                "iterations": self.array
            }
        elif error < self.tol:
            return {
                "method_status": "success",
                "result": f"{float(self.x0)} approaches a root of the function with a tolerance of {self.tol}",
                "iterations": self.array
            }
        elif dfx == 0:
            return {
                "method_status": "success",
                "result": f"{float(self.x0)} is a possible multiple root",
                "iterations": self.array
            }
        else:
            return {
                "method_status": "failed",
                "result": "Max interactions exceeded",
                "iterations": self.array
            }


import numpy as np
import json

class SlamMap:
    def __init__(self, markers = np.zeros((2,0)), covariance = np.zeros((0,0)), taglist = []):
        self.markers = markers
        self.covariance = covariance
        self.taglist = taglist
        self.check_valid()

    def check_valid(self):
        assert (markers.shape[0] == 2), "Markers must be 2xn."
        n = markers.shape[1]
        assert (n == len(taglist)), "No. of markers and tags do not match."
        assert (2*n == self.covariance.shape[0]), "Covariance matrix does not match size of markers."
        assert (self.covariance.shape[0] == self.covariance.shape[1]), "Covariance matrix is not square."
        cov_sym_score = np.linalg.norm(self.covariance - self.covariance.T)
        assert(cov_sym_score < 0.01), "Covariance matrix is not symmetric."

    def save(self, fname="slam_map.txt"):
        map_attributes = {"taglist":self.taglist,
                          "markers":self.markers.tolist(),
                          "covariance":self.covariance.tolist()}
        with open(fname,'w') as map_file:
            json.dump(map_attributes, map_file, indent=2)

    def load(self, fname="slam_map.txt"):
        with open(fname,'r') as map_file:
            map_attributes = json.load(map_file)
        self.taglist = map_attributes["taglist"]
        self.markers = np.array(map_attributes["markers"])
        self.covariance = np.array(map_attributes["covariance"])
        self.check_valid()

    def compute_tf(self, other):
        markers1 = self.markers.copy()
        markers2 = other.markers.copy()

        # re-order the other markers to match self
        idxlist = [other.taglist.index(tag) for tag in self.taglist]
        markers2 = markers2[:, idxlist]

        armse, R, t = self.compute_armse(markers1, markers2)

        return armse, R, t

    @staticmethod
    def compute_armse(markers1, markers2):
        # An implementation of Umeyama's method
        n = markers1.shape[1]
        mu1 = 1.0/n * np.sum(markers1, axis=1, keepdims=True)
        mu2 = 1.0/n * np.sum(markers2, axis=1, keepdims=True)
        Sigma = 1.0/n * np.dot((markers2 - mu2), (markers1 - mu1).transpose())

        S = np.eye(2)

        U, D, VH = np.linalg.svd(Sigma)

        R = np.dot(U, np.dot(S, VH))
        t = mu2 - np.dot(R, mu1)

        errors = markers2 - np.dot(R,markers1) - t

        armse = np.sqrt( 1.0/n * np.sum(errors**2) )
        return armse, R, t


"""
Q3: Fourier filtering and smoothing
Plot the actual data, then calculate Fourier coefficients
Then plot the transformed data
"""

import numpy as np
import matplotlib.pyplot as plt

# import the Dow data
dow = np.loadtxt("dow.txt", float)

# plot it all onto a graph
dow_l = np.arange(len(dow))

plt.figure(1)
plt.title('Closing values of DOW index per day')
plt.xlabel('Number of days since start of data set')
plt.ylabel('Closing value')
plt.xlim(0,(len(dow)))
plt.plot(dow_l, dow, 'g', label='Unprocessed data')
# we won't display this plot yet, since we want to add to it later

# B
# calculate Fourier coefficients
dowft = np.fft.rfft(dow)
#print dowft #and, oh! look. they're complex.

# C
# what point do we cut off for 10%? LET'S FIND OUT
dowft_l = len(dowft)
dowft_10pc_l = (((dowft_l)//10)) # no need to +1 because we're operating on zero

dowft_f10 = np.copy(dowft)
dowft_f10[(dowft_10pc_l):(dowft_l)] = 0.0 # set all values past the 1/10th point to zero

# D
# inverse transform
inv_dowft_f10 = np.fft.irfft(dowft_f10)
# plot
plt.plot(dow_l,inv_dowft_f10, 'r', label='Inverse transform, 10% of coefficients') # plot individual ones too for clarity
plt.legend(loc=0)
plt.show()

# E
# what point do we cut off for 2%? LET'S FIND OUT
dowft_2pc_l = (((dowft_l)//50))

dowft_f2 = np.copy(dowft)
dowft_f2[(dowft_2pc_l):(dowft_l)] = 0.0 # set all values past the 1/10th point to zero

# inverse transform again
inv_dowft_f2 = np.fft.irfft(dowft_f2)

# plot
plt.figure(2)
plt.plot(dow_l,dow,'g', label='Unprocessed data')
plt.plot(dow_l,inv_dowft_f2, 'b', label='Inverse transform, 2% of coefficients')
plt.title('Closing values of DOW index per day')
plt.xlabel('Number of days since start of data set')
plt.ylabel('Closing value')
plt.xlim(0,(len(dow)))
plt.legend(loc=0)
plt.show()

#plot the whole lot too
plt.figure(3)
plt.plot(dow_l,dow,'g', label='Unprocessed data')
plt.plot(dow_l,inv_dowft_f10, 'r', label='Inverse transform, 10% of coefficients') # plot individual ones too for clarity
plt.plot(dow_l,inv_dowft_f2, 'b', label='Inverse transform, 2% of coefficients')
plt.title('Closing values of DOW index per day')
plt.xlabel('Number of days since start of data set')
plt.ylabel('Closing value')
plt.legend(loc=0)
plt.xlim(0,(len(dow)))
plt.show()


#! /usr/bin/env python
from numpy import *
import argparse

def bilinear_kernel(ratio, normalize = False):
    '''
    For upsample ratio of ratio,
    Kernel size is ratio*2-1
    '''
    assert ratio > 1
    kern_size = ratio * 2 - 1
    kern = zeros((kern_size, kern_size))
    for ii in range(-ratio+1, ratio):
        for jj in range(-ratio+1, ratio):
            kern[ii+ratio-1,jj+ratio-1] = (ratio-abs(ii)) * (ratio-abs(jj))
    if normalize:
        kern /= ratio**2
    return kern

def bilinear_weights(dim, ratio, normalize = True):
    '''
    creates blinear tensor for convolution.

    :type dim: int
    :param dim: the number of channels to be upsampled

    :type dim: ratio
    :param dim: the upsampling ratio

    :type normalize: bool
    : param normalize: whether to normalize the kernel or not

    returns: a 4D tensor of shape (dim, dim, bilinear_kernel_row, bilinear_kernel_col)
             where all elements are zero, except elements in the diagonal of the first
             two dimensions, e.g. [0,0,:,:], [1,1,:,:], [2,2,:,:]
    '''
    kernel = bilinear_kernel(ratio, normalize=normalize)
    row, col = kernel.shape
    bl_weights = zeros((dim, dim, row, col))
    bl_weights[range(dim),range(dim)] = kernel
    return bl_weights

def main():
    parser = argparse.ArgumentParser(description='Computes bilinear kernel')
    parser.add_argument('upsample_ratio', type = int, default = 3, help = 'ratio to upsample by')
    args = parser.parse_args()

    print 'Kernel for an upsample ratio of %d is\n1/%d *' % (args.upsample_ratio, args.upsample_ratio**2)
    print bilinear_kernel(args.upsample_ratio)
    print '='
    print bilinear_kernel(args.upsample_ratio, normalize = True)

if __name__ == '__main__':
    main()


# suppress the warnings when logarithm calculates infinity
import warnings
warnings.simplefilter("ignore", RuntimeWarning)

# The problem to be solved:
# We have forty samples of fish, that we have measured on two dimensions - weight and length. Based on the measurements, half of them was categorized as 'good' and half as 'bad' (represented by 'o' and 'x', respectively). We now want to work out a model able to predict, which category will a newly caught fish be, given its measurements.

import numpy as np
print('Welcome to Machine Learning with Python!')
print('Lesson 2: Logistic regression')
print('\n'+40*'=')

print('The task now is to create a model which would separate good fish from the bad. Too small or weirdly built fish are harder to process and so we want to see which are the ones we may make profit from.\n')

# data representing the measurements and category 1 and 2 (or 'o' and 'x')
data =\
[(10, 12, 1), (15, 14, 1), (6, 18, 1), (12, 10, 2),\
(17, 13, 1), (18, 18, 1), (11, 15, 1), (3, 17, 1),\
(5, 15, 1), (16, 9, 1), (16, 16, 1), (24, 16, 1),\
(22, 18, 1), (25, 19, 1), (27, 17, 1), (26, 10, 1),\
(18, 12, 1), (24, 7, 1), (23, 12, 1), (28, 9, 2),\
(7, 2, 2), (6, 8, 2), (2, 3, 2), (14, 1, 2),\
(7, 5, 2), (20, 1, 2), (2, 9, 1), (10, 4, 2),\
(2, 7, 2), (4, 1, 2), (13, 2, 2), (10, 1, 2),\
(4, 5, 2), (17, 3, 2), (9, 6, 2), (12, 5, 1),\
(5, 3, 2), (14, 4, 2), (23, 2, 2), (17, 1, 2)]

# Just like in the case of the linear regression model, we want build a model able to predict the fish category based on the measurement values. We thus have to convert the data to the matrix type.

# X matrix will hold the measurements - remember to add a column of '1's for the intercept term
dataX = np.matrix(data)[:,:2]
X = np.ones((len(dataX),3))
X[:,1:] = dataX

# y matrix will hold the labels - but we have to convert them into 0 and 1 (bad or good)
y = 2-np.matrix(data)[:,2]

'''
Below is the show() function, which will use the ASCII to graphically "plot" the charts
'''

def show(arr, p):
    for j in range(np.size(arr,1)-1, -1, -1):
        print('{:>2}'.format(j if j%5 == 0 else ' ')+' '+chr(179+(j%5==0)), end='')
        for i in range(np.size(arr,0)):
            print(p[arr[i][j]], end='')
        print()
    print('   '+chr(192)+(chr(194)+chr(196)*4)*(np.size(arr,0)//5))
    print('    ', end='')
    print(*list(('{:<4}'.format(i*5) for i in range(np.size(arr,0)//5))))
    print()

# t and p are the plot variables
t = np.zeros((30,20), np.int8)
p = [' ', '#', 'o', 'x']

# Populating the plot array from the measurement data
for i in data:
    t[i[0]][i[1]] = i[2]+1

# show is displaying a chart with the fish data points
print('Plotting the datapoints:', 'o - good fish', 'x - bad fish', sep='\n')
show(t, p)

'''
In contrast to linear regression from Lesson 1, we are now not looking for a model predicting the continous values, but rather a classificator able to predict categories (or labels). In this case it is a binary classification (fish is either of two states only). So, we will be searching for a _probability_ that a given sample is of either of the two states.

We have to use the logistic (or sigmoid) function and plug our regular X*theta function into it. So it will be of a format:

g(z) = 1 / (1 + e^(-z)), which in the numpy matrix format will be as below:

'''

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

'''
In this relatively simple situation, we will assume that whenever the hypothesis is greater than 0.5, the prediction will return 1 and if it is lower than 0.5 - it will return 0. The situation where the hypothesis is equal to 0.5 is a so-called "decision boundary" - which is exactly the thing we are looking for. Our model is going to find that boundary and according to it, it will make predictions on future, unseen fish.

'''

print('It can be observed that there is no possibility to draw a straight line which would separate the two groups. We are thus looking for a decision boundary like this (a sample)')

# a sample decision boundary
for i in range(np.size(t, 0)):
    t[i][13-int(i/2.5)] = 1

show(t, p)

'''
The decision boundary is where the cost function is the lowest possible, so again we are to optimize it. However, this time it will not be calculated as a square of errors anymore. Since our primary function is a logistic function, its cost function will be of a logarithmic shape. The concrete formula is as below:

pred = sigmoid(X*theta)
J = 1/m * sum(-y*log(pred) - (1-y)*log(1-pred))

or in numpy, as below:
'''

def J(X, y, theta):
    theta = np.matrix(theta).T # we again need a transposed matrix theta
    m = len(y) # m is the number of datapoints
    predictions = sigmoid(X * theta) # stores the sigmoid predictions, translated into binary classes
    l0 = np.log(predictions.T)
    l1 = np.log(1-predictions.T)
    l0[l0==-np.inf] = 0
    l0[l0==np.inf] = 0
    l1[l1==-np.inf] = 0
    l1[l1==np.inf] = 0
    return -1/(m * np.sum(y*l0 + (1-y)*l1)), 1/m * (X.T * (predictions-y)) # the second parameter is the gradient

print('\nLet\'s find that theta vector!')

# let's check the cost if we would assume theta at two different values
print('\nChecking two example cases of theta:')
for th in [0, 0, 0], [1, 2, 3]:
    print('Assuming theta vector at {}, the cost would be {:.12f}'.format(th, J(X, y, th)[0].item()))

'''
The optimal theta vector will be needed for our model to predict with the smallest possible error.
Assuming that J is a cost function, this is again an optimization problem - we need to find the minimum of J.
The gradient descent function looks EXACTLY the same as for linear regression! This time, however, our hypothesis is a sigmoid function.

'''

# gradient descent function will iteratively update theta by a small fraction alpha (also called the learning rate) for a number of iterations
def gradient(X, y, alpha, theta, iters):
    J_history = np.zeros(iters) # will store historical values of J for each iteration
    m = len(y) # m is the number of datapoints
    theta = np.matrix(theta).T # theta has to be transposed again
    for i in range(iters):
        h0 = sigmoid(X * theta) # zero hypothesis for each datapoint
        delta = (1 / m) * (X.T * h0 - X.T * y) # the gradient descent
        theta = theta - alpha * delta # update theta by learning rate times gradient
        J_history[i] = J(X, y, theta.T)[0] # save the J of a particular iteration, it should drop in the next
    return J_history, theta # return the history of J plus the optimal theta

print('\n'+40*'=')

# theta = np.matrix([np.random.random()-0.5,np.random.random()-0.5, np.random.random()-0.5]) # we initialize theta at random values
theta = np.matrix([np.random.random(),np.random.random(),np.random.random()]) # we initialize theta at random values
alpha = 0.005 # learning rate - if too low, the algorithm will not converge, if too high, it can "explode"
iters = 20000 # number of iterations - reduce if "Time limit exceeded"
print('\n== Model summary ==\nLearning rate: {}\nIterations: {}\nInitial theta:\n{}\nInitial J: {:.12f}\n'.format(alpha, iters, theta, J(X,y,theta)[0].item()))

print('Training the model...', end='')
J_history, theta_min = gradient(X, y, alpha, theta, iters)
print('Done.')

print('\nFinal theta:\n{}\nFinal J: {:.12f}'.format(theta_min.T, J(X,y,theta_min.T)[0].item()))

'''
Now that we have the model trained, let's check if it works. We will plot the chart with the decision boundary calculated by the model and on top of that, we will plot the real datapoints (our training set). To do that, we will iteratively cast a 'predict' function (defined below) on every possible measurement pair.
'''

def predict(a,b,theta_):
    return np.round(sigmoid(np.matrix([1,a,b])*theta_))

print('\n'+40*'=')

# db will store the chart for displaying the decision boundary
db = np.zeros((30,20), dtype=np.int8)
for j in range(np.size(db, 1)):
    for i in range(np.size(db, 0)):
        db[i][j] = predict(i,j,theta_min)
        # np.round(sigmoid(np.matrix([1,i,j])*theta_min))

# Populating the plot array from the measurement data
for i in data:
    db[i[0]][i[1]] = i[2]+1

# let's show it now
print('\nDoing predictions...\nEverything in the dotted area will be predicted by the model as a "good" fish, while datapoints belonging to the blank area will be predicted as "bad" ones.')
show(db, [' ', '.', 'o', 'x'])

'''
Some of the good fish were predicted as bad ones and vice-versa. It would still be wise to know what accuracy our model has (remember - anything better than a pure-luck 50% is great :)
'''

def score(X, y, theta):
    return np.mean(np.round(sigmoid(np.matrix(X)*theta))==y), np.sum(np.round(sigmoid(np.matrix(X)*theta))==y)

print('Model accuracy {:.1%} ({}/{} points)'.format(score(X, y, theta_min)[0], score(X, y, theta_min)[1], len(y)))

print('Feel free to modify the datapoints and see what happens! :)')

print('\n'+40*'=')
print('\nNOTE: The code initializes the model with different theta each time, thus the results may very between runtimes.')


import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plot

# Re-define mathematical function as necessary
# f(x) is the derivative function
def square(x):
    return x**2

def identity(x):
    return x

def y_prime(y):
    k = 3
    return k*y

# y(x_i) = y_i
# f is y'
# n is number of steps
# [x_i, x_f] interval
def euler(f, x_i, x_f, n, y_i):

    x_list = []
    y_list = []

    delta_x = (float(x_f) - float(x_i))/n

    x_list.append(x_i)
    y_list.append(y_i)

    for i in range(1, n + 1):
        x_i = x_list[i - 1] + delta_x
        y_i = y_list[i - 1] + f(y_list[i - 1]) * delta_x
        x_list.append(x_i)
        y_list.append(y_i)

    print x_list
    print y_list
    return x_list, y_list

print y_prime(3)
x, y = euler(identity, 0.0, 1.0, 50, 1.0)

def truefunc(x):
    return math.exp(x)

def true_generator(f, x_i, x_f, n):
    delta_x = (float(x_f) - float(x_i))/n

    x_list = [x_i + i * delta_x for i in range(n + 1)]
    y_list = [f(x_list[i]) for i in range(n + 1)]

    return x_list, y_list

def simple_plot(x, y, color, true, truecolor):
    fig, ax = plot.subplots()
    ax.plot(x, y, color)

    x2, y2 = true_generator(true,  0.0, 1.0, 50)
    ax.plot(x2, y2, truecolor)

    ax.set(xlabel = 'x axis', ylabel = 'y axis', title='y versus x')
    ax.grid()

    plot.show()

simple_plot(x,y, '.r-', truefunc, '.g-')


# coding: utf-8

# ### DEMOPT05
# # Optimization with qnewton

# In[1]:

from compecon import OP
import numpy as np
from demos import demo
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

np.set_printoptions(precision=4,suppress=True)
plt.style.use('seaborn')

# ## Example 1
# Find the optimal value of
# $$f(x) = x^3 - 12x^2 + 36x + 8$$

# In[2]:

def f(x):
    return x ** 3 - 12 * x ** 2 + 36 * x + 8

F = OP(f)

x = F.qnewton(x0=4.0)
J = F.jacobian(x)
E = F.hessian(x)
E = np.linalg.eig(F.hessian(x))[0]

print('x = ', x, '\nJ = ', J, '\nE = ', E)

# In[3]:

figs = []
figs.append(demo.figure('','',''))
xx = np.linspace(0,8.2,100)
plt.plot(xx,f(xx))
demo.bullet(4,f(4),'b.',10)
demo.bullet(x,f(x),'r.',18)

# ## Find the optimum for
# $$ g(x_0,x_1) = 5 - 4x_0^2 - 2x_1^2 - 4x_0 x_1 - 2x_1$$

# In[4]:

def g(x):
    return 5 - 4 * x[0] ** 2 - 2 * x[1] ** 2 - 4 * x[0] * x[1] - 2 * x[1]

G = OP(g)
x = G.qnewton(x0=[-1, 1])
J = G.jacobian(x)
E = np.linalg.eig(G.hessian(x))[0]
print('x = ', x, '\nJ = ', J, '\nE = ', E)

# In[5]:

xx0 = np.linspace(-1.0,2.0,25)
xx1 = np.linspace(-2.5,0.5,25)
x0, x1 = np.meshgrid(xx0,xx1)

figs.append(plt.figure())
ax = figs[-1].add_subplot(1, 1, 1, projection='3d')
ax.plot_surface(x0, x1, g([x0, x1]), rstride=1, cstride=1,
                cmap=cm.Spectral, linewidth=0, antialiased=False)
ax.set_xlabel('$x_0$')
ax.set_xticks(np.linspace(-1.0,2.0,4))
ax.set_ylabel('$x_1$')
ax.set_yticks(np.linspace(-2.5,0.5,4))
ax.set_zlabel('$g(x_0,x_1)$')
plt.show()

demo.savefig(figs)


# %%
from mountaintools import client as mt
from matplotlib import pyplot as plt

# %%
# Other imports
import numpy as np
import mlprocessors as mlpr

# Compute the first n primes

def compute_n_primes(n):
    prime_list = [2]
    num = 3
    while len(prime_list) < n:
        for p in prime_list:  # todo: we only need to check up to the square root of the number
            if num % p == 0:
                break
        else:
            prime_list.append(num)
        num += 2
    return np.array(prime_list)

# MountainTools processor wrapper for compute_n_primes()
class ComputeNPrimes(mlpr.Processor):
    NAME = 'ComputeNPrimes'
    VERSION = '0.1.3'

    n = mlpr.IntegerParameter('The integer n.')
    output = mlpr.Output('The output .npy file.')

    def __init__(self):
        mlpr.Processor.__init__(self)

    def run(self):
        primes = compute_n_primes(self.n)
        print('Prime {}: {}'.format(self.n, primes[-1]))
        np.save(self.output, primes)

# %%
# Execute the processor (retrieve from cache if already computed)
ComputeNPrimes.execute(n=int(1e5), output='primes.npy')

# Load the output into a numpy array and print
primes = np.load('primes.npy')
print(primes)

# %%
# Plot the number of primes vs the predicted based on the prime number theorem

N = len(primes)
primes_N = primes[0:N]

nn = np.arange(1, N + 1)
plt.plot(primes_N, primes_N / np.log(primes_N), 'r', primes_N, nn, 'b')
plt.xlabel('The integer n')
plt.ylabel('Number of primes less than n')
plt.legend(['Predicted', 'Computed'])
plt.title('Actual and predicted number of primes')
plt.show()

# %%


# Tyler Elton
# MTH220 - Fall 2017
# Homework 6/7
# Best fit line for large dataset.

import csv
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style

# Style for the plot.
style.use('fivethirtyeight')

if __name__ == "__main__":
    # Read the data from the CSV file.
    # Open the file in universal line ending mode.
    with open('film-death-counts.csv', 'rU') as infile:
        # Read the file as a dictionary for each row ({header : value}).
        reader = csv.DictReader(infile)
        data = {}
        for row in reader:
            for header, value in row.items():
                try:
                    data[header].append(value)
                except KeyError:
                    data[header] = [value]

    # Set the variables we need.
    length = data['Length_Minutes']
    rating = data['IMDB_Rating']
    bodyCount = data['Body_Count']

    # Create the x and y data.
    x = np.array(bodyCount, dtype = np.float64)
    y = np.array(rating, dtype = np.float64)

    # Create A from x values, and all 1's for the b values (y = mx + b).
    A = np.vstack([x, np.ones(len(x))]).T

    # Compute least square to find m, b.
    m, b = np.linalg.lstsq(A, y)[0]

    # Set up the equation for the best fit line.
    bestFitLine = (m * x) + b

    # Compute the mean square error.
    # SS = (1/n)||b - bHat||
    xHat = np.vstack([m, b])
    ssNorm = np.matmul(A, xHat) - y
    meanSquareError = ((1/len(x)) * np.linalg.norm(ssNorm))
    print('Mean square error: ', meanSquareError)

    fig = plt.figure()
    fig.suptitle('IMDB Ratings of Movies Based on Body Count', fontsize = 16)

    # Plot the points.
    plt.scatter(x, y, color = 'r', label = 'IMDB rating by body count', s = 8)
    plt.plot(x, bestFitLine, color = 'b', label = 'Best fit line', linewidth = 2)

    # Add labels to the x/y axes, create the legend, then show the graph.
    plt.ylabel('IMDB Rating', fontsize = 12)
    plt.xlabel('Body count', fontsize = 12)
    plt.legend(title = 'Legend')
    plt.show()


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):
    soft_max = []
    denom = np.sum(np.exp(L))
    for i in range(len(L)):
        soft_max.append(np.exp(L[i])/denom)
    return soft_max


# Program 13c: The Leslie matrix. See Example 4.
# Compute the population distribution after 50 years.
# Determine the eigenvalues and eigenvectors of a Leslie matrix.

import numpy as np
import numpy.linalg as LA

L = np.array([[0,3,1], [0.3,0,0], [0,0.5,0]])
X0 = np.array([[1000], [2000], [3000]])
X_50 = np.dot(LA.matrix_power(L, 50), X0)
X_50 = X_50.round()
print('X(50) = {}'.format(X_50))

dL, VL = LA.eig(L)
print('Eigenvalues = {}'.format(dL))
print('Eigenvectors = {}'.format(VL))


#Universidad Autónoma de San Luis Potosí Enero 2020

#----- INTRODUCCION -----
#Trata de ejecutar paso a paso cada una de las siguientes instrucciones y analizalas con ayuda de tu instructor de laboratorio.

import sympy as sym
from sympy.abc import s,t,x,y,z
from sympy.integrals import laplace_transform
from sympy.integrals import inverse_laplace_transform

# Transformada de Laplace (t->s)
U = laplace_transform(5*t, t, s)
print('U')
print(U[0])
# Resultado: 5/s**2

# Transformada inversa de Laplace (s->t)
X = inverse_laplace_transform(U[0],s,t)
print('X')
print(X)
# Resultado: 5*t*Heaviside(t)

# Función
F = 5*(s+1)/(s+3)**2
print('F')
print(F)
# Resultado: (5*s + 5)/(s + 3)**2

# Descomposición en fracciones parciales
G = sym.apart(F)
print('G')
print(G)
# Resultado: 5/(s + 3) - 10/(s + 3)**2

# Denominador de una función de transferencia
d1 = (s+1)*(s+3)*(s**2+3*s+1)

# Expandir polinomio
d2 = sym.expand(d1)
print('d2')
print(d2)
# Resultado: s**4 + 7*s**3 + 16*s**2 + 13*s + 3

# Encontrar raíces de un polinomio
print(sym.roots(d2))
# Resultado: {-1: 1, -3: 1, -3/2 - sqrt(5)/2: 1, -3/2 + sqrt(5)/2: 1}


from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style
import random

style.use('fivethirtyeight')

def create_dataset(hm, variance, step=2, correlation=False):
    val = 1
    ys = []
    for i in range(hm):
        y = val + random.randrange(-variance, variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val+= step
        elif correlation and correlation == 'neg':
            val-= step
    xs = [i for i in range(len(ys))]
    return np.array(xs, dtype=np.float64), np.array(ys, dtype=np.float64)

def best_fit_slope_and_intercept(xs, ys):
    m = (((mean(xs) * mean(ys)) - mean(xs*ys)) / ((mean(xs)**2) - mean(xs**2)))
    b = mean(ys) - m*mean(xs)
    return m, b

def squared_error(ys_orig, ys_line):
    return sum((ys_line-ys_orig)**2)

def coeffiecient_of_determination(ys_orig, ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]
    squared_error_regr = squared_error(ys_orig, ys_line)
    squared_error_y_mean = squared_error(ys_orig, y_mean_line)
    return 1 - (squared_error_regr / squared_error_y_mean)

xs, ys = create_dataset(40, 40, 2, correlation='pos')

m, b = best_fit_slope_and_intercept(xs, ys)

regression_line = [(m*x)+b for x in xs]

predict_x = 8

# calculate predictions for y
predict_y = (m*predict_x)+b

# check if our regression line is good for our data
r_squared = coeffiecient_of_determination(ys, regression_line)

plt.scatter(xs, ys)
# predicted y vlaue for predict_x
plt.scatter(predict_x, predict_y, color="g")
plt.plot(xs, regression_line)
plt.show()


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import collections
import scipy
from scipy.stats import poisson
from scipy.stats import norm
from math import sqrt

sns.set(style = "darkgrid", context = "paper")
plt.rc('text', usetex=True)
plt.rc('font', family='serif')

def poipmf(start, stop, lamb):
	k = np.arange(start, stop, 1)
	pmf = scipy.stats.poisson.pmf(k, lamb)

	mu = lamb
	sigma = sqrt(lamb)

	x = np.linspace(mu - 5*sigma, mu + 5*sigma, 100)

	plt.plot(x, scipy.stats.norm.pdf(x, mu, sigma), color = '#353131')
	plt.stem(k, pmf, '--', bottom=0)
	plt.title(r"Poisson Normal Appr. with $\lambda$ = %d" % (lamb))
	plt.show()

lamb = [1, 5, 10, 200]

poipmf(0, 13, lamb[0])
poipmf(0, 17, lamb[1])
poipmf(0, 25, lamb[2])
poipmf(0, 200, lamb[3])


import numpy as np

x = 1.0 	# define floats
y = 2.0

# exp and logs

print(np.exp(x))		# e^x
print(np.log(x))		# lnx
print(np.log10(x))		# log base 10
print(np.log2(x))		# log base 2

# min-max-misc

print(np.fabs(x))		# abs value as float
print(np.fmin(x,y))		# min of x,y as float
print(np.fmax(x,y))		# max of x,y as float

# populate arrays

n = 100 						# define an int
z = np.arange(n, dtype=float)	# get an array ]0.0, n-1]
z *= 2.0*np.pi/float(n-1)		# z = [0.0, 2pi] inclusive
sin_z = np.sin(z)				# get an array sinz

# interpolation

print(np.interp(0.75, z, sin_z))	# interpolate value  sine at 0.75
print(np.sin(0.75))

# using n = 10000

n = 100000						# define an int
z = np.arange(n, dtype=float)	# get an array ]0.0, n-1]
z *= 2.0*np.pi/float(n-1)		# z = [0.0, 2pi] inclusive
sin_z = np.sin(z)


import sympy as sy
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp

R0 = 5e3
B = 4e3
T0 = 273
T1 = T0+20

As, Bs, Ts, T1s, ys, xs = sy.symbols('A, B, T, T1, y, x')
Rs = As*sy.exp(Bs/Ts)
dRdT = sy.diff(Rs, Ts)
print(Rs)
print(dRdT)

A = R0*np.exp(-B/T0)
A = 0.002

dRdTp = float(dRdT.subs({As: A, Bs:B, Ts:T1}))
print(dRdTp)
print('a = %f' %dRdTp)

R1 = float(Rs.subs({As: A, Bs:B, Ts:T1}))
print('R1 = %f' %R1)

def Rf(T):
    global A, B
    return A*np.exp(B/T)

def Rfapprox(T):
    global A, B, T1, dRdTp, R1
    return (R1 + dRdTp*(T-T1))

print('R1 = %f' %Rf(T1))

TT = np.linspace(273, 320, 60)
plt.figure(figsize=(10,8))
plt.plot(TT, Rf(TT))
plt.plot(TT, Rfapprox(TT))
plt.xlabel('Temperature')
plt.ylabel('Resistance')
plt.show()


"""
This class handles all of the symbolic math, including computing terms for series expansions.
Mainly, it spits out terms of expansion evaluation.
Expansion evaluation involves taking the Tayler series expansion of the function about a point.
We then evaluate the series at a certain point and spit out the results term by term.
"""
import sympy as sym
from sympy.parsing.sympy_parser import parse_expr
from sympy.abc import z
from sympy import diff
import numpy as np

class Function:
    def __init__(self, string):
        try:
            self.expr = parse_expr(string)
        except:
            print('Please input a valid function.')
            1/0
        self.term_gen = None
        self.partial_sum = 0.0

    def make_term_gen(self, expr, difference, a):
        """
        Creates a generator for the consecutive terms of the expansion evaluation of the function.
        """
        diff_power = 1
        f_prime = expr
        n = 0
        factorial = 1
        while True:
            output = (f_prime / factorial)*diff_power
            f_prime = diff(f_prime, z)
            diff_power = diff_power * difference
            n = n + 1
            factorial = factorial * n
            yield complex(output.subs({z:a}).evalf())

    def expand_eval(self, a, z0):
        """
        Compute series expansion of the function about the point "a", and evaluated at "z0".
        This actually creates a generator for the terms.
        """
        difference = z0 - a
        self.term_gen = self.make_term_gen(self.expr, difference, a)

    def next_term(self):
        """
        Returns the next term in the expansion evaluation.
        If evaluation and expansion points have not been chosen, we return None.
        """
        if (self.term_gen == None):
            print("You need to set expansion and evaluation points.")
            return None
        else:
            term = np.complex(next(self.term_gen))
            self.partial_sum += term
            return term


import numpy as np
from copy import deepcopy as dc
import math
def proj(vec1,vec2):
    # print(vec1)
    # print(vec2)
    norm2=math.sqrt(np.sum(np.power(vec2,2)))
    # print(norm2)
    projectionVal=np.dot(vec1,vec2)/norm2
    return vec2/norm2*projectionVal

def grahmschmidt(basis):
    ans=np.zeros(basis.shape)
    ans[0]=basis[0]
    # print(len(ans))
    for i in range(1,len(ans)):
        # basis_vector=np.zeros((1,basis.shape[1]))
        basis_vector=dc(basis[i])
        # print(basis_vector)
        for j in range(i):
            # print(j)
            basis_vector=basis_vector-proj(basis_vector,ans[j])
            # print(basis_vector)
        ans[i]=basis_vector
    return ans

def LLL_reduce(basis,delta):
    num_vecs=len(basis)
    ortho=grahmschmidt(basis)
    # print(ortho)
    # return
    def mu(i,j):
        # print(ortho[i],ortho[j])
        return np.dot(basis[i],ortho[j])/np.dot(ortho[j],ortho[j])
    k=1
    while k<num_vecs:
        for j in range(k-1,-1,-1):
            mukj=mu(k,j)
            if(abs(mukj)>0.5):
                basis[k]=basis[k]-basis[j]*round(mukj)
                ortho=grahmschmidt(basis)
        if(np.dot(ortho[k],ortho[k])>=(delta-mu(k,k-1)**2)*np.dot(ortho[k-1],ortho[k-1])):
            k=k+1
        else:
            bk1temp=dc(basis[k])
            bktemp=dc(basis[k-1])
            basis[k]=bktemp
            basis[k-1]=bk1temp
            ortho=grahmschmidt(basis)
            k=max(k-1,1)
    return basis
b=np.array([[1, 1, 1], [-1, 0, 2],[3,5,6]])
print(LLL_reduce(b,0.75))
#Referred https://github.com/orisano/olll for help


import numpy as np

def obj_func(params):
	x = params[0]
	y = params[1]
	# Minima are (-1, 12)
	return (x + 1)**2 + (y - 12)**2

def calc_gradient(f, params):
	# h value for finite differnce approximation of derivative
	h = 0.0001
	# Calculate the gradient by calculating partial derivatives w.r.t each variable
	partials = np.empty(len(params))
	# Calculate partial derivatives with central finite difference approximation: (f(x + h) - f(x - h)) / 2h
	for i in range(len(params)):
		upper_params = params.copy()
		upper_params[i] += h
		lower_params = params.copy()
		lower_params[i] -= h
		partials[i] = partial_i = (f(upper_params) - f(lower_params)) / (2 * h)
	return partials

def calc_step_size(f, grad, position, gamma):
	# f = function, v = negative gradient, a = current position
	c = 0.5
	v = np.multiply(grad, -1)
	ready = False
	while not ready:
		left_side = f(position + (gamma * v))
		norm_sq = np.linalg.norm(grad) ** 2
		right_side = f(position) - (c * gamma * norm_sq)
		if(left_side > right_side):
			gamma /= 2
		else:
			ready = True
	return gamma

def grad_desc(f, params):
	position = np.array(params).astype(float)
	step_size = 50
	done = False
	tresh = 0.00000001
	# Loop until the magnitude (norm) of the gradient is close to zero
	while not done:
		current_gradient = calc_gradient(f, position)
		if(np.linalg.norm(current_gradient) < tresh):
			done = True
			break
		# Find optimum step size
		step_size = calc_step_size(f, current_gradient.copy(), position.copy(), step_size)
		# Descend in the direction and magnitude of the gradient times the step size
		position = position - (step_size * current_gradient)
	return position

# We should get (-1, 12) for the minimum of the function
position = grad_desc(obj_func, [7,3])
print(position)


import numpy as np
'''
    source repository: https://github.com/oliviaguest/gini.git
'''
def gini(array):
    """Calculate the Gini coefficient of a numpy array."""
    # based on bottom eq:
    # http://www.statsdirect.com/help/generatedimages/equations/equation154.svg
    # from:
    # http://www.statsdirect.com/help/default.htm#nonparametric_methods/gini.htm
    # All values are treated equally, arrays must be 1d:
    array = np.array([float(i) for i in array])
    # array = array.flatten()
    if np.amin(array) < 0:
        # Values cannot be negative:
        array -= np.amin(array)
    # Values cannot be 0:
    array += 0.0000001
    # Values must be sorted:
    array = np.sort(array)
    # Index per array element:
    index = np.arange(1,array.shape[0]+1)
    # Number of array elements:
    n = array.shape[0]
    # Gini coefficient:
    return ((np.sum((2 * index - n  - 1) * array)) / (n * np.sum(array)))

if __name__ == '__main__':
    # print gini(np.array([1.0,0,0]))
    print(gini(np.array([0.1,0.5,0.2,0.1])))


import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
import pandas as pd
from scipy.optimize import curve_fit

# Write with LaTeX
rc('text', usetex=True)
rc('font', family='serif')

# Read .csv file
file = pd.read_csv('Exp1.csv', header=None)

a = file[1][1:16]
a = np.array([float(i) for i in a])
da = np.array([0.05 for i in range(15)])

b = file[2][1:16]
b = np.array([float(i) for i in b])
db = np.array([0.05 for i in range(15)])

aa = file[3][1:16]
aa = np.array([float(i) for i in aa])
daa = np.array([0.05 for i in range(15)])

bb = file[4][1:16]
bb = np.array([float(i) for i in bb])
dbb = np.array([0.05 for i in range(15)])

# Consentrate Measurements
y = np.concatenate((a,aa)) + np.concatenate((b,bb))
x = np.concatenate((a,aa))

# Compute Errors with error propagation rule
dy = np.sqrt(2) * np.concatenate((da,daa))
dx = np.concatenate((da,daa))

# Fit to Data
# Functions to fit
def func(x, a, b):
    return x**2/(a * x + b)

def f(x, a, b):
    return a * x + b

def F(y, a, b):
    return a * y + b

# Use not only the data but also its errors
popt, pcov = curve_fit(func, xdata=x, ydata=y, sigma=1./(dy*dy))
err = np.sqrt(np.diag(pcov))

popt1, pcov1 = curve_fit(F, xdata=y[:3], ydata=x[:3], sigma=1./(dx[:3]*dx[:3]))
err1 = np.sqrt(np.diag(pcov1))

popt2, pcov2 = curve_fit(f, xdata=x[15:-2], ydata=y[15:-2], sigma=1./(dy[15:-2]*dy[15:-2]))
err2 = np.sqrt(np.diag(pcov2))

# Plot to Visualize
fig, ax = plt.subplots(1,1)

xx = np.linspace(17.78, 111.6, 100)
yy1 = np.linspace(60, 130, 20)
xx2 = np.linspace(25, 111.5, 20)

ax.errorbar(x, y, xerr=dx, yerr=dy, color='black', capsize=3, elinewidth=1, markeredgewidth=1, linestyle='None', marker='.')
ax.plot(xx, func(xx, *popt), label='$y=\\frac{x^2}{(%1.1f x  %1.2f)}$' % (popt[0], popt[1]), color = 'blue')
ax.plot(F(yy1, *popt1), yy1, label='$x=%.2f y + %.2f$' % (popt1[0], popt1[1]), color = 'green')
ax.plot(xx2, f(xx2, *popt2), label='$y=%.2f x + %.2f$' % (popt2[0], popt2[1]), color = 'magenta')
ax.set_xlabel("$\\alpha$ $(\mathrm{cm})$")
ax.set_ylabel("$\\alpha+\\beta$ $(\mathrm{cm})$")
ax.legend()

fig.tight_layout()

plt.show()

# Print line slopes and constant coefficients
print(f" a ± da = {popt[0]} ± {err[0]},\n\n b ± db = {popt[1]} ± {err[1]}\n\n\n")
print(f" a ± da = {popt1[0]} ± {err1[0]},\n\n b ± db = {popt1[1]} ± {err1[1]}\n\n\n")
print(f" a ± da = {popt2[0]} ± {err2[0]},\n\n b ± db = {popt2[1]} ± {err2[1]}")


import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
from scipy import stats
from scipy.stats import kurtosis, skew

def pdf(costh, P_mu):
    # define our probability density function
    return 0.5 * (1.0 - 1.0 / 3.0 * P_mu * costh)

def inv_cdf_pos(r, P_mu):
    # inverse of the cumulative density function
    # since we have positive and negative solutions for x(r), we have to consider both
    return 3./P_mu*(1.+np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_cdf_neg(r, P_mu):
    return 3./P_mu*(1.-np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_trans(N_measurements, P_mu):
    r = np.random.uniform(0.0, 1.0, size=N_measurements)
    data_pdf = inv_cdf_neg(r, P_mu)
    return data_pdf

def acc_rej(N_measurements, P_mu):
    x = np.random.uniform(-1.0, 1.0, size=N_measurements)

    x_axis = np.linspace(-1.0,1.0, 1000)
    fmax= np.amax(pdf(x_axis,P_mu)) #find the maximum of the function

    u = np.random.uniform(0, fmax, size= N_measurements)

    # we use a mask in order to reject the values we don't want
    data_pdf = x[u < pdf(x,P_mu)]
    return data_pdf

N_measurements=100
P_mu=0.5
N_experiments_max=10000
N_experiments = 0
N_bins=50
number_of_bin=25

#changing the value of the bin we obtain the mean in a different point of the pdf (not x), ie the y axis.

single_bin = np.array([])

while N_experiments < N_experiments_max:
    data_pdf = inv_trans(N_measurements, P_mu)
    counts_bins, edges_bins = np.histogram(data_pdf, bins=N_bins, density=True)
    single_bin=np.append(single_bin, counts_bins[number_of_bin])
    N_experiments += 1

center_bin = (edges_bins[number_of_bin] + edges_bins[number_of_bin+1])/2.
parameter = pdf(center_bin, P_mu)
print(parameter)

plt.hist(single_bin, 12, normed=True, histtype="step", color="black", linewidth = "1")
#x_axis=np.linspace(0.4, 0.6, 100)
#plt.plot(x_axis, stats.poisson.pmf(x_axis, parameter), color="orange", linewidth="1", label ="$Poisson distribution$")
#plt.plot(x_axis, stats.norm.pdf(x_axis, loc=parameter, scale=sqrt(parameter)), color="red", linewidth="1", label = r"$\mathcal{N} \ (N,\sqrt{2N})$")
plt.xlabel(r'$n_i$')
plt.ylabel(r'$N(n_i)$')
plt.xlim(0, 2)

plt.show()

Projects/Probability and Monte Carlo Techniques/_6_single_bin.py


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
# Main Driver Function
if __name__ == '__main__':

	# Part c: Plot data and the optimal linear fit
	X = np.array([0, 2, 3, 4])
	y = np.array([1, 3, 6, 8])
	plt.style.use('ggplot')
	plt.plot(X, y, 'ro')
	# generate space for optimal linear fit
	m_opt = 62. / 35 # solution from part a
	b_opt = 18. / 35 # solution from part a
	X_space = np.linspace(-1, 5, num=100).reshape(-1, 1)
	y_space = (m_opt * X_space + b_opt).reshape(-1, 1)
	plt.plot(X_space, y_space)
	plt.savefig('hw1_pr2_c.png', format='png')
	plt.close()

    # Part d: Optimal linear fit with random data points
	# generate random data points
	mu, sigma, sampleSize = 0, 1, 100
	noise = np.random.normal(mu, sigma, sampleSize).reshape(-1, 1)
	# generate y-coordinate of the 100 points with noise
	y_space_rand = m_opt * X_space + b_opt + noise
	# calculate new weights
	X_space_stacked = np.hstack((np.ones_like(y_space), X_space))
	W_opt = np.linalg.solve(X_space_stacked.T @ X_space_stacked,
		X_space_stacked.T @ y_space_rand)
    # get the new m, and new b from W_opt obtained above
	b_rand_opt, m_rand_opt = W_opt.item(0), W_opt.item(1)
	# generate the y-coordinate of 100 points with the new parameters obtained
	y_pred_rand = np.array([m_rand_opt * x + b_rand_opt for x in X_space]).reshape(-1, 1)
	# generate plots with legend
	# plot original data points and line
	plt.plot(X, y, 'ro')
	orig_plot, = plt.plot(X_space, y_space, 'r')
	# plot the generated 100 points with white gaussian noise and the new line
	plt.plot(X_space, y_space_rand, 'bo')
	rand_plot, = plt.plot(X_space, y_pred_rand, 'b')
	# set up legend and save the plot to the current folder
	plt.legend((orig_plot, rand_plot), \
		('original fit', 'fit with noise'), loc = 'best')
	plt.savefig('hw1_pr2_d.png', format='png')
	plt.close()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

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

##################################################
#Ecuación de onda
#
#d^2/dt^2 u = c^2*d^2/dx^2 u     to<t<tf, a<x<b
#
#u(a,t) = c1, u(b,t) = c2 para todo t
#u(x,to) = f(x) para todo x
#
#[x,t,u] = onda(f,g.c1,c2,a,b,to,tf,c,n,m)
#
#Argumentos de entrada:
#=====================
#f 		= función de la condición inical
#g 		= función de la condición inical (derivada)
#c1,c2 	= condiciones de borde
#a,b	= límites de la variable espacial
#to,tf	= tiempos inicial y final
#c		= constante de la ecuación diferencial
#n		= número de intervalos en la variable espacial
#m		= número de intervalos en la variable temporal
#
#Argumentos de salida:
#====================
#x		= grilla en la variable espacial
#t		= grilla en la variable temporal
#u		= resultado

def waveEq(f, g, a, b, t0, tf, c, xDots, tDots):
    """Solve wave equation:  d^2/dt^2 u(x,t) = c^2 * d^2/dx^2 u(x,t)

    For t0 < t < tf  and  a < x < b

    Contour conditions:
    u(a,t) = u(b,t) = 0     For t >= 0

    Algorithm refractored from lecture example.

    Arguments:
        f       : Initial condition function. Valid for x in [a;b]
        g       : Derivative of f.            Valid for x in [a;b]
        a, b    : Space interval
        t0, tf  : Time interval
        c       : Wave speed
        xDots   : Grill dots in space interval
        tDots   : Grill dots in time interval

    Returns:
        x, t, u

        x       : Grill for the spatial variable
        t       : Grill for the time variable
        u       : Equation numerical solution

    Raises:
        ArithmeticError.
    """

    class Grill:
        def __init__(self, sizeX, sizeY):
            self.X = sizeX
            self.T = sizeY

    # Delta x
    deltaX = (b-a)/(xDots-1)

    # Delta t
    deltaT = (tf-t0)/(tDots-1)

    #Grill
    grill = Grill(np.linspace(a, b, xDots), np.linspace(t0, tf, tDots))

    # Numeric integration constants
    r = c * deltaT / deltaX

    if (r > 1):
        raise ArithmeticError('r value ends up being greater than 1. '
                    'Please modify your grill and/or wave speed parameters.')

    rSquared = r * r
    rSquaredOverTwo = rSquared / 2
    oneMinusRSqd = 1 - rSquared
    twiceOneMRS = 2 * oneMinusRSqd

    u = np.zeros((xDots, tDots))

    # Initial condition

    # First row
    u[1:-1, 0] = f(grill.X[1:-1])

    # Second row (derivative)
    # NOT Using an approximation for the second derivative (needs to add it)
    #u[1:-1, 1] = u[1:-1, 0] + g(grill.X[1:-1]) * deltaT

    # Using an approximation for the second derivative
    u[1:-1, 1] = oneMinusRSqd * u[1:-1, 0] + deltaT * g(grill.X[1:-1]) \
                + rSquaredOverTwo * (u[2:, 0] + u[0:-2, 0])

    for j in range(2,tDots):
        u[1:-1, j] = twiceOneMRS * u[1:-1, j-1] \
                    + rSquared * (u[0:-2, j-1] + u[2:, j-1]) - u[1:-1, j-2]

    return grill.X,grill.T,u

def testWave():
    """Test for the Wave equation algorithm taken from lecture.
    """
    f = lambda x: (np.sin(np.pi*x)+np.sin(2*np.pi*x))
    g = lambda x: np.zeros(x.size)

    a = 0.0
    b = 1.0
    t0 = 0.0
    tf = 1.0
    c = 2.0
    n = 111
    m = int(n*c - 1)

    print('Calculating equation using:')
    print(f'\tx in ({a} ; {b})\n\tt in ({t0}; {tf})')
    print(f'\n\tc = {c}\n\tDots for x = {n}\n\tDots for t = {m}')
    print(f'\tr = {c * ((tf-t0)/(m-1)) / ((b-a)/(n-1))}\n')

    x, t, u = waveEq(f, g, a, b, t0, tf, c, n, m)

    print('-> Finished <-')

    # Analytical solution
    ua = np.zeros(u.shape)
    for k in range(x.shape[0]):
        ua[k,:] = np.sin(np.pi * x[k]) * np.cos(2.0 * np.pi * t) \
                    + np.sin(2.0 * np.pi * x[k]) * np.cos(4.0 * np.pi * t)
    print('Error: ', np.max(np.abs(u-ua)))

if __name__ == "__main__":
    testWave()


"""MC1-P2: Optimize a portfolio."""

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
from util import get_data, plot_data
import math as m
import scipy.optimize as spo

# This is the function that will be tested by the autograder
# The student must update this code to properly implement the functionality
def optimize_portfolio(sd=dt.datetime(2008,1,1), ed=dt.datetime(2009,1,1), \
    syms=['GOOG','AAPL','GLD','XOM'], gen_plot=False):

    # Read in adjusted closing prices for given symbols, date range
    dates = pd.date_range(sd, ed)
    prices_all = get_data(syms, dates, sd, ed)  # automatically adds SPY
    prices = prices_all[syms]  # only portfolio symbols
    prices_SPY = prices_all['SPY']  # only SPY, for comparison later

    # find the allocations for the optimal portfolio
    sv = 1000000
    normalized_prices = prices / prices.ix[0, :]
    x0 = np.array([0.2, 0.2, 0.3, 0.3, 0.0])
    optimal_allocs = spo.minimize(f, x0, args=(normalized_prices, sv),
                        method='SLSQP', options={'disp': True},
                        bounds=tuple((0, 1) for i in range(0, x0.size)),
                        constraints = ({'type': 'eq', 'fun': \
                        lambda inputs: 1.0 - np.sum(inputs)})
                        )
    allocs = optimal_allocs.x
    port_val, cr, adr, sddr, sr = calc_portfolio_stats(allocs, normalized_prices, sv)

    # Compare daily portfolio value with SPY using a normalized plot
    if gen_plot:
        df_temp = pd.concat([port_val, prices_SPY], keys=['Portfolio', 'SPY'], axis=1)
        df_temp = df_temp / df_temp.ix[0, :]
        df_temp.plot()
        plt.show()
        pass

    return allocs, cr, adr, sddr, sr

def f(x, normalized_prices, sv):

    port_val, cr, adr, sddr, sr = calc_portfolio_stats(x, normalized_prices, sv)

    # Return Sharpe Ratio
    # Multiply by -1 because we are minimizing this function
    return sr * -1

def calc_portfolio_stats(allocs, normalized_prices, sv):
    # Calculate position values and sum for portfolio values
    position_values = allocs * normalized_prices * sv
    port_val = position_values.sum(axis=1)

    # Get portfolio statistics
    portfolio_returns = (port_val / port_val.shift(1) - 1).fillna(0)
    cr = (portfolio_returns + 1).cumprod()[-1] - 1
    adr = portfolio_returns.mean()
    sddr = portfolio_returns.std()
    sr = ((adr - 0) / sddr) * m.sqrt(252)

    return port_val, cr, adr, sddr, sr

def test_code():
    # This function WILL NOT be called by the auto grader
    # Do not assume that any variables defined here are available to your function/code
    # It is only here to help you set up and test your code

    # Define input parameters
    # Note that ALL of these values will be set to different values by
    # the autograder!

    start_date = dt.datetime(2009,1,1)
    end_date = dt.datetime(2010,1,1)
    symbols = ['GOOG', 'AAPL', 'GLD', 'XOM', 'IBM']

    # Assess the portfolio
    allocations, cr, adr, sddr, sr = optimize_portfolio(sd = start_date, ed = end_date,\
        syms = symbols, \
        gen_plot = False)

    # Print statistics
    print "Start Date:", start_date
    print "End Date:", end_date
    print "Symbols:", symbols
    print "Allocations:", allocations
    print "Sharpe Ratio:", sr
    print "Volatility (stdev of daily returns):", sddr
    print "Average Daily Return:", adr
    print "Cumulative Return:", cr

if __name__ == "__main__":
    # This code WILL NOT be called by the auto grader
    # Do not assume that it will be called
    test_code()


"""
    File name: 3.py
    Author: AyumiizZ
    Date created: 2020/10/04
    Python Version: 3.8.5
    About: Merge k sorted arrays, each with n elements problem
"""
from random import randint
import numpy as np
import pprint

DEBUG = False

def generate_data(k: int, n: int, min_data: int = 1, max_data: int = 100):
    '''Generate k sorted arrays, each with n elements

    Parameters
    ----------
    k : integer
        Number of sorted arrays
    n : integer
        Number of elements in each arrays
    min_data, max_data: integer
        All element in the array be like min_data <= each elements <= max_data
    '''
    if(k*n > max_data-min_data-1):
        print("Please change max min data")
        exit()
    used = []
    dataset = []
    for i in range(k):
        temp = []
        for j in range(n):
            random_number = None
            while random_number == None or random_number in used:
                random_number = randint(min_data, max_data)
            used.append(random_number)
            temp.append(random_number)
        dataset.append(sorted(temp))
    return dataset

def merge_2(arr1: list, arr2: list):
    '''Merge two 1d arrays to one array

    Parameters
    ----------
    arr1 : 1d array
           Elements to merge
    arr2 : 1d array
           Elements to merge
    '''
    merged = list(np.zeros((len(arr1)+len(arr2))))
    i = j = k = 0
    while(i < len(arr1) and j < len(arr2)):
        if(arr1[i] < arr2[j]):
            merged[k] = arr1[i]
            i += 1
        else:
            merged[k] = arr2[j]
            j += 1
        k += 1
    while(i < len(arr1)):
        merged[k] = arr1[i]
        i += 1
        k += 1
    while(j < len(arr2)):
        merged[k] = arr2[j]
        j += 1
        k += 1
    return merged

def merge_k(arr_k: list):
    '''Recursive merge all k sorted arrays

    Parameters
    ----------
    arr_k : k dimension array
            Elements to merge
    '''
    if len(arr_k) == 1:
        return arr_k[0]
    mid = len(arr_k) // 2
    arr1 = arr_k[:mid]
    arr2 = arr_k[mid:]

    arr1 = merge_k(arr1)
    arr2 = merge_k(arr2)
    merged = merge_2(arr1, arr2)
    if (DEBUG):
        print(f"arr1: {arr1}")
        print(f"arr2: {arr2}")
        print(f"merged: {merged}")
    return merged

def print_result(dataset: list, merged: list):
    '''Print result pretty format'''
    pp = pprint.PrettyPrinter(indent=4)
    print("Dataset")
    print("-------")
    pp.pprint(dataset)
    print("Result")
    print("-------")
    print(merged)

if __name__ == "__main__":
    k = int(input("K: "))
    n = int(input("N: "))
    dataset = generate_data(k=k, n=n)
    merged = merge_k(dataset)
    print_result(dataset, merged)


#gauss suicidal

import numpy as np
from scipy.linalg import solve
import collections

vals = []

def gauss(A, b, x):
    L = np.tril(A)

    U = A - L

    aux1 = np.linalg.inv(L)
    aux2 = b - np.dot(U, x)

    print(aux2)
    x = np.dot(aux1, aux2)
    vals.append(x)
    return x
def gaussseidel(A,b,tol,lim):
    cont = 0
    x = [1, 1, 1]
    while(True):
        old = x
        x = gauss(A,b,x)
        cont = cont + 1
        tols = []
        for val1,val2 in zip(x,old):
            if((abs(val1-val2))<tol):
                tols.append(True)
            else:
                tols.append(False)
        aux = np.all(tols)
        if np.allclose(x,old):
            return x
        elif aux:
            print("TOL excedido")
            return x
        elif cont == lim:
            print("Iteracoes excedidas")
            return x

def calcularVetor():
    valx1 = []
    valx2 = []
    valx3  = []
    valfinal = [valx1,valx2,valx3]
    for val in vals:
        for j in range(len(val)):
            valfinal[j].append(val[j])
    return valfinal

A = np.array([[1.0, 0.0, -1.0], [-0.5, 1.0, -0.25], [1.0, -0.5, 1.0]])
b = [0.2, -1.425, 2.0]
#val = gaussseidel(A,b,1e-2,300)
#valfinal = calcularVetor()


# Undergraduate Student: Arturo Burgos
# Professor: João Rodrigo Andrade
# Federal University of Uberlândia - UFU, Fluid Mechanics Laboratory - MFLab, Block 5P, Uberlândia, MG, Brazil

# Fourth exercise: Solving a Linear System --> ax = b

# Here I first set conditions

from numba import jit
import numpy as np
from numpy import linalg as La
import time
np.seterr(divide='ignore', invalid='ignore')

print('\n')

n = 3000
K = np.sqrt(n)
k = int(K)

# Here I set the Matrix

a = np.zeros((n,n))

for i in range(n):
    for j in range(n):
        if i == j:
            a[i,j] = -4
        elif i == j-3 or i ==j+3:
            a[i,j] = 1
        elif ((i+1) % 3 != 0 and i == j-1) or ((i+1) % 3 != 1 and i == j+1): # (i+1) because in Python we start from 0
            a[i,j] = 1

print('The coefficient Matrix is:')
print(a)
print('\n')

b = np.zeros(n)

for i in range(k):
    if i < k-1: # (k-1) because in Python we start from 0
        b[i] = -50
    else:
        b[i] = -150

for i in range(k,n-k):
    if (i+1)%k != 0: # (i+1) because in Python we start from 0
        b[i] = 0
    else:
        b[i] = -100

for i in range(n-k,n):
    if i < n-1: # (k-1) because in Python we start from 0
        b[i] = -50
    else:
        b[i] = -150

print('The result Matrix is:')
print(b)
print('\n')

@jit(nopython=True)
def linearsystem(coeff,resul,size):

    # Initial x_k and x_k1 value

    x_k = np.zeros(size)
    x_k1 = np.ones(size)

    # Here I set the tolerance
    tolerance = 1e-9
    # Here I set the iterations
    ite = 0

    # Here I set the error based in the Infinite norm
    erro = La.norm((x_k1 - x_k),np.inf)
    #erro = (x_k1 - x_k)/x_k1

    while (erro > tolerance): #
        for i in range(0,size):

            x_k1[i] = resul[i]

            for j in range(0,n):
                if j!=i:
                    x_k1[i] =  x_k1[i] - coeff[i,j]*x_k[j]

            x_k1[i] =  x_k1[i]/ coeff[i,i]

        #erro = (x_k1 - x_k)/x_k1
        erro = La.norm((x_k1 - x_k),np.inf)

        x_k = x_k1.copy()
        #x_k[:] = x_k1[:] # -> the same as above

        ite = ite + 1

    print('The number of iterations is: ')
    print(ite)
    print('\n')

    print('Note that now the error is not an array anymore, but is normalized :')
    print(erro)
    print('\n')

    return x_k1

t_initial = time.time()  # I set here the initial time.
res = linearsystem(a,b,n)

print('\n')
print("--- %s seconds ---\n" % (time.time() - t_initial))
print('\n')
# First we compile the code, then now we execute from cache.

t_initial = time.time()  # I set here the initial time.
res = linearsystem(a,b,n)
print("--- %s seconds after compilation---\n" % (time.time() - t_initial))

print('The solution is:')
print(res)
print('\n')


from typing import Any
import math

import numpy as np

import Util.Array2DUtil as Array2DUtil

def applyKernel(image: list[list[int]], kernal: list[list[float]]) -> list[list[float]]:
    (row, col) = Array2DUtil.getRowCol(image)
    k = Array2DUtil.getRadius(kernal)
    result = Array2DUtil.paddingZero(row, col, k)
    for (resultRow, resultCol), (paddingRow, paddingCol) in zip(
        Array2DUtil.iter(result), Array2DUtil.paddingIter(image, k)
    ):
        sum: int = 0
        for (kernalRow, kernalCol), (offsetRow, offsetCol) in zip(
            Array2DUtil.iter(kernal), Array2DUtil.centerAlignOffsetIter(kernal)
        ):
            sum += (
                image[paddingRow + offsetRow][paddingCol + offsetCol]
                * kernal[kernalRow][kernalCol]
            )
        result[resultRow][resultCol] = sum
    return result

def genMeanKernel(k: int = 1) -> list[list[float]]:
    length = 2 * k + 1
    return np.full((length, length), 1 / (length * length))

def gaussianFunc(x: int, y: int, sd: float = 1.0) -> float:
    return math.exp(-(x**2 + y**2) / (2 * sd**2)) / (2 * math.pi * sd**2)

def genGaussianKernel(k: int = 1, sd: float = 1.0) -> list[list[float]]:
    result = list(Array2DUtil.centerAlignOffsetArray2D(k))
    result = Array2DUtil.map(result, lambda ele, r, c: gaussianFunc(ele[0], ele[1], sd))
    sum = Array2DUtil.sum(result)
    result = Array2DUtil.map(result, lambda ele, r, c: ele / sum)
    return result


#!/usr/bin/python3

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

COLOR = {
    'train': '#1453ad',
    'test': '#ad1453'
}

def plot_scatter(x: np.ndarray, y: np.ndarray, scatter_fname: str) -> None:
    fig, ax = plt.subplots()
    ax.scatter(x, y, color = COLOR['train'])
    ax.grid()
    fig.savefig(scatter_fname, dpi = 800, transparent=True)
    plt.clf()

def expand(x: np.ndarray, degree: int) -> np.ndarray:
    degree = max(0, degree)
    size = len(x)
    x_expansion = np.empty([size, degree + 1], dtype = x.dtype)
    x_expansion[:, 0] = 1
    for i in range(degree):
        x_expansion[:, i + 1] = x_expansion[:, i] * x
    return x_expansion

def ridge_regression(x: np.ndarray, y: np.ndarray, lambda_param: float) -> np.ndarray:
    lambda_param = max(0.0, lambda_param)
    if lambda_param == 0.0:
        return np.dot(np.linalg.pinv(x), y)
    weight = lambda_param * np.eye(np.shape(x)[1], dtype = x.dtype)
    weight += np.dot(x.T, x)
    weight = np.linalg.inv(weight)
    weight = np.dot(weight, x.T)
    weight = np.dot(weight, y)
    return weight

def main(x_train: np.ndarray, y_train: np.ndarray,
        x_test: np.ndarray, y_test: np.ndarray,
        degree: int, lambda_param: float = 0.0, plot: bool = True) -> tuple:
    x_train_expansion = expand(x_train, degree)
    weight = ridge_regression(x_train_expansion, y_train, lambda_param)
    y_train_regression = np.dot(x_train_expansion, weight)
    mse_train = np.mean(np.square(y_train - y_train_regression))
    x_test_expansion = expand(x_test, degree)
    y_test_regression = np.dot(x_test_expansion, weight)
    mse_test = np.mean(np.square(y_test - y_test_regression))

    if not plot:
        return weight, mse_train, mse_test
    fig, ax = plt.subplots()
    ax.scatter(x_test, y_test, color = '#1453ad')
    linespace = np.linspace(np.min(x_train), np.max(x_train), 1000)
    ax.plot(linespace, np.dot(expand(linespace, degree), weight))
    ax.grid()
    fig.savefig('./out/curve_' + str(degree) + '_' + str(lambda_param) + '.png', transparent=True)
    plt.clf()
    return weight, mse_train, mse_test

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--x-train', help = 'x_train file', required = True)
    parser.add_argument('--y-train', help = 'y_train file', required = True)
    parser.add_argument('--x-test', help = 'x_test file', required = True)
    parser.add_argument('--y-test', help = 'y_test file', required = True)
    parser.add_argument('-d', '--degree', nargs = '+', help = 'Degrees of polynomials', required = True)
    parser.add_argument('-l', '--lambda', nargs = '+', help = 'Lambda in idge regression')
    parser.add_argument('--scatter', help = 'Filename of scatter graph')
    parser.add_argument('--mse-train', help = 'Filename of MSE vs degree graph')
    parser.add_argument('--mse-test', help = 'Filename of MSE vs degree graph')
    parser.add_argument('--mse-lambda', help = 'Filename of MSE vs lambda graph')
    args = parser.parse_args()

    x_train, y_train = np.genfromtxt(args.x_train), np.genfromtxt(args.y_train)
    x_test, y_test = np.genfromtxt(args.x_test), np.genfromtxt(args.y_test)

    if args.scatter:
        plot_scatter(x_train, y_train, args.scatter)
    degrees = np.array(args.degree, dtype = np.int)
    if getattr(args, 'lambda') == None:
        setattr(args, 'lambda', ['0'])
    lambdas = np.array(getattr(args, 'lambda'), dtype = np.float64)

    if len(degrees) ==  1 or len(lambdas) == 1:
        mse_train_list = np.empty(len(degrees) * len(lambdas), dtype = np.float64)
        mse_test_list = np.empty(len(degrees) * len(lambdas), dtype = np.float64)
    for i in range(len(degrees)):
        for j in range(len(lambdas)):
            weight, mse_train, mse_test = main(x_train, y_train, x_test, y_test, degrees[i], lambdas[j])
            if len(lambdas) == 1:
                mse_train_list[i] = mse_train
                mse_test_list[i] = mse_test
            elif len(degrees) == 1:
                mse_train_list[j] = mse_train
                mse_test_list[j] = mse_test
            print('Degree: %d, Lambda: %f' % (degrees[i], lambdas[j]))
            print('Weight vector: %s' % weight)
            print('Training Data MSE: %f' % mse_train)
            print('Test Data MSE: %f' % mse_test)
            print('')

    if args.mse_train and len(lambdas) == 1:
        fig, ax = plt.subplots()
        ax.plot(degrees, mse_train_list, '-o', label = 'Training MSE', color = COLOR['train'])
        ax.set_xticks(degrees)
        ax.legend()
        ax.grid()
        fig.savefig(args.mse_train, dpi = 800, transparent=True)
        plt.clf()
    if args.mse_test and len(lambdas) == 1:
        fig, ax = plt.subplots()
        ax.plot(degrees, mse_test_list, '-o', label = 'Test MSE', color = COLOR['test'])
        ax.set_xticks(degrees)
        ax.legend()
        ax.grid()
        fig.savefig(args.mse_test, dpi = 800, transparent=True)
        plt.clf()
    if args.mse_lambda and len(degrees) == 1:
        fig, ax = plt.subplots()
        ax.plot(lambdas, mse_train_list, '-o', label = 'Training MSE', color = COLOR['train'])
        ax.plot(lambdas, mse_test_list, '-o', label = 'Test MSE', color = COLOR['test'])
        ax.set_xscale('log')
        ax.set_xticks(lambdas)
        ax.legend()
        ax.grid()
        fig.savefig(args.mse_lambda, dpi = 800, transparent=True)
        plt.clf()


import numpy
from numpy import pi

__all__ = ['morlet', 'chirp', 'mexican', 'meyeraux']

def morlet(lb, ub, n):
    r"""Generate the Morlet waveform

    The Morlet waveform is defined as follows:

    .. math:: w[x] = \cos{5x}  \exp^{-x^2/2}

    :param lb: lower bound
    :param ub: upper bound
    :param int n: waveform data samples

    .. plot::
        :include-source:
        :width: 80%

        from spectrum import morlet
        from pylab import plot
        plot(morlet(0,10,100))

    """
    if n <= 0:
        raise ValueError("n must be strictly positive")

    x = numpy.linspace(lb, ub, n)
    psi = numpy.cos(5*x) * numpy.exp(-x**2/2.)
    return psi

def chirp(t, f0=0., t1=1., f1=100., form='linear', phase=0):
    r"""Evaluate a chirp signal at time t.

    A chirp signal is a frequency swept cosine wave.

    .. math:: a = \pi  (f_1 - f_0) / t_1
    .. math:: b = 2  \pi  f_0
    .. math:: y = \cos\left( \pi\frac{f_1-f_0}{t_1}  t^2 + 2\pi f_0 t + \rm{phase} \right)

    :param array t: times at which to evaluate the chirp signal
    :param float f0: frequency at time t=0 (Hz)
    :param float t1: time t1
    :param float f1: frequency at time t=t1 (Hz)
    :param str form: shape of frequency sweep in ['linear', 'quadratic', 'logarithmic']
    :param float phase: phase shift at t=0

    The parameter **form** can be:

        * 'linear'      :math:`f(t) = (f_1-f_0)(t/t_1) + f_0`
        * 'quadratic'   :math:`f(t) = (f_1-f_0)(t/t_1)^2 + f_0`
        * 'logarithmic' :math:`f(t) = (f_1-f_0)^{(t/t_1)} + f_0`

    Example:

    .. plot::
        :include-source:
        :width: 80%

        from spectrum import chirp
        from pylab import linspace, plot
        t = linspace(0, 1, 1000)
        y = chirp(t, form='linear')
        plot(y)
        y = chirp(t, form='quadratic')
        plot(y, 'r')

    """
    valid_forms = ['linear', 'quadratic', 'logarithmic']
    if form not in valid_forms:
        raise ValueError("Invalid form. Valid form are %s"
            % valid_forms)
    t = numpy.array(t)
    phase = 2. * pi * phase / 360.
    if form == "linear":
        a = pi * (f1 - f0)/t1
        b = 2. * pi * f0
        y = numpy.cos(a * t**2 + b*t + phase)
    elif form == "quadratic":
        a = (2/3. * pi * (f1-f0)/t1/t1)
        b = 2. * pi * f0
        y = numpy.cos(a*t**3 + b * t + phase)
    elif form == "logarithmic":
        a = 2. * pi * t1/numpy.log(f1-f0)
        b = 2. * pi * f0
        x = (f1-f0)**(1./t1)
        y = numpy.cos(a * x**t + b * t + phase)

    return y

def mexican(lb, ub, n):
    r"""Generate the mexican hat wavelet

    The Mexican wavelet is:

    .. math:: w[x] = \cos{5x}  \exp^{-x^2/2}

    :param lb: lower bound
    :param ub: upper bound
    :param int n: waveform data samples
    :return: the waveform

    .. plot::
        :include-source:
        :width: 80%

        from spectrum import mexican
        from pylab import plot
        plot(mexican(0, 10, 100))

    """
    if n <= 0:
        raise ValueError("n must be strictly positive")

    x = numpy.linspace(lb, ub, n)
    psi = (1.-x**2.) * (2./(numpy.sqrt(3.)*pi**0.25)) * numpy.exp(-x**2/2.)
    return psi

def meyeraux(x):
    r"""Compute the Meyer auxiliary function

    The Meyer function is

    .. math:: y = 35 x^4-84 x^5+70 x^6-20 x^7

    :param array x:
    :return: the waveform

    .. plot::
        :include-source:
        :width: 80%

        from spectrum import meyeraux
        from pylab import linspace, plot
        t = linspace(0, 1, 1000)
        plot(t, meyeraux(t))

    """

    return  35*x**4-84.*x**5+70.*x**6-20.*x**7


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

class Curve2D():

    def __init__(self, name: str, t: sp.Symbol, vector_sym, init_t: float, color: str,
                 speed=1, scale=1, offset=[0, 0]):

        ## Symbolic attributes

        # Parameter
        self.t_sym = t * speed

        # Parametrized Vector
        self.x_sym = vector_sym[0].subs(t, self.t_sym) * scale + offset[0]
        self.y_sym = vector_sym[1].subs(t, self.t_sym) * scale + offset[1]

        # Velocity
        self.dx_sym = sp.diff(self.x_sym)
        self.dy_sym = sp.diff(self.y_sym)

        # Acceleration
        self.ddx_sym = sp.diff(self.dx_sym)
        self.ddy_sym = sp.diff(self.dy_sym)

        ## Numeric functions (used internaly)

        self.x_ = sp.lambdify(t, self.x_sym, "numpy")
        self.y_ = sp.lambdify(t, self.y_sym, "numpy")
        self.dx_ = sp.lambdify(t, self.dx_sym, "numpy")
        self.dy_ = sp.lambdify(t, self.dy_sym, "numpy")
        self.ddx_ = sp.lambdify(t, self.ddx_sym, "numpy")
        self.ddy_ = sp.lambdify(t, self.ddy_sym, "numpy")

        ## Manim Objects

        # Time - used to trace the curve, equals actual time_manim passed since
        #        the start of the animation
        self.time_m = ValueTracker(init_t)

        def time_m_updater(time_m, dt):
            time_m.increment_value(dt)

        self.time_m.add_updater(time_m_updater)

        # Dot - mark the current location of the curve with a dot
        self.dot_m = Dot(point=self.vector(init_t), color=color)

        def dot_m_updater(dot_m):
            t = self.time_m.get_value()
            dot_m.move_to(self.vector(t))

        self.dot_m.add_updater(dot_m_updater)

        # Velocity vector on screen
        self.velocity_m = Vector(self.velocity(init_t), color=color)

        def velocity_m_updater(velocity_m):
            t = self.time_m.get_value()
            start = self.dot_m.get_center()
            end = start + self.velocity(t)
            velocity_m.put_start_and_end_on(start, end)

        self.velocity_m.add_updater(velocity_m_updater)

        # Acceleration vector on screen
        self.acceleration_m = Vector(self.acceleration(init_t), color=color)

        def acceleration_m_updater(acceleration_m):
            t = self.time_m.get_value()
            start = self.dot_m.get_center()
            end = start + self.acceleration(t)
            acceleration_m.put_start_and_end_on(start, end)

        self.acceleration_m.add_updater(acceleration_m_updater)

        # Path to be traced
        self.path_m = TracedPath(self.dot_m.get_center, stroke_width=3,
                                 stroke_color=color)

        # Velocity label
        self.velocity_name_m = MathTex("\\" + name + r"^{\prime}").scale(.75)

        def velocity_name_m_updater(velocity_name_m):
            t = self.time_m.get_value()
            velocity_name_m.move_to(self.vector(t) + self.velocity(t)*1.2)

        self.velocity_name_m.add_updater(velocity_name_m_updater)

        # Acceleration label
        self.acceleration_name_m = MathTex("\\" + name + r"^{\prime\prime}").scale(.75)

        def acceleration_name_m_updater(acceleration_name_m):
            t = self.time_m.get_value()
            acceleration_name_m.move_to(self.vector(t) + self.acceleration(t)*1.2)

        self.acceleration_name_m.add_updater(acceleration_name_m_updater)

    def x(self, t):
        return self.x_(t)

    def y(self, t):
        return self.y_(t)

    def vector(self, t):
        return np.array([self.x(t), self.y(t), 0])

    def dx(self, t):
        return self.dx_(t)

    def dy(self, t):
        return self.dy_(t)

    def velocity(self, t):
        return np.array([self.dx(t), self.dy(t), 0])

    def ddx(self, t):
        return self.ddx_(t)

    def ddy(self, t):
        return self.ddy_(t)

    def acceleration(self, t):
        return np.array([self.ddx(t), self.ddy(t), 0])


import numpy as np
from .base_signal import BaseSignal
from torch import Tensor
from typing import Callable
import torch

__all__ = ["Sinusoidal"]

class Sinusoidal(BaseSignal):
    """Signal generator for harmonic (sinusoidal) waves.

    Parameters
    ----------
    amplitude : number (default 1.0)
        Amplitude of the harmonic series
    frequency : number (default 1.0)
        Frequency of the harmonic series
    ftype : function (default np.sin)
        Harmonic function

    """

    def __init__(self,
                 amplitude:float=1.0,
                 frequency:float=1.0,
                 ftype=np.sin):
        super().__init__(vectorizable=True)
        self.amplitude:float = amplitude
        self.ftype:Callable[[np.array],np.array]= ftype
        self.frequency:float = frequency

    def sample_next(self, time:int, samples:Tensor, errors:Tensor)->float:
        """Sample a single time point

        Parameters
        ----------
        time : number
            Time at which a sample was required

        Returns
        -------
        float
            sampled signal for time t

        """
        return self.amplitude * self.ftype(2 * np.pi * self.frequency * time)

    def sample_vectorized(self, time_vector:Tensor)->Tensor:
        """Sample entire series based off of time vector

        Parameters
        ----------
        time_vector : array-like
            Timestamps for signal generation

        Returns
        -------
        array-like
            sampled signal for time vector

        """
        if self.vectorizable is True:
            signal = torch.tensor(self.amplitude * self.ftype(
                2 * np.pi * self.frequency * time_vector
            ))
            return signal
        else:
            raise ValueError("Signal type not vectorizable")


'''
Created on 19.4.2017

@author: Markus.Walden
'''

from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# use seaborn plotting style defaults
import seaborn as sns; sns.set()

# premade pca SKLEARN
from sklearn.decomposition import PCA, KernelPCA
from sklearn.datasets import make_circles, load_digits

def main ():
    linearPCA()
    nonLinearPCA()

def nonLinearPCA():

    X, y = generateData(method = 'nonLinear')

    kpca = KernelPCA(kernel="rbf", fit_inverse_transform=True, gamma=10)
    X_kpca = kpca.fit_transform(X)
    X_back = kpca.inverse_transform(X_kpca)
    pca = PCA()                             # pca = PCA(2) project from 64 to 2 dimensions
    X_pca = pca.fit_transform(X)

    X1, X2 = np.meshgrid(np.linspace(-1.5, 1.5, 50), np.linspace(-1.5, 1.5, 50))
    X_grid = np.array([np.ravel(X1), np.ravel(X2)]).T
    # projection on the first principal component (in the phi space)
    Z_grid = kpca.transform(X_grid)[:, 0].reshape(X1.shape)
    plt.contour(X1, X2, Z_grid, colors='grey', linewidths=1, origin='lower')

    reds = y == 0
    blues = y == 1

    visualizePCA(X1, X2, Z_grid, X_pca, reds, blues, X_kpca, X_back)

def linearPCA():
    '''
    generate data
    '''
    x = generateData()

    pca = PCA(n_components=2)
    pca.fit(x)
    print(pca.explained_variance_)
    print(pca.components_)

    plt.plot(x[:, 0], x[:, 1], 'o', alpha=0.5)
    for length, vector in zip(pca.explained_variance_, pca.components_):
        v = vector * 3 * np.sqrt(length)
        plt.plot([0, v[0]], [0, v[1]], '-k', lw=3)
    plt.axis('equal');
    plt.show()

    clf = PCA(0.95) # keep 95% of variance
    X_trans = clf.fit_transform(x)
    print(x.shape)
    print(X_trans.shape)

    X_new = clf.inverse_transform(X_trans)
    plt.plot(x[:, 0], x[:, 1], 'o', alpha=0.2)
    plt.plot(X_new[:, 0], X_new[:, 1], 'ob', alpha=0.8)
    plt.axis('equal')
    plt.show()

def generateData (rendomSeed = 1, method = 'linear'):
    '''
    '''
    if method == 'linear':
        np.random.seed(1)
        X = np.dot(np.random.random(size=(2, 2)), np.random.normal(size=(2, 200))).T
        plt.plot(X[:, 0], X[:, 1], 'o')
        plt.axis('equal');
        plt.show()
        return X

    elif method == 'nonLinear':
        X, y = make_circles(n_samples=400, factor=.3, noise=.05)

        plt.figure()
        plt.subplot(2, 2, 1, aspect='equal')
        plt.title("Original space")
        reds = y == 0
        blues = y == 1

        plt.plot(X[reds, 0], X[reds, 1], "ro")
        plt.plot(X[blues, 0], X[blues, 1], "bo")
        plt.xlabel("$x_1$")
        plt.ylabel("$x_2$")

        return X,y

def visualizePCA(X1, X2, Z_grid, X_pca, reds, blues, X_kpca, X_back):
    plt.contour(X1, X2, Z_grid, colors='grey', linewidths=1, origin='lower')

    plt.subplot(2, 2, 2, aspect='equal')
    plt.plot(X_pca[reds, 0], X_pca[reds, 1], "ro")
    plt.plot(X_pca[blues, 0], X_pca[blues, 1], "bo")
    plt.title("Projection by PCA")
    plt.xlabel("1st principal component")
    plt.ylabel("2nd component")

    plt.subplot(2, 2, 3, aspect='equal')
    plt.plot(X_kpca[reds, 0], X_kpca[reds, 1], "ro")
    plt.plot(X_kpca[blues, 0], X_kpca[blues, 1], "bo")
    plt.title("Projection by KPCA")
    plt.xlabel("1st principal component in space induced by $\phi$")
    plt.ylabel("2nd component")

    plt.subplot(2, 2, 4, aspect='equal')
    plt.plot(X_back[reds, 0], X_back[reds, 1], "ro")
    plt.plot(X_back[blues, 0], X_back[blues, 1], "bo")
    plt.title("Original space after inverse transform")
    plt.xlabel("$x_1$")
    plt.ylabel("$x_2$")

    plt.subplots_adjust(0.02, 0.10, 0.98, 0.94, 0.04, 0.35)

    plt.show()

if __name__ == '__main__':
    main()


# Copyright 2017 Bruno Sciolla. All Rights Reserved.
# ==============================================================================
# Generator for 2D scale-invariant Gaussian Random Fields
# ==============================================================================

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# Main dependencies
import numpy
import scipy.fftpack

def fftind(size):
    """ Returns a numpy array of shifted Fourier coordinates k_x k_y.

        Input args:
            size (integer): The size of the coordinate array to create
        Returns:
            k_ind, numpy array of shape (2, size, size) with:
                k_ind[0,:,:]:  k_x components
                k_ind[1,:,:]:  k_y components

        Example:

            print(fftind(5))

            [[[ 0  1 -3 -2 -1]
            [ 0  1 -3 -2 -1]
            [ 0  1 -3 -2 -1]
            [ 0  1 -3 -2 -1]
            [ 0  1 -3 -2 -1]]

            [[ 0  0  0  0  0]
            [ 1  1  1  1  1]
            [-3 -3 -3 -3 -3]
            [-2 -2 -2 -2 -2]
            [-1 -1 -1 -1 -1]]]

        """
    k_ind = numpy.mgrid[:size, :size] - int( (size + 1)/2 )
    k_ind = scipy.fftpack.fftshift(k_ind)
    return( k_ind )

def gaussian_random_field(alpha = 3.0,
                          size = 128,
                          flag_normalize = True):
    """ Returns a numpy array of shifted Fourier coordinates k_x k_y.

        Input args:
            alpha (double, default = 3.0):
                The power of the power-law momentum distribution
            size (integer, default = 128):
                The size of the square output Gaussian Random Fields
            flag_normalize (boolean, default = True):
                Normalizes the Gaussian Field:
                    - to have an average of 0.0
                    - to have a standard deviation of 1.0

        Returns:
            gfield (numpy array of shape (size, size)):
                The random gaussian random field

        Example:
        import matplotlib
        import matplotlib.pyplot as plt
        example = gaussian_random_field()
        plt.imshow(example)
        """

        # Defines momentum indices
    k_idx = fftind(size)

        # Defines the amplitude as a power law 1/|k|^(alpha/2)
    amplitude = numpy.power( k_idx[0]**2 + k_idx[1]**2 + 1e-10, -alpha/4.0 )
    amplitude[0,0] = 0

        # Draws a complex gaussian random noise with normal
        # (circular) distribution
    noise = numpy.random.normal(size = (size, size)) \
        + 1j * numpy.random.normal(size = (size, size))

        # To real space
    gfield = numpy.fft.ifft2(noise * amplitude).real

        # Sets the standard deviation to one
    if flag_normalize:
        gfield = gfield - numpy.mean(gfield)
        gfield = gfield/numpy.std(gfield)

    return gfield

def main():
    import matplotlib
    import matplotlib.pyplot as plt
    example = gaussian_random_field()
    plt.imshow(example, cmap='gray')
    plt.show()

if __name__ == '__main__':
    main()


# ============================================================================
# ~/cerebstats/cerebstats/stat_scores/chi2GOFScore.py
#
# This py-file contains custum score functions initiated by
#
# from cerebstats import scoreScores
# from cerebstats.scoreScores import ABCScore
# ============================================================================

import numpy as np
from scipy.stats import chisquare
import sciunit

# ==============================Chi2GOFScore==================================
class Chi2GOFScore(sciunit.Score):
    """
    Compute chi2-statistic for chi-squared goodness-of-fit Test of proportions.

    One may think of this as a **one-way contingency table.**

    +--------------+-------------------------------------------------------------+
    | sample size  | :math:`k` categories of a categorial variable of interest   |
    +              +--------------+--------------+----------------+--------------+
    | :math:`n`    | :math:`x_1`  | :math:`x_2`  | :math:`\\ldots` | :math:`x_k`  |
    +==============+==============+==============+================+==============+
    | observations | :math:`O_1`  | :math:`O_2`  | :math:`\\ldots` | :math:`O_k`  |
    +--------------+--------------+--------------+----------------+--------------+
    | probabilities| :math:`p_1`  | :math:`p_2`  | :math:`\\ldots` | :math:`p_k`  |
    +--------------+--------------+--------------+----------------+--------------+
    | expected     | :math:`np_1` | :math:`np_2` | :math:`\\ldots` | :math:`np_k` |
    +--------------+--------------+--------------+----------------+--------------+

    Notice that for probabilities of *k* categories :math:`\\sum_{\\forall i} p_i = 1`. The expected counts for each category can be derived from it (or already given) such that :math:`\\sum_{\\forall i} np_i = n`.

    .. table:: Title here

    ==================== ==============================================================================
      Definitions          Interpretation
    ==================== ==============================================================================
     :math:`n`             sample size; total number of experiments done
     :math:`k`             number of categorical variables
     :math:`O_i`           observed count (frequency) for :math:`i^{th}` variable
     :math:`p_i`           probability for :math:`i^{th}` category such that
                           :math:`\\sum_{\\forall i} p_i = 1`
     :math:`E_i`           expected count for  :math:`i^{th}` category such that
                           :math:`E_i = n p_i`
     test-statistic        :math:`\\chi^2 = \\sum_{\\forall i} \\frac{(O_i - E_i)^2}{E_i}`
     :math:`df`            degrees of freedom, :math:`df = k-1`
    ==================== ==============================================================================

    *Note* the modification made when compared with a two-way :math:`\\chi^2` test is

    - the calculation of expected counts :math:`E_i = n p_i`
    - the degree of freedom :math:`df = k-1`

    This class uses `scipy.stats.chisquare <https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.chisquare.html>`_.

    **Use Case:**

    ::

      x = Chi2GOFScoreForProportionChi2GOFTest.compute( observation, prediction )
      score = Chi2GOFScoreForProportionChi2GOFTest(x)

    *Note*: As part of the `SciUnit <http://scidash.github.io/sciunit.html>`_ framework this custom :py:class:`.TScore` should have the following methods,

    * :py:meth:`.compute` (class method)
    * :py:meth:`.sort_key` (property)
    * :py:meth:`.__str__`

    """
    #_allowed_types = (float,)
    _description = ( "ZScoreForSignTest gives the z-statistic applied to medians. "
                   + "The experimental data (observation) is taken as the sample. "
                   + "The sample statistic is 'median' or computed median form 'raw_data'. "
                   + "The null-value is the 'some' specified value whic is taken to be the predicted value generated from running the model. " )

    @classmethod
    def compute(cls, observation, prediction):
        """
        +---------------------+-----------------------------------------------------------------------+
        | Argument            | Value type                                                            |
        +=====================+=======================================================================+
        | first argument      |dictionary; observation/experimental data must have keys "sample_size" |
        |                     |with a number as its value and "observed_freq" whose value is an array |
        +---------------------+-----------------------------------------------------------------------+
        | second argument     |dictionary; model prediction must have either "probabilities" or       |
        |                     |"expected" whose value is an array (same length as "observed_freq")    |
        +---------------------+-----------------------------------------------------------------------+

        *Note:*

        * chi squared tests (for goodness-of-fit or contingency table) by nature are two-sided so there is not option for one-sidedness.

        """
        name = "chi2_goodness_of_fit_test_for_proportions"
        if "probabilities" in prediction:
            probabilities = np.array( prediction["probabilities"] )
            expected_counts = observation["sample_size"] * probabilities
        elif "expected" in prediction:
            expected_counts = np.array( prediction["expected"] )
            probabilities = expected_counts / observation["sample_size"]
        #
        k_categories = expected_counts.size
        score, pvalue = chisquare( observation["observed_freq"], f_exp = expected_counts )
        #return self.score # chi2_statistic
        return {"name": name, "sample_statistic": probabilities, "expected_values": expected_counts,
                "test_statistic": score, "df": k_categories-1, "p_value": pvalue}

    @property
    def sort_key(self):
        return self.score

    def __str__(self):
        return "ChiSqGOFScore is " + str(self.score)
# ============================================================================


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

# (a)
n = 100
rng = np.random.default_rng(42)

mu = 5
data = rng.normal(mu, 1, size=n)

# (b)
mu_lim = (3, 7)
k = 5000
mus = np.linspace(*mu_lim, k)
likelihood = np.ones(k)
for x in data:
    likelihood *= stats.norm.pdf(x, loc=mus)

mu_density = likelihood * 1
mu_density /= mu_density.sum()

fig_b, ax_b = plt.subplots()
ax_b.set(xlabel="Mu", ylabel="Posterior density")
ax_b.plot(mus, mu_density)
fig_b.savefig("11-02b.png", bbox_inches="tight")

# (c)
drawn_mus = rng.choice(mus, size=1000, p=mu_density)
fig_c, ax_c = plt.subplots()
ax_c.set(xlabel="Mu", ylabel="Frequency")
ax_c.hist(drawn_mus, bins=150, range=mu_lim)
fig_c.savefig("11-02c.png", bbox_inches="tight")

# (d)
drawn_thetas = np.exp(rng.choice(mus, size=10000, p=mu_density))

theta_lims = (75, 220)
fig_d, axs_d = plt.subplots(1, 2, figsize=(14, 6))
axs_d[0].set(xlabel="Theta", ylabel="Frequency")
axs_d[0].hist(drawn_thetas, bins="auto", range=theta_lims)

thetas = np.geomspace(*theta_lims, k)
log_thetas = np.log(thetas)
diff = np.tile(log_thetas, n).reshape((n, -1)).transpose() - data
propto = np.exp(-0.5 * np.sum(diff ** 2, axis=1))
theta_density = propto / propto.sum()
axs_d[1].set(xlabel="Theta", ylabel="Posterior density")
axs_d[1].plot(thetas, theta_density)

fig_d.savefig("11-02d.png", bbox_inches="tight")

# (e)
mu_cdf = mu_density.cumsum()
alpha = 0.05
mu_left_idx = np.argmax(mu_cdf >= alpha / 2)
mu_right_idx = np.argmax(mu_cdf >= 1 - alpha / 2)
mu_pi = (mus[mu_left_idx], mus[mu_right_idx])
print(
    "95% posterior interval for mu is given by",
    f"[{mu_pi[0]:.3f}, {mu_pi[1]:.3f}]",
)

# (f)
theta_cdf = theta_density.cumsum()
alpha = 0.05
theta_left_idx = np.argmax(theta_cdf >= alpha / 2)
theta_right_idx = np.argmax(theta_cdf >= 1 - alpha / 2)
theta_pi = (thetas[theta_left_idx], thetas[theta_right_idx])
print(
    "95% posterior interval for theta is given by",
    f"[{theta_pi[0]:.3f}, {theta_pi[1]:.3f}]",
)