''' mbinary
#########################################################################
# File : numerical_integration.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-10-02  21:14
# Description:
#########################################################################
'''

#########################################################################
# File : numerical integration.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-05-11  08:58
# Description:
#       numerical intergration: using Newton-Cotes integration,  and Simpson
#       数值积分, 使用 牛顿-科特斯积分, 辛普森
#########################################################################

import numpy as np

def trapezoidal(a, b, h, fs):
    '''梯形积分公式'''
    xs = [i for i in np.arange(a, b+h, h)]
    print(xs)
    ret = h*(sum(fs)-fs[0]/2 - fs[-1]/2)
    print(ret)
    return ret

def simpson(a, b, h, fs):
    '''辛普森积分公式'''
    xs = [i for i in np.arange(a, b+h, h)]
    print(xs)
    ret = h/3*(4 * sum(fs[1::2]) + 2*sum(fs[2:-1:2]) + fs[0]+fs[-1])
    print(ret)
    return ret

def romberg(a, b, f, epcilon):
    '''romberg(龙贝格) 数值积分'''
    h = b-a
    lst1 = [h*(f(a)+f(b))/2]
    print(lst1)
    delta = epcilon
    k = 1
    while delta >= epcilon:
        h /= 2
        k += 1
        lst2 = []
        lst2.append((lst1[0]+h*2*sum(f(a+(2*i-1)*h)
                                     for i in range(1, 2**(k-2)+1)))/2)
        for j in range(0, k-1):
            lst2.append(lst2[j]+(lst2[j]-lst1[j])/(4**(j+1)-1))
        delta = abs(lst2[-1]-lst1[-1])
        lst1 = lst2
        print(lst1)

if __name__ == '__main__':
    a, b, h = 0.6, 1.8, 0.2
    fs = [5.7, 4.6, 3.5, 3.7, 4.9, 5.2, 5.5]
    trapezoidal(a, b, h, fs)
    simpson(a, b, h, fs)
    romberg(1, 2, lambda x: sin(x**4), 1e-4)


"""Test ghz."""
import numpy as np

from toqito.states import basis
from toqito.states import ghz
from toqito.matrix_ops import tensor

def test_ghz_2_3():
    """Produces the 3-qubit GHZ state: `1/sqrt(2) * (|000> + |111>)`."""
    e_0, e_1 = basis(2, 0), basis(2, 1)
    expected_res = 1 / np.sqrt(2) * (tensor(e_0, e_0, e_0) + tensor(e_1, e_1, e_1))

    res = ghz(2, 3).toarray()

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)

def test_ghz_4_7():
    r"""
    The following generates the following GHZ state in `(C^4)^{\otimes 7}`.

    `1/sqrt(30) * (|0000000> + 2|1111111> + 3|2222222> + 4|3333333>)`.
    """
    e0_4 = np.array([[1], [0], [0], [0]])
    e1_4 = np.array([[0], [1], [0], [0]])
    e2_4 = np.array([[0], [0], [1], [0]])
    e3_4 = np.array([[0], [0], [0], [1]])

    expected_res = (
        1
        / np.sqrt(30)
        * (
            tensor(e0_4, e0_4, e0_4, e0_4, e0_4, e0_4, e0_4)
            + 2 * tensor(e1_4, e1_4, e1_4, e1_4, e1_4, e1_4, e1_4)
            + 3 * tensor(e2_4, e2_4, e2_4, e2_4, e2_4, e2_4, e2_4)
            + 4 * tensor(e3_4, e3_4, e3_4, e3_4, e3_4, e3_4, e3_4)
        )
    )

    res = ghz(4, 7, np.array([1, 2, 3, 4]) / np.sqrt(30)).toarray()

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)

def test_ghz_invalid_dim():
    """Tests for invalid dimensions."""
    with np.testing.assert_raises(ValueError):
        ghz(0, 2)

def test_ghz_invalid_qubits():
    """Tests for invalid number of qubits."""
    with np.testing.assert_raises(ValueError):
        ghz(2, 0)

def test_ghz_invalid_coeff():
    """Tests for invalid coefficients."""
    with np.testing.assert_raises(ValueError):
        ghz(2, 3, [1, 2, 3, 4, 5])

if __name__ == "__main__":
    np.testing.run_module_suite()


"""

Cosine Similarity VS Pearson Correlation Coefficient
The two quantities represent two different physical entities.
The cosine similarity computes the similarity between two samples, whereas the Pearson correlation
coefficient computes the correlation between two jointly distributed random variables.
"""

from scipy.spatial import distance
from scipy.stats import linregress
from scipy.stats.stats import pearsonr
import math

class Similarity:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # Euclidean Distance is the shortest distance between two points
    def Euclidean_distance(self):
        ed = distance.euclidean(self.x, self.y)

        formula = math.sqrt(sum((a - b) ** 2 for a, b in zip(self.x, self.y)))

        return ed, formula

    # Manhattan Distance is the sum of absolute differences between points across all the dimensions
    def Manhattan_distance(self):
        md = distance.cityblock(self.x, self.y)

        formula = sum(abs(a - b) for a, b in zip(self.x, self.y))

        return md, formula

    # Minkowski Distance generalized form of Euclidean and Manhattan distance
    # p represents the order of the norm
    # Manhattan distance when q = 1, and Euclidean distance when q = 2
    def Minkowski_distance(self):
        p = 3
        md = distance.minkowski(self.x, self.y, p)

        formula = pow(sum(abs(a - b) ** p for a, b in zip(self.x, self.y)), (1 / p))

        return md, formula

    # The different between Cosine Similarity and Cosine Distance is the intuition behind this is
    # that if 2 vectors are perfectly the same then similarity is 1 (angle=0) and
    # thus, distance is 0 (1-1=0)
    def Cosine_similarity(self):
        cs = 1 - distance.cosine(self.x, self.y)

        formula = (sum(a * b for a, b in zip(self.x, self.y))
                   / ((math.sqrt(sum(a * a for a in self.x))) *
                      (math.sqrt(sum(b * b for b in self.y)))))

        return cs, formula

    def Cosine_distance(self):
        cd = distance.cosine(self.x, self.y)

        formula = 1 - self.Cosine_similarity()[1]

        return cd, formula

class Correlation:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def Pearson_correlation_coefficient(self):
        """
        pcc_1 Return:
             Pearson's correlation coefficient, 2-tailed p-value

        pcc_2 Return:
            slope : slope of the regression line
            intercept : intercept of the regression line
            r-value : correlation coefficient
            p-value : two-sided p-value for a hypothesis test whose null hypothesis is that
                      the slope is zero
            stderr : Standard error of the estimate
        """

        pcc1 = pearsonr(self.x, self.y)
        pcc2 = linregress(self.x, self.y)

        return pcc1, pcc2


import matplotlib.pyplot as plt
import numpy as np

# plt.plot([1, 2, 3, 4])
# plt.ylabel('some numbers')
# plt.show()

def e(t):
    return 1/ np.exp(-2 * t)

t_x = np.arange(0, 1, 0.1)

plt.figure(1)
plt.subplot(211)
# plt.plot(t_x, decay(t_x), 'r--')

plt.plot(t_x,  e(t_x), 'r--')

plt.show()

print(np.exp(1))

# plt.figure(1)
# plt.subplot(211)
# plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')
#
# plt.subplot(212)
# plt.plot(t2, np.cos(2 * np.pi * t2), 'r--')
# plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Equation for diode external forward voltage (VAK) given various
# parameters used in SPICE simulators and partial derivatives
# of this equation with respect to these parameters.
from sympy import *

N, k, T, q, ID, Is, Rs, VAK = symbols('N, k, T, q, ID, Is, Rs, VAK', real=True, positive=True)
pdiff_Is, pdiff_N, pdiff_Rs = symbols('pdiff_Is, pdiff_N, pdiff_Rs')

VAK = (N*k*T/q) * log(ID/Is + 1) + Rs * ID
print('VAK = ', VAK)

pdiff_Is = diff(VAK, Is)
print('dVAK/dIs = ', pdiff_Is)

pdiff_N = diff(VAK, N)
print('dVAK/dN', pdiff_N)

pdiff_Rs = diff(VAK, Rs)
print('dVAK/dRs', pdiff_Rs)


import numpy as np
from accelerate.cuda import rand, cuda_compatible

def initWeights(noOfInputs, noOfHidden):
    return np.random.normal(0, noOfInputs ** -0.5, size=(noOfInputs, noOfHidden))

#input data
features = []

# network looks like
#        o              w11 w12
#       / \             w21 w22
#      h1  h2           w31 w32
#     / \  / \
#    x1  x2  x3

#Network size
N_input = 4
N_hidden = 3
N_output = 2

np.random.seed(42)

def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1/(1+np.exp(-x))

shape = (N_input, N_hidden)

# Make some fake data
if cuda_compatible() is True:
    X = rand.normal(0, 1, (4))
else:
    X = np.random.randn(4)

weights_input_to_hidden = np.random.normal(0, scale=0.1, size=(N_input, N_hidden))
weights_hidden_to_output = np.random.normal(0, scale=0.1, size=(N_hidden, N_output))

# TODO: Make a forward pass through the network

hidden_layer_in = np.dot(X, weights_input_to_hidden)
hidden_layer_out = sigmoid(hidden_layer_in)

print('Hidden-layer Output:')
print(hidden_layer_out)

output_layer_in = np.dot(hidden_layer_out, weights_hidden_to_output)
output_layer_out = sigmoid(output_layer_in)

print('Output-layer Output:')
print(output_layer_out)

# Hidden-layer Output: (using consistent random seed
# [ 0.41492192  0.42604313  0.5002434 ]
# Output-layer Output:
# [ 0.49815196  0.48539772]


import sys

sys.path.insert(0, "/Users/eman/Documents/code_projects/pysim")

import numpy as np
from scipy import stats
import seaborn as sns
import matplotlib.pyplot as plt

plt.style.use(["seaborn-paper"])

from pysim.information.entropy import univariate_entropy

seed = 123
np.random.seed(seed)

n_samples = 1_000
a = 5

# initialize data distribution
data_dist = stats.gamma(a=a)

# get some samples
X_samples = data_dist.rvs(size=n_samples)[:, None]

# sns.distplot(X_samples)
# plt.show()

# ===========================
# True Entropy
# ===========================
H_true = data_dist.entropy()

print(f"Entropy (True): {H_true:.4f}")

# ===========================
# Histogram Entropy
# ===========================
method = "histogram"
correction = True

X_hist_entropy = univariate_entropy(X_samples, method=method, correction=correction)

print(f"Entropy (Histogram): {X_hist_entropy:.4f}")

# ===========================
# KNN Entropy
# ===========================
method = "knn"
n_neighbors = 5
n_jobs = 1
algorithm = "brute"

X_knn_entropy = univariate_entropy(
    X_samples,
    method=method,
    n_neighbors=n_neighbors,
    n_jobs=n_jobs,
    algorithm=algorithm,
)

print(f"Entropy (KNN): {X_knn_entropy:.4f}")

# ===========================
# KDE Entropy
# ===========================
method = "kde"
kernel = "gau"
bw = "normal_reference"

X_kde_entropy = univariate_entropy(X_samples, method=method, kernel=kernel, bw=bw)

print(f"Entropy (KDE): {X_kde_entropy:.4f}")

# ===========================
# Gaussian Assumption
# ===========================
method = "gaussian"

X_gaus_entropy = univariate_entropy(X_samples, method=method)

print(f"Entropy (Gauss): {X_gaus_entropy:.4f}")


#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt

def exp_beta_pdf(beta, x):
    '''Calculate the exponential distribution Exp(beta) PDF'''
    gamma = 1.0 / beta
    curve = np.exp(-gamma * x)
    return gamma * curve

def exp_beta_cdf(beta, x):
    '''Calculate the exponential distribution Exp(beta) CDF'''
    gamma = 1.0 / beta
    curve = np.exp(-gamma * x)
    return 1 - curve

def plot_exp_beta(beta):
    x = np.linspace(0, 10, 100)
    pdf = exp_beta_pdf(beta, x)
    cdf = exp_beta_cdf(beta, x)
    ints = np.arange(11)
    pdf_ints = exp_beta_pdf(beta, ints)
    cdf_ints = exp_beta_cdf(beta, ints)

    plt.subplot(211)
    plt.plot(x, pdf)
    plt.title('Exp(%0.1f) Probability Density Function (PDF)' % beta)
    for xy in zip(ints, pdf_ints):
        plt.annotate('%0.2f' % xy[1], xy=xy, textcoords='data')

    plt.subplot(212)
    plt.plot(x, cdf)
    plt.title('Exp(%0.1f) Cummulative Distribution Function (CDF)' % beta)
    for xy in zip(ints, cdf_ints):
        plt.annotate('%0.2f' % xy[1], xy=xy, textcoords='data')

    plt.show()

beta = 2
plot_exp_beta(beta)


import numpy as np
from sklearn.datasets import make_regression

parameters = {
    'num_features' : 1,
    'sample_range' : (30, 300),
    'noise_range' : (1, 60),
    'random_noise_range' : (200, 400),
    'tail_strength_range' : (0.3, 0.6),
}

'''
Title: How to Generate Test Datasets in Python with scikit-learn
Author: Jason Brownlee
Date: 2020
Code version: N/A
Availability: https://machinelearningmastery.com/generate-test-datasets-python-scikit-learn/

The basis for the following generation functions was taken from the above
'''

def generate_data():
    # generate hyperparameters
    noise_level = np.random.uniform(*parameters.get('noise_range', ()))
    random_noise_level = np.random.uniform(*parameters.get('random_noise_range', ())) # for no correlation
    num_samples = np.random.randint(*parameters.get('sample_range', ()))
    tail_strength = np.random.uniform(*parameters.get('tail_strength_range', ()))
    num_features = parameters.get('num_features', 1)
    correlation = np.random.choice(['none', 'positive', 'negative'])

    # calculate data points from random linear regression model
    X, y = make_regression(
        n_samples=num_samples,
        n_features=num_features,
        noise=(random_noise_level if correlation == 'none' else noise_level),
        tail_strength=tail_strength,
    )

    # y correlation is flipped if generating a negative scatter
    y = -y if correlation == 'negative' else y

    # quick fix to convert data to be one dimensional
    X = X.flatten()
    y = y.flatten()

    return {
        'X': X,
        'y': y,
        'correlation': correlation,
    }


import sys
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

num = 0
data = []

with open(sys.argv[1]) as fp:
    num = int(fp.readline())
    for i in range(num):
        line = fp.readline()
        s = line.split()
        data.append([float(s[0]),float(s[1])])
data = np.array(data)
data_x = data[:,0]
data_y = data[:,1]

def square(x):
    return x*x

def culR(a,b,x,y):
    child = np.array(list(map(square,y-(a*x+b)))).sum()
    mam = np.array(list(map(square,y-y.mean()))).sum()
    return 1-(child/mam)

def approximation(x,y,N):
    y_sum = y.sum()
    x_sum = x.sum()
    x_sq = np.matmul(x,x)
    a_child = N * np.matmul(x,y) - (x_sum*y_sum)
    b_child = np.matmul(x,x) * y_sum - np.matmul(x,y) * x_sum
    mam = N * x_sq - square(x_sum)
    a = a_child/mam
    # b = (y_sum-a*x_sum)/N
    b = b_child/mam
    return a,b

a,b = approximation(data_x,data_y,num)
print("y = {:.3f}x + {:.3f}".format(a,b))
R = culR(a,b,data_x,data_y)
print("R:{}".format(R))

x = np.arange(100)
plt.plot(x,a*x + b,color = "r")
plt.scatter(data_x[:100],data_y[:100])
# plt.scatter(data_x,data_y)
plt.show()

# x_std = np.sqrt(np.matmul(data_x - data_x.mean(),data_x - data_x.mean())/num)
# y_std = np.sqrt(np.matmul(data_y - data_y.mean(),data_y - data_y.mean())/num)
# C_xy = np.matmul(data_x-data_x.mean(),data_y - data_y.mean())/num
# R2 = C_xy/(x_std*y_std)
# print(R2)


import matplotlib.pyplot as plt
import numpy as np
import networkx as nx

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

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

# math (from slides)       zero-indexed         permutation matrix for P*(P*B)^T
# 1->1                     0->0                 [[ 1,  0,  0,  0,  0],
# 4->2                     3->1                  [ 0,  0,  0,  1,  0],
# 2->3                     1->2                  [ 0,  1,  0,  0,  0],
# 5->4                     4->3                  [ 0,  0,  0,  0,  1],
# 3->5                     2->4                  [ 0,  0,  1,  0,  0]]

# define how rows should be permuted (see Appendix C in the Python Essentials book)
r = np.array([0, 3, 1, 4, 2])

# for drawing the graph, only
G = nx.from_numpy_matrix(B)
plt.figure()
plt.subplot(211)

# the annoying of zero- and one- indexing let's us rename the depicted labels
mathLabels = {0: 1, 1:2, 2:3, 3:4, 4:5}
nx.draw(G, with_labels=True, labels=mathLabels)

# As taught in the lecture P*(P*A)^T
A = np.matmul(P,np.transpose(np.matmul(P,B)))

# More clever (see Appendix C or the numpy documentation)
A = (B[np.array(r)].T)[np.array(r)]

H = nx.from_numpy_matrix(A)
plt.subplot(212)
nx.draw(H, with_labels=True, labels=mathLabels)
plt.show()


import numpy as np
class Function:

    def __init__(self, function, functionDerivate):
        self.function = function
        self.functionDerivate = functionDerivate

    def __call__(self, x):
        return self.function(x)

    def derivate(self, x):
        return self.functionDerivate(x)

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

def sigmoidFunctionDerivate(x):
    return sigmoid(x)*sigmoid(1-x)

sigmoid = Function(sigmoidFunction, sigmoidFunctionDerivate)

def reluFunction(x):
    x[x<=0] = 0
    return x

def reluFunctionDerivate(x):
    x[x<=0] = 0
    x[x>0] = 1
    return x

relu = Function(reluFunction, reluFunctionDerivate)

def softmax(x):
    return (np.exp(x-max(x))) / sum(np.exp(x-max(x)))

def softmaxDeviate(x):
    pass

softmax = Function(softmax, softmaxDeviate)

class lossFunction:

    def __init__(self, function, functionDerivate):
        self.function = function
        self.functionDerivate = functionDerivate

    def __call__(self, prediction, labels):
        return self.function(prediction, labels)

    def derivate(self, prediction, labels):
        return self.functionDerivate(prediction, labels)

def l2error(prediction, labels):
    retunr (labels-prediction)**2

def l2errorDerivate(prediction, labels):
    return 2*(prediction-labels)

sse = lossFunction(l2error, l2errorDerivate)


import numpy as np
import argparse
from pulp import *
#####################################################################

def b_star(func):
	return np.max(np.sum(T*(R + gamma*func), axis=2), axis=1)

def get_pi(Q):
	return np.argmax(Q, axis=1)

def get_V(pi):
	A = gamma*T[np.arange(n), pi, :] - np.eye(n)
	b = -np.sum(T[np.arange(n), pi, :] * R[np.arange(n), pi, :], axis=1)
	return np.linalg.solve(A,b)

def get_Q(V):
	return np.sum(T*(R+gamma*V), axis=2)

def print_v_and_pi(arr):
	for i in range(n):
		print(arr[0,i], arr[1,i])

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

def value_iteration():
	old_val = np.random.randn(n)/10
	while True:
		new_val = b_star(old_val)
		if np.max(np.abs(new_val - old_val)) < 1e-10: break
		else: old_val = new_val
	return np.array([old_val, get_pi(get_Q(old_val))])

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

def policy_improvement(pi):
	Q = get_Q(get_V(pi))
	return np.argmax(Q, axis=1)

def howards_policy_iteration():
	old_pi = np.random.randint(0, k, n)
	while True:
		new_pi = policy_improvement(old_pi)
		if (new_pi == old_pi).all() : break
		else: old_pi = new_pi
	return np.array([get_V(old_pi), old_pi])

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

def linear_programming():
	N = range(n); K = range(k)

	prob = LpProblem("MDP_Planning", LpMinimize)
	V = LpVariable.dicts('V', (N,))

	prob += lpSum(V)
	for s in N:
		for a in K:
			prob += V[s] >= lpSum(
				[T[s,a,s2] * (R[s,a,s2] + gamma*V[s2]) for s2 in N])

	prob.solve(PULP_CBC_CMD(msg=0))

	val = np.array([V[i].value() for i in N])
	return np.array([val, get_pi(get_Q(val))])

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

parser = argparse.ArgumentParser()
parser.add_argument('--mdp', help='Path to the input MDP file')
parser.add_argument('--algorithm', help='One of vi, hpi, and lp')
args = parser.parse_args()

ifile = args.mdp
algo = args.algorithm

n = 0; k = 0

f = open(ifile, 'r')

for line in f:
	words = line.split()
	if words[0] == 'numStates': n = int(words[1])
	elif words[0] == 'numActions': k = int(words[1])
	else: break

R = np.zeros((n,k,n)); T = np.zeros((n,k,n))
gamma = 0
mdptype = ''

for line in f:
	words = line.split()
	if words[0] == 'transition':
		s1, a, s2 = [int(x) for x in words[1:4]]
		r, p = [float(x) for x in words[4:]]
		T[s1,a,s2] = p
		R[s1,a,s2] = r

	elif words[0] == 'mdptype': mdptype = words[1]
	elif words[0] == 'discount': gamma = float(words[1])

f.close()

if algo == 'vi': print_v_and_pi(value_iteration())
elif algo == 'hpi': print_v_and_pi(howards_policy_iteration())
elif algo == 'lp': print_v_and_pi(linear_programming())
else: print('Not implemented yet')


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

from matplotlib.font_manager import FontProperties
zhfont = FontProperties(fname=r"simhei.ttf", size=18)

fig = plt.figure(figsize=(6, 5))  # 确定绘图区域尺寸
ax1 = fig.add_subplot(1, 1, 1)
x = np.arange(0.01, 20, 0.01)

# 绘制gamma分布曲线
y1 = st.gamma.pdf(x, 1, scale=2)  # "α=1,β=2"
y2 = st.gamma.pdf(x, 2, scale=2)  # "α=2,β=2"
y3 = st.gamma.pdf(x, 3, scale=2)  # "α=3,β=2"
y4 = st.gamma.pdf(x, 5, scale=1)  # "α=5,β=1"
y5 = st.gamma.pdf(x, 9, scale=0.5)  # "α=9,β=0.5"
# 设置图例
ax1.plot(x, y1, label="α=1,β=2")
ax1.plot(x, y2, label="α=2,β=2")
ax1.plot(x, y3, label="α=3,β=2")
ax1.plot(x, y4, label="α=5,β=1")
ax1.plot(x, y5, label="α=9,β=0.5")

# 设置坐标轴标题
plt.tick_params(labelsize=15)
ax1.set_xlabel('x',size=18)
ax1.set_ylabel('PDF',size=18)
ax1.set_title("Gamma分布示意图",FontProperties=zhfont)
ax1.legend(loc="best",fontsize=10)

plt.show()


import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as plt

# functions for fitting
def fx1(x, a, b):
    """
    returns f(x) = a+b*x
    """
    f = a + b*x
    return f

def fx2(x,a,b,c,d,e):
    """
    returns f(x) = (a*x)/d*ln(b*x +c) + e
    """
    f = (a*x)/d*np.log(b*x +c) + e
    return f

def fx3(x,a,b,c,d):
    """
    returns f(x) = a*ln(b*x +c) + d
    """
    f = a*np.log(b*x +c)+d
    return f

infile = open("curvefit_happy.csv","r")
infile.readline()
# inflie.readline() gets rid of the column headers
# read in file generated by happy_numbers.py

lines=[] # empty list for lines from file
for line in infile:
    lines.append(line)

infile.close()

x = np.array([])
y = np.array([])

for line in lines:
    data = line.strip().split(",")
    x = np.append(x, float(data[0])) #x array for happy numbers
    y = np.append(y, float(data[1])) #y array for the happy number count. Total happy numbers is len(x) or the last index in the y array
                                     #for 10,000 happy numbers this is 1442

# generating fit parameters by using curve_fit. Maxfev is the amount of function calls, we found the defualt of 200 per variable was to low
p1, cov = opt.curve_fit(fx1,x,y,maxfev=1000000)
p2, cov2 = opt.curve_fit(fx2,x,y,maxfev=1000000)
p3, cov3 = opt.curve_fit(fx3,x,y,maxfev=1000000)

# fit parameters, you will need them
print ("Fit-parameters = ", p1,'\n',p2,'\n',p3)

# this creates the fitted coordinate arrays with the fit parameters
yp1 = fx1(x,p1[0],p1[1],) #y=a*x +b
yp2 = fx2(x,p2[0],p2[1],p2[2],p2[3],p2[4]) #y= (a*x)/d*ln(b*x +c) + e
yp3 = fx3(x,p3[0],p3[1],p3[2],p3[3]) #y = a*ln(b*x +c) + d

# Create plots of fitted curves
axes = plt.gca()

plt.figure(1)
plt.plot(x,y,'b--')
plt.plot(x,yp1,'r-')
plt.ylabel('Happy number value')
plt.xlabel('Cumulative amount of happy numbers')
plt.grid()
plt.title('Happy number curve fit y=x')

plt.figure(2)
plt.plot(x,y,'b--')
plt.plot(x,yp2,'r-')
plt.ylabel('Happy number value')
plt.xlabel('Cumulative amount of happy numbers')
plt.grid()
plt.title('Happy number curve fit y = x/ln(x)')

plt.figure(3)
plt.plot(x,y,'b--')
plt.plot(x,yp3,'r-')
plt.ylabel('Happy number value')
plt.xlabel('Cumulative amount of happy numbers')
plt.grid()
plt.title('Happy number curve fit y = ln(x)')
#we find that y=ln(x) provides the best fit, however x/ln(x) is also very close, y=x is not that great.
#it is also recommended to look at the images in full screen for the best judgement of the fit by eye (chi-by-eye)
plt.show()

code/Python/Basic ML curve fitting project on happy numbers/Happy_Curvefit.py


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

g = 9.81  # gravitational accel. m/s^2
Hs = 200  # m shelf break depth
f = 1.0E-4  # inertial frequency

data_mode0 = pd.read_csv('dispc_mode0_AST.dat', delim_whitespace=True,
                         header=None, names=['wavenum', 'freq', 'perturbation'])
data_mode1 = pd.read_csv('dispc_mode1_AST.dat', delim_whitespace=True,
                         header=None, names=['wavenum', 'freq', 'perturbation'])
data_mode2 = pd.read_csv('dispc_mode2_AST.dat', delim_whitespace=True,
                         header=None, names=['wavenum', 'freq', 'perturbation'])

omega0 = data_mode0['freq']
omega1 = data_mode1['freq'][:-3]
omega2 = data_mode2['freq']

k0 = data_mode0['wavenum']*100
k1 = data_mode1['wavenum'][:-3]*100
k2 = data_mode2['wavenum']*100

fig1, (ax0, ax1) = plt.subplots(1, 2, figsize=(8, 4))
ax0.plot(k0*1E5, omega0*1E4, 'k.-', label='mode 0')
ax0.plot(k1*1E5, omega1*1E4, '.-', color='0.4', label='mode 1')
ax0.plot(k2*1E5, omega2*1E4, '.-', color='0.8', label='mode 2')
ax0.plot(k1*1E5, (k1*(g*Hs)**0.5)*1E4, 'r-', label=r'$\omega=k(gH_s)^{1/2}$')
ax0.axhline(f*1E4, linestyle='--', color='0.6', label='$f$')
ax1.plot(k0*1E5, omega0/k0, 'k.-',
         label='$c_0=$%1.2f m/s' % (np.mean(omega0/k0)))
ax1.plot(k1*1E5, omega1/k1, '.-', color='0.4',
         label='$c_1=$%1.2f m/s' % (np.mean(omega1/k1)))
ax1.plot(k2*1E5, omega2/k2, '.-', color='0.8',
         label='$c_2=$%1.2f m/s' % (np.mean(omega2/k2)))
ax1.axhline((g*Hs)**0.5, linestyle='-', color='red', label='$(gH_s)^{1/2}$')

ax0.set_xlabel(r'$k$ / $10^{-5}$ rad m$^{-1}$', labelpad=0.1)
ax1.set_xlabel(r'$k$ / $10^{-5}$ rad m$^{-1}$', labelpad=0.1)
ax0.set_ylabel(r'$\omega$ / $10^{-5}$ rad s$^{-1}$', labelpad=0.1)
ax1.set_ylabel(r'$c$ / m s$^{-1}$', labelpad=-0.1)
ax0.set_ylim(0, f*2*1E4)
ax0.legend()
ax1.legend()

plt.savefig('disp_curve_AST.png', format='png', bbox_inches='tight')


# Exercise 8.25
# Author: Noah Waterfield Price

import numpy as np
from scipy.misc import factorial as fact

def binomial(x, n, p):
    B = float(fact(n)) / (fact(x) * fact(n - x)) * \
        p ** x * (1 - p) ** (n - x)
    return B

def simulate_binomial(p, n, x, N=1000):
    r = np.random.rand(N, n)
    outcomes = np.sum(np.where(r <= p, 1, 0), axis=1)
    p_approx = np.sum(outcomes == x) / float(N)
    p_exact = binomial(x, n, p)
    return p_approx, abs(p_approx - p_exact)

print 'What is the probability of gettinger two heads when \
flipping a coin 5 times?'
p, error = simulate_binomial(0.5, 5, 2)
print 'p = %.5f (error: %.5f)\n' % (p, error)
print 'What is the probability of getting fours ones in a rown when \
throwing a die?'
p, error = simulate_binomial(1./6, 4, 4)
print 'p = %.5f (error: %.5f)\n' % (p, error)
print 'What is the probability that a skier will experience a ski \
break during five\n competitions in a world championship?'
p, error = simulate_binomial(1./120, 5, 0)
print 'p = %.5f (error: %.5f)\n' % (1 - p, error)

"""
Sample run:
python simulate_binomial.py
What is the probability of gettinger two heads when flipping a coin 5 times?
p = 0.30600 (error: 0.00650)

What is the probability of getting fours ones in a rown when throwing a die?
p = 0.00000 (error: 0.00077)

What is the probability that a skier will experience a ski break during five
 competitions in a world championship?
p = 0.05300 (error: 0.01202)
"""


import numpy as np
import time

# Task 1
def get_distance_matrix(array: np.array) -> np.array:
    n = array.shape[0]
    result = np.zeros((n, n))
    for x, y in np.ndindex(result.shape):
        result[x, y] = np.linalg.norm(array[x] - array[y])
    return result

# Task 2
def eliminate_distances(probability: float, array: np.array) -> np.array:
    result = array.copy()
    indices = np.random.uniform(0, 1, array.shape)
    result[probability >= indices] = np.inf
    return result

# Task 3
def calculate_floyd_warshall(array: np.array) -> np.array:
    n = array.shape[0]
    result = array.copy()
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if array[i, j] > array[i, k] + array[k, j]:
                    result[i, j] = array[i, k] + array[k, j]
    return result

# Task 4
def apply_functions(probability: float, array: np.array) -> float:
    return float(np.sum(calculate_floyd_warshall(
        eliminate_distances(probability, get_distance_matrix(array)))))

# Task 5
def generate_system(solution: np.array) -> tuple:
    n = solution.shape[0]
    coefficients = np.random.uniform(0, 100, (n, n))
    np.fill_diagonal(coefficients, 0)
    non_diag = np.sum(abs(coefficients))
    np.fill_diagonal(coefficients, [non_diag + np.random.uniform(0, non_diag)
                                    for _ in range(n)])
    return coefficients, coefficients @ solution

# Task 6
def solve_system(coefficients: np.array, free_terms: np.array,
                 threshold=0.01, limit=1000) -> np.array:
    x = np.zeros((coefficients.shape[0], 1))
    diag = np.array([np.diag(coefficients)]).T
    for i in range(limit):
        prev_x = x.copy()
        x = (free_terms - ((coefficients - np.diagflat(diag)) @ x)) / diag
        if np.allclose(x, prev_x, threshold):
            break
    return x

# Task 7
def inversion(coefficients: np.array, free_terms: np.array) -> np.array:
    return np.linalg.inv(coefficients).dot(free_terms)

def execute_solution_method(method: callable, *args) -> None:
    t = time.perf_counter()
    method(*args)
    print("%s: %.5fs" % (method.__name__, time.perf_counter() - t))

def measure_execution_times() -> None:
    equations_number = [100, 200, 500, 1000, 2000, 5000]
    methods = [solve_system, np.linalg.solve, inversion]
    for number in equations_number:
        x = np.array([np.arange(number)]).T
        coefficients, free_terms = generate_system(x)
        print("Number of equations: %d" % number)
        for method in methods:
            execute_solution_method(method, coefficients, free_terms)
        print("\n")

def main():
    # Task 1
    matrix = np.array([[2, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 2]])
    print(get_distance_matrix(matrix))

    # Task 2
    print(eliminate_distances(0.2, matrix.astype(float)))

    # Task 3
    distance_array = np.array([[10, 1, 10], [3, 4, 5], [6, 7, 8]])
    print(calculate_floyd_warshall(distance_array))

    # Task 4
    test_matrix = np.random.uniform(0, 100, (100, 3))
    print("Original: %f" % np.sum(test_matrix))
    msg = "Modified (probability = %.2f): %.2f"
    for probability in [0.5, 0.9, 0.99]:
        print(msg % (probability, apply_functions(probability, test_matrix)))
    # for probabilities 0.9 and 0.99 there are too many infs to reduce them

    # Task 5
    linear_system = generate_system(np.array([np.arange(3)]).T)
    print(linear_system)

    # Task 6
    print(solve_system(*linear_system))

    # Task 7
    print(np.linalg.solve(*linear_system))
    print(np.linalg.inv(linear_system[0]).dot(linear_system[1]))
    measure_execution_times()
    """
    Task 7 results:

    Number of equations: 100
    solve_system: 0.00055s
    solve: 0.00608s
    inversion: 0.00081s

    Number of equations: 200
    solve_system: 0.00210s
    solve: 0.00146s
    inversion: 0.00211s

    Number of equations: 500
    solve_system: 0.00903s
    solve: 0.00583s
    inversion: 0.01032s

    Number of equations: 1000
    solve_system: 0.02899s
    solve: 0.01925s
    inversion: 0.04739s

    Number of equations: 2000
    solve_system: 0.11639s
    solve: 0.09915s
    inversion: 0.29726s

    Number of equations: 5000
    solve_system: 0.44502s
    solve: 0.89216s
    inversion: 2.94675s
    """

if __name__ == "__main__":
    main()


from sympy.functions.special.bessel import besselj
from sympy.vector import CoordSys3D
from symplyphysics import (
    symbols, Function, Eq, pretty, cos, pi
)
from symplyphysics.laws.nuclear.buckling import geometric_buckling_from_neutron_flux
from symplyphysics.laws.nuclear.buckling import neutron_flux_for_uniform_slab

# Description
## Neutron flux formula for the uniform cylindrical reactor of physical radius R and height H.
## The cylindrical reactor is situated in cylindrical geometry at the origin of coordinates.

## Law: Ф(r, z) = C1 * J0(2.405 * r / R) * cos(Pi * z / H)
## Where:
## C1 - neutron flux power constant.
## Pi - Pi constant.
## J0 - Bessel function of the first kind, of the 0 order.
## r - radial distance from the center of cylinder.
## z - axial distance from the center of cylinder.
## R - radius of the cylinder.
## H - height of the cylinder.
## Ф(r, z) - neutron flux density.

neutron_flux_power_constant = symbols('C1', constant=True)
radial_distance_from_center = symbols('radial_distance_from_center')
axial_distance_from_center = symbols('axial_distance_from_center')
cylinder_radius, cylinder_height = symbols('cylinder_radius cylinder_height')
neutron_flux_function = symbols('neutron_flux_function', cls = Function)

# These constants are being used for geometric buckling calculation
# See: [geometric buckling for uniform cylinder](geometric_buckling_for_uniform_cylinder.py)
radial_constant = 2.405 / cylinder_radius
axial_constant = pi / cylinder_height

# derived the same way as uniform slab axial_constant
assert axial_constant == neutron_flux_for_uniform_slab.axial_constant.subs(neutron_flux_for_uniform_slab.slab_width, cylinder_height)

law = Eq(neutron_flux_function(radial_distance_from_center, axial_distance_from_center),
    neutron_flux_power_constant * besselj(0, radial_constant * radial_distance_from_center) *
    cos(axial_constant * axial_distance_from_center))

# Check the solution by passing the known neutron flux to the geometric_buckling_from_neutron_flux.
# Neutron flux is a function of radius and height in the cylindrical coordinates.

# Boundary conditions:
# - vacuum boundary condition: Ф(R + d) = Ф(Re) = Ф(a + d) = Ф(ae) = 0
# - finite flux condition: 0 <= Ф(r, x) < ∞
# - interface condition: the neutron flux and the normal component of the neutron current must be continuous
# - source condition: all neutrons flowing through the bounding area of the source must come from the neutron source
# - albedo boundary condition: Ф(Ralbedo) = 0

# radial_constant is the solution of the Bessel function J0, with a condition the neutron flux cannot
# have negative values (finite flux condition) and with zero flux boundary condition

# define flux function in cylindrical coordinates as a function of cylinder radius and height
cylindrical_coordinates = CoordSys3D('cylindrical_coordinates', transformation='cylindrical')
neutron_flux_function_cylindrical = law.subs({
    radial_distance_from_center: cylindrical_coordinates.r,
    axial_distance_from_center: cylindrical_coordinates.z})

solved = geometric_buckling_from_neutron_flux.apply_neutron_flux_function(neutron_flux_function_cylindrical.rhs)

# check with the derived law: Bg^2 = radial_constant**2 + axial_constant**2
# limit decimals to bypass rounding errors
assert solved.rhs.evalf(7) == (radial_constant**2 + axial_constant**2).evalf(7)

def print():
    return pretty(law, use_unicode=False)

# There is no calculate() method. Neutron flux is usually being used internally to pass to other laws.


import numpy as np
from typing import List, Optional

def metric_tensor(a: float,
                  b: float,
                  c: float,
                  alpha: float,
                  beta: float,
                  gamma: float,
                  degrees: bool = True) -> np.ndarray:
    """Calculates the metric tensor of an arbitrary lattice.

    Args:
        a: The a distance lattice parameter.
        b: The b distance lattice parameter.
        c: The c distance lattice parameter.
        alpha: The alpha angle lattice parameter.
        beta: The beta angle lattice parameter.
        gamma: The gamma angle lattice parameter.
        degrees: Flag indicating that the angles are provided in degrees.
    """
    if degrees:
        alpha = np.deg2rad(alpha)
        beta = np.deg2rad(beta)
        gamma = np.deg2rad(gamma)
    return np.array([[a**2, a * b * np.cos(gamma), a * c * np.cos(beta)],
                     [b * a * np.cos(gamma), b**2, b * c * np.cos(alpha)],
                     [c * a * np.cos(beta), c * b * np.cos(alpha), c**2]])

def fractional_cartesian_matrix(a: float,
                                b: float,
                                c: float,
                                alpha: float,
                                beta: float,
                                gamma: float,
                                degrees: bool = True) -> np.ndarray:
    """Returns the Fractional to Cartesian coordinate transform matrix for an
       arbitrary lattice.

    Notes:
        Given that X is a Cartesian coordinate vector, M^-1 is the transform
        matrix returned by this function, and x is a fractional coordinate
        matrix:

            x = MX and X = M^-1x

        https://www.ruppweb.org/Xray/tutorial/Coordinate%20system%20transformation.htm

    Args:
        a: The a distance lattice parameter.
        b: The b distance lattice parameter.
        c: The c distance lattice parameter.
        alpha: The alpha angle lattice parameter.
        beta: The beta angle lattice parameter.
        gamma: The gamma angle lattice parameter.
        degrees: Flag indicating that the angles are provided in degrees.
    """
    if degrees:
        alpha = np.deg2rad(alpha)
        beta = np.deg2rad(beta)
        gamma = np.deg2rad(gamma)
    v = volume(a, b, c, alpha, beta, gamma, False)
    term_00 = a
    term_01 = b * np.cos(gamma)
    term_02 = c * np.cos(beta)
    term_11 = b * np.sin(gamma)
    term_12 = c * (np.cos(alpha) - np.cos(beta) * np.cos(gamma))
    term_12 /= np.sin(gamma)
    term_22 = v / (a * b * np.sin(gamma))
    return np.array([[term_00, term_01, term_02], [0, term_11, term_12],
                     [0, 0, term_22]])

def cartesian_fractional_matrix(a: float,
                                b: float,
                                c: float,
                                alpha: float,
                                beta: float,
                                gamma: float,
                                degrees: bool = True) -> np.ndarray:
    """Returns the Cartesian to Fractional coordinate transform matrix for an
       arbitrary lattice.

    Notes:
        Given that X is a Cartesian coordinate vector, M is the transform
        matrix returned by this function, and x is a fractional coordinate
        matrix:

            x = MX and X = M^-1x

        https://www.ruppweb.org/Xray/tutorial/Coordinate%20system%20transformation.htm

    Args:
        a: The a distance lattice parameter.
        b: The b distance lattice parameter.
        c: The c distance lattice parameter.
        alpha: The alpha angle lattice parameter.
        beta: The beta angle lattice parameter.
        gamma: The gamma angle lattice parameter.
        degrees: Flag indicating that the angles are provided in degrees.
    """
    if degrees:
        alpha = np.deg2rad(alpha)
        beta = np.deg2rad(beta)
        gamma = np.deg2rad(gamma)
    v = volume(a, b, c, alpha, beta, gamma, False)
    # yapf: disable
    term_00 = 1 / a
    term_01 = -np.cos(gamma) / (a * np.sin(gamma))
    term_02 = (b * np.cos(gamma) * c * (np.cos(alpha) - np.cos(beta) * np.cos(gamma)))
    term_02 /= np.sin(gamma)
    term_02 -= (b * c * np.cos(beta) * np.sin(gamma))
    term_02 /= v
    term_11 = 1 / (b * np.sin(gamma))
    term_12 = -a * c * (np.cos(alpha) - np.cos(beta) * np.cos(gamma))
    term_12 /= v * np.sin(gamma)
    term_22 = (a * b * np.sin(gamma)) / v
    return np.array([
        [term_00, term_01, term_02],
        [0,       term_11, term_12],
        [0,       0,       term_22]
    ])
    # yapf: enable

def volume(a: float,
           b: float,
           c: float,
           alpha: float,
           beta: float,
           gamma: float,
           degrees: bool = True) -> float:
    """Calculates the volume of an arbitrary lattice.

    Args:
        a: The a distance lattice parameter.
        b: The b distance lattice parameter.
        c: The c distance lattice parameter.
        alpha: The alpha angle lattice parameter.
        beta: The beta angle lattice parameter.
        gamma: The gamma angle lattice parameter.
        degrees: Flag indicating that the angles are provided in degrees.
    """
    if degrees:
        alpha = np.deg2rad(alpha)
        beta = np.deg2rad(beta)
        gamma = np.deg2rad(gamma)
    mt = metric_tensor(a, b, c, alpha, beta, gamma, False)
    return np.sqrt(np.linalg.det(mt))

def position_index(existing_positions: List[np.ndarray],
                   new_position: np.ndarray,
                   tolerance: float = 0.001) -> Optional[int]:
    """Returns the index of a position if it is within range of an existing
       position.

    Args:
        existing_positions: List of existing positions to compare to.
        new_position: The position to check.
        tolerance: The radius in which a position is considered occupied
                   relative to another position.
    """
    for i, ep in enumerate(existing_positions):
        distance = np.sum(np.sqrt((new_position - ep)**2))
        if distance < tolerance:
            return i
    return None

def orientation_100():
    return np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])

def orientation_110():
    return np.array([[-1, 1, 0], [0, 0, 1], [1, 1, 0]])

def orientation_111():
    return np.array([[1, -1, 0], [1, 1, -2], [1, 1, 1]])


import numpy as np
from scipy.integrate import quad
import matplotlib.pyplot as plt
plt.figure(figsize=(6.0, 6.0))

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

SAMPLE_SIZE = 1000
p = np.pi/4
q = 1.0 - p

rand_x = np.random.rand(SAMPLE_SIZE)
rand_y = np.random.rand(SAMPLE_SIZE)
plot_x = np.linspace(start=0, stop=1.0, num=101, endpoint=True)

in_circle = (rand_y - 1.0) ** 2 + rand_x ** 2 < 1.0
not_in_circle = np.logical_not(in_circle)

p_empirical = np.mean(in_circle)
q_empirical = np.mean(not_in_circle)

rand_x_in = rand_x[in_circle]
rand_y_in = rand_y[in_circle]
rand_x_out = rand_x[not_in_circle]
rand_y_out = rand_y[not_in_circle]

plt.xlim(0, 1)
plt.ylim(0, 1)
plt.plot(plot_x, f(plot_x), color="blue")
plt.scatter(rand_x_in, rand_y_in, marker=".", color="blue", label="Accepted points")
plt.scatter(rand_x_out, rand_y_out, marker=".", color="red", label="Rejected points")
plt.legend(loc="best")
plt.xlabel("$x$")
plt.ylabel("$y$")

pi_empirical = 4 * p_empirical
print(f"Estimate: {pi_empirical:.6f}")
print(f"Empirical relative uncertainty: {4 * np.sqrt(p_empirical*q_empirical/SAMPLE_SIZE) / pi_empirical * 100:.4f}%")
print(f"Expected relative uncertainty: {4 * np.sqrt(p*q/SAMPLE_SIZE) / np.pi * 100:.4f}%")

plt.savefig("pi_hit_and_miss.png")
plt.show()


import numpy as np
import matplotlib.pyplot as plt

def show(img):
    plt.imshow(img.reshape(231, 195), cmap='gray')
    plt.show()

def showEigenface(ver, hor, X):
    L = []
    R = []

    for i in range(ver):
        for j in range(hor):
            L.append(X[i * hor + j, :].reshape(231, 195))
        R.append(np.hstack(L))
        L = []

    A = np.vstack(R)

    plt.imshow(A, cmap='gray')
    plt.show()

def findPrincipalComponents(dataset, k):
    mean = np.mean(dataset, axis=0)
    std = np.std(dataset, axis=0)

    dataset2 = (dataset - mean) / std

    cov = np.dot(dataset2, dataset2.T)
    e, v = np.linalg.eigh(cov)

    # sort the eigenvalue and same time eigen vector and choose them
    idx = np.argsort(e)[::-1]
    v = v[:, idx]
    e = e[idx]
    v = v[:, :k]

    u = np.dot(dataset2.T, v)

    return u

def pcaCompress(image, component):
    c = np.dot(component.T, image)
    q = np.dot(component, c)

    return q

dataset = np.load('FaceImages.npy')
image = [dataset[0, :], dataset[1, :]]
karray = [16, 32, 64]

for i in range(len(image)):
    for j in range(len(karray)):
        pca = findPrincipalComponents(dataset, karray[j])

        precon = pcaCompress(image[i], pca)

        show(image[i])
        show(precon)

        if karray[j] == 16:
            showEigenface(4, 4, pca.T)
        elif karray[j] == 32:
            showEigenface(4, 8, pca.T)
        elif karray[j] == 64:
            showEigenface(8, 8, pca.T)


"""
Various potential formulations for disks (Miyamoto-Nagai, exponential)

"""
from numpy import power, square, pi

def mn_dens(M, a, r):
    """
    Surface density of the Miyamoto-Nagai disk when the scale height (b) is zero
    M - mass
    a - scale length
    r - (x^2 + y^2)^1/2

    returns Sigma (in units of M/r^2)
    """
    return M * a / ((a*a+r*r)**1.5 * 2*pi)

def mn_grav3(pos, M, a, b, G=43018.7):
    """ Gravity for Miyamoto-Nagai (disk) Potential """
    R2 = square(pos[:,:2]).sum(1)
    z2 = square(pos[:,2])
    acc = -G * M * reshape(power(R2+(a+(b*b+z2)**0.5)**2, -1.5), (len(pos),1))
    acc = acc*pos
    acc[:,2] *=1+a/(b*b+z2)**0.5

    return acc

def mn_grav(R, GM, a, b):
    """ Gravity for Miyamoto-Nagai (disk) Potential in plane"""
    return -R* GM * power(R*R+(a+b)**2, -1.5)

def mn_mean_dens(R, M, sum_ab):
    """
    Implied mean density from gravity in mn_grav, i.e. spherically averaged via

    4 pi r^2 rho_mean(r) = d/dr [ r^2 g / G]
    """

    return 3*M * square(sum_ab)/(4*pi*(R*R+square(sum_ab))**2.5)


# -*- coding: utf-8 -*-
"""
Created on Sun Jun 10 15:16:44 2018

@author: Marcus
"""

import numpy as np
import copy

def generate_phase_angles(N):
    """Generate the complex phase angle factors for the FFT.

    We use this function to pre-calculate the phase angle factors. Although it
    is redundant to calculate the factors for k >= N/2, this function will be
    left simple for now, just calculating them all. It may be optimised in the
    future, but since it will not be part of timing measurements, it doesn't
    matter for now.

    Parameters
    ----------
    N : int
        The number of points in the FFT, and the number of phase angle factors
        to calculate.

    Returns
    -------
    np.array
        Array of the phase angle factors.

    Raises
    ------
    ValueError
        If N is not an integer.

    Examples
    --------
    In [1]: generate_phase_angles(2)
    Out[1]: array([ 1. +0.00000000e+00j, -1. -1.22464680e-16j])

    In [2]: generate_phase_angles(4)
    Out[2]:
    array([  1.00000000e+00 +0.00000000e+00j,
             6.12323400e-17 -1.00000000e+00j,
            -1.00000000e+00 -1.22464680e-16j,
            -1.83697020e-16 +1.00000000e+00j])
    """

    if(type(N) != int):
        raise ValueError("N must be an integer")

    W_N_factors = \
                np.array([np.exp((-1j * 2 * np.pi * k) / N) for k in range(N)])

    return W_N_factors

def single_butterfly(x, W_N):
    """Compute a single DFT butterfly.

    This is equivalent to the computation of a 2-point DFT.

    This butterfly is based off a bit-reversal input FFT, as follows:

    x is an array of time-domain samples (the input)
    X is an array of frequency-domain samples (the output)

          x[k]    ------->      X[m] = x[k] + x[k + N/2] * (W_N)**(n)
                  \      ^
                   \    /
                    \  /(W_N)**(n)
                     \/
                     /\
                    /  \
                   /    \
                  /      v
    x[k + N/2]    ------->      X[m + N/2] = x[k] + x[k + N/2] * (W_N)**(N/2)
                 (W_N)**(n + N/2)          = x[k] - x[k + N/2] * (W_N)**(n)

    Note that the simplification on the output of the lower wing of the
    butterfly is possible because (W_N)**(n + N/2) = -1 * (W_N)**(n). This
    allows us to perform one less multiplication.

    Parameters
    ----------
    x : np.array
        An array of time-domain samples. Must only be of size 2
    W_N : np.complex128
        A phase angle factor

    Returns
    -------
    np.array
        An array of frequency-domain samples, being the result of a 2-point
        DFT.

    Examples
    --------
    In [1]: x = np.array([0.5432 + 1.1443j, 0.9985 - 0.0114j])
    In [2]: single_butterfly(x, 1)
    Out[2]: array([ 1.5417+1.1329j, -0.4553+1.1557j])
    """

    if(len(x) != 2):
        raise ValueError("x must be only two elements long")

    phase_angle_multiplied = W_N * x[1]

    X = np.array([x[0] + phase_angle_multiplied,
                  x[0] - phase_angle_multiplied])

    return X

def fft(x):
    """Compute a radix-2 FFT.

    Parameters
    ----------
    x : np.array
        An array of time-domain samples to be transformed into the frequency
        domain.

    Returns
    -------
    np.array
        An array of frequency-domain samples, the output of the FFT.

    Raises
    ------
    ValueError
        If x is not exactly 2**n elements long, where n is a positive integer.
    """

    if(not((np.log2(len(x))).is_integer())):
        raise ValueError("N must be an integer power of two")

    #Save the size of the desired FFT
    N = len(x)

    #Generate the phase angle factors
    W_N_factors = generate_phase_angles(N)

    #Get the depth of the FFT (how many butterflies deep we need to go).
    fft_depth = int(np.log2(N))

    #Get the number of bits for a given index for an FFT of size N
    num_bits = int(np.log2(N))

    #Generate eversed bit indices
    indices = [(int('{:0{width}b}'.format(n, width=num_bits)[::-1], base=2),
               int('{:0{width}b}'.format(n + 1, width=num_bits)[::-1], base=2)) \
                                                            for n in range(0, N, 2)]

    f_indices = [(n, n + 1) for n in range(0, N, 2)]

    #Initialise the output array
    X = np.zeros(N, dtype=np.complex128)
    intermediate_results = copy.deepcopy(X)

    #Perform the FFT

    #Calculate the first level with bit-reversed indices
    for f_idcs, t_idcs in zip(f_indices, indices):
        array = np.array([x[t_idcs[0]], x[t_idcs[1]]])
        W_N = W_N_factors[0]

        butterfly_result = single_butterfly(array, W_N)

        intermediate_results[f_idcs[0]] = butterfly_result[0]
        intermediate_results[f_idcs[1]] = butterfly_result[1]

    X = copy.deepcopy(intermediate_results)

    #Calculate further levels of the DFT
    #This is for FFTs of N > 2
    for level in range(fft_depth - 1):
        pass

    return X

if __name__ == "__main__":
    N = 2

    power_of_two = np.log2(N)

    if(not(power_of_two.is_integer())):
        raise ValueError("N must be an integer power of two")

    x = np.random.normal(size=N) + 1j * np.random.normal(size=N)

    numpy_fft = np.fft.fft(x)
    my_fft = fft(x)

    print(np.allclose(numpy_fft, my_fft))


# -*- coding: utf-8 -*-
"""musawenkosi- Tensorflow Classification Practical4.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1E9TWn9biImKHqPIGzecG-RAfEC-ySwHB

## Classification practical

Total marks: 21
"""

from keras.datasets import mnist
import numpy as np
np.random.seed(13378)
import pandas
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras import metrics
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, accuracy_score
import matplotlib.pyplot as plt
from keras.utils import np_utils

"""## Load the dataset"""

(X_train, Y_train), (X_test, Y_test) = mnist.load_data()

"""## View the shape"""

print('Training data shape : ', X_train.shape, Y_train.shape)
print('Testing data shape : ', X_test.shape, Y_test.shape)

"""## Find the unique numbers from the train labels"""

classes = np.unique(Y_train)
nClasses = len(classes)
print('Total number of outputs : ', nClasses)
print('Output classes : ', classes)

"""## Plot some of the data"""

plt.figure(figsize=[10,5])

# Display the first image in training data
plt.subplot(121)
plt.imshow(X_train[0,:,:], cmap='gray')
plt.title("Ground Truth : {}".format(Y_train[0]))

# Display the first image in testing data
plt.subplot(122)
plt.imshow(X_test[0,:,:], cmap='gray')
plt.title("Ground Truth : {}".format(Y_test[0]))

"""## Flatten the data

In this notebook we won't be making use of the data as "images" but rather as long vectors of length 784

## This is what an example in the dataset looks like
"""

X_train[0].shape

X_train[0]

"""## Task: Convert from image shape to a vector shape

We go from 28x28 pixel sized images to a vector of length 784.

We would like to reshape the training data from shape (60000, 28, 28) to (60000,784). To do this, we can make use of Numpy's *reshape* function.

Hint: ...reshape(...).astype('float32')
"""

X_train.shape

num_pixels = X_train.shape[1] * X_train.shape[2]

X_train = np.reshape(X_train, (60000,784))
X_test = np.reshape(X_test, (10000,784))

"""## Now the data is a long vector

There are 60,000 examples for which each is a vector of length 784
"""

X_train.shape

"""## View the first example"""

X_train[0].shape

X_train[0]

"""## Task: Normalise

We need to normalise the data since the values range from 0 to 255. Training NNs on data ranging between [0,1] can be easier. To do this, we simply divide by the maximum value, in this case 255.
"""

X_train = (X_train) / (255)# Your code here
X_test = (X_test) / (255)# Your code here

"""## One hot encoding

We're going to want our labels as one-hot vectors, which are vectors that holds mostly 0's and one 1. It's easiest to see this in a example. As a one-hot vector, the number 0 is represented as [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], and 4 is represented as [0, 0, 0, 0, 1, 0, 0, 0, 0, 0].

One-hot encoded vectors allow us to map each category in our set of labels to a vector where only a single value is 1.

0 maps to [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]

1 maps to [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

2 maps to [0, 0, 2, 0, 0, 0, 0, 0, 0, 0]

Notes on one-hot encoding: https://machinelearningmastery.com/why-one-hot-encode-data-in-machine-learning/

## Before
"""

Y_test[0]

"""## Task: Convert from categorical labels to one-hot encoded vectors

In this case there are 10 classes so we can tell the function to convert into a vector of length 10. You need to convert both the training targets and the testing targets.
"""

Y_train = np_utils.to_categorical(Y_train)
Y_test = np_utils.to_categorical(Y_test)

"""## After"""

Y_test[0]

"""## Task: Create a neural network model"""

def baseline():

    model = Sequential()
    model.add(Dense(20, input_dim=784, activation='relu'))
    model.add(Dense(20, activation='relu'))
    model.add(Dense(10, activation='softmax'))

    # Compile model
    model.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])

    return model

"""## Task: Initialise the model"""

model = baseline()# Your code here

"""## Task: Determine the number of trainable parameters

## 13,002
"""

model.summary()# Your code here

"""## Task: Begin training

Fit on the training features and targets. Also make use of the validation data you've set aside above. Set the number of epochs, batch size and also explore various *verbose* values.
"""

X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=0.3)

history = model.fit(X_train, Y_train, validation_data=(X_val, Y_val) ,epochs = 20)

"""## Task: Predict on the test data"""

prediction = model.predict(X_test)
prediction
prediction_classes = np.argmax(prediction, axis=1)
prediction_classes

confusion_matrix(np.argmax(Y_test,1), prediction_classes)

"""## Task: Compute the accuracy"""

accuracy_score(np.argmax(Y_test,1), prediction_classes)

"""###***Extra Work***"""

def plot_hist(h, xsize=6, ysize=10):

    fig_size = plt.rcParams["figure.figsize"]
    plt.rcParams["figure.figsize"] = [xsize, ysize]
    fig, axes = plt.subplots(nrows=1, ncols=1, sharex=True)

    # summarize history for Accuracy
    plt.subplot(211)
    plt.plot(h['accuracy'])
    plt.plot(h['val_accuracy'])
    plt.title('Training Performance')
    plt.ylabel('Accuracy')
    plt.xlabel('Epochs')
    plt.legend(['Train', 'Validation'], loc='best')

    plt.draw()
    plt.show()

    return

plot_hist(history.history, xsize=8, ysize=12)


# %% [markdown]
# # Comparing Binary and Linear Search

# %% [markdown]
# ## Configuration

# %% [markdown]
# ### Import

# %%
import numpy as np
from IPython.display import clear_output
from timeit import default_timer as timer

# %% [markdown]
# ### Integer Check

# %%
def int_check(prompt, no_zero = None):
    while True:
        try:
            value = int(input(prompt))
        except ValueError:
            if no_zero is None:
                value = np.random.randint(np.iinfo(np.int32).max)
            else:
                print("Check input")
                continue

        if no_zero is None and value < 0:
            print("Please input a number larger or equal to 0")
            continue

        if no_zero is not None and (value < 1 or value > 10**6):
            print("Please input a number between 1 and 1,000,000")
            continue

        clear_output()
        return value

# %% [markdown]
# ### Ramdom setup

# %%
seed_info = int_check("Choose your seed: ") # If ValueError then a random seed between 0 and top limit of int32 would be selected
np.random.seed(seed_info)

arr = np.random.randint(low = 1, high = 10**6, size = 10**5)

# %% [markdown]
# ### Duplication handling

# %%
_, idx = np.unique(arr, return_index=True)

unique_arr_unsorted = arr[np.sort(idx)]

unique_arr_sorted = np.unique(arr)

print(f"{arr.size - unique_arr_sorted.size} duplicate values removed from array")
print(f"{unique_arr_sorted.size} values remaining")
print(f"Seed: {seed_info}")

# %% [markdown]
# ### Get Number

# %%
tofind = int_check("Number to search for: ", True)

# %% [markdown]
# ## ***Binary* Search** ##

# %%
def BinarySearch():

    start = timer() # Performance - Start timer

    i_value = 1
    min_value = 0
    max_value = unique_arr_sorted.size

    while min_value <= max_value:
        mid_value = int(min_value + (max_value - min_value) / 2)
        if unique_arr_sorted[mid_value] == tofind:
            end = timer() # Performance - End timer
            print(f"Number {tofind} found \
                \nPosition in array: {mid_value + 1} \
                \nLoops: {i_value} \
                \nTime: {(end-start)*10**6} microseconds")
            return
        elif tofind < unique_arr_sorted[mid_value]:
            max_value = mid_value - 1
            i_value += 1
        else:
            min_value = mid_value + 1
            i_value += 1

    end = timer() # Performance - End timer
    print(f"Number {tofind} NOT found \
        \nMaximum Loops: {i_value} \
        \nMax time: {(end - start)*10**6} mircoseconds")

BinarySearch()

# %% [markdown]
# ## ***Linear* Search**

# %% [markdown]
# ### General Function

# %%
def LinearSearch(arraytosearch, flag):

    start = timer() # Performance - Start timer

    for index, item in enumerate(arraytosearch):
        if tofind == item:
            end = timer() # Performance - End timer
            strvalue = "sorted"
            if flag:
                strvalue = "unsorted"

            print(f"Number {tofind} found \
                \nPosition in {strvalue} array: {index + 1} \
                \nLoops: {index + 1} \
                \nTime: {(end-start)*10**6} microseconds")
            return
    end = timer() # Performance - End timer
    print(f"Number {tofind} NOT found \
        \nMaximum Loops: {arraytosearch.size} \
        \nMax time: {(end-start)*10**6} microseconds")

# %% [markdown]
# ### *Unsorted* array

# %%
LinearSearch(unique_arr_unsorted, True)

# %% [markdown]
# ### *Sorted* array

# %%
LinearSearch(unique_arr_sorted, False)


import numpy as np

# Training Utils ===========================================

def minibatch(x, batchsize=256, shuffle=True):
    pass

# Padding Utils ============================================

def calc_pad_dims_2D(X_shape, out_dim, kernel_shape, stride, dilation=0):
    pass

def calc_pad_dims_1D(X_shape, l_out, kernel_width, stride, dilation=0, causal=False):
    pass

def pad1D(X, pad, kernel_width=None, stride=None, dilation=0):
    pass

def pad2D(X, pad, kernel_width=None, stride=None, dilation=0):
    pass

def dilate(D, d):
    pass

# Convolution Arithmetic ===================================

def calc_fan(weight_shape):
    pass

def calc_conv_out_dims(X_shape, W_shape, stride=1, pad=0, dilation=0):
    pass

# Convolution Vectorization Utils ==========================

def _im2col_indices(X_shape, fr, fc, p, s, d=0):
    pass

def im2col(X, W_shape, pad, stride, dilation=0):
    pass

def col2im(X_col, X_shape, W_shape, pad, stride, dilation=0):
    pass

# Convalution ==============================================

def conv2D(X, W, stride, pad, dilation=0):
    pass

def conv1D(X, W, stride, pad, dilation=0):
    pass

def deconv2D_naive(X, W, stride, pad, dilation=0):
    pass

def conv2D_naive(X, W, stride, pad, dilation=0):
    pass

# Weight Initialization ====================================

def he_uniform(weight_shape):
    """
    Initializes network weights `W` with using the He uniform initialization
    strategy.
    """
    pass

def he_normal(weight_shape):
    pass

def lorot_uniform(weight_shape, gain=1.0):
    pass

def glorot_normal(weight_shape, gain=1.0):
    pass

def truncated_normal(mean, std, out_shape):
    pass


class MeanVariance():
    """Online algorithm to compute the mean and variance"""

    def __init__(self):
        self.n_ = 0
        self.mean_ = 0
        self.sn_ = 0
        self.lastmean_ = 0

    def append(self, x):
        """
        Appends the new value 'x' into the running mean and variance computation.
        """
        self.n_ += 1
        self.lastmean_ = self.mean_
        self.mean_ += (x-self.lastmean_)/self.n_
        if self.n_ == 1:
            self.sn_ = 0
        else:
            self.sn_ += (x-self.lastmean_)*(x-self.mean_)

    def mean(self):
        """Returns the current mean"""
        return self.mean_
    def var(self):
        """Returns the current variance"""
        return self.sn_ / self.n_
    def count(self):
        """Returns the current number of points"""
        return self.n_

if __name__== "__main__":
    import numpy as np
    import unittest

    class Test(unittest.TestCase):
        def test_against_numpy(self):
            lengths = [1, 2, 5, 20, 1000]
            for length in lengths:
                xs = np.random.random(length)
                # expected
                mean_np = np.mean(xs)
                var_np = np.var(xs)
                # actual
                mv = MeanVariance()
                for x in xs:
                    mv.append(x)
                self.assertEqual(mv.count(), length)
                self.assertAlmostEqual(mv.mean(), mean_np)
                self.assertAlmostEqual(mv.var(), var_np)

    unittest.main()


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

# calculate exact distribution
all_prob = 0.0
for i in range(1, 10000):
    prob = i ** (-3./2.)
    all_prob += prob

probs = []
for i in range(1, 9):
    prob = (i ** (-3./2.)) / all_prob
    probs.append(prob)
probs = np.array(probs)

print('exact results of power law distribution is:')
print(probs)

n = 1000000
sim_list = np.zeros(n, dtype=np.int32)
sim_list[0] = 2

for i in range(1, n):
    if sim_list[i-1] == 1:
        p = (1./2.) ** (5./2.)
        new = np.random.choice([1, 2], 1, p=[1-p, p])[0]
        sim_list[i] = new
    else:
        left_right = np.random.choice([-1, 1], 1)[0]
        if left_right == -1:
            sim_list[i] = sim_list[i-1] -1
        else:
            p = (float(sim_list[i-1]) / (sim_list[i-1]+1)) ** (3./2.)
            new = np.random.choice([sim_list[i-1], 1+sim_list[i-1]], 1, p=[1-p, p])[0]
            sim_list[i] = new

counts, _  = np.histogram(sim_list, bins=np.max(sim_list))
tab = counts/float(n)
print('The simulation results of power law distribution is:')
print(tab[:8])

counts, _  = np.histogram(sim_list[n//10:], bins=np.max(sim_list))
tab_burn_in = counts/float((n - n//10))
print('The burn-in simulation results of power law distribution is:')
print(tab_burn_in[:8])

print('approximate gaps is:')
print(np.abs(probs[:8] - tab[:8]))

print('approximate gaps of burn is:')
print(np.abs(probs[:8] - tab_burn_in[:8]))

# convergence rate
values = []
step = 1000
ends = []
for i in range(1, step+1):
   end = n // step * i
   counts, _  = np.histogram(sim_list[:end], bins=np.max(sim_list))
   tab = counts/float(end)
   v = np.mean(np.abs(probs[:8] - tab[:8]))
   ends.append(end)
   values.append(v)

fig = plt.figure()
ax = fig.gca()

plt.plot(np.array(ends), np.array(values))

plt.xlabel('iteration T')
plt.ylabel('approximate error')
plt.title('power law convergence rate')
plt.grid()
plt.savefig('power_law_convergence_rate.png', dpi=600)
plt.show()


# -*- coding: utf-8 -*-

'''
The following code implements a Sequential Monte Carlo for a "local level" Dynamic Linear Model
The algorithm is from Petris et al. - Dynamic Linear Models with R
'''

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

'''
The Dynamic Linear Model is specified by the observation and state equation as follows:
y[t] = theta[t] + v, where v is distributed as a Normal(0,V)
theta[t] = theta[t-1] + w, where w is distributed as a Normal(0,W)

In addition, the prior on theta is a distributed as a Normal(m0,c0)

In the following implementation, the parameters of the model are considered known.
We then generate a process of dimension t with the specified parameters.
'''
m0, C0, V, W = 5, 3, 2, 1
t = 100
theta = np.zeros(t)
theta[0] = stats.norm.rvs(loc = m0, scale = C0)
y = np.zeros(t)
for t in range(1, t):
    theta[t] = stats.norm.rvs(loc = theta[t-1], scale = W)
    mt = theta[t]
    y[t] = stats.norm.rvs(loc = mt, scale = V, size = 1)

fig, ax = plt.subplots(figsize=(16,9)) # Plotting the generated process - latent state theta and observation y
ax.plot(y[1:])
ax.plot(theta[1:])

N = 1000 # N is the number of "particles", i.e. the dimension of the sample generated.
tol = N/2 # Tolerance level for the Effective Sample Size.

sd_importance = np.sqrt(W - W**2/(V + W)) # Definition of the importance distribution standard deviation
sd_theta_y = np.sqrt(V + W)

'''
In the following, the algorithm is implemented.
Firstly, the arrays used in the algorithm are initialized.
'''

w_t = np.zeros(shape = (t + 1, N))
thetas_sim = np.zeros(shape = (t + 1, N))
pi_hat_sample = np.zeros(shape = (t+1,N))
ESS = np.zeros(t+1)
theta_res = np.zeros(shape = (t+1, N)) # auxiliary array used for the resampling step
thetas_est = np.zeros(t) # Monte Carlo approximations of filtering mean of theta_t|y_1:t
filt_est = np.zeros(shape = (t + 1, N)) # approximate sample from theta_t|y_1:t at each t

thetas_sim[1] = stats.norm.rvs(loc = m0, scale = C0) # initialization from the prior

w_t[1] = np.repeat(1/N,N) # initialization with equal weights

filt_est[1] = np.random.choice(thetas_sim[1], N, p=w_t[1])

for i in range(2,t+1):

    # Drawing theta_i's from the importance distribution
    y_theta = (y[i-1] - thetas_sim[i-1])
    var_sum = W + V
    mean_importance = thetas_sim[i-1] + W * y_theta/var_sum
    thetas_sim[i] = stats.norm.rvs(loc = mean_importance, scale = sd_importance**2)

    # Updating the weights w_t
    pi_g = w_t[i-1] * stats.norm.pdf(y[i-1], loc = thetas_sim[i-1], scale = sd_theta_y**2)
    w_t[i] = pi_g / np.sum(pi_g)

    # Evaluating ESS
    ESS[i] = (np.sum(w_t[i]**2))**(-1)

    # Multinomial resampling
    if ESS[i] < tol:
        index = np.random.choice(range(N), N , p= w_t[i])

        for c in range(N):
            theta_res[:,c] = thetas_sim[:,index[c]]
        thetas_sim = theta_res

        w_t[i] = np.repeat(1/N, N)

    # Drawing a sample from the approximate filtering distribution in t:
    filt_est[i] = np.random.choice(thetas_sim[i], N, p=w_t[i])

    # Monte Carlo approximations of filtering mean at t
    thetas_est[i-1] = np.dot(thetas_sim[i],w_t[i]) / np.sum(w_t[i])

# Graph of ESS, which indicates the points at which the multinomial resampling has been implemented:
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(ESS[1:], label = "Effective Sample Size")
ax.legend();

'''
In the following code, some plots are drawn in order to qualitatively assess the performance of the algorithm compared to the (exact) Kalman filter.
The first plot shows the observation y and the filtering estimates of the algorithm.
Then, some draws from the approximate filtering distribution at various t are plotted.
After computing the Kalman filter estimates, the second plot shows a comparison between Kalman filter and the algorithm implemented.
'''

# Observation and filtering
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(y[1:], label = "Observations")
ax.plot(thetas_est[1:], label = "Estimated thetas")
ax.legend();

# Graph of approximate filtering distributions
fig, ax = plt.subplots(figsize=(12,8), nrows = 3, ncols = 2)
c = [10,40,90]
for i,j in enumerate([2,30,80]):
    k = 0
    ax[i][k].hist(filt_est[j], alpha=0.5, bins=100, density=True, stacked=True, label = f"Filtering at t={j}")
    ax[i][k].legend();
    k += 1
    ax[i][k].hist(filt_est[c[i]], alpha=0.5, bins=100, density=True, stacked=True,label = f"Filtering at t={c[i]}")
    ax[i][k].legend();

# Closed-form solutions for Kalman filter
r = np.zeros(t)
q = np.zeros(t)
m = np.zeros(t)
f = np.zeros(t)
c = np.zeros(t)
a = np.zeros(t)
m[0] = m0
c[0] = C0
r[0] = c[0] + W
for t in range(1,t):
    a[t] = m[t-1]
    r[t] = c[t-1] + W

    f[t] = a[t]
    q[t] = r[t] + V

    m[t] = a[t] + r[t]*(y[t]-f[t])/q[t]
    c[t] = r[t] - (r[t]**2) / q[t]
theta_kalman = m

# Comparison between Kalman filter and Sequential MC
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(theta_kalman[1:], label = "Kalman filter")
ax.plot(thetas_est[1:], label = "Sequential MC")
ax.legend();

Sequential Monte Carlo for Dynamic Linear Models/Ruggieri_mc_particle.py


"""Provide utility functions used by the various geometry tools in
this package.

"""

import numpy as np

from scipy.optimize import linprog

def rotation_matrix(angle):
    """Get a 2x2 rotation matrix rotating counterclockwise by the
    specified angle.

    """
    return np.array([[np.cos(angle), -1*np.sin(angle)],
                     [np.sin(angle), np.cos(angle)]])

def dimension_to_axis(array, dimension, axis):
    """Return an array where the given axis has length dimension, if
    possible.
    """
    dim_index = axis
    if array.shape[axis] != dimension:
        dim_index = self.shape.index(dimension)
        return array.swapaxes(axis, dim_index), dim_index

    return array, axis

def permutation_matrix(permutation):
    """Return an nxn permutation matrix representing the given
    permutation.

    Parameters:
    -----------
    permutation: a sequence of n numbers (indices), specifying a
    permutation of (1, ... n).

    Return:
    ---------
    numpy array storing an n * n permutation matrix

    """
    n = len(permutation)
    p_mat = np.zeros((n, n))
    for i,j in enumerate(permutation):
        p_mat[i,j] = 1.

    return p_mat

def diagonalize_form(bilinear_form, order_eigenvalues="signed", reverse=False):
    """Return a matrix conjugating a symmetric real bilinear form to a
    diagonal form.

    """
    n, _ = bilinear_form.shape

    eigs, U = np.linalg.eigh(bilinear_form)
    D = np.diag(1 / np.sqrt(np.abs(eigs)))

    perm = np.identity(n)

    if order_eigenvalues:
        if order_eigenvalues == "signed":
            order = np.argsort(eigs)
        if order_eigenvalues == "minkowski":
            negative = np.count_nonzero(eigs < 0)
            positive = np.count_nonzero(eigs > 0)

            if negative <= positive:
                order = np.concatenate(
                    ((eigs < 0).nonzero()[0],
                     (eigs > 0).nonzero()[0],
                     (eigs == 0).nonzero()[0])
                )
            else:
                order = np.concatenate(
                    ((eigs > 0).nonzero()[0],
                     (eigs < 0).nonzero()[0],
                     (eigs == 0).nonzero()[0])
                )
        if reverse:
            order = np.flip(order)

        perm = permutation_matrix(order)

    W = U @ D @ np.linalg.inv(perm)

    return W

def circle_angles(center, coords):
    """Return angles relative to the center of a circle.

    Parameters:
    -----------
    center: ndarray of shape (..., 2) representing x,y coordinates the
    centers of some circles.

    coords: ndarray of shape (..., 2) representing x,y coordinates of
    some points.

    Return:
    --------
    ndarray representing angles (relative to x-axis) of each of the
    pair of points specified by coords.

    """
    xs = (coords - np.expand_dims(center, axis=-2))[..., 0]
    ys = (coords - np.expand_dims(center, axis=-2))[..., 1]

    return np.arctan2(ys, xs)

def apply_bilinear(v1, v2, bilinear_form=None):
    """apply a bilinar form to a pair of arrays of vectors.

    if v1 and v2 are ndarrays of shape (..., n) and (..., n), apply
    the bilinear form elementwise to them, using standard broadcasting
    rules.

    """

    if bilinear_form is None:
        bilinear_form = np.identity(v1.shape[-1])

    return ((v1 @ bilinear_form) * v2).sum(-1)

def normsq(vectors, bilinear_form=None):
    """norm of an ndarray of vectors"""
    return apply_bilinear(vectors, vectors, bilinear_form)

def normalize(vectors, bilinear_form=None):
    norms = normsq(vectors, bilinear_form)
    return vectors / np.sqrt(np.abs(np.expand_dims(norms, axis=-1)))

def short_arc(thetas):
    """reorder angles so that the counterclockwise arc between them is
    shorter than the clockwise angle.

    Parameters:
    ------------
    thetas: ndarray of pairs of angles in the range (-2pi, 2pi)

    Return:
    ------------
    ndarray of pairs of angles. a subset of the pairs in thetas have
    been swapped.

    """
    shifted_thetas = np.copy(thetas)

    shifted_thetas[shifted_thetas < 0] += 2 * np.pi
    shifted_thetas.sort(axis=-1)

    to_flip = shifted_thetas[...,1] - shifted_thetas[...,0] > np.pi
    shifted_thetas[to_flip] = np.flip(shifted_thetas[to_flip], axis=-1)

    return shifted_thetas

def right_to_left(thetas):
    """reorder angles so that the counterclockwise arc goes right to left.

    Parameters:
    -------------
    thetas: ndarray of pairs of angles.

    Return:
    ----------
    ndarray of pairs of angles. a subset of the pairs have been
    swapped.

    """
    flipped_thetas = np.copy(thetas)

    to_flip = np.cos(thetas[..., 0]) < np.cos(thetas[..., 1])
    flipped_thetas[to_flip] = np.flip(thetas[to_flip], axis=-1)

    return flipped_thetas

def arc_include(thetas, reference_theta):
    """Reorder angles so that the counterclockwise arc between them always
    includes some reference point on the circle.

    Parameters:
    --------------
    thetas: ndarray of pairs of angles in the range (-2pi, 2pi)

    reference_theta: ndarray of angles in the range (-2pi, 2pi)

    Return:
    -------------
    ndarray theta_p of pairs of angles, so that reference_theta lies
    in the counterclockwise angle between theta_p[0] and theta_p[1].

    """

    s_thetas = np.copy(thetas)
    s_theta1 = thetas[..., 1] - thetas[..., 0]
    s_reference = np.expand_dims(reference_theta - thetas[..., 0],
                                 axis=-1)

    s_theta1[s_theta1 < 0] += 2 * np.pi
    s_reference[s_reference < 0] += 2 * np.pi

    to_swap = (s_theta1 < s_reference[..., 0])

    s_thetas[to_swap] = np.flip(s_thetas[to_swap], axis=-1)
    return s_thetas

def sphere_inversion(points):
    with np.errstate(divide="ignore", invalid="ignore"):
        return (points.T / (normsq(points)).T).T

def swap_matrix(i, j, n):
    permutation = list(range(n))
    permutation[i] = j
    permutation[j] = i
    return permutation_matrix(permutation)

def projection(v1, v2, bilinear_form):
    """project v1 onto v2, with respect to a (hopefully nondegenerate)
    bilinear form"""

    return (v2.T *
            apply_bilinear(v1, v2, bilinear_form).T /
            normsq(v2, bilinear_form).T).T

def indefinite_orthogonalize(form, matrices):
    """apply the Gram-Schmidt algorithm, but for a possibly indefinite
    bilinear form."""
    if len(matrices.shape) < 2:
        return normalize(matrices, form)

    n, m = matrices.shape[-2:]

    result = np.zeros_like(matrices)

    #we're using a python for loop, but only over dimension^2 which
    #is probably small

    for i in range(n):
        row = matrices[..., i, :]
        for j in range(i):
            row -= projection(row, result[..., j, :], form)
        result[..., i, :] = row

    return normalize(result, form)

def find_isometry(form, partial_map, force_oriented=False):
    """find a form-preserving matrix agreeing with a specified map on
    the flag defined by the standard basis.

    Parameters:
    -----------------
    form: the bilinear map to preserve

    partial_map: array of shape (..., k , n), representing the images
    of the first k standard basis vectors (row vectors)

    force_oriented: whether we should apply a reflection to force the
    resulting map to be orientation-preserving.

    Return:
    -------------
    ndarray of shape (..., n, n) representing an array of matrices
    whose rows and columns are "orthonormal" with respect to the
    bilinear form (may have norm -1).

    For all j < k, the subspace spanned by the first j standard basis
    vectors is sent to the subspace spanned by the first j rows of the
    result.

    """

    orth_partial = indefinite_orthogonalize(form, partial_map)
    if len(orth_partial.shape) < 2:
        orth_partial = np.expand_dims(orth_partial, axis=0)

    _, _, vh = np.linalg.svd(orth_partial @ form)

    kernel = vh[..., orth_partial.shape[-2]:, :]

    orth_kernel = indefinite_orthogonalize(form, kernel)
    iso = np.concatenate([orth_partial, orth_kernel], axis=-2)

    if force_oriented:
        iso = make_orientation_preserving(iso)

    return iso

def find_definite_isometry(partial_map, force_oriented=False):
    pmap = np.array(partial_map)
    if len(pmap.shape) < 2:
        pmap = pmap.reshape((len(pmap), 1))
    h, w = pmap.shape[-2:]
    n = max(h, w)
    if w > h:
        mat = np.concatenate([pmap.swapaxes(-1, -2),
                             np.identity(n)], axis=-1)
    else:
        mat = np.concatenate([pmap, np.identity(n)], axis=-1)

    q, r = np.linalg.qr(mat)

    iso = np.sign(r[..., 0,0]) * q

    if force_oriented:
        iso = make_orientation_preserving(iso)

    return iso

def make_orientation_preserving(matrix):
    """apply a reflection to the last row of matrix to make it orientation
    preserving.

    if matrix is already orientation preserving, do nothing.

    """
    preserved = matrix.copy()
    preserved[np.linalg.det(preserved) < 0, -1, :] *= -1
    return preserved

def expand_unit_axes(array, unit_axes, new_axes):
    """expand the last axes of an array to make it suitable for ndarray
    elementwise multiplication.

    array is an ndarray whose last unit_axes axes are a "unit"
    (treated as a single unit for elementwise multiplication). Its
    shape is ([object axes], [unit axes]), where [object axes] and
    [unit axes] are tuples.

    This function returns an array of shape
    ([object axes], 1, ..., 1, [unit axes]),
    where the number of 1's is either (new_axes - unit_axes) or 0.

    """
    if new_axes <= unit_axes:
        return array

    return np.expand_dims(array.T, axis=tuple(range(unit_axes, new_axes))).T

def squeeze_excess(array, unit_axes, other_unit_axes):
    """squeeze excess axes from an ndarray of arrays with unit_axes axes.

    This undoes expand_unit_axes. If array is an ndarray of shape

    `([object axes], [excess axes], [unit axes])`,

    where [unit axes] has length unit_axes, and [excess axes] has
    length other_unit_axes - unit_axes, this function reshapes the
    array by squeezing out all the 1's in [excess axes].

    """
    squeezable = np.array(array.T.shape[unit_axes:other_unit_axes])
    (to_squeeze,) = np.nonzero(squeezable == 1)
    to_squeeze += unit_axes

    return np.squeeze(array.T, axis=tuple(to_squeeze)).T

def matrix_product(array1, array2, unit_axis_1=2, unit_axis_2=2,
                   broadcast="elementwise"):
    """do ndarray multiplication, where we treat array1 and array2 as
    ndarrays of ndarrays with ndim unit_axis1 and unit_axis2.

    broadcasting is either elementwise or pairwise.
    """

    reshape1 = expand_unit_axes(array1, unit_axis_1, unit_axis_2)
    reshape2 = expand_unit_axes(array2, unit_axis_2, unit_axis_1)

    if broadcast == "pairwise" or broadcast == "pairwise_reversed":
        large_axes = max(unit_axis_1, unit_axis_2)

        excess1 = reshape1.ndim - large_axes
        excess2 = reshape2.ndim - large_axes

        if excess1 > 0:
            if broadcast == "pairwise_reversed":
                reshape1 = np.expand_dims(reshape1,
                                          axis=tuple(range(excess1, excess1 + excess2)))
            else:
                reshape1 = np.expand_dims(reshape1,
                                          axis=tuple(range(excess2)))

        if excess2 > 0:
            if broadcast == "pairwise_reversed":
                reshape2 = np.expand_dims(reshape2, axis=tuple(range(excess1)))
            else:
                reshape2 = np.expand_dims(reshape2,
                                          axis=tuple(range(excess2, excess1 + excess2)))

    product = reshape1 @ reshape2

    if unit_axis_1 < unit_axis_2:
        product = squeeze_excess(product, unit_axis_1, unit_axis_2)

    return product

def find_positive_functional(positive_points):
    dim = positive_points.shape[-1]
    codim = positive_points.shape[-2]

    functionals = np.zeros(positive_points.shape[:-2] +
                           (positive_points.shape[-1],))

    for ind in np.ndindex(positive_points.shape[:-2]):
        res = linprog(
            np.zeros(dim),
            -1 * positive_points[ind],
            -1 * np.ones(codim),
            bounds=(None, None))

        if not res.success:
            return None

        functionals[ind] = res.x

    return normalize(functionals)


import numpy as np
from math import ceil, log

n = 2

def add(A, B):
    n = len(A)
    C = [[0 for j in range(0, n)] for i in range(0, n)]
    for i in range(0, n):
        for j in range(0, n):
            C[i][j] = A[i][j] + B[i][j]
    return C

def subtract(A, B):
    n = len(A)
    C = [[0 for j in range(0, n)] for i in range(0, n)]
    for i in range(0, n):
        for j in range(0, n):
            C[i][j] = A[i][j] - B[i][j]
    return C

def ikj_matrix_product(A, B):
    n = len(A)
    C = [[0 for i in range(n)] for j in range(n)]
    for i in range(n):
        for k in range(n):
            for j in range(n):
                C[i][j] += A[i][k] * B[k][j]
    return C

def strassenR(A, B):
    """
    Implementation of the strassen algorithm, similar to
    http://en.wikipedia.org/w/index.php?title=Strassen_algorithm&oldid=498910018#Source_code_of_the_Strassen_algorithm_in_C_language
    """
    n = len(A)

    if n <= 1:
        return ikj_matrix_product(A, B)
    else:
        # initializing the new sub-matrices
        new_size = n//2
        a11 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        a12 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        a21 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        a22 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]

        b11 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        b12 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        b21 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        b22 = [[0 for j in range(0, new_size)] for i in range(0, new_size)]

        aResult = [[0 for j in range(0, new_size)] for i in range(0, new_size)]
        bResult = [[0 for j in range(0, new_size)] for i in range(0, new_size)]

        # dividing the matrices in 4 sub-matrices:
        for i in range(0, new_size):
            for j in range(0, new_size):
                a11[i][j] = A[i][j]            # top left
                a12[i][j] = A[i][j + new_size]    # top right
                a21[i][j] = A[i + new_size][j]    # bottom left
                a22[i][j] = A[i + new_size][j + new_size] # bottom right

                b11[i][j] = B[i][j]            # top left
                b12[i][j] = B[i][j + new_size]    # top right
                b21[i][j] = B[i + new_size][j]    # bottom left
                b22[i][j] = B[i + new_size][j + new_size] # bottom right

        # Calculating p1 to p7:
        aResult = add(a11, a22)
        bResult = add(b11, b22)
        p1 = strassenR(aResult, bResult) # p1 = (a11+a22) * (b11+b22)

        aResult = add(a21, a22)      # a21 + a22
        p2 = strassenR(aResult, b11)  # p2 = (a21+a22) * (b11)

        bResult = subtract(b12, b22) # b12 - b22
        p3 = strassenR(a11, bResult)  # p3 = (a11) * (b12 - b22)

        bResult = subtract(b21, b11) # b21 - b11
        p4 =strassenR(a22, bResult)   # p4 = (a22) * (b21 - b11)

        aResult = add(a11, a12)      # a11 + a12
        p5 = strassenR(aResult, b22)  # p5 = (a11+a12) * (b22)

        aResult = subtract(a21, a11) # a21 - a11
        bResult = add(b11, b12)      # b11 + b12
        p6 = strassenR(aResult, bResult) # p6 = (a21-a11) * (b11+b12)

        aResult = subtract(a12, a22) # a12 - a22
        bResult = add(b21, b22)      # b21 + b22
        p7 = strassenR(aResult, bResult) # p7 = (a12-a22) * (b21+b22)

        # calculating c21, c21, c11 e c22:
        c12 = add(p3, p5) # c12 = p3 + p5
        c21 = add(p2, p4)  # c21 = p2 + p4

        aResult = add(p1, p4) # p1 + p4
        bResult = add(aResult, p7) # p1 + p4 + p7
        c11 = subtract(bResult, p5) # c11 = p1 + p4 - p5 + p7

        aResult = add(p1, p3) # p1 + p3
        bResult = add(aResult, p6) # p1 + p3 + p6
        c22 = subtract(bResult, p2) # c22 = p1 + p3 - p2 + p6

        # Grouping the results obtained in a single matrix:
        C = [[0 for j in range(0, n)] for i in range(0, n)]
        for i in range(0, new_size):
            for j in range(0, new_size):
                C[i][j] = c11[i][j]
                C[i][j + new_size] = c12[i][j]
                C[i + new_size][j] = c21[i][j]
                C[i + new_size][j + new_size] = c22[i][j]
        return C
'''
def squareMatrixMul(A, B):
	n = len(A)

	C = []
	for i in range(n):
		C.append([0]*n)

	if n==1:
		C[0][0] = A[0][0] * B[0][0]
	else:
		A = np.matrix(A)
		B = np.matrix(B)
		C = np.matrix(C)
		A11 = A[0:n//2, 0:n//2]
		A12 = A[0:n//2, n//2:]
		A21 = A[n//2:, 0:n//2]
		A22 = A[n//2:, n//2:]
		B11 = B[0:n//2, 0:n//2]
		B12 = B[0:n//2, n//2:]
		B21 = B[n//2:, 0:n//2]
		B22 = B[n//2:, n//2:]

		C11 = np.matrix(squareMatrixMul(A11, B11)) + np.matrix(squareMatrixMul(A12, B21))
		C12 = np.matrix(squareMatrixMul(A11, B12)) + np.matrix(squareMatrixMul(A12, B22))
		C21 = np.matrix(squareMatrixMul(A21, B11)) + np.matrix(squareMatrixMul(A22, B21))
		C22 = np.matrix(squareMatrixMul(A21, B12)) + np.matrix(squareMatrixMul(A22, B22))
		print(C11)
		C[0:n//2, 0:n//2] = C11[0:n/2, 0:n//2]
		C[0:n//2, n//2:] = C12[0:n/2, 0:n//2]
		C[n//2:, 0:n//2] = C21[0:n/2, 0:n//2]
		C[n//2:, n//2:] = C22[0:n/2, 0:n//2]

	return C
'''
def strassen(A, B):
    assert type(A) == list and type(B) == list
    assert len(A) == len(A[0]) == len(B) == len(B[0])

    # Make the matrices bigger so that you can apply the strassen
    # algorithm recursively without having to deal with odd
    # matrix sizes
    nextPowerOfTwo = lambda n: 2**int(ceil(log(n,2)))
    n = len(A)
    m = nextPowerOfTwo(n)
    APrep = [[0 for i in range(m)] for j in range(m)]
    BPrep = [[0 for i in range(m)] for j in range(m)]
    for i in range(n):
        for j in range(n):
            APrep[i][j] = A[i][j]
            BPrep[i][j] = B[i][j]
    CPrep = strassenR(APrep, BPrep)
    C = [[0 for i in range(n)] for j in range(n)]
    for i in range(n):
        for j in range(n):
            C[i][j] = CPrep[i][j]
    return(C)

A = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
B = [[2, 3, 4], [4, 5, 6], [7, 8, 9]]
print(strassen(A, B))


from math import sqrt

import numba
import numpy as np
import numpy.fft as fft
from numba import float32, jit, objmode
from numba.experimental import jitclass

@jit(nopython=True, error_model='numpy')
def get_moment(y, moment) -> np.float32:
    """
    Returns the (non centered) moment of the distribution y:
    E[y**moment] = \\sum_i[index(y_i)^moment * y_i] / \\sum_i[y_i]
    :param y: the discrete distribution from which one wants to calculate the moment
    :type y: pandas.Series or np.array
    :param moment: the moment one wants to calcalate (choose 1,2,3, ... )
    :type moment: int
    :return: the moment requested
    :return type: float
    """
    return y.dot(np.arange(len(y), dtype=np.float64)**moment) / y.sum()

@jit(nopython=True, error_model='numpy')
def get_centroid(y) -> np.float32:
    """
    :param y: the discrete distribution from which one wants to calculate the centroid
    :type y: pandas.Series or np.array
    :return: the centroid of distribution y (aka distribution mean, first moment)
    :return type: float
    """
    return get_moment(y, 1)

@jit(nopython=True, error_model='numpy')
def get_variance(y) -> np.float32:
    """
    :param y: the discrete distribution from which one wants to calculate the variance
    :type y: pandas.Series or np.array
    :return: the variance of distribution y
    :return type: float
    """
    return get_moment(y, 2) - get_centroid(y) ** 2

@jit(nopython=True, error_model='numpy')
def get_skew(y) -> np.float32:
    """
    Calculates the skew as the third standardized moment.
    Ref: https://en.wikipedia.org/wiki/Skewness#Definition
    :param y: the discrete distribution from which one wants to calculate the skew
    :type y: pandas.Series or np.array
    :return: the skew of distribution y
    :return type: float
    """

    variance = get_variance(y)
    # In the limit of a dirac delta, skew should be 0 and variance 0.  However, in the discrete limit,
    # the skew blows up as variance --> 0, hence return nan when variance is smaller than a resolution of 0.5:
    if variance < 0.5:
        return np.nan
    else:
        return (
            get_moment(y, 3) - 3 * get_centroid(y) *
            variance - get_centroid(y)**3
        ) / get_variance(y)**(1.5)

@jit(nopython=True, error_model='numpy')
def get_kurtosis(y) -> np.float32:
    """
    Calculates the kurtosis as the fourth standardized moment.
    Ref: https://en.wikipedia.org/wiki/Kurtosis#Pearson_moments
    :param y: the discrete distribution from which one wants to calculate the kurtosis
    :type y: pandas.Series or np.array
    :return: the kurtosis of distribution y
    :return type: float
    """

    variance = get_variance(y)
    # In the limit of a dirac delta, kurtosis should be 3 and variance 0.  However, in the discrete limit,
    # the kurtosis blows up as variance --> 0, hence return nan when variance is smaller than a resolution of 0.5:
    if variance < 0.5:
        return np.nan
    else:
        return (
            get_moment(y, 4) - 4 * get_centroid(y) * get_moment(y, 3)
            + 6 * get_moment(y, 2) * get_centroid(y)**2 -
            3 * get_centroid(y)
        ) / get_variance(y)**2

@jit(debug=True, nopython=True, error_model='numpy')
def compute_frequency_features(x):
    """
    Returns the spectral centroid (mean), variance, skew, and kurtosis of the absolute fourier transform spectrum.
    :param x: the time series to calculate the feature of
    :type x: numpy.ndarray
    :param param: contains dictionaries {"aggtype": s} where s str and in ["centroid", "variance",
        "skew", "kurtosis"]
    :type param: list
    :return: the different feature values
    :return type: pandas.Series
    """

    with objmode(fft_abs='float64[:]'):
        fft_abs = np.abs(fft.rfft(x))

    ps = fft_abs**2

    data = np.zeros(8, dtype=np.float32)
    data[0] = get_centroid(fft_abs)
    data[1] = get_variance(fft_abs)
    data[2] = get_skew(fft_abs)
    data[3] = get_kurtosis(fft_abs)
    data[4] = get_centroid(ps)
    data[5] = get_variance(ps)
    data[6] = get_skew(ps)
    data[7] = get_kurtosis(ps)
    return data


import numpy as np
from sigmoid import sigmoid

#returns [J, grad]

def costFunction(theta, X, y, l):

	grad = np.zeros(theta.shape)

	m = len(X)

	h = sigmoid(X.dot(theta))

	reg_theta = np.concatenate(([[0.]], theta[1:]))

	p = (l/(2*m)) * (reg_theta.T.dot(reg_theta))

	J = (1.0/m) * (-y.T.dot(np.log(h)) - (1 - y).T.dot(np.log(1 - h))) + p

	grad = 1.0/m * (X.T.dot(h - y)) + l * reg_theta * (1.0/m)

	return J, grad

def optimizableCostFunction(theta, *args):
	X, y, l = args

	theta = np.reshape(theta, (len(theta),1))

	m = len(X)

	h = sigmoid(X.dot(theta))

	reg_theta = np.concatenate(([[0.]], theta[1:]))

	p = (l/(2*m)) * (reg_theta.T.dot(reg_theta))

	J = (1.0/m) * (-y.T.dot(np.log(h)) - (1 - y).T.dot(np.log(1 - h))) + p

	return J[0, 0]

def gradFunction(theta, *args):
    X, y, l = args

    #reshape theta
    theta = np.reshape(theta, (len(theta),1))

    m = len(X)

    h = sigmoid(X.dot(theta))

    reg_theta = np.concatenate(([[0.]], theta[1:]))

    grad = 1.0/m * (X.T.dot(h - y)) + l * reg_theta * (1.0/m)

    return grad.flatten()


import numpy

import log_reg_funcs

def cost_function(theta, x_data, y_data):

    num_examples = y_data.shape[0]
    hypothesis = log_reg_funcs.sigmoid_function(numpy.dot(x_data, theta))

    term1 = y_data*(numpy.log(hypothesis))
    term2 = (1.0 - y_data)*(numpy.log(1.0 - hypothesis))
    cost_J = numpy.sum(-term1 - term2)/num_examples

    return cost_J


from phaseportrait import PhasePortrait2D
import matplotlib.pyplot as plt
import numpy as np

"""
Example 1: sliders for 2 parameters
"""

def dFPolar(r, θ, *, μ=0.5,η=0):
    return μ*r*(1 - r*r), 1+η*θ

PolarCoordinates = PhasePortrait2D(dFPolar, [-3, 3], Polar=True, Title='Limit cicle')
PolarCoordinates.add_slider('μ', valinit=0.5)
PolarCoordinates.add_slider('η', valinit=0.0)
PolarCoordinates.add_nullclines()
PolarCoordinates.plot()

"""
Example 2: sliders for 4 parameters on the Love Affairs system
"""

def dFLoveAffairs(R, J, *, a=1, b=0, c=-1, d=1):
    return a*J + b*R, c*R + d*J

LoveAffairsPortrait = PhasePortrait2D(dFLoveAffairs, [-3, 3], Density=1.5, Title='Love Affairs', xlabel='R', ylabel='J')
LoveAffairsPortrait.add_slider('d', valinit=1)
LoveAffairsPortrait.add_slider('c', valinit=-1)
LoveAffairsPortrait.add_slider('b', valinit=0)
LoveAffairsPortrait.add_slider('a', valinit=1)
LoveAffairsPortrait.add_nullclines()

LoveAffairsPortrait.plot()

"""
Example 3: sliders for 4 parameters, showing a non-linear center
"""

def dFNonLinearCenter(x, y, *, a=1, b=1, c =1, d=1):
    return a*(y-b*y**3), -c*x-d*y**2

NonLinearCenterPortrait = PhasePortrait2D(dFNonLinearCenter, [-2,2], Density=2, Title='Non-linear center at (0,0)', xlabel='X', ylabel='Y')
NonLinearCenterPortrait.add_slider('d', valinit=1)
NonLinearCenterPortrait.add_slider('c', valinit=1)
NonLinearCenterPortrait.add_slider('b', valinit=1)
NonLinearCenterPortrait.add_slider('a', valinit=1)
NonLinearCenterPortrait.add_nullclines()

NonLinearCenterPortrait.plot()

plt.show()


import numpy as np
import matplotlib.pyplot as plt

def LineFitWt(x, y, sig):
    """
    Returns slope and y-intercept of weighted linear fit to (x,y) data set.
    Inputs: x and y data array and uncertainty array (unc) for y data set.
    Ouputs: slope and y-intercept of best fit to data.
    """
    sig2 = sig**2
    norm = (1./sig2).sum()
    xhat = (x/sig2).sum() / norm
    yhat = (y/sig2).sum() / norm
    slope = ((x-xhat)*y/sig2).sum()/((x-xhat)*x/sig2).sum()
    yint = yhat - slope*xhat
    sig2_slope = 1./((x-xhat)*x/sig2).sum()
    sig2_yint = sig2_slope * (x*x/sig2).sum() / norm
    return slope, yint, np.sqrt(sig2_slope), np.sqrt(sig2_yint)

def redchisq(x, y, dy, slope, yint):
    chisq = (((y-yint-slope*x)/dy)**2).sum()
    return chisq/float(x.size-2)

# Read data from data file
t, N, dN = np.loadtxt("betaDecay.txt", skiprows=2, unpack=True)

########## Code to tranform & fit data starts here ##########

# Transform data and parameters to linear form: Y = A + B*X
X = t           # transform t data for fitting (not necessary since X=t)
Y = np.log(N)   # transform N data for fitting
dY = dN/N       # transform uncertainties for fitting

# Fit transformed data X, Y, dY to obtain fitting parameters A & B
# Also returns uncertainties in A and B
B, A, dB, dA = LineFitWt(X, Y, dY)
# Return reduced chi-squared
redchisqr = redchisq(X, Y, dY, B, A)

# Determine fitting parameters for original exponential function
# N = N0 exp(-t/tau) ...
N0 = np.exp(A)
tau = -1.0/B
# ... and their uncertainties
dN0 = N0 * dA
dtau = tau**2 * dB

########## Code to plot transformed data and fit starts here ##########

# Create line corresponding to fit using fitting parameters
# Only two points are needed to specify a straight line
Xext = 0.05*(X.max()-X.min())
Xfit = np.array([X.min()-Xext, X.max()+Xext]) # smallest & largest X points
Yfit = A + B*Xfit                   # generates Y from X data & fitting function

plt.errorbar(X, Y, dY, fmt="bo")
plt.plot(Xfit, Yfit, "r-", zorder=-1)
plt.xlim(0, 100)
plt.ylim(1.5, 7)
plt.title("$\mathrm{Fit\\ to:}\\ \ln N = -t/\\tau + \ln N_0$")
plt.xlabel("t")
plt.ylabel("ln(N)")

plt.text(50, 6.6, "A = ln N0 = {0:0.2f} $\pm$ {1:0.2f}".format(A, dA))
plt.text(50, 6.3, "B = -1/tau = {0:0.4f} $\pm$ {1:0.4f}".format(-B, dB))
plt.text(50, 6.0, "$\chi_r^2$ = {0:0.3f}".format(redchisqr))

plt.text(50, 5.7, "N0 = {0:0.0f} $\pm$ {1:0.0f}".format(N0, dN0))
plt.text(50, 5.4, "tau = {0:0.1f} $\pm$ {1:0.1f} days".format(tau, dtau))

plt.savefig("betaDecay.pdf")
plt.show()