import numpy as np
import pandas as pd
# import matplotlib.pyplot as plt
import argparse
# import seaborn as sns
import csv

def load_data(file_name, has_label=True):

    df = pd.read_csv(file_name, header=None)
    y = None
    if has_label:
        y = df[[0]]
        x = df.drop([0], axis=1)

        #   if v == 3, y = 1.0; if v == 5, y = -1.0
        def translate(v):
            return 1.0 if v == 3 else -1.0
        y = y.applymap(translate)

        #   translate to np.array
        x = np.array(x)
        y = np.array(y)

        #   insert bias
        x = np.insert(x, 0, values=1.0, axis=1)

        #   transfer label to array, not matrix
        y = y.T[0]
    else:
        x = df
        #   translate to np.array
        x = np.array(x)

        #   insert bias
        x = np.insert(x, 0, values=1.0, axis=1)
    return x, y

def predict(w, x):
    res = np.matmul(w, np.transpose(x))
    vect_sign = np.vectorize(np.sign)
    return vect_sign(res)

def test_accuracy(w, x, y):
    pre_res = predict(w, x)
    sum_diff = 0
    for i in range(len(pre_res)):
        if pre_res[i] != y[i]:
            sum_diff += 1
    return 1 - sum_diff / len(y)

def online_perceptron(x_train, y_train, x_valid, y_valid, iters=15):
    w = np.zeros(len(x_train[0]))
    acc_train, acc_valid = [], []
    print("iteration number\taccuracy on the training set\taccuracy on the validation set")
    for i in range(iters):
        for t in range(len(x_train)):
            u = np.sign(x_train[t].dot(w))
            if u * y_train[t] <= 0:
                w += y_train[t] * x_train[t]
        acc_train.append(test_accuracy(w, x_train, y_train))
        acc_valid.append(test_accuracy(w, x_valid, y_valid))
        print("%d\t%f\t%f" % (i+1, acc_train[i], acc_valid[i]))
    return w, acc_train, acc_valid

def average_perceptron(x_train, y_train, x_valid, y_valid, iters=15):
    w = np.zeros(len(x_train[0]))
    w_a = np.zeros(len(x_train[0]))
    acc_train, acc_valid = [], []
    c, s = 0, 0
    print("iteration number\taccuracy on the training set\taccuracy on the validation set")
    for i in range(iters):
        for t in range(len(x_train)):
            u = np.sign(x_train[t].dot(w))
            if u * y_train[t] <= 0:
                if s + c > 0:
                    w_a = (s * w_a + w * c) / (s + c)
                s = s + c
                w += y_train[t] * x_train[t]
                c = 0
            else:
                c += 1
        if c > 0:
            w_a = (s * w_a + w * c) / (s + c)
        acc_train.append(test_accuracy(w_a, x_train, y_train))
        acc_valid.append(test_accuracy(w_a, x_valid, y_valid))
        print("%d\t%f\t%f" % (i+1, acc_train[i], acc_valid[i]))
    return w_a, acc_train, acc_valid

def kernel_function(x, y, p):
    # return (1 + np.dot(x, y)) ** p
    return np.power((np.matmul(x, y.T) + 1), p)

def kernel_perceptron(x_train, y_train, x_valid, y_valid, p=3, iters=15):
    acc_train, acc_valid = [], []
    N = len(x_train)
    alpha = np.zeros(N)

    #   Gram Matrix
    K_train = kernel_function(x_train, x_train, p)
    K_validation = kernel_function(x_valid, x_train, p)
    print("iteration number\taccuracy on the training set\taccuracy on the validation set")
    for it in range(iters):
        for i in range(N):
            u = np.sign(np.dot(K_train[i], np.multiply(alpha, y_train)))
            if y_train[i] * u <= 0:
                alpha[i] += 1

        pred = predict_kernel(K_train, alpha, y_train)
        acc_train.append(test_accuracy_kernel(pred, y_train))

        pred = predict_kernel(K_validation, alpha, y_train)
        acc_valid.append(test_accuracy_kernel(pred, y_valid))
        print("%d\t%f\t%f" % (it+1, acc_train[it], acc_valid[it]))
    return alpha, acc_train, acc_valid

def predict_kernel(gram_matrix, alpha, y_train):
    #   Gram Matrix
    K = gram_matrix
    return np.sign(np.matmul(K, np.multiply(alpha, y_train)))

def test_accuracy_kernel(pre, y):
    sum_diff = 0
    for i in range(len(pre)):
        if pre[i] != y[i]:
            sum_diff += 1
    return 1 - sum_diff / len(y)

def plot_accuracy(acc_train, acc_valid, title):
    iters = range(1, len(acc_train)+1)
    plt.figure()
    plt.plot(iters, acc_train, "b-", linewidth=1, label="training accuracy")
    plt.plot(iters, acc_valid, "r-", linewidth=1, label="validation accuracy")
    plt.legend()
    plt.xlabel("Iterations")
    plt.ylabel("Accuracy")
    plt.title("Accuracy vs. Iterations (" + title + ")")
    plt.show()

def save_result(file_name, results):
    with open(file_name, mode='w') as file:
        writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
        for i in range(len(results)):
            writer.writerow([results[i]])

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Perceptron Implementation')

    parser.add_argument('--run', help="run method: op (Online Perceptron), ap (Average Perceptron), kp (Kernel Perceptron)", default='')
    parser.add_argument("-i", "--iterations", help="iteration number (default: 15)", default='15')
    parser.add_argument("-p", "--p", help="p for the kernel function (default: 3)", default='3')
    args = parser.parse_args()

    if args.run == '':
        print('usage: main.py [--runfunc RUNFUNC: op, ap, kp] [-p p]')
        print('main.py: error: invalid parameter')
        exit(0)

    sample_train, label_train = load_data("pa2_train.csv")
    sample_valid, label_valid = load_data("pa2_valid.csv")
    sample_test, _ = load_data("pa2_test_no_label.csv", has_label=False)
    #
    if args.run == 'op':
        w_op, acc_t_op, acc_v_op = online_perceptron(sample_train, label_train, sample_valid, label_valid, iters=int(args.iterations))
        # plot_accuracy(acc_t_op, acc_v_op, "Online Perceptron")
    elif args.run == 'ap':
        w_ap, acc_t_ap, acc_v_ap = average_perceptron(sample_train, label_train, sample_valid, label_valid, iters=int(args.iterations))
        # plot_accuracy(acc_t_ap, acc_v_ap, "Average Perceptron")
    elif args.run == 'kp':
        a_kp, acc_t_kp, acc_v_kp = kernel_perceptron(sample_train, label_train, sample_valid, label_valid, p=int(args.p), iters=int(args.iterations))
        # plot_accuracy(acc_t_kp, acc_v_kp, "Kernel Perceptron")

    #   Online Perceptron
    # w_op, acc_t_op, acc_v_op = online_perceptron(sample_train, label_train, sample_valid, label_valid, iters=14)
    # plot_accuracy(acc_t_op, acc_v_op, "Online Perceptron")
    # #   predict the test data set
    # results = predict(w_op, sample_test)
    # save_result("oplabel.csv", results)

    #   Average Perceptron
    # w_ap, acc_t_ap, acc_v_ap = average_perceptron(sample_train, label_train, sample_valid, label_valid, iters=15)
    # plot_accuracy(acc_t_ap, acc_v_ap, "Average Perceptron")

    #   Kernel Perceptron
    # p = 3
    # a_kp, acc_t_kp, acc_v_kp = kernel_perceptron(sample_train, label_train, sample_valid, label_valid, p=p, iters=15)
    # plot_accuracy(acc_t_kp, acc_v_kp, "Kernel Perceptron")
    # # predict the test data set
    # K_test = kernel_function(sample_test, sample_train, p)
    # save_result("kplabel.csv", predict_kernel(K_test, a_kp, label_train))


# ou_sim.py
#
# Simulates an Ornstein-Uhlenbeck process.
#
# Jason Blevins <jrblevin@sdf.lonestar.org>
# Durham, March 24, 2008 09:16 EDT

import numpy
from numpy import exp, sqrt

def ou_sim(T, sigma=1.0, eta=1.0, mu=0.0):
    """Ornstein-Uhlenbeck Simulator

    Parameters
    ----------

    T -- number of periods to simulate

    sigma -- standard deviation of the Brownian Motion
    eta -- speed of mean reversion
    mu -- mean

    Returns
    -------

    x -- an array containing the realization of the process
    """
    x = numpy.ones((T)) * mu                  # Initialize storage for x

    # Store a series of independent Normal(0,1) draws
    omega = numpy.random.normal(size=T)

    for t in numpy.arange(1,T):
        x[t] = x[t-1] * exp(-eta) + mu * (1 - exp(-eta)) \
            + sigma * sqrt((1 - exp(-2*eta))/(2*eta)) * omega[t]

    return x

if __name__ == '__main__':
    import pylab

    sigma = 0.01
    eta = 0.03
    mu = 0.0

    x = ou_sim(250, sigma, eta, mu)

    pylab.plot(x, label = ("eta = %0.2f, sigma=%0.2f" % (eta, sigma)))
    pylab.title('Ornstein-Uhlenbeck process')
    pylab.legend(loc='upper left')
    pylab.show()


"""
Problem Statement

mean

The mean tool computes the arithmetic mean along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.mean(my_array, axis = 0)        #Output : [ 2.  3.]
print numpy.mean(my_array, axis = 1)        #Output : [ 1.5  3.5]
print numpy.mean(my_array, axis = None)     #Output : 2.5
print numpy.mean(my_array)                  #Output : 2.5
By default, the axis is None. Therefore, it computes the mean of the
flattened array.

var

The var tool computes the arithmetic variance along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.var(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.var(my_array, axis = 1)         #Output : [ 0.25  0.25]
print numpy.var(my_array, axis = None)      #Output : 1.25
print numpy.var(my_array)                   #Output : 1.25
By default, the axis is None. Therefore, it computes the variance of the
flattened array.

std

The std tool computes the arithmetic standard deviation along the specified
axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.std(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.std(my_array, axis = 1)         #Output : [ 0.5  0.5]
print numpy.std(my_array, axis = None)      #Output : 1.11803398875
print numpy.std(my_array)                   #Output : 1.11803398875
By default, the axis is None. Therefore, it computes the standard deviation
of the flattened array.

Task

You are given a 2-D array of size NXM.
Your task is to find:

The mean along axis `1`
The var along axis `0`
The std along axis `None`
Input Format

The first line contains the space separated values of N and M.
The next N lines contains M space separated integers.

Output Format

First, print the mean.
Second, print the var.
Third, print the std.

Sample Input

2 2
1 2
3 4
Sample Output

[ 1.5  3.5]
[ 1.  1.]
1.11803398875
"""
import numpy

n, m = map(int, raw_input().split())
a = numpy.array([map(int, raw_input().split()) for i in range(n)],
                dtype=numpy.int)
print numpy.mean(a, axis=1)
print numpy.var(a, axis=0)
print numpy.std(a)


'''
How is it optimal?

The function np.polyfit() that you used to get your regression parameters finds the optimal slope and
intercept. It is optimizing the sum of the squares of the residuals, also known as RSS (for residual
sum of squares). In this exercise, you will plot the function that is being optimized, the RSS, versus
the slope parameter a. To do this, fix the intercept to be what you found in the optimization. Then,
plot the RSS vs. the slope. Where is it minimal?

INSTRUCTIONS
100XP
-Specify the values of the slope for which to compute the RSS. Use np.linspace() to get 200 points
in the range between 0 and 0.1. For example, to get 100 points in the range between 0 and 0.5, you
could use np.linspace() like so: np.linspace(0, 0.5, 100).
-Initialize an array, rss, to contain the RSS using np.empty_like() and the array you created above.
The empty_like() function returns a new array with the same shape and type as a given array
(in this case, a_vals).
-Write a for loop to compute the sum of RSS of the slope. Hint: the RSS is given by
np.sum((y_data - a * x_data - b)**2). The variable b you computed in the last exercise is already in
your namespace. Here, fertility is the y_data and illiteracy the x_data.
-Plot the RSS (rss) versus slope (a_vals).
Hit 'Submit Answer' to see the plot!
'''
import numpy as np
import matplotlib.pyplot as plt

# Specify slopes to consider: a_vals
a_vals = np.linspace(0, 0.1, 200)

# Initialize sum of square of residuals: rss
rss = np.empty_like(a_vals)

# Compute sum of square of residuals for each value of a_vals
for i, a in enumerate(a_vals):
    rss[i] = np.sum((fertility - a*illiteracy - b)**2)

# Plot the RSS
plt.plot(a_vals, rss, '-')
plt.xlabel('slope (children per woman / percent illiterate)')
plt.ylabel('sum of square of residuals')

plt.show()


"""Implements Gaussian Discriminant Analysis from Stanford 229."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from collections import namedtuple

import tensorflow as tf
import numpy as np

def regression(x, y):
    """IImplements Gaussian Discriminant Analysis from Stanford 229.
    (http://cs229.stanford.edu/notes/cs229-notes2.pdf)
    Args:
      x: The covariates or factors of the model in an n by m array (n is number)
        of data points and m is number of factors
      y: The targets or labels of the model in an n by 1 array (either 0 or 1)
    Raises:
      TODO
    Returns:
      A (p, u0, u1, Sigma) tuple, where p is the probability of the target being
      1, u0 is the gaussian mean of the Y=0 distribution, u1 for the Y=1, and
      Sigma is their joint covariance matrix.
    """
    num_predictors = len(x[0])
    num_data = len(x)

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

    print(x)
    x0 = x[np.where( y == 0)]
    print(x0)

    x1 = x[np.where( y == 1)]

    with tf.Graph().as_default() as _:
        Y = tf.placeholder(tf.float32, [num_data])
        X0 = tf.placeholder(tf.float32, [len(x0), num_predictors])
        X1 = tf.placeholder(tf.float32, [len(x1), num_predictors])

        # sum along the num_data axis
        U0 = tf.reduce_mean(X0, 0)
        U1 = tf.reduce_mean(X1, 0)

        Sigma0 = tf.matmul(tf.transpose(X0 - U0), (X0 - U0))
        Sigma1 = tf.matmul(tf.transpose(X1 - U1), (X1 - U1))
        Sigma = (Sigma1 + Sigma0)/num_data

        P = tf.reduce_mean(Y)

        init = tf.initialize_all_variables()

        with tf.Session() as sess:
            sess.run(init)

            p, u0, u1, sigma = sess.run(
                [P, U0, U1, Sigma],
                feed_dict={X0:x0, X1: x1, Y:y})

            Parameters = namedtuple("Parameters", ["Probability_of_target",
                "Gaussian_mean_of_the_Y0_distribution",
                "Gaussian_mean_of_the_Y1_distribution",
                "Joint_covariance_matrix"])
            return Parameters(p, u0, u1, sigma)

if __name__ == "__main__":
    X_TEST = np.reshape([[0.5, 0.2], [0.5, 0.2], [0.5, 0.2],
        [0.25, 1.0], [0.25, 1.0], [0.25, 1.0]], (6, 2))
    Y_TEST = np.reshape([0, 0, 0, 1, 1, 1], (6))

    print(regression(X_TEST, Y_TEST))


# **************************************************************************** #
#                                                                              #
#                                                         :::      ::::::::    #
#    maths.py                                           :+:      :+:    :+:    #
#                                                     +:+ +:+         +:+      #
#    By: alngo <alngo@student.42.fr>                +#+  +:+       +#+         #
#                                                 +#+#+#+#+#+   +#+            #
#    Created: 2020/02/24 09:45:07 by alngo             #+#    #+#              #
#    Updated: 2020/03/02 11:25:39 by alngo            ###   ########.fr        #
#                                                                              #
# **************************************************************************** #

import numpy as np
import sys

def mean_squared_error(p0, p1, X, y):
    cost = ((p0 + (p1 * X)) - y) * ((p0 + (p1 * X)) - y)
    return sum((1 / (2 * len(X))) * cost)

def normalize(list):
    minValue = min(list)
    maxValue = max(list)
    newList = (list - minValue) / (maxValue - minValue)
    return [newList, minValue, maxValue]

def denormalize_theta(dataset, normalized_dataset, theta0, theta1, min, max):
    x0 = dataset.iloc[0, 0]
    x1 = dataset.iloc[1, 0]
    y0 = dataset.iloc[0, 1]
    nx0 = normalized_dataset[0]
    nx1 = normalized_dataset[1]
    hy0 = theta0 + (nx0 * theta1)
    hy1 = theta0 + (nx1 * theta1)

    try:
        p0 = (x1 / (x1 - x0)) * (upscale(hy0, min, max) - (x0 / x1 * upscale(hy1, min, max)))
        p1 = (y0 - p0) / x0
    except ZeroDivisionError:
        print(f'Zero division error')
        sys.exit(1)
    return p0, p1

def scale(value, min, max):
    try:
        res = np.float64((value - min) / (max - min))
    except ZeroDivisionError:
        print(f'Zero division error')
        sys.exit(1)
    return res

def upscale(value, min, max):
    return np.float64((value * (max - min)) + min)


import numpy as np
import math

def r2_score(y_true, y_pred):
    """Calculate the coefficient of determination"""

    # ybar = numpy.sum(y)/len(y)
    ssreg = np.sum((y_pred - y_true.mean()) ** 2)
    sstot = np.sum((y_true - y_true.mean()) ** 2)
    r2 = ssreg / sstot
    return r2

def rmse_score(y_true, y_pred):
    """Calculate the root mean squared error"""

    rmse = math.sqrt(np.sum(y_pred - y_true) ** 2 / y_true.size)
    return rmse

def mse_score(y_true, y_pred):
    """Calculate the mean squared error"""

    mse = np.sum(y_pred - y_true) ** 2 / y_true.size
    return mse

def mae_score(y_true, y_pred):
    """Calculate the mean absolute error"""

    mae = np.sum(np.abs(y_pred - y_true) ** 2) / y_true.size
    return mae

def accuracy(y_true, y_pred):
    """Calculate the accuracy"""

    acc = (y_true == y_pred).mean()
    return acc

def recall(y_true, y_pred):
    """Calculate recall metric"""

    dict_recall = {}
    for label in np.unique(y_pred):
        tp = len(y_pred[(y_pred == label) & (y_pred == y_true)])
        tp_fn = len(y_pred[y_true == label])
        dict_recall[label] = tp / tp_fn

    return dict_recall


from scipy.spatial.distance import pdist, squareform
from scipy.linalg import eigh

import numpy as np

def rbf_kernel_pca(X, gamma, n_components):
    """
    RBF kernel PCA implementation.

    Parameters
    ------------
    X: {NumPy ndarray}, shape = [n_examples, n_features]

    gamma: float
        Tuning parameter of the RBF kernel

    n_components: int
        Number of principal components to return

    Returns
    -----------
    X_pc: {NumPy ndarray}, shape = [n_examples, k_features]
        Projected dataset

    """
    # Calculate pairwise squared Euclidean distances in the
    # MxN dimensional dataset.
    sq_dists = pdist(X, 'sqeuclidean')

    # Convert pairwise distances into a square matrix.
    mat_sq_dists = squareform(sq_dists)

    # Compute the symmetric kernel matrix.
    K = np.exp(-gamma * mat_sq_dists)

    # Center the kernel matrix.
    N = K.shape[0]
    one_n = np.ones((N, N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

    # Obtaining eigenpairs from the centered kernel matrix
    # scipy.linalg.eigh returns them in ascending order
    eigvals, eigvecs = eigh(K)
    eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

    # Collect the top k eigenvectors (projected examples)
    alphas = np.column_stack([eigvecs[:, i] for i in range(n_components)])

    # Collect the corresponding eigenvalues
    lambdas = [eigvals[i] for i in range(n_components)]

    return alphas, lambdas


from sympy import *

# input
x1, y1, dx1, dy1 = symbols('x1 y1 dx1 dy1')
x2, y2, dx2, dy2 = symbols('x2 y2 dx2 dy2')

# intermediate: arcs
ax1, ay1, ax2, ay2 = symbols('ax1 ay1 ax2 ay2')

# output: arc radiuses
r1, r2 = symbols('r1 r2')

solutions = solve(
   [
    r2 - r1,  # radiuses are equal
    x1 + r1*dy1 - ax1,
    y1 - r1*dx1 - ay1,
    x2 + r2*dy2 - ax2,
    y2 - r2*dx2 - ay2,
    (ax1-ax2)**2 + (ay1-ay2)**2 - (r1+r2)**2
], (ax1, ay1, ax2, ay2, r1, r2))

for solution, i  in zip(solutions, range(1, len(solutions)+1)):
    print('const ax1_'+str(i)+' = ' + jscode(solution[0]))
    print('const ay1_'+str(i)+' = ' + jscode(solution[1]))
    print('const ax2_'+str(i)+' = ' + jscode(solution[2]))
    print('const ay2_'+str(i)+' = ' + jscode(solution[3]))
    print('const r1_'+str(i)+' = ' + jscode(solution[4]))
    print('const r2_'+str(i)+' = ' + jscode(solution[5]))
    print('')


"""

"""
import numpy as np
import nashpy

# Matching pennies game
A = np.array([[1, -1], [-1, 1]])
matching_pennies = nash.Game(A)
print(matching_pennies)
equilibria = matching_pennies.support_enumeration()
for eq in equilibria:
    print(eq)
# zero sum game with payoff matices


import os
from montepython.likelihood_class import Likelihood
import math as m
import statistics as s
#from scipy import stats
#import scipy.linalg as la
import numpy as np
#from scipy.integrate import quad

#defines a simple linear regression to be used later by the york linear regression
def lin_reg(x,y):

    #length of data set
	n = len(x)

    #summing independent variable
	x_sum = sum(x)
    #summing dependent variable
	y_sum = sum(y)

    #mean of independent variable
	x_mean = s.mean(x)

    #mean of dependent variable
	y_mean= s.mean(y)

    #sum of x squared
	x_sqr = []
	for i in range(len(x)):
		x_temp = x[i]**2
		x_sqr.append(x_temp)
	x_sqr_sum = sum(x_sqr)

    #sum of y squared
	y_sqr = []
	for i in range(len(y)):
		y_temp = y[i]**2
		y_sqr.append(y_temp)
	y_sqr_sum = sum(y_sqr)

    #sum of xy product
	xy_prod = []
	for i in range(len(y)):
		xy_temp = y[i]*x[i]
		xy_prod.append(xy_temp)
	xy_prod_sum = sum(xy_prod)

    #numerator and denominator of slope estimate
	S_xx = x_sqr_sum - (x_sum**2/n)
	S_xy = xy_prod_sum - (x_sum*y_sum/n)

    #slope estimate
	B_1 = S_xy/S_xx

    #intercept estimate
	B_0 = y_mean - B_1*x_mean

	return B_0, B_1

# linear regression using error as a weighting scheme, fixing slope and slope error
def weighted_fixed_slope(x,y,m,dm,dx,dy):
    #wx = 1/dx^2
    #wy = 1/dy^2
	w = 1/(dx**2+dy**2)

	ybar = sum(w*y)/sum(w)
	xbar = sum(w*x)/sum(w)

	B0 = ybar - m*xbar

	r = y - (B0 +m*x)
	wr = np.sqrt(w)*r
	n = len(x)

	SE_wr = np.sqrt(sum(wr**2)/(n-2))
	dB0 = np.sqrt((SE_wr**2/sum(w))+dm*xbar**2)
	B1 = m
	dB1 = dm

	return B0, B1, dB0, dB1

#york linear regression

# York correction to linear regression including error in both x and y
# https://aapt.scitation.org/doi/abs/10.1119/1.1632486

def york_fit(x,y,sigma_x,sigma_y,r,tol,n_max):
#make sure inputs are a numpy array
	#if the error is 0, replace with something very very small to
	# to prevent nan
	sigma_x[sigma_x == 0] = 10**-15
	sigma_y[sigma_y == 0] = 10**-15

#define an array which tracks the changes in slope, B_1
	b_hist = np.ones(n_max)

#1) choose an approximate initial value for the slope, b
	# -> simple linear regression
# B_0 is intercept, B_1 is slope from simple linear regression
	[B_0_simple, B_1_simple] = lin_reg(x,y)
	b_hist[0] = B_1_simple
	B_0 = B_0_simple
	B_1 = B_1_simple

#2) determine the weights omega of each point for both x and y
	# usually 1/sigma where sigma is the error associated with x and y
	# at the i'th point

	omega_x = 1/np.square(np.array(sigma_x))
	omega_y = 1/np.square(np.array(sigma_y))

#3) use these weights with B_1 and the correlation r (if any) to
	# evaluate W_i for each point
	alpha = np.sqrt(omega_x*omega_y)
#6) calculate B_1_new until the difference between B_1_new and B_1 is
	# less than the tolerance provided
	counter = 1
	while counter < n_max:
		W = (omega_x*omega_y)/(omega_x + (B_1**2)*omega_y - 2*B_1*r*alpha)

#4) use the observed points and W to calculate x_bar and y_bar from
    # from which U V and beta can be evaluated for each point

		x_bar = sum(W*x)/sum(W)
		y_bar = sum(W*y)/sum(W)

		U = x - x_bar
		V = y - y_bar
		beta = W*((U/omega_y)+(B_1*V/omega_x)-(B_1*U+V)*(r/alpha))

#5) use W U V and beta to calculate a new estimate of B_1

		B_1_new = sum(W*beta*V)/sum(W*beta*U)
		b_hist[counter] = B_1_new

		if(abs(B_1_new-B_1)< tol):
			B_1 = B_1_new
			break

		counter += 1
		B_1 = B_1_new

#7) using the final value of B_1, x_bar, y_bar, calculate B_0

	B_0 = y_bar - B_1*x_bar

#8) for each point x and y, calculate the adjusted values
	x_adj = x_bar + beta
	y_adj = y_bar + B_1*beta

#9) use x_adj and W to calc x_bar_adj and u abd v
	x_bar_adj = sum(W*x_adj)/sum(W)
    #y_bar_adj = sum(W*y_adj)/sum(W)

	u = x_adj - x_bar_adj
    #v = y_adj - y_bar_adj

#10) use W x_bar and u to calculate sigma_a and sigma_b

	var_b = 1/(sum(W*u**2))
	var_a = 1/sum(W)+(x_bar**2)*var_b

	sigma_a_new = np.sqrt(var_a)
	sigma_b_new = np.sqrt(var_b)

	return B_0, B_1, sigma_a_new, sigma_b_new, b_hist, B_0_simple, B_1_simple

#######################################################################################
#CLASS DEFINITIONS OF STEPS OF THE DISTANCE LADDER#
#######################################################################################

# This class defines the structure in which Anchors with known distances
# and contain cepheids are imported

class Anchor:
	"This class defines a structure which holds data for objects that have geometric distance measurements, and cepheid variables."

	def __init__(self,Name='',Dist=0,dDist=0):

		self.Name = Name
		self.Dist = Dist
		self.dDist = dDist
		self.mu = 5*np.log10(self.Dist)-5
		self.dmu = 5*np.log10(np.exp(1))*self.dDist/self.Dist

	#computes the absolute magnitude of cepheids in the anchor galaxy, assuming cepheid data is available
	def Compute_abs_ceph_mag(self,period,dperiod,mh,dmh):
		r = 0
		tol = 10**-15
		n = 20
		[B_0, B_1, sigma_B_0, sigma_B_1, b_save, B_0_simple, B_1_simple] = york_fit(period,mh,dperiod,dmh,r,tol,n)

		self.Mceph = B_0 - self.mu
		self.dMceph = np.sqrt(np.power(sigma_B_0,2)+np.power(self.dmu,2))

# This class defines the structure in which the SHOES cepheid data is
# imported, and the DM calculated to those cepheids

class SHOES_ceph_data:
	"This class creates a structure which holds SHOES cepheid data for a given host"

	def __init__(self,Host='',ID='',Period=0,V=0,dV=0,I=0,dI=0,NIR=0,dNIR=0,OH=0):

		self.Host = Host
		self.ID = ID
		self.Period = Period
		self.NIR = NIR
		self.dNIR = dNIR
		self.V = V
		self.dV = dV
		self.I = I
		self.dI = dI
		# R value is a correlation coeffecient, forced to be the same
		#as what is used in previous analysis
		self.R = 0.386
		self.mh = NIR - self.R*(V-I)
		self.dmh = np.sqrt(np.power(dNIR,2)+self.R*np.power(dV,2)+self.R*np.power(dI,2))

	# Using the absolute magnitude of cepheids, the distance modulus to SH0ES cepheids is calculated.
	def proto_Compute_mu(self,mh,dmh,period,dperiod,Mceph,dMceph,slope,dslope):

		[B0, B_1, dB0, dB1] = weighted_fixed_slope(period,mh,slope,dslope,dperiod,dmh)

		self.mu = B0 - Mceph
		self.dmu = np.sqrt(np.power(dB0,2)+np.power(dMceph,2))

# This class defines the structure in which the LMC cepheid data is imported, and the DM calculated to those cepheids

class LMC_ceph_data:
	"This class creates a structure which holds cepheid data in the LMC"

	def __init__(self,Host='',ID='',Period=0,mh=0,dmh=0):

		self.Host = Host
		self.ID = ID
		self.Period = Period
		self.mh = mh
		self.dmh = dmh

	# Using the absolute magnitude of cepheids, the distance modulus to LMC cepheids is calculated.
	def proto_Compute_mu(self,mh,dmh,period,dperiod,Mceph,dMceph,slope,dslope):

		[B0, B_1, dB0, dB1] = weighted_fixed_slope(period,mh,slope,dslope,dperiod,dmh)

		self.mu = B0 - Mceph
		self.dmu = np.sqrt(np.power(dB0,2)+np.power(dMceph,2))

# This class defines the structure in which the supernova calibarted using the TRGB method is imported
class TRGB_sn_data:
	def __init__(self,Host='',ID='',mu=0,dmu=0,m=0,dm=0):
		self.Host = Host
		self.ID = ID
		self.mu = mu
		self.dmu = dmu
		self.m = m
		self.dm = dm

# This class defines the structure in which the SN which are nearby and calibrated are imported

class Local_SN_data:
	"This class creates a structure which holds SN data for a given host."

	def __init__(self,Host='',ID='',m=0,dm=0):

		self.Host = Host
		self.ID = ID
		self.m = m
		self.dm = dm

	def Compute_abs_sn_mag(self,m,dm,mu,dmu):

		x = mu
		y = m
		xerror = dm
		yerror = dmu
		[Msn, B1, dMsn, sigma_B1] = weighted_fixed_slope(x,y,1,0,xerror,yerror)
		self.Msn = Msn
		self.dMsn = dMsn

# This class defines the structure in which hubble flow SN are imported

class Hubble_SN_data:
	"This class creates a structure which holds SN data in the hubble flow."

	def __init__(self,ID='',z_hel = 0,z_cmb=0,dz=0,m=0,dm=0):

		self.ID = ID
		self.m = m
		self.dm = dm
		self.z_hel = z_hel
		self.z_cmb = z_cmb
		self.dz = dz

	def Compute_hubble_mu(self,m,dm,Msn,dMsn):
		#self.mu = hubble_sn.m - Msn
		#self.dmu = np.sqrt(np.power(hubble_sn.dm,2)+np.power(dMsn,2))
		self.mu = m - Msn
		self.dmu = np.sqrt(np.power(dm,2)+np.power(dMsn,2))

class distanceladder(Likelihood):

	# initialization routine. Here we will establish the subclasses needed to calculate the
	#attributes of the distance ladder, up until the distance modulus calculation of
	#hubble flow SN

	def __init__(self, path, data, command_line):

		#initialization of Likelihood class
		Likelihood.__init__(self,path,data,command_line)

				#simple linear regression used by Yorkfit for initial parameter estimation

##########################################################################################
#TABLE READERS#
##########################################################################################
		# reads in anchor distance data
		def anchor_reader(file,index_array,header_length,delim):

			base_dir = self.data_directory

		    #stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

		    #defining the length of the actual data of the table
			i = len(table)-header_length

		    #preallocating the arrays for memory
			host_list = []
			dist_np = np.zeros(i)
			ddist_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:
					if index_array[0] is not np.NaN:
						host_list.append(data[index_array[0]])
					else:
						host_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						dist_np[i] = data[index_array[1]]
					else:
						dist_np[i] = np.NaN

					if index_array[2] is not np.NaN:
						ddist_np[i] = data[index_array[2]]
					else:
						ddist_np[i] = np.NaN

			name = []
			for a in range(len(host_list)):
				name.append([host_list[a],dist_np[a],ddist_np[a]])

			return name

		# reads in the SHOES cepheid data
		def SHOES_ceph_table_reader(file,index_array,header_length,delim):
		    #file(string): location of table wished to be read in
		    #index_array(array): array containing the column index of the
			#following attributes:
			#host,ID,period,V,dV,I,dI,NIR,dNIR,OH
			# if attribute does not occur in table, put -1
			#host: index of host column in table
			    # list, strings
			#ID: index of ID column in table
			    # list, strings
			#period: index of period column in table
			    # np array, float
			# the remainder of entries are the index of said variable in table
			    # all are np array's, with float values
		    #header_length(int): line number of last row of header
		    #name(string):user desired prefix for data
			# name_host, name_ID, etc
		    #cc(string): character which splits your data

		    #All data should be located in this parent folder
			base_dir = self.data_directory

		    #stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

		    #defining the length of the actual data of the table
			i = len(table)-header_length

		    #preallocating the arrays for memory
			host_list = []
			ID_list = []
			period_np = np.zeros(i)
			V_np = np.zeros(i)
			dV_np = np.zeros(i)
			I_np = np.zeros(i)
			dI_np = np.zeros(i)
			NIR_np = np.zeros(i)
			dNIR_np = np.zeros(i)
			OH_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:
					if index_array[0] is not np.NaN:
						host_list.append(data[index_array[0]])
					else:
						host_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						ID_list.append(data[index_array[1]])
					else:
						ID_list.append(np.NaN)

					if index_array[2] is not np.NaN:
						period_np[i] = data[index_array[2]]
					else:
						period_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						V_np[i] = data[index_array[3]]
					else:
						V_np[i] = np.NaN

					if index_array[4] is not np.NaN:
						dV_np[i] = data[index_array[4]]
					else:
						dV_np[i] = np.NaN

					if index_array[5] is not np.NaN:
						I_np[i] = data[index_array[5]]
					else:
						I_np[i] = np.NaN

					if index_array[6] is not np.NaN:
						dI_np[i] = data[index_array[6]]
					else:
						dI_np[i] = np.NaN

					if index_array[7] is not np.NaN:
						NIR_np[i] = data[index_array[7]]
					else:
						NIR_np[i] = np.NaN

					if index_array[8] is not np.NaN:
						dNIR_np[i] = data[index_array[8]]
					else:
						dNIR_np[i] = np.NaN

					if index_array[9] is not np.NaN:
						OH_np[i] = data[index_array[9]]
					else:
						OH_np[i] = np.NaN

				name = []
			for a in range(len(host_list)):
				name.append([host_list[a],ID_list[a],period_np[a],V_np[a],dV_np[a],I_np[a],dI_np[a],NIR_np[a],dNIR_np[a],OH_np[a]])

			return name

		# reads in the LMC data
		def LMC_ceph_reader(file,index_array,header_length,delim):
			#file(string): location of table wished to be read in
			#index_array(array): array containing the column index of the following attributes:
			#host,ID,period,V,dV,I,dI,NIR,dNIR,OH
			# if attribute does not occur in table, put -1
			#host: index of host column in table
			    # list, strings
			#ID: index of ID column in table
			    # list, strings
			#period: index of period column in table
			    # np array, float
			# the remainder of entries are the index of said variable in table
			    # all are np array's, with float values
			#header_length(int): line number of last row of header
			#name(string):user desired prefix for data
			# name_host, name_ID, etc
			#cc(string): character which splits your data

			#All data should be located in this parent folder
			base_dir = self.data_directory

			#stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

			#defining the length of the actual data of the table
			i = len(table)-header_length

			#preallocating the arrays for memory
			host_list = []
			ID_list = []
			period_np = np.zeros(i)
			mHW_np = np.zeros(i)
			dmHW_np = np.zeros(i)

			lc = 0
			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)
				if lc>header_length:
					if index_array[0] is not np.NaN:
						host_list.append(data[index_array[0]])
					else:
						host_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						ID_list.append(data[index_array[1]])
					else:
						ID_list.append(np.NaN)

					if index_array[2] is not np.NaN:
						period_np[i] = data[index_array[2]]
					else:
						period_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						mHW_np[i] = data[index_array[3]]
					else:
						mHW_np[i] = np.NaN

					if index_array[4] is not np.NaN:
						dmHW_np[i] = data[index_array[4]]
					else:
						dmHW_np[i] = np.NaN

			name = []
			for a in range(len(host_list)):
				if period_np[a] > .8:
					name.append(['LMC',ID_list[a],period_np[a],mHW_np[a],dmHW_np[a]])

			return name

		#reads in the calibrated TRGB supernova
		def TRGB_sn_reader(file,index_array,header_length,delim):

			#All data should be located in this parent folder
			base_dir = self.data_directory

			#stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

			#defining the length of the actual data of the table
			i = len(table)-header_length

			#preallocating the arrays for memory
			host_list = []
			ID_list = []
			mu_np = np.zeros(i)
			dmu_np = np.zeros(i)
			m_np = np.zeros(i)
			dm_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:

					if index_array[0] is not np.NaN:
						host_list.append(data[index_array[0]])
					else:
						host_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						ID_list.append(data[index_array[1]])
					else:
						ID_list.append(np.NaN)
					if index_array[2] is not np.NaN:
						mu_np[i] = data[index_array[2]]
					else:
						mu_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						dmu_np[i] = data[index_array[3]]
					else:
						dmu_np[i] = np.NaN
					if index_array[4] is not np.NaN:
						m_np[i] = data[index_array[4]]
					else:
						m_np[i] = np.NaN

					if index_array[5] is not np.NaN:
						dm_np[i] = data[index_array[5]]
					else:
						dm_np[i] = np.NaN

			name = []
			for a in range(len(host_list)):
				name.append([host_list[a],ID_list[a],mu_np[a],dmu_np[a],m_np[a],dm_np[a]])

			return name

		# reads in cepheid calibrated SN data
		def SHOES_sn_table_reader(file,index_array,header_length,delim):

		    #All data should be located in this parent folder
			base_dir = self.data_directory

		    #stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

		    #defining the length of the actual data of the table
			i = len(table)-header_length

		    #preallocating the arrays for memory
			host_list = []
			ID_list = []
			m_np = np.zeros(i)
			dm_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:

					if index_array[0] is not np.NaN:
						host_list.append(data[index_array[0]])
					else:
						host_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						ID_list.append(data[index_array[1]])
					else:
						ID_list.append(np.NaN)

					if index_array[2] is not np.NaN:
						m_np[i] = data[index_array[2]]
					else:
						m_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						dm_np[i] = data[index_array[3]]
					else:
						dm_np[i] = np.NaN

			name = []
			for a in range(len(host_list)):
				name.append([host_list[a],ID_list[a],m_np[a],dm_np[a]])

			return name

		#reads in the pantheon hubble flow supernova data
		def panth_sn_table_reader(file,index_array,header_length,delim):

			#All data should be located in this parent folder
			base_dir = 'data/'

			#stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

			#defining the length of the actual data of the table
			i = len(table)-header_length

			#preallocating the arrays for memory
			ID_list = []
			z_hel_np = np.zeros(i)
			z_cmb_np = np.zeros(i)
			dz_cmb_np = np.zeros(i)
			m_np = np.zeros(i)
			dm_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:

					if index_array[0] is not np.NaN:
						ID_list.append(data[index_array[0]])
					else:
						ID_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						z_hel_np[i] = data[index_array[1]]
					else:
						z_hel_np[i] = np.NaN

					if index_array[2] is not np.NaN:
						z_cmb_np[i] = data[index_array[2]]
					else:
						z_cmb_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						dz_cmb_np[i] = data[index_array[3]]
					else:
						dz_cmb_np[i] = np.NaN

					if index_array[4] is not np.NaN:
						m_np[i] = data[index_array[4]]
					else:
						m_np[i] = np.NaN

					if index_array[5] is not np.NaN:
						dm_np[i] = data[index_array[5]]
					else:
						dm_np[i] = np.NaN

			name = []
			for a in range(len(ID_list)):
				if z_cmb_np[a] > 0.0023:
					if z_cmb_np[a] < 0.15:
						 name.append([ID_list[a],z_hel_np[a],z_cmb_np[a],dz_cmb_np[a],m_np[a],dm_np[a]])
			return name

		#reads in the CSP hubble flow supernova data
		def csp_sn_table_reader(file,index_array,header_length,delim):

			base_dir = self.data_directory

			#stable method to read in tabular data
			file = open(base_dir + file)
			table = file.readlines()
			file.close()

			#defining the length of the actual data of the table
			i = len(table)-header_length

			#preallocating the arrays for memory
			ID_list = []
			filter_list = []
			z_hel_np = np.zeros(i)
			z_cmb_np = np.zeros(i)
			dz_cmb_np = np.zeros(i)
			m_np = np.zeros(i)
			dm_np = np.zeros(i)

			lc = 0

			for line in table:

				lc += 1
				i = lc - header_length - 1
				data = line.strip('\n').split(delim)

				if lc>header_length:

					if index_array[0] is not np.NaN:
						ID_list.append(data[index_array[0]])
					else:
						ID_list.append(np.NaN)

					if index_array[1] is not np.NaN:
						filter_list.append(data[index_array[1]])
					else:
						filter_list.append(np.NaN)

					if index_array[2] is not np.NaN:
						z_hel_np[i] = data[index_array[2]]
					else:
						z_hel_np[i] = np.NaN

					if index_array[3] is not np.NaN:
						z_cmb_np[i] = data[index_array[3]]
					else:
						z_cmb_np[i] = np.NaN

					if index_array[4] is not np.NaN:
						m_np[i] = data[index_array[4]]
					else:
						m_np[i] = np.NaN

					if index_array[5] is not np.NaN:
						dm_np[i] = data[index_array[5]]
					else:
						dm_np[i] = np.NaN

			name = []

			for a in range(len(ID_list)):
				if z_cmb_np[a] > 0.0023:
					if z_cmb_np[a] < 0.15:
						if filter_list[a] == 'B':
							name.append([ID_list[a],z_hel_np[a],z_cmb_np[a],dz_cmb_np[a],m_np[a],dm_np[a]])
			return name
		################################################################################################
#READ IN TABULAR DATA#
################################################################################################

		#read in the data from the tables
		#file location
		file = '/distanceladder/anchor_data.txt'
                #columns to be read in
		index_array = [0,1,2]
		#length of header
		header_length = 1
		#deliminator
		delim = ' '

		anchor_dat = anchor_reader(file,index_array,header_length,delim)

		if self.calib == 'cepheid':
			file = '/distanceladder/SHOES_cepheid_data.txt'
			index_array = [0,1,2,3,4,5,6,7,8,9]
			header_length = 1
			delim = ' '

			SHOES_ceph = SHOES_ceph_table_reader(file,index_array,header_length,delim)

			file = "/distanceladder/LMC_ceph_data.txt"
			index_array = [0,0,2,12,22]
			header_length = 1
			delim = ' '
			LMC_ceph = LMC_ceph_reader(file,index_array,header_length,delim)

			# This reads in local SN data from Riess16 table 4

			file = '/distanceladder/SHOES_sn_data.txt'
			index_array = [0,1,2,3]
			header_length = 1
			delim = ' '

			R16_local_sn = SHOES_sn_table_reader(file,index_array,header_length,delim)

		if self.calib == 'TRGB':

			#placeholder function if CSP is added later, currently not functioning
			if self.supernova_data == 'csp':
				file = "/distanceladder/bprime_TRGB_sn_data.txt"
				index_array = [0,1,2,3,4,5]
				header_length = 1
				delim = ' '

				TRGB_sn = TRGB_sn_reader(file,index_array,header_length,delim)

			#reads in local SN data from Freedman table 2, using hubble photometry
			if self.supernova_data == 'panth':
				file = "/distanceladder/b_TRGB_sn_data.txt"
				index_array = [0,1,2,3,4,5]
				header_length = 1
				delim = ' '

				TRGB_sn = TRGB_sn_reader(file,index_array,header_length,delim)
		if self.calib == 'concordant':

			# reads in the local SN data which agrees between Riess and Freedman to one sigma
			if self.supernova_data == 'panth':
				file = "/distanceladder/combined_localsn_data.txt"
				index_array = [0,1,2,3,4,5]
				header_length = 1
				delim = ' '

				TRGB_sn = TRGB_sn_reader(file,index_array,header_length,delim)

		# This block is where the hubble flow SN data will be loaded and organized
		# Pantheon
		# Steinhardt
		if self.supernova_data == 'panth':
			file = '/distanceladder/panth_sn_data.txt'
			index_array = [0,1,2,3,4,5]
			header_length = 1
			delim = ' '

			PS_sn = panth_sn_table_reader(file,index_array,header_length,delim)
		#placeholder for future itteration, currently not functioning
		if self.supernova_data == 'csp':
			file = "/distanceladder/csp_sn_data.txt"
			index_array = [0,3,1,2,6,7]
			header_length = 1
			delim = ' '

			csp_sn = csp_sn_table_reader(file,index_array,header_length,delim)

################################################################################################
#CONSTRUCT DATA DICTIONARIES#
################################################################################################

		# This takes the imported anchor data, and places it into an anchor
		#data class instance

		# NGC4258 Megamaser
		# LMC DEB
		anchor_dict = {}

		for i in range(len(anchor_dat)):
			key = anchor_dat[i][0]
			value = Anchor(anchor_dat[i][0],anchor_dat[i][1],anchor_dat[i][2])
			anchor_dict[key] = value

		# This takes the imported cepheid data, creates a dictionary where
		# the elements of the dictionary are instances
		# of the cephied class.
		# For clarity the dictionary contains elements organized by host galaxy,
		# each element containing all cephieds in that host.
		if self.calib == 'cepheid':
			hosts=[]
			for i in range(len(SHOES_ceph)):
				hosts.append(SHOES_ceph[i][0])
			hosts = np.unique(hosts)

			ceph_dict = {}

			for i in range(len(hosts)):
				temp=[]

				for j in range(len(SHOES_ceph)):
					if hosts[i] == SHOES_ceph[j][0]:
						temp.append(SHOES_ceph[j])

				k = len(temp)
				host = []
				ID = []
				per = np.zeros(k)
				V = np.zeros(k)
				dV = np.zeros(k)
				I = np.zeros(k)
				dI = np.zeros(k)
				NIR = np.zeros(k)
				dNIR = np.zeros(k)
				OH = np.zeros(k)

				key = hosts[i]

				for l in range(len(temp)):
					host.append(temp[l][0])
					ID.append(temp[l][1])
					per[l] = temp[l][2]
					V[l] = temp[l][3]
					dV[l] = temp[l][4]
					I[l] = temp[l][5]
					dI[l] =  temp[l][6]
					NIR[l] = temp[l][7]
					dNIR[l] = temp[l][8]
					OH[l] = temp[l][9]

				value = SHOES_ceph_data(host,ID,per,V,dV,I,dI,NIR,dNIR,OH)
				ceph_dict[key] = value

			host = [i[0] for i in LMC_ceph]
			idceph =[i[1] for i in LMC_ceph]
			period = [i[2] for i in LMC_ceph]
			mh = [i[3] for i in LMC_ceph]
			dmh = [i[4] for i in LMC_ceph]
			value = LMC_ceph_data(host,idceph,period,mh,dmh)
			ceph_dict['LMC'] = value
			ceph_dict['LMC'].Period = 10**np.array(ceph_dict['LMC'].Period)
			ceph_dict['LMC'].mh = np.array(ceph_dict['LMC'].mh)
			ceph_dict['LMC'].dmh = np.array(ceph_dict['LMC'].dmh)

# This takes the imported TRGB SN data, creates a diction where where the elements of the dictionary are instances
# of the local SN class
		if self.calib == 'TRGB' or self.calib == 'concordant':
			hosts=[]
			for i in range(len(TRGB_sn)):
				hosts.append(TRGB_sn[i][0])
			hosts = np.unique(hosts)

			TRGB_sn_dict = {}

			for i in range(len(hosts)):
				temp=[]

				for j in range(len(TRGB_sn)):
					if hosts[i] == TRGB_sn[j][0]:
						temp.append(TRGB_sn[j])

				k = len(temp)
				host = []
				ID = []
				mu = np.zeros(k)
				dmu = np.zeros(k)
				m = np.zeros(k)
				dm = np.zeros(k)

				key = hosts[i]

				for l in range(len(temp)):
					host.append(temp[l][0])
					ID.append(temp[l][1])
					mu[l] = temp[l][2]
					dmu[l] = temp[l][3]
					m[l] = temp[l][4]
					dm[l] = temp[l][5]

				value = TRGB_sn_data(host,ID,mu,dmu,m,dm)
				TRGB_sn_dict[key] = value

		# This takes the imported local SN data, creates a diction
		#where where the elements of the dictionary are instances
		# of the local SN class
		if self.calib == 'cepheid':
			hosts=[]
			for i in range(len(R16_local_sn)):
				hosts.append(R16_local_sn[i][0])
			hosts = np.unique(hosts)

			local_sn_dict = {}

			for i in range(len(hosts)):
				temp=[]

				for j in range(len(R16_local_sn)):
					if hosts[i] == R16_local_sn[j][0]:
						temp.append(R16_local_sn[j])

				k = len(temp)
				host = []
				ID = []
				m = np.zeros(k)
				dm = np.zeros(k)

				key = hosts[i]

				for l in range(len(temp)):
					host.append(temp[l][0])
					ID.append(temp[l][1])
					m[l] = temp[l][2]
					dm[l] = temp[l][3]

				value = Local_SN_data(host,ID,m,dm)
				local_sn_dict[key] = value

		# this block is where the hubble flow sn data will be oragnized
		#into the hubble flow sn class
		if self.supernova_data == 'panth':
			k = len(PS_sn)
			ID = []
			z_hel = np.zeros(k)
			z_cmb = np.zeros(k)
			dz = np.zeros(k)
			m = np.zeros(k)
			dm = np.zeros(k)

			for i in range(len(PS_sn)):

				ID.append(PS_sn[i][0])
				z_hel[i] = PS_sn[i][1]
				z_cmb[i] = PS_sn[i][2]
				dz[i] = PS_sn[i][3]
				m[i] = PS_sn[i][4]
				dm[i] = PS_sn[i][5]

			hubble_sn = Hubble_SN_data(ID,z_hel,z_cmb,dz,m,dm)
		#place holder function, currently not working.
		if self.supernova_data == 'csp':
			k = len(csp_sn)
			ID = []
			z_hel = np.zeros(k)
			z_cmb = np.zeros(k)
			dz = np.zeros(k)
			m = np.zeros(k)
			dm = np.zeros(k)

			for i in range(len(csp_sn)):

				ID.append(csp_sn[i][0])
				z_hel[i] = csp_sn[i][1]
				z_cmb[i] = csp_sn[i][2]
				dz[i] = csp_sn[i][3]
				m[i] = csp_sn[i][4]
				dm[i] = csp_sn[i][5]

			hubble_sn = Hubble_SN_data(ID,z_hel,z_cmb,dz,m,dm)

##############################################################################################
#CALCULATIONS#
##############################################################################################
		if self.calib == 'cepheid':
			# This block is where M_ceph will be calculated from each Anchor
			for i in self.anchor:
				# period is divided by 10 as does by Esthafio (2020)
				period = np.log10(ceph_dict[i].Period/10)
				dperiod = np.zeros(len(period))
				mh = ceph_dict[i].mh
				dmh = ceph_dict[i].dmh

				anchor_dict[i].Compute_abs_ceph_mag(period,dperiod,mh,dmh)
			num = 0
			dem = 0
			Mceph = 0
			dMceph = 0
			for i in self.anchor:
				num += anchor_dict[i].dMceph*anchor_dict[i].Mceph
				dem += anchor_dict[i].dMceph
				dMceph += anchor_dict[i].dMceph**2
			Mceph = num/dem
			dMceph = np.sqrt(dMceph)
			# This is where the slope of the cephied PL relationship is defined

			slope_array = np.zeros(len(ceph_dict))
			dslope_array = np.zeros(len(ceph_dict))
			w_slope_array = np.zeros(len(ceph_dict))
			w_dslope_array = np.zeros(len(ceph_dict))
			count = 0
			tot = 0
			# the following 3 lines are parameters for the york fit and may be
			#removed in future updates
			r = 0
			tol = 10**-15
			n = 20

			for key in ceph_dict:
				x = np.log10(ceph_dict[key].Period/10)
				xerror = np.zeros(len(x))
				y = ceph_dict[key].mh
				yerror = ceph_dict[key].dmh

				[B_0, B_1, sigma_B_0, sigma_B_1, b_save, B_0_simple, B_1_simple] = york_fit(x,y,xerror,yerror,r,tol,n)

			    # this calculates the weighted slope and weighted error of the slope for
			#each cephied host galaxy
				weight = len(x)
				tot += weight
				w_slope_array[count] = B_1*weight
				w_dslope_array[count] = np.power(sigma_B_1,2)*weight
				count+=1

			# This calculates the overall slope of the PL relationship for cephieds, and its error
			slope = sum(w_slope_array)/tot
			dslope = np.sqrt(sum(w_dslope_array)/tot)/np.sqrt(len(ceph_dict))

			for key in ceph_dict:
				period = np.log10(ceph_dict[key].Period/10)
				dperiod = np.zeros(len(period))
				mh = ceph_dict[key].mh
				dmh = ceph_dict[key].dmh

				ceph_dict[key].proto_Compute_mu(mh,dmh,period,dperiod,Mceph,dMceph,slope,dslope)

		# This block is where Msn will be calculated from SN hosts
		x = []
		y = []
		xerror = []
		yerror = []

		if self.calib == 'cepheid':

			for key in local_sn_dict:
				local_sn_dict[key].mu = ceph_dict[key].mu
				local_sn_dict[key].dmu = ceph_dict[key].dmu

				x.append(local_sn_dict[key].mu)
				y.append(float(local_sn_dict[key].m))
				xerror.append(local_sn_dict[key].dmu)
				yerror.append(float(local_sn_dict[key].dm))

			x = np.array(x)
			y = np.array(y)
			xerror = np.array(xerror)
			yerror = np.array(yerror)

			[Msn, B1, dMsn, sigma_B1] = weighted_fixed_slope(x,y,1,0,xerror,yerror)

			# This block is where DM_sn_obs will be calculated for each SN

		if self.calib == 'TRGB' or self.calib == 'concordant':

			for key in TRGB_sn_dict:
				cc = TRGB_sn_dict[key].mu
				for i in range(len(cc)):
					xtemp = TRGB_sn_dict[key].mu
					x.append(xtemp[i])
					ytemp = TRGB_sn_dict[key].m
					y.append(ytemp[i])
					xerrortemp = TRGB_sn_dict[key].dmu
					xerror.append(xerrortemp[i])
					yerrortemp = TRGB_sn_dict[key].dm
					yerror.append(yerrortemp[i])

			x = np.array(x)
			y = np.array(y)
			xerror = np.array(xerror)
			yerror = np.array(yerror)

			[Msn, B1, dMsn, sigma_B1] = weighted_fixed_slope(x,y,1,0,xerror,yerror)

		m = hubble_sn.m
		dm = hubble_sn.dm
		hubble_sn.Compute_hubble_mu(m,dm,Msn,dMsn)

		#passes data to loglkl
		self.hubble_mu = hubble_sn.mu
		self.hubble_dmu = hubble_sn.dmu
		self.hubble_z = hubble_sn.z_cmb
		self.hubble_dz = hubble_sn.dz
		self.hubble_dm = dm
		self.hubble_dMsn = dMsn

		if self.calib == 'cepheid':
			print("Cepheid Calibration")
			print("Anchors Used:", self.anchor)
			print("Abs. Ceph. Mag:", Mceph)
			print("Abs. Ceph. Mag Error:",dMceph)
			print('')
			print("P-L Slope:", slope)
			print("P-L Slope Error:", dslope)
			print('')
		if self.calib == 'TRGB':
			print("TRGB Calibration")
			print("Abs. TRGB. Mag:",0)
			print("Abs. TRGB. Mag error:",0)
			print('')
		print("Msn:",Msn)
		print("dMsn:",dMsn)

	def loglkl(self, cosmo, data):

		z = self.hubble_z
		dz = self.hubble_dz
		dMsn = self.hubble_dMsn
		dm = self.hubble_dm

		k = len(self.hubble_dmu)

		cosmo_mu = np.zeros(k)
		jac = np.zeros(k)

		# Compute the distance modulus according the to presented cosmological model
		for i in range(k):

			cosmo_mu[i] = 5*np.log10(cosmo.luminosity_distance(z[i]))+25
			jac[i] = ((1+z[i])/cosmo.Hubble(z[i]))+(cosmo.luminosity_distance(z[i])/(1+z[i]))

		# compute the difference between distance modulus from the cosmological model and the local distance ladder
		residuals = np.zeros(k)
		residuals = self.hubble_mu - cosmo_mu

		cov = np.diag(dm**2+(jac*dz)**2)

		systematic = np.ones((k,k))*dMsn**2

		cov = cov + systematic

		cov = la.cholesky(cov, lower=True, overwrite_a=True)

		residuals = la.solve_triangular(cov,residuals, lower=True, check_finite=False)

		chi2 = (residuals**2).sum()

		lkl = -0.5*chi2

		return lkl


import numpy as np
print(f'numpy version = {np.__version__}')

w = np.array([70,48, 52, 60, 52])
print(f'w => {w}')
print(f'w.size => {w.size}')
print(f'np.min(w) => {np.min(w)}')
print(f'np.max(w) => {np.max(w)}')
print(f'np.sum(w) => {np.sum(w)}')
print(f'np.mean(w) => {np.mean(w)}')
print(f'np.median(w) => {np.median(w)}')
print(f'np.sort(w) => {np.sort(w)}')

from scipy import stats

m = stats.mode(w)
print(f'stats.mode(w) => {m}')
print(f'm[0] => {m[0]}')
print(f'm[0][0] => {m[0][0]}')

print(f'm[1] => {m[1]}')
print(f'm[1][0] => {m[1][0]}')
print(f'np.sort(w)[::-1] => {np.sort(w)[::-1]}')
w.sort()
print(f'w.sort() => {w} #inplace')

a = np.reshape(np.arange(1,21), (4,5))
print(f'np.reshape(np.arange(1,21), (4,5)) => {a}')
print(f'np.mean(a) => {np.mean(a)}')
print(f'a.size => {a.size}')
print(f'a.shape => {a.shape}')
print(f'np.mean(a[0]) => {np.mean(a[0])}')
print(f'np.mean(a[:,1]) => {np.mean(a[:, 1])}')
print(f'np.mean(a, axis=0) => {np.mean(a, axis=0)}')
print(f'np.sum(a, axis=0) => {np.sum(a, axis=0)}')
print(f'np.sum(a, axis=1) => {np.sum(a, axis=1)}')
print(f'np.std(a) => {np.std(a)}')
print(f'np.var(a) => {np.var(a)}')
print(f'np.ptp(a) => {np.ptp(a)} # peak to peak  np.max(a) - np.min(a) => {np.max(a) - np.min(a)} ')


from scipy.special import comb
import math
import numpy as np
import matplotlib.pyplot as plt

def ensemble_error(n_classifier, error):
    k_start = int(math.ceil(n_classifier / 2.))
    probs = [comb(n_classifier, k) * error**k * (1-error)**(n_classifier - k)
             for k in range(k_start, n_classifier + 1)]
    return sum(probs)

ensemble_error(n_classifier=11, error=0.25)

error_range = np.arange(0.0, 1.01, 0.01)
ens_errors = [ensemble_error(n_classifier=11, error=error)
              for error in error_range]

plt.plot(error_range,
         ens_errors,
         label='Ensemble error',
         linewidth=2)

plt.plot(error_range,
         error_range,
         linestyle='--',
         label='Base error',
         linewidth=2)

plt.xlabel('Base error')
plt.ylabel('Base/Ensemble error')
plt.legend(loc='upper left')
plt.grid(alpha=0.5)
plt.show()

Chapter 07/Learning with ensembles/program1.py


#! /bin/python3

"""
Polygon.py

Created by Nicolas Fricker on 1/1/21.
Copyright © 2021 Nicolas Fricker. All rights reserved.
"""

from MMCL.HyperSpace 		import Point, Vector, Plane

import numpy as np
import sys

class Polygon(object):
	"""
	Polygon Object Class

	https://mathworld.wolfram.com/RegularPolygon.html
	"""
	def __init__(self, n, a = 1, origin = None):
		self._n = n
		self._a = a

		if not isinstance(origin, Point):
			if (isinstance(origin, np.ndarray) or isinstance(origin, list)):
				self._origin = Point(*origin)
			elif origin == None:
				self._origin = Point()
			else:
				raise ValueError("Origin must be Point Ojbect or list or None")
		else:
			self._origin = origin

		# exterior angle radians
		self._alpha = (n - 2) * np.pi

		# interior angle radians
		self._beta = np.pi - (self._alpha / n)

		# inradius
		self._r = a/2 * np.arctan(np.pi / n)

		# circumradius
		self._R = a/2 * np.arcsin(np.pi / n)

		# area
		self._A = n/4 * a**2 * np.arctan(np.pi / n)

	def __call__(self):
		return self._points()

	def _points(self):
		point = [
			Point(	self._origin.x + self._a * np.cos(self._beta * i),
					self._origin.y + self._a * np.sin(self._beta * i),
					self._origin.z
			) for i in range(self._n)
		]

		# point.append(point[0])

		return point


"""
Solving system of equations:
x[0]**3+x[1]**3-9 = 0
x[0]-0.5*x[1] = 0
cos(x[2])+x[0]-1.5 = 0
with some constraints:
150 <= x[2] <= 158
and possible non-linear constraint:
(x[2] - 150.8)**2 <= 1.5

Note:
1. Using Ax <= b constraints is also allowed
2. You can try using equality constraints (h(x)=0, Aeq x = beq) as well.
3. Required function tolerance is p.ftol, constraints tolerance is p.contol,
and hence using h(x)=0 constraints is not 100% same
to some additional f coords
"""

from openopt import SNLE
from numpy import *

# you can define f in several ways:
f = lambda x: (x[0]**3+x[1]**3-9, x[0]-0.5*x[1], cos(x[2])+x[0]-1.5)
#f = (lambda x: x[0]**3+x[1]**3-9, lambda x: x[0]-0.5*x[1], lambda x: cos(x[2])+x[0]-1.5)
# Python list, numpy.array are allowed as well:
#f = lambda x: [x[0]**3+x[1]**3-9, x[0]-0.5*x[1], cos(x[2])+x[0]-1.5]
#or f = lambda x: asfarray((x[0]**3+x[1]**3-9, x[0]-0.5*x[1], cos(x[2])+x[0]-1.5))

#optional: gradient
def df(x):
    df = zeros((3,3))
    df[0,0] = 3*x[0]**2
    df[0,1] = 3*x[1]**2
    df[1,0] = 1
    df[1,1] = -0.5
    df[2,0] = 1
    df[2,2] = -sin(x[2])
    return df

x0 = [8,15, 80]

#w/o gradient:
#p = SNLE(f, x0)

p = SNLE(f, x0, df = df, maxFunEvals = 1e5, iprint = 10, plot=1, ftol = 1e-8, contol=1e-15)

#optional: user-supplied gradient check:
#p.checkdf()

#optional: graphical output, requires matplotlib installed
#p.plot = 1

#set some constraints
p.lb, p.ub = [-inf]*3, [inf]*3
p.lb[2], p.ub[2] = 145, 150

# you could try also comment/uncomment nonlinear constraints:
p.c = lambda x: (x[2] - 146)**2-1.5
# optional: gradient
p.dc = lambda x: asfarray((0, 0, 2*(x[2]-146)))
# also you could set it via p=SNLE(f, x0, ..., c = c, dc = dc)

#optional: user-supplied dc check:
#p.checkdc()

#r = p.solve('nssolve', debug=0, maxIter=1e9)
# using converter to nlp, try to minimize sum(f_i(x)^2):
r = p.solve('nlp:ralg', xlabel='iter', iprint=10, plot=1)

print('solution: %s' % r.xf)
print('max residual: %e' % r.ff)


# -*- coding: utf-8 -*-
from random import choice
from numpy import array,dot,random

unit_step = lambda x:0 if x < 0 else 1

def train(training_data,errors,eta,n,weight):
    for i in range(n):
        x,expect = choice(training_data)
        output = dot(x,weight)
        error = expect - unit_step(output)
        errors.append(error)
        weight += eta * error * x
    return weight,errors

def show(training_data,weight):
    for x, _ in training_data:
      results = dot(x,weight)
      print("{}:{} -> {}".format(x[:2],results,unit_step(results)))

def perceptron(training_data):
    # constant variable declare
    eta = 0.1
    n = 100
    weight = random.rand(3)
    errors = []
    weight ,errors = train(training_data,errors,eta,n,weight)
    show(training_data,weight)

if __name__ == '__main__':
    training_data_or = [
        (array([0,0,1]), 0),
        (array([0,1,1]), 1),
        (array([1,0,1]), 1),
        (array([1,1,1]), 1),
    ]

    training_data_and = [
        (array([0,0,1]), 0),
        (array([0,1,1]), 0),
        (array([1,0,1]), 0),
        (array([1,1,1]), 1),
    ]

    training_data_not = [
        (array([0,0,1]), 1),
        (array([0,1,1]), 0),
        (array([1,0,1]), 0),
        (array([1,1,1]), 1),
    ]
    # data_list = [training_data_not,training_data_and,training_data_or]
    data_list = [training_data_and,training_data_or]
    map(perceptron,data_list)


import numpy as np

def _createWeights(arr):
    '''
        Given a list of edges in a graph, returns the labels of distinct nodes
        and a dictionary of their weights.
    '''
    labels, counts = np.unique(arr[:, 0], return_counts=True)   # Finds the unique entries in first column, returns their values
                                                                # and their counts to calculate weights
    weight_dict = dict()
    for index, label in enumerate(labels):                      # Creates a dictionary that holds the normalization factor,
        weight_dict[label] = 1 / counts[index]                  # 1/(number of outgoing links) for each node

    return labels, weight_dict

def _createTransitionMatrix(arr, labels, weight_dict):
    '''
        Create a transition matrix based on the transitions in arr of shape (n, 2).
        Labels is the list of distinct nodes in the original graph.
        Weight_dict is a dictionary where the weight of a link for each node in labels are kept.
    '''
    transitionMatrix = np.zeros((labels.size, labels.size))

    for i, label in enumerate(labels):
        links_from_label = arr[np.nonzero(label == arr[:, 0]), 1][0]
    #    if links_from_label.shape == (0,):                              # For dangling nodes, nodes that have no outgoing links
    #        weight_dict[label] = 1 / labels.size                        # Not necessary while using with a damping factor
    #        links_to_label = labels
        transitionMatrix[np.searchsorted(labels, links_from_label), i] = weight_dict[label]

    return transitionMatrix

def _checkStochastic(matrix, eps=1e-10, verbose=False):
    '''
        Checks if a given matrix is stochastic, within an error of eps.
        Returns the number of columns that violates the condition.
        If verbose is set to True, prints the results.
    '''
    if verbose:
        print(f"Sums of columns that do not add to 1 in a reasonable error margin {eps} will be shown.")
    nonstochastic_columns = 0
    for i in matrix.sum(0):
        if not (i - 1 < eps):
            nonstochastic_columns += 1
            if verbose: print(i)
    if verbose:
        print(f"Calculation finished, average value of sums of all columns is {np.mean(matrix.sum(0))}.")
    return nonstochastic_columns

def _checkSparsity(matrix, verbose=False):
    '''
        Checks the sparsity of a given matrix. Returns the sparcity rate.
        Set verbose to true to print the results.
    '''
    zeros = np.count_nonzero(matrix==0)
    elements = matrix.size
    sparse_rate = np.divide(zeros,elements)
    if verbose:
        print(f"There are {elements} elements in total, {zeros} of them are zero.")
        print(f"Sparsity rate of this matrix is {sparse_rate*100}%")
    return sparse_rate

def addDamping(transitionMatrix, labels, p = 0.15):
    '''
        Adds damping vector to the given matrix. Damping vector simulates
        the random surfing between two nodes, even if they are not linked.
    '''
    if(p < 0 or p > 1):
        throw ValueError("Please try again with a damping factor in interval [0,1].")
    rankMatrix = (1 - p) * transitionMatrix + p * ((1/labels.size) * np.ones((labels.size,labels.size)))
    return rankMatrix

def solveRank(rankMatrix, tol=1e-7, verbose=False):
    '''
    Given the stochastic transition matrix of a graph, returns the PageRank vector of the graph,
    using the power method until the average distance between two vectors are less than 'tol'.

    Set verbose to True in order the see the iteration number and the difference of last two vectors.
    '''
    v0 = np.ones(rankMatrix.shape[0]) / rankMatrix.shape[0]

    iteration_counter = 1
    while True:
        v = np.dot(rankMatrix, v0)
        v = v / np.linalg.norm(v)
        if (np.mean(np.abs(np.subtract(v,v0))) < 2 * tol):
            break
        iteration_counter += 1
        v0 = v
        if verbose:
            print(f"Iteration: {iteration_counter}, Error: {np.mean(np.abs(np.subtract(v,v0)))}")

    if verbose:
        print(f"Appropriate vector found in {iteration_counter} iterations, final difference between two iteration result vectors was less than {tol}.")
    return v

def calculateRankVector(data, damping=0):
    '''
        Calculates the PageRank ranking vector on a (n, 2) array, which
        represents the edges of a directed graphs. Each row represents an edge
        as a tuple (node_from, node_to).
        Set damping to a positive value < 1 to apply damping on the final matrix.
    '''
    labels, weights = createWeights(data)
    rankMatrix = createTransitionMatrix(data, labels, weights)
    if damping = 0:
        rankMatrix = addDamping(rankMatrix, labels, damping)
    resultVector = solveRank(rankMatrix)
    return resultVector

def rankItemsByRankings(labels, final):
    '''
        Ranks the items in 'labels' using the values in the 'final' vector, in descending order.
    '''
    return labels[final.argsort()][::-1]


# -*- coding: utf-8 -*-
"""
Created on Sun Jun 04 00:01:19 2017

Implement a neuron that learns from two dimensional input data using Oja's Hebb rule

@author: Niranjan Thakurdesai
"""

import numpy as np
import matplotlib.pyplot as plt
from numpy import linalg as LA
import pickle
with open('../data/c10p1.pickle', 'rb') as f:
    data = pickle.load(f)
u_mat = data['c10p1']

# Zero-mean centering
# wi+1=wi+Δtη(vu−αv^2w)
mu = np.mean(u_mat, axis=0)
num_points = u_mat.shape[0]
u_centered = u_mat - np.tile(mu, (num_points, 1))

plt.scatter(u_centered[:,0], u_centered[:,1])
plt.show()

# Implement learning rule
n = 1   # n = 1/time constant
alpha = 1
timestep = 0.01
w0 = np.random.rand(2)    # initial weight vector
max_iters = 100000
w = w0
iters = 0
for i in range(max_iters):
    u = u_centered[np.remainder(i,num_points),:]    # current input vector
    v = np.dot(u,w)    # current output
    w = w + timestep*n*(v*u - alpha*v*v*w)    # update

C = np.dot(u_centered.T, u_centered)/num_points   # input covariance matrix
ev, e = LA.eig(C)
"""
The correlation matrix has only one principal eigenvector, but there are
two vectors of length 1/√α that are parallel to this eigenvector.
w can converge to either of these two vectors.
"""

"""
What happens when the data is not zero-mean centered before the learning process?
"""
# Add a constant offset to input data
offset = [5,10]
u_mat1 = u_mat + np.tile(offset, (num_points, 1))
mu1 = mu + offset
w1 = w0
iters = 0
for i in range(max_iters):
    u = u_mat1[np.remainder(i,num_points),:]    # current input vector
    v = np.dot(u,w1)    # current output
    w1 = w1 + timestep*n*(v*u - alpha*v*v*w1)    # update
"""
The two vectors that w converges to in different runs of the algorithm are parallel to the vector that points roughly towards the mean of the data.
"""

"""
What happens when the pure Hebb rule is used instead of Oja's rule?
"""
w2 = w0
iters = 0
for i in range(max_iters):
    u = u_centered[np.remainder(i,num_points),:]    # current input vector
    v = np.dot(u,w2)    # current output
    w2 = w2 + timestep*n*(v*u)    # update
"""
The vectors found by the learning rule have the same direction as those found by Oja's rule, but the length grows without bound as a function of the number of iterations.
"""


from sympy import Symbol, pprint, summation

def add_series(expr, num, values):
    """Print addition of series given by user, if values of the terms provided show the result as well"""
    n = Symbol("n")
    series = summation(expr, (n, 1, num))

    if values:
        listo = []

        values = values.split(",")
        for i in values:
            listo.append(tuple(i.split("=")))

        listo = dict(listo)
        series_value = series.subs(listo)
        print("Addition of the series: {}, result: {}".format(series, series_value))

    else:
        pprint(series)

if __name__ == '__main__':
    # expr = a+(n-1)*d
    # num = 3
    # values = a=5,d=4
    expr = input('Enter the nth term of series: ')
    num = int(input('Enter the number of terms you want in the series: '))
    values = input("Enter the variable values(a=3,b=4... etc, leave blank if not desired):")
    add_series(expr, num, values)


import numpy as np

class KMeans():
    def __init__(self, n_clusters):
        """
        This class implements the traditional KMeans algorithm with hard assignments:

        https://en.wikipedia.org/wiki/K-means_clustering

        The KMeans algorithm has two steps:

        1. Update assignments
        2. Update the means

        While you only have to implement the fit and predict functions to pass the
        test cases, we recommend that you use an update_assignments function and an
        update_means function internally for the class.

        Use only numpy to implement this algorithm.

        Args:
            n_clusters (int): Number of clusters to cluster the given data into.

        """
        self.n_clusters = n_clusters
        self.means = None

    def fit(self, features):
        """
        Fit KMeans to the given data using `self.n_clusters` number of clusters.
        Features can have greater than 2 dimensions.

        Args:
            features (np.ndarray): array containing inputs of size
                (n_samples, n_features).
        Returns:
            None (saves model - means - internally)
        """
        # initialize means
        self.means = features[np.random.randint(features.shape[0], size=self.n_clusters), :]
        old_means = np.zeros((self.n_clusters, features.shape[1]))
        labels = np.zeros(features.shape[0])

        while not np.array_equal(self.means, old_means):
            old_means = self.means.copy()
            self.update_assignments(features, labels)
            self.update_means(features, labels)

    def update_assignments(self, features, labels):
        for row in range(features.shape[0]):
            dists = np.linalg.norm(self.means - features[row,:], axis=1)
            labels[row] = np.argmin(dists)

    def update_means(self, features, labels):
        for cluster in range(self.means.shape[0]):
            bool_ind = labels == cluster
            self.means[cluster] = np.mean(features[bool_ind,:], axis=0)

    def predict(self, features):
        """
        Given features, an np.ndarray of size (n_samples, n_features), predict cluster
        membership labels.

        Args:
            features (np.ndarray): array containing inputs of size
                (n_samples, n_features).
        Returns:
            predictions (np.ndarray): predicted cluster membership for each features,
                of size (n_samples,). Each element of the array is the index of the
                cluster the sample belongs to.
        """
        predictions = np.zeros(features.shape[0])
        self.update_assignments(features, predictions)
        return predictions


"""
hilbert matrix using numpy, contains:
    - hilb(n, m): generate Hilbert Matrix of size (n, m)

"""

import numpy as np
from math import factorial

def binomial(n, k):
    """ binomial(n, k): Generate Binomial Coefficient for nCk """
    if k < 0 or k > n:
        return 0
    if k == n or k == 0:
        return 1
    return factorial(n) // (factorial(k) * factorial(n - k))

def hilb(n, m=0):
    """
    hilb Hilbert Matrix.
    :param n: number of rows
    :param m: number of cols

    hilb(n,m) is the n-by-m matrix with elements 1/(i + j - 1)
    """
    if n < 1 or m < 0:
        raise ValueError("Matrix size must be one or greater")
    elif n == 1 and (m == 0 or m == 1):
        return np.array([[1]])
    elif m == 0:
        m = n
    # np.arange(0,m) is [0, 1, 2, ..., m - 1]
    # np.arange(0,m)[:,np.newaxis] is [[0] [1]]
    # [1, 2, 3] + [[1] [2] [3]] = [[2, 3, 4] [3, 4, 5], [4, 5, 6]]
    v = np.arange(1, n + 1) + np.arange(0, m)[:, np.newaxis]
    return 1. / v


import numpy

def normal_equation(x_data, y_data):

    x_transpose = numpy.transpose(x_data)
    term1 = numpy.dot(x_transpose, y_data)
    term2 = numpy.dot(x_transpose, x_data)
    term3 = numpy.linalg.inv(term2)
    theta = numpy.dot(term3, term1)
    return theta


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

x = np.linspace(0, 2*math.pi, 100)
y = [math.sin(i) for i in x]
plt.plot(x, y)
plt.savefig("./sin.png")
plt.cla()

y = [math.cos(i) for i in x]
plt.plot(x, y)
plt.savefig("./cos.png")
plt.cla()

x = np.linspace(-1, 1, 100)
y = [i**2 for i in x]
plt.plot(x, y)
plt.savefig("./square.png")
plt.cla()

y = [math.exp(i) for i in x]
plt.plot(x, y)
plt.savefig("./exp.png")


import math
import numpy as np

#bayes classifier class
class GaussianBayesClassifier:
    def __init__(self):
        return
    def calc_mean(self, data):
        mean_value = sum(data) / float(len(data))
        return mean_value
    def calc_standart_deviation(self, data):
        mean = self.calc_mean(data)
        variance = sum([pow(ith_value-mean,2) for ith_value in data])/float(len(data)-1)
        return math.sqrt(variance)
    def calc_standart_deviation(self, data, mean):
        variance = sum([pow(ith_value-mean,2) for ith_value in data])/float(len(data)-1)
        return math.sqrt(variance)
    def calculateProbability(self, x, mean, standard_deviation):
		#calculate probabilty of given x value using Gaussian distribution
        exponential_part = math.exp(-(math.pow(x-mean,2)/(2*math.pow(standard_deviation,2))))
        factor_part = (1 / (math.sqrt(2*math.pi) * standard_deviation));
        return factor_part * exponential_part
    def calc_2d_covariance_matrix(self, data1, data2, mean1, mean2, variance1, variance2):
        covariance_matrix = np.zeros((2, 2))
        covariance_matrix[0][0] = variance1
        covariance_matrix[1][1] = variance2
        covariance_matrix[0][1] = sum([(ith_value1-mean1)*(ith_value2-mean2)
          for ith_value1, ith_value2  in zip(data1, data2)])/float(len(data1)-1)
        covariance_matrix[1][0] = covariance_matrix[0][1]
        return covariance_matrix
    def calc_covariance_matrix(self, data_vector, mean_vactor, stddev_vector):
        covariance_matrix = np.zeros((mean_vactor.size(), mean_vactor.size() ))
        for i in range(0, mean_vector.size()):
            for j in range(0, mean_vector.size()):
                if(i==j):
                    covariance_matrix[i][j] =  stddev_vector[0][j]**2;
                elif( covariance_matrix[i][j] == 0):
                    covariance_matrix[i][j] = sum([(ith_value1-mean1)*(ith_value2-mean2)
                     for ith_value1, ith_value2  in zip(data_vector[0][i], data_vector[0][j])])/float(len(data1)-1)
                    #it is diagonal matrix
                    covariance_matrix[j][i] = covariance_matrix[i][j]
    def getClassDistribution(self, class_features):
        params = {}
        means = []
        sigmas = []
        #for each feature calculate the mean and standard deviation
        # (in our case features are x, and y coordinate values)
        for feature in class_features:
            mean = self.calc_mean(feature)
            sigma = self.calc_standart_deviation(feature, mean)
            means.append(mean)
            sigmas.append(sigma)
        params[0] = means
        params[1] = sigmas
        return params
    #input_data represents input features
    def calculateClassProbabilities(self, input_data, class_distributions):
        probabilities = {}
        for class_key, classDistribution in class_distributions.iteritems():
            probabilities[class_key] = 1
            #According to bayesian classification multiplication of conditional
            #probabilites (likelihoods) of independent features yields a posteriori probability of that class
            for i in range(len(classDistribution)):
                mean = classDistribution[i]
                x = input_data[i]
                # multiply likelihoods of features
                probabilities[class_key] *= self.calculateProbability(x, mean, stdev)
        return probabilities
    #def calculateDecisionBoundary(class_distributions):


import numpy as np

def waysToArrange(N, K, k):
    C = np.zeros((N + 1, N + 1))
    for i in range(N + 1):
        for j in range(i + 1):
            if (j == 0 or j == i):
                C[i][j] = 1
            else:
                C[i][j] = (C[i - 1][j - 1] +
                           C[i - 1][j])
    dp = np.zeros((K + 1))
    count = 0
    dp[0] = 1
    for i in range(K):
        dp[i + 1] = (dp[i] * C[count + k[i] - 1][k[i] - 1])
        count += k[i]
    return dp[K]

if __name__ == "__main__":
    N = 4
    k = [2, 2]

    K = len(k)
    print(int(waysToArrange(N, K, k)))


import numpy as np

class DHs:
    def __init__(self, **kwargs):
        self.alpha = kwargs['alpha']
        self.theta = kwargs['theta']
        self.d = kwargs['d']
        self.a = kwargs['a']

    def DH(self):

        wholeDT = []

        #  T = np.where((T < 0) & (T > -0.01) , 0, T) <= it is not inportant
        for i in range(0,len(self.theta)): # you can use others it doesnt matter
            aco = 0 if ((np.cos(np.deg2rad(self.alpha[i] % 360)) < 1e-10) & (np.cos(np.deg2rad(self.alpha[i] % 360)) > -1e-10)) else np.cos(np.deg2rad(self.alpha[i] % 360))
            asi = 0 if ((np.sin(np.deg2rad(self.alpha[i] % 360)) < 1e-10) & (np.sin(np.deg2rad(self.alpha[i] % 360)) > -1e-10)) else np.sin(np.deg2rad(self.alpha[i] % 360))
            tco = 0 if ((np.cos(np.deg2rad(self.theta[i] % 360)) < 1e-10) & (np.cos(np.deg2rad(self.theta[i] % 360)) > -1e-10)) else np.cos(np.deg2rad(self.theta[i] % 360))
            tsi = 0 if ((np.sin(np.deg2rad(self.theta[i] % 360)) < 1e-10) & (np.sin(np.deg2rad(self.theta[i] % 360)) > -1e-10)) else np.sin(np.deg2rad(self.theta[i] % 360))

            DT = [
                [tco, -tsi*aco, tsi*asi, self.a[i]*tco],
                [tsi, tco*aco, -tco*asi, self.a[i]*tsi],
                [0, asi, aco, self.d[i]],
                [0, 0, 0, 1]
                ]

            wholeDT.append(DT)
        return np.reshape(wholeDT, [len(self.theta), 4, 4]), len(self.theta)

    def multDT(self):

        multipliedDTs = np.matmul(
            self.DH()[0][self.DH()[1] - 2],
            self.DH()[0][self.DH()[1] - 1]
            )

        if self.DH()[1] > 2:
            for i in range(self.DH()[1] - 3, -1, -1):
                multipliedDTs = np.matmul(
                    self.DH()[0][i],
                    multipliedDTs
                    )
        return multipliedDTs

    def pointInPlane(self, nokta, **axis):
        if axis['axis'] == 'global':
            return np.matmul(self.multDT(), np.reshape(np.append(nokta, [1]), [4, 1]))
        else:
            return np.matmul(np.linalg.inv(self.multDT()), np.reshape(np.append(nokta, [1]), [4, 1]))


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

def set_initial_state(N=1000, I_0=1, R_0=0, beta=0.01, gamma=0.01, t=np.linspace(0, 200, 200)):
    S_0 = N - I_0 - R_0
    return N, S_0, I_0, R_0, beta, gamma, t

def solve_diffential_equations(y, beta, gamma, t, N):
    S, I, R = y
    dSdt = - beta * S * I / N
    dIdt = beta * S * I/ N - gamma * I
    dRdt = gamma * I
    return dSdt, dIdt, dRdt

def main():
    N, S_0, I_0, R_0, beta, gamma, t = set_initial_state()
    y_0 = S_0, I_0, R_0
    result = odeint(solve_diffential_equations, y_0, t, args=(beta, gamma, N))
    S, I, R = result.T

    fig = plt.figure(facecolor="w")
    ax = fig.add_subplot(111, axisbelow=True)
    ax.plot(t, S, 'b', alpha=0.5, lw=2, label='Susceptible')
    ax.plot(t, I, 'r', alpha=0.5, lw=2, label='Infected')
    ax.plot(t, R, 'g', alpha=0.5, lw=2, label='Recovered')
    ax.set_xlabel("time")
    ax.set_ylabel("Users")
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend(loc='best')
    plt.show()

if __name__ == "__main__":
    main()


import pylab
import numpy
import string

def chi_sq(fit,y,err):
    '''
    Returns chi_sq estimate for the fit.
    '''
    X_sq=numpy.sum(((y-fit)/err)**2)
    nu=len(y)-2.0
    sigma=numpy.sum(y-fit)/nu
    return X_sq

def fit(x,y,err,which):
    '''
    Performs fit to input data.  Computes function (gauss or lorentz) with
    a spread of function parameters, then returns the parameters for the
    function with the smalles chi squared value.
    Returns the fit to optimal parameters.
    '''
    max_index=numpy.argmax(y)
    x_0=x[numpy.argmax(y)]
    chi_all=[]
    alpha_all=[]
    x_0_all=[]
    if which=='gaussian':
        print 'Gaussian'
        for x_0 in numpy.linspace(x[max_index-4],x[max_index+4],40): #iterate from four points below and four points above the peak in 40 steps.
            for alpha in numpy.linspace(1,30,40):
                fit=(1.0/alpha)*numpy.sqrt(numpy.log(2)/numpy.pi)*numpy.exp((-1*numpy.log(2)*(x-x_0)**2)/alpha**2)
                chi_all.append(chi_sq(fit,y,err))
                alpha_all.append(alpha)
                x_0_all.append(x_0)
        alpha=alpha_all[numpy.argmin(chi_all)]   #find index of minimum in array
        x_0=x_0_all[numpy.argmin(chi_all)]       #find index of minimum in array
        print 'alpha,x_0=',alpha,x_0
        fit=(1.0/alpha)*numpy.sqrt(numpy.log(2)/numpy.pi)*numpy.exp((-1*numpy.log(2)*(x-x_0)**2)/alpha**2)
    if which=='lorentzian':
        print 'Lorentzian'
        for x_0 in numpy.linspace(x[max_index-4],x[max_index+4],40): #iterate from four points below and four points above the peak in 40 steps.
            for alpha in numpy.linspace(1,30,40):
                fit=(1.0/numpy.pi)*(alpha)/((x-x_0)**2+alpha**2)
                chi_all.append(chi_sq(fit,y,err))
                alpha_all.append(alpha)
                x_0_all.append(x_0)
        alpha=alpha_all[numpy.argmin(chi_all)]   #find index of minimum in array
        x_0=x_0_all[numpy.argmin(chi_all)]       #find index of minimum in array
        print 'alpha,x_0=',alpha,x_0
        fit=(1.0/numpy.pi)*(alpha)/((x-x_0)**2+alpha**2)  #refit with parameters of minimum chi_sq
    fit=numpy.array(fit)
    nu=len(y)-2.0
    sigma=float(numpy.sum(y-fit))/nu  #estimate error
    print 'Error=',sigma
    print
    return fit

###############
##Begin main
###############
freq=[]
strn=[]
err=[]

fin=open('ps3.dat')  #open input data
for line in fin.readlines(): #iterate of lines in the file
    line=line.split() #split line at whitespaces
    freq.append(float(line[0])) #split freq,strength,and error to different lists
    strn.append(float(line[1]))
    err.append(float(line[2]))

###convert lists to arrays
freq=numpy.array(freq)
strn=numpy.array(strn)
err=numpy.array(err)

#calls fitting function in the plotting call
pylab.ioff()
pylab.errorbar(freq,strn,yerr=err,fmt='bo-',label='Data')
pylab.plot(freq,fit(freq,strn,err,'lorentzian'),'ro-',label='Lorentzian')
pylab.plot(freq,fit(freq,strn,err,'gaussian'),'go-',label='Gaussian')
pylab.legend(numpoints=1,shadow=True)
pylab.xlabel('Frequency')
pylab.ylabel('Line Strength')
#pylab.show()
pylab.savefig('fit.pdf')
pylab.close()


'''
    Topic: Gradient Descent (Practical) using only NumPy and Python.
    Author: Yash Sharma (github.com/Yashs744)
'''

# Library
import numpy as np

# Class GradientDescent
class GradientDescent:
    def __init__(self, lr):
        '''
            Initialize 'm' and 'c' (from y = mx + c) and Learning Rate.

            Arguments:
                lr {float} -- Learning Rate
        '''

        self.m = 0
        self.c = 0

        self.lr = lr

    def predict(self, x):
        '''
            Arguments:
                x {float | int} -- Value of independent variable to produce output.

            Returns:
                float -- Value of yHat i.e value of 'mx + c'
        '''

        return (x * self.m + self.c)

    def cost(self, x, y):
        '''
            Calculate Cost i.e error between real value of y and yHat.
            Error Function: Sum of Squares.

            Arguments:
                x {float | int} -- Independent Variable
                y {float | int} -- Dependent Variable

            Returns:
                float -- Cost (or error)
        '''

        cost = 0

        for i in range(len(x)):
            cost += ((y[i] - self.predict(x[i])) ** 2)

        return (cost / float(len(x)))

    def generate_batches(self, x, y, batch_size):
        '''
            Generate Batches of Data to fit model.

            Arguments:
                x {float | int} -- Values (array) of Independent Variable
                y {float | int} -- Values (array) of Dependent Variable
                batch_size {int} -- Size of each batch on Data.
        '''

        num_points = len(x)

        idx = np.random.permutation(num_points)
        batches = range(0, num_points - batch_size + 1, batch_size)

        for batch in batches:
            X_mini = x[idx[batch:batch + batch_size]]
            y_mini = y[idx[batch:batch + batch_size]]

            yield (X_mini, y_mini)

    def derivative(self, x, y):
        '''
            Calculate Derivative of Cost (or Error) w.r.t m and c variable.

            m_derivative = -2x(y - yhat)
            c_derivate = -2(y - yhat)

            Arguments:
                x {float | int} -- Value of Independent Variable
                y {float | int} -- Value of Dependent Variable

            Returns:
                Tuple -- Tuple of Derivatives.
        '''

        y_hat = self.predict(x)

        dJ_m = -2 * (x * (y - y_hat))
        dJ_c = -2 * (y - y_hat)

        return (dJ_m, dJ_c)

    def update_parameters(self, x, y):
        '''
            Update the Parameters (i.r Variable m and c).
            Parameter are updates as follow:
                m = m - ((m_derivative / N) * lr)
                c = c - ((c_derivative / N) * lr)

            Arguments:
                x {float | int} -- Values (array) of Independent Variable
                y {float | int} -- Values (array) of Dependent Variable
        '''

        m_derv = 0
        c_derv = 0

        for i in range(len(x)):
            dJ = self.derivative(x[i], y[i])

            m_derv += dJ[0]
            c_derv += dJ[1]

        self.m -= ((m_derv / float(len(x))) * self.lr)
        self.c -= ((c_derv / float(len(x))) * self.lr)

    def fit(self, X, y, epochs = 5, batch_size = 10):
        '''
            Fit the data.

            Arguments:
                X {float | int} -- Values (array) of Independent Variable
                y {float | int} -- Values (array) of Dependent Variable

            Keyword Arguments:
                epochs {int} -- Number of Training Epochs (default: {5})
                batch_size {int} -- Size of the Batch (default: {10})
        '''

        loss = 0

        for e in range(epochs):
            loss = 0
            for (batch_x, batch_y) in self.generate_batches(X, y, batch_size):
                loss = self.cost(batch_x, batch_y)

                self.update_parameters(batch_x, batch_y)

            print (f"Epoch: {e+1} - Loss: {loss}")

if __name__ == "__main__":
    X = np.linspace(start = 1, stop = 100 , num = 100)
    y = 12 * X

    grad = GradientDescent(lr = 0.0001)
    grad.fit(X, y, epochs = 10)

    print (f"Weight: {grad.m} & Bias: {grad.c}")


# The NumPy module also comes with a number of built-in routines for linear algebra calculations.
# These can be found in the sub-module linalg.

# linalg.det
# The linalg.det tool computes the determinant of an array.
# print numpy.linalg.det([[1 , 2], [2, 1]])       #Output : -3.0

# linalg.eig
# The linalg.eig computes the eigenvalues and right eigenvectors of a square array.
# vals, vecs = numpy.linalg.eig([[1 , 2], [2, 1]])
# print vals                                      #Output : [ 3. -1.]
# print vecs                                      #Output : [[ 0.70710678 -0.70710678]
#                                                 #          [ 0.70710678  0.70710678]]

# linalg.inv
# The linalg.inv tool computes the (multiplicative) inverse of a matrix.
# print numpy.linalg.inv([[1 , 2], [2, 1]])       #Output : [[-0.33333333  0.66666667]
#                                                 #          [ 0.66666667 -0.33333333]]

# Task :
# You are given a square matrix with dimensions X. Your task is to find the determinant.
# Note: Round the answer to 2 places after the decimal.

import numpy as np

N = int(input())
l = []
for i in range(N):
    l.append(list(map(float, input().split())))
l = np.array(l).reshape(N, N)
print(round(np.linalg.det(l), 2), end = '')

# Sample Input
# 2
# 1.1 1.1
# 1.1 1.1

# Sample Output
# 0.0


import numpy as np

def function(x):
    return np.tanh(x)

def derivative(x):
    return 1.0 - np.tanh(x) ** 2


import os
import sys
import json
import re
import random
import copy
import math
import collections

import numpy as np
import scipy.stats
import scipy.spatial

def perform_t_test(points1, points2, two_sided=True):
    n1 = float(len(points1))
    n2 = float(len(points2))
    x1_bar = float(np.mean(points1))
    x2_bar = float(np.mean(points2))
    s1_2 = float(np.var(points1))
    s2_2 = float(np.var(points2))
    s = math.sqrt(s1_2 / n1 + s2_2 / n2)
    t = (x1_bar - x2_bar) / s
    df_numerator = pow(s1_2 / n1 + s2_2 / n2, 2.0)
    df_denominator = pow(s1_2 / n1, 2.0) / (n1 - 1.0) + pow(s2_2 / n2, 2.0) / (n2 - 1.0)
    df = df_numerator / df_denominator

    probability = scipy.stats.t.cdf(t, df)
    if two_sided:
        if probability > 0.5:
            p_value = 1.0 - probability
        else:
            p_value = probability
        probability = 1.0 - 2 * p_value

    p_value = 1.0 - probability
    return p_value

def calculate_confidence_interval(points, confidence_threshold=0.95):
    n = float(len(points))
    v = n - 1.0
    x_bar = np.mean(points)
    s = math.sqrt(np.var(points))
    A = scipy.stats.t.ppf(confidence_threshold, v)
    lower_bound = x_bar - A * s / math.sqrt(n)
    upper_bound = x_bar + A * s / math.sqrt(n)
    return (lower_bound, upper_bound)


from sympy import sympify, lambdify, integrate
from sympy.abc import p, q
from scipy.integrate import dblquad

class Phi:
    def __init__(self, name, phi_pq=None, phi_p=None, phi_q=None, eps=1e-7):
        self.name = name
        self.phi_pq_S = phi_pq
        self.phi_p_S = phi_p
        self.phi_q_S = phi_q
        self.eps = eps
        self.__gen_phi()
        if self.__check() is False:
            raise Exception('input phi does not integrate to one')

    def __gen_phi(self):
        if self.phi_p_S is not None and self.phi_q_S is not None:
            self.phi_p_S = sympify(self.phi_p_S)
            self.phi_q_S = sympify(self.phi_q_S)
            self.phi_pq_S = self.phi_p_S * self.phi_q_S
            self.phi_qcp_S = self.phi_q_S
        elif self.phi_pq_S is not None:
            self.phi_pq_S = sympify(self.phi_pq_S)
            self.phi_p_S = integrate(self.phi_pq_S, (q, 0 , 1))
            self.phi_q_S = integrate(self.phi_pq_S, (p, 0 , 1))
            self.phi_qcp_S = self.phi_pq_S / self.phi_p_S
        else:
            raise Exception('please provide both phi_p and phi_q, or the joint pdf phi_pq.')

        self.phi_p_N = lambdify(p, self.phi_p_S)
        self.phi_q_N = lambdify(q, self.phi_q_S)
        self.phi_pq_N = lambdify((q, p), self.phi_pq_S)
        self.phi_qcp_N = lambdify((q, p), self.phi_qcp_S)

    def __check(self):
        phi_tot = dblquad(self.phi_pq_N, 0, 1, lambda p: 0, lambda p: 1)[0]
        if abs(phi_tot - 1) < self.eps:
            phi_one = True
        else:
            phi_one = False
        return phi_one


import numpy as np

def qubic_spline_coeff(x_nodes, y_nodes):
    """Here underscored variables are related to the matrix equation,
    whereas normal ones stand for the spline coefficients
    """
    polynomials_num = len(x_nodes) - 1
    coeffs = np.zeros((polynomials_num, 3))
    hs = (x_nodes - np.roll(x_nodes, 1))[1:]
    ys = (y_nodes - np.roll(y_nodes, 1))[1:]
    # Build A
    upper_diag = hs.copy()
    upper_diag[0] = 0
    lower_diag = hs.copy()
    lower_diag[-1] = 0
    diag = np.r_[[1], 2 * (upper_diag[1:] + lower_diag[:-1]), [1]]
    A_ = np.diag(upper_diag, 1) + np.diag(diag) + np.diag(lower_diag, -1)
    # Build b
    b_ = np.r_[[0], 3 / hs[1:] * ys[1:] - 3 / hs[:-1] * ys[:-1], [0]]
    c = np.dot(np.linalg.inv(A_), b_)
    a = y_nodes[:-1]
    b = 1 / hs * ys - hs / 3 * (2 * c[:-1] + c[1:])
    d = 1 / (3 * hs) * (c[1:] - c[:-1])
    return np.c_[a, b, c[:-1], d]

def qubic_spline(x, x_nodes, qs_coeff):
    for i in range(len(x_nodes)):
        if x < x_nodes[i] or i == len(x_nodes) - 1:
            dx = x - x_nodes[i - 1]
            return np.dot(qs_coeff[i - 1], np.array([1., dx, dx**2, dx**3]))
    return 0

def make_qubic_spline(x_nodes, y_nodes):
    coeff = qubic_spline_coeff(x_nodes, y_nodes)
    spline_vectorized = np.vectorize(qubic_spline, excluded=set((1, 2)))
    def _qubic_spline(x):
        return spline_vectorized(x, x_nodes, coeff)
    return _qubic_spline


import math

import numpy as np
from sympy.ntheory.primetest import (
    is_square
)
from sympy import (
    Matrix
)

from ..utils import (
    get_letters_alphabetically
)

class HillError(Exception):
    pass

def transform(
        text: str,
        key: str,
        alphabet: str,
        mode: str = "encrypt"
) -> str:
    """Hill Cipher. Encryption/decryption function.

    Args:
        text: text to be encrypted/decrypted.
        key: a set of letters of the same alphabet.
        alphabet: alphabet compiled by the user.
        mode: encryption or decryption (default "encrypt").

    Returns:
        Encrypted or decrypted string.
    """
    if not text:
        raise HillError("Input text is empty!")

    if not key:
        raise HillError("The key is missing!")

    if not alphabet:
        raise HillError("Alphabet is empty!")

    if not is_square(len(key)):
        raise HillError("Key length must be a square!")

    if not set(key.lower()).issubset(alphabet):
        raise HillError("The key must be alphabetic characters!")

    if len(alphabet) != len(set(alphabet)):
        raise HillError("The alphabet must be composed of unique characters!")

    n = math.isqrt(len(key))
    matrix_key = np.array(list(map(lambda x: alphabet.index(x.lower()), key))).reshape((n, n))

    matrix_key_det = int(np.linalg.det(matrix_key))
    if matrix_key_det == 0:
        raise HillError("Matrix determinant is zero! The matrix is degenerate!")

    if math.gcd(matrix_key_det, len(alphabet)) != 1:
        raise HillError("Matrix determinant and key length must be coprime!")

    match mode:
        case "encrypt":
            pass

        case "decrypt":
            matrix_key = np.array(Matrix(matrix_key).inv_mod(len(alphabet)))

        case _:
            raise HillError(f"Invalid processing type! -> {mode}")

    letters, indices = get_letters_alphabetically(text, alphabet)

    new_letters = ""
    for i in range(0, len(letters), n):
        vct = list(map(lambda x: alphabet.index(x.lower()), letters[i:i + n]))
        vct += [0 for _ in range(len(vct), n)]

        new_vct = np.matmul(matrix_key, vct) % len(alphabet)
        new_letters += "".join(map(lambda x: alphabet[x], new_vct))

    text_list = list(text + new_letters[len(letters)::])
    for i, index in enumerate(indices):
        old_letter = text_list[index]
        new_letter = new_letters[i]

        if old_letter.isupper():
            new_letter = new_letter.upper()

        text_list[index] = new_letter

    return "".join(text_list)

def encrypt(
        text: str,
        key: str,
        alphabet: str
):
    """Hill Cipher. Interface for calling encryption functions.

    Args:
        text: text to be encrypted.
        key: a set of letters of the same alphabet.
        alphabet: alphabet compiled by the user.

    Returns:
        Encrypted string.
    """
    return transform(text, key, alphabet, "encrypt")

def decrypt(
        text: str,
        key: str,
        alphabet: str
):
    """Hill Cipher. Interface for calling decryption functions.

    Args:
        text: text to be decrypted.
        key: a set of letters of the same alphabet.
        alphabet: alphabet compiled by the user.

    Returns:
        Decrypted string.
    """
    return transform(text, key, alphabet, "decrypt")

def make(
        text: str,
        key: str,
        alphabet: str,
        mode: str = "encrypt"
):
    """Hill Cipher. Interface for calling encryption/decryption functions.

    Args:
        text: text to be encrypted/decrypted.
        key: a set of letters of the same alphabet.
        alphabet: alphabet compiled by the user.
        mode: encryption or decryption (default "encrypt").

    Returns:
        Encrypted or decrypted string.
    """
    match mode:
        case "encrypt":
            return encrypt(text, key, alphabet)

        case "decrypt":
            return decrypt(text, key, alphabet)

        case _:
            raise HillError(f"Invalid processing type! -> {mode}")


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

def compute_r_squared(data, predictions):
    '''
    In exercise 5, we calculated the R^2 value for you. But why don't you try and
    and calculate the R^2 value yourself.

    Given a list of original data points, and also a list of predicted data points,
    write a function that will compute and return the coefficient of determination (R^2)
    for this data.  numpy.mean() and numpy.sum() might both be useful here, but
    not necessary.

    Documentation about numpy.mean() and numpy.sum() below:
    http://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html
    http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html
    '''

    # your code here
    SST = ((data-np.mean(data))**2).sum()
    SSReg = ((predictions-data)**2).sum()
    r_squared = 1 - SSReg / SST
    return r_squared


import numpy as np

def rotation_matrix(a):
    return np.array([[np.cos(a), -np.sin(a)],
                     [np.sin(a), np.cos(a)]])

def margin(angle, size, v1, v2):
    vv = v2 - v1
    vv = vv / np.sqrt((vv**2).sum())
    edgelen = size / np.sin(angle)
    v3 = v1 + edgelen * np.dot(rotation_matrix(angle), vv)
    v4 = v2 - edgelen * np.dot(rotation_matrix(-angle), vv)
    return np.c_[v2, v4, v3, v1].T

def regular_polygon(n, v1, v2):
    ang = 2 * np.pi / n
    mat = rotation_matrix(ang)
    vs = np.c_[v1, v2]
    vv = v2 - v1
    vprev = v2
    for i in range(n - 2):
        v = np.dot(mat, vv) + vprev
        vs = np.c_[vs, v]
        vv = v - vprev
        vprev = v
    return vs.T