import numpy as np
import statsmodels.stats.api as sms
from logzero import logger
from scipy.stats import norm, binom
from math import sqrt

class ABSizer():
    ''' Finds the size needed to perform an AB Test by using different methodologies '''
    def __init__(self, p_hat, significance=0.05, power=0.8, two_sided=True):
        ''' Initializes the AB Sizer object
        Args:
            - p_hat:              (float) the proportion in the variant A (control)
            - significance level: (float) alpha, default: 0.05 (5%)
            - power:              (float) 1-beta, default: 0.8 (80%)
            - two_sided:          (bool)  wheter a two-sided test is used or not, default True (two-sided)
        '''
        logger.debug(f'Initializing ABSizer with p_hat: {p_hat}, significance: {significance}, power: {power} and two-sided: {two_sided}]')
        self.p_hat = p_hat
        self.significance = significance
        self.power = power
        self.two_sided = two_sided

    def get_sample_size_using_method(self, method='approx1', p_hat=None, min_detectable_effect=0.1, significance=None):
        ''' Calculation the required sample size by using on of the available methods:
         Args:
            - method:       (string) Options: 'approx1', 'evan_miller', 'R', 'standford'
            - p_hat:        (float or null) proportion, if None, the control variant A in initialization will be used
            - min_detectable_effect:   (float) minimum detectable effect, relative to base conversion rate.
            - significance: (float or null) alpha, in case of null, defaults to the significance level given in the initialization
        Returns:
            - sample size   (float)
        '''
        if not p_hat:
            p_hat = self.p_hat
        if method == 'approx1':
            sample_size = self.get_sample_size_approx_1(p_hat, min_detectable_effect, significance)
        elif method == 'evan_miller':
            sample_size = self.get_sample_size_evan_miller(p_hat, min_detectable_effect, significance)
        elif method == 'R':
            sample_size = self.get_sample_size_as_R(p_hat, min_detectable_effect, significance)
        elif method == 'standford':
            sample_size = self.get_sample_size_standford(p_hat, min_detectable_effect, significance)
        else:
            raise ValueError(f'error in method input: {method}')
        return sample_size

    def get_sample_size_approx_1(self, p_hat=None, min_detectable_effect=0.05, significance=None):
        '''
        Finds the sample size using the formula: n=16·σ²/Δ², where:
          - σ is the standard deviation of the performance measure, σ² would be the variance
          - Δ is the sensitvity or the amount of change you want to detect (min detectable diff between control and treatment).
          - significance: (float or null) alpha, in case of null, defaults to the significance level given in the initialization
        Example, in an e-commerce:
            - p_hat: 2% users who visit end up purchasing
            - What is the minimum sample size needed for detecting a 5% `change in conversion rate`?
            - A purchase, a convention event, can be modeled as a Bernoulli trial with p = 0.05
            - For a Bernoulli distribution, σ = √[p·(1-p)]
                - n = 16·σ²/Δ² = 16·(0.02·(1-0.02)) / (0.02·0.05)² = 121600 users per variant
            - TODO: how to include the significance level here?
        '''
        if not p_hat:
             p_hat = self.p_hat
        logger.debug('Finding sample size, method: approx1...')
        variance = p_hat * (1 - p_hat)
        Delta = p_hat * min_detectable_effect
        n = 16 * variance / Delta**2
        logger.debug(f'variance, σ²: {variance:.4f}, σ: {sqrt(variance)}, Δ: {Delta:.4f}')
        logger.info(f'\nA minimum sample size of \
                     \n  n = {n:.0f} is needed\n\
                     \n  to detect a change of {min_detectable_effect:.0%} (relative) of a base proportion of {p_hat:.2%}')
        return n

    def get_sample_size_evan_miller(self, p_hat=None, min_detectable_effect=0.05, significance=None):
         '''
         Based on https://www.evanmiller.org/ab-testing/sample-size.html
         Explanation here: http://www.alfredo.motta.name/ab-testing-from-scratch/
         and here, two sided or one sided: https://www.itl.nist.gov/div898/handbook/prc/section2/prc242.htm
         Args:
             - p_hat:                   (float) the estimation of proportion
             - min_detectable_effect:   (float) minimum detectable effect, relative to base conversion rate.
             - significance: (float or null) alpha, in case of null, defaults to the significance level given in the initialization
         - TODO: add option to select whether the min_detectable_effect is absolute or relative
         '''
         if not p_hat:
             p_hat = self.p_hat
         delta = p_hat * min_detectable_effect
         if not significance:
             significance = self.significance
         if self.two_sided:
             t_alpha = norm.ppf(1.0 - significance / 2)  # two-sided interval
         else:
             t_alpha = norm.ppf(1.0 - significance)  # one-sided interval
         t_beta = norm.ppf(self.power)
         sd1 = np.sqrt(2 * p_hat * (1.0 - p_hat))
         sd2 = np.sqrt(p_hat * (1.0 - p_hat) + (p_hat + delta) * (1.0 - p_hat - delta))
         sample_size = ((t_alpha * sd1 + t_beta * sd2) / delta) ** 2
         logger.info(f'\nA minimum sample size of \
                      \n  n = {sample_size:.0f} is needed\n\
                      \n  to detect a change of {min_detectable_effect:.0%} (relative) of a base proportion of {p_hat:.2%}\
                      \n  with a power of {self.power:.0%}\
                      \n  and a significance level of {significance:.1%}')
         return sample_size

    def get_sample_size_as_R(self, p_hat=None, min_detectable_effect=0.05, significance=None):
        '''
        Sample size needed for a two-sided test, given a minimun detectable effect
        Args:
            - p_hat:                  (float) the base proportion
            - min_detectable_effect:  (float) Minimum detectable effect, relative to base conversion rate.
            - significance: (float or null) alpha, in case of null, defaults to the significance level given in the initialization
        Matches the results obtained in R by using:
        # power.prop.test(p1=.1, p2=.12, power=0.8, alternative='two.sided', sig.level=0.05, strict=T)
        '''
        if not p_hat:
            p_hat = self.p_hat
        if not significance:
            significance = self.significance
        effect_size = sms.proportion_effectsize(p_hat, p_hat * (1 + min_detectable_effect))
        sample_size = sms.NormalIndPower().solve_power(effect_size, power=self.power, alpha=significance, ratio=1)
        # alternative way of calculating it:
        # smp.zt_ind_solve_power(nobs1=None, effect_size=es, alpha=0.05, power=0.8, ratio=1, alternative='two-sided')
        logger.info(f'\n A minimum sample size of \
                     \n  n = {sample_size:.0f} is needed\n\
                     \n  to detect a change of {min_detectable_effect:.0%} (relative) of a base proportion of {p_hat:.2%}\
                     \n  with a power of {self.power:.0%}\
                     \n  and a significance level of {significance:.1%}')
        return sample_size

    def get_sample_size_standford(self, p_hat=None, min_detectable_effect=0.05, significance=None):
        """Returns the minimum sample size to set up a split test
        Arguments:
           - p_hat:                  (float) the base proportion
           - mde (float): minimum change in measurement between control
           - group and test group if alternative hypothesis is true, sometimes referred to as minimum detectable effect
            - significance: (float or null) alpha, in case of null, defaults to the significance level given in the initialization
        Returns:
            minimum sample size (float)
        References:
            Stanford lecture on sample sizes
            http://statweb.stanford.edu/~susan/courses/s141/hopower.pdf
        """
        # TODO: fix this one!!!!
        if not p_hat:
           p_hat = self.p_hat
        if not significance:
            significance = self.significance
        # average of probabilities from both groups, pooled_probability:
        pooled_prob = (p_hat + p_hat * (1 + min_detectable_effect)) / 2
        sigma = sqrt(pooled_prob * (1 - pooled_prob))
        # Delta: the effect size:the number of standard deviations the true mean is away from the test done
        Delta = (p_hat - p_hat*(1 + min_detectable_effect)) / sigma
        # alternative way: effect_size = sms.proportion_effectsize(p_hat, p_hat * (1 + min_detectable_effect))
        # standard normal distribution used to determine z-values
        standard_norm = norm(0, 1)

        # find Z_beta from desired power
        Z_beta = standard_norm.ppf(self.power)

        # find Z_alpha
        if self.two_sided:
            Z_alpha = standard_norm.ppf(1 - significance / 2)
        else:
            Z_alpha = standard_norm.ppf(1 - significance)

        sample_size = ((Z_beta + Z_alpha)**2 / Delta**2)
        logger.info(f'\n A minimum sample size of \
                     \n  n = {sample_size:.0f} is needed\n\
                     \n  to detect a change of {min_detectable_effect:.0%} (relative) of a base proportion of {p_hat:.2%}\
                     \n  with a power of {self.power:.0%}\
                     \n  and a significance level of {significance:.1%}')
        return sample_size

    def find_power_given_min_effect_and_sample_size(self, min_detectable_effect, sample_size, p_hat=None, significance=None):
        ''' Calculates the power of an AB test when control is defined as variant A, and given the inputs:
        Args:
            - min_detectable_effect (float): Minimum detectable effect, relative to base conversion rate (p_hat).
            - sample_size:          (int) number of data points that are available for the test
            - p_hat:                (float or null) base conversion rate (proportion), if None, the one calculated in the initialization will be used
            - significance:         (float or null) if None, the one given in the initialization will be used
        '''
        if not p_hat:
            p_hat = self.p_hat
        if not significance:
            significance = self.significance
        effect_size = sms.proportion_effectsize(p_hat, p_hat * (1 + min_detectable_effect))
        power = sms.NormalIndPower().solve_power(effect_size, nobs1=sample_size, alpha=significance, ratio=1)
        return power

import numpy,from scipy,import statsmodels


import numpy as np
import matplotlib.pyplot as plt
plt.ion()

v1 = np.loadtxt('T-Bot_FilteredData.dat')
v1[np.where(v1[:,0]<0.001),0]=0.013

################   Simple Combination Filter    ################
'''
This code serves as a guide to show how the filters work.
A filter_weighting of 0 zero will be equivalent to using
the gyro only. A value of 1 will be equivalent to using the accelerometer
only. You can use serial_GetData.py to collect the data from the T-BOT
to see how effective your filter is.

This is not an efficient way of writing this in Python but the
structure is matched to the C code on the T-Bot for illustration.
'''

angle = 0
filter_weighting = 0.06
'''
def getAngleCFilter(pitch, gyro_rate, dt):
    global angle
    angle += gyro_rate * dt
    angle += filter_weighting * (pitch - angle)
    return angle
'''
def getAngleCFilter(pitch, gyro_rate, dt):
    global angle
    angle += filter_weighting * (pitch - (angle + gyro_rate * dt))
    return angle

####################   Kalman Filter    ########################
'''
Based on Arduino code written by Kristian Lauszus 2012
                 TKJ Electronics.
'''

bias = 0
R_measure = 0.15 # measurement noise
Q_angle = 0.1 # process noise
Q_bias = 0.3 #
R_measure = 1 # measurement noise
P = np.zeros((2,2))
K = np.zeros(2)

def getAngle(pitch, gyrorate, dt):
    global bias
    global angle
    global P
    rate = gyrorate - bias;
    angle += dt * rate;
    # Update estimation error covariance - Project the error covariance ahead
    P[0,0] += dt * (dt*P[1,1] - P[0,1] - P[1,0] + Q_angle)
    P[0,1] -= dt * P[1,1]
    P[1,0] -= dt * P[1,1]
    P[1,1] += Q_bias * dt

    # Calculate Kalman gain - Compute the Kalman gain
    S = P[0,0] + R_measure
    K[0] = P[0,0] / S
    K[1] = P[1,0] / S
    y = pitch - angle

    #Calculate angle and bias - Update estimate with measurement zk (newAngle)
    angle += K[0] * y
    bias += K[1] * y

    #Calculate estimation error covariance - Update the error covariance
    P[0,0] -= K[0] * P[0,0]
    P[0,1] -= K[0] * P[0,1]
    P[1,0] -= K[1] * P[0,0]
    P[1,1] -= K[1] * P[0,1]
    return angle

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

angleCF = np.array([getAngleCFilter(v1[x,1],v1[x,2],v1[x,0]) for x in range(v1.shape[0])])

gyroangle = np.cumsum(v1[:,2]*v1[:,0])

angle = 0
angleKF = np.array([getAngle(v1[x,1],v1[x,2],v1[x,0]) for x in range(v1.shape[0])])

t = np.cumsum(v1[:,0])

###############   Plot the data  ########################

plt.figure(figsize=(10, 8))

ax = plt.subplot(211)
plt.title('On-board Processing')
ax.plot(t, v1[:,1], c=(91/255.,111/255.,189/255.),label = 'Measured angle from accelerometer')
ax.plot(t, v1[:,4], c=(56/255.,192/255.,255/255.),label = 'Unfiltered Gyro Angle from T-Bot (integrated angular velocity)')
ax.plot(t, v1[:,3], c=(255/255.,0/255.,0/255.),label = 'Filtered Angle by T-Bot')
ax.legend(loc = 'best',prop={ 'size': 8})
plt.xlabel('t (s)')
plt.ylabel('angle (deg)')
plt.axis('tight')
ax = plt.subplot(212)
plt.title('From Python Code')
ax.plot(t, v1[:,1],  c=(91/255.,111/255.,189/255.),label = 'Measured angle from accelerometer')
ax.plot(t, gyroangle, c=(56/255.,192/255.,255/255.),label = 'Unfiltered Gyro Angle (integrated angular velocity)')
ax.plot(t, angleKF, 'g',label = 'Kalman Filter',linewidth=2)
ax.plot(t, angleCF, 'r--',label = 'Combination Filter',linewidth=2)

ax.legend(loc = 'best',prop={ 'size': 8})
plt.xlabel('t (s)')
plt.ylabel('angle (deg)')
plt.axis('tight')
plt.subplots_adjust(hspace=0.3)
plt.show()
plt.savefig('Filtering.svg')


#Import necessary libraries
import numpy as np
import matplotlib.pyplot as plt
import time as t

#Basic function that shifts all the values in an array x number of indices 'to the right'.
def shiftFunc(Array,Shift):
    # A quick check to see if the desired shift length exceeds the length of the array
    if Shift > len(Array):
        print('Array Shift exceeds length of array. Function will return input Array')
        return Array
    else:
        #Seperates the Array into two parts at the point where it is to be shifted the second part is then moved to be in front and the arrays are concatenated together to make a shifted array
        ArrayFront, ArrayBack = Array[0:int(Shift)],Array[int(Shift):len(Array)]
        ShiftedArray = np.concatenate([ArrayBack,ArrayFront])
    return ShiftedArray

#Function that performs a chi squared test using the chi squared formula
def chiSQ(TotalNum,Observed,binNo):
    #Observed corresponds to the number of counts in a given bin, this has already been calcauted by plt.hist

    #This is the expected number of counts of values that fall within a given bin
    Expected = TotalNum / binNo

    #Peforms the final summation to attain the chi sqaured value
    ChiSq = np.sum((Observed - Expected)**2 / Expected)

    return ChiSq

#A function that performs calculations relevant to the Freedman–Diaconis rule, which is used to find the number of bins in a histogram should use for a given dataset

def FDBinFinder(RandArray):

    #Gets the lower and upper quartiles for the input array
    RandArray25, RandArray75 = np.percentile(RandArray, [25, 75])

    #The calcualtion that is the Freedman–Diaconis rule is performed
    BinWidth = 2 * (RandArray75 - RandArray25) * len(RandArray) ** (-1/3)

    #Calcualtes number of bins from bin width
    BinNum = round((RandArray.max() - RandArray.min()) / BinWidth)

    return BinNum

#All input variables for all tasks are declared here
#Number of samples for task 1
TotalNum = 5000
#Number of particles and events for tasks 2-4
TotalParticles = 1000
TotalEvents = 10000

#Shift value for lag plot, task 1
ShiftVal = 2000

#Seed is set using the t.time() function, returns the current time in Unix time, which is seconds since 01/01/1970 00:00 (UTC)
#Seed could also be set with a integer value

#Seed = int(t.time())
Seed = 22061910

#The Bit Generators PCG64 and SFC64 were chosen for this script
PCG64 = np.random.Generator(np.random.PCG64(seed=np.random.SeedSequence(Seed)))
SFC64 = np.random.Generator(np.random.SFC64(seed=np.random.SeedSequence(Seed)))

#Generates an array of n lengths filled with random numbers from 0 to 1
PCRand = PCG64.random(TotalNum)
SFRand = SFC64.random(TotalNum)

#Generates the number of bins needed for the histograms
PCRandBinNum = FDBinFinder(PCRand)
SFRandBinNum = FDBinFinder(SFRand)

#Create and plot the histograms while obtaning the bin numbers needed for the chi squared test
plt.figure(1)
PCRandHistOut = plt.hist(PCRand, bins = PCRandBinNum, alpha=0.5,edgecolor='black',color='navy')
plt.title(f'BitGenrator PCG64 Uniform Distribution with {TotalNum} samples \n Seed = {Seed}')
plt.xlabel('Random Values')
plt.ylabel('Occurance of Random values')

plt.figure(2)
SFRandHistOut = plt.hist(SFRand, bins = SFRandBinNum, alpha=0.5,edgecolor='black',color='navy')
plt.title(f'BitGenrator SFC64 Uniform Distribution with {TotalNum} samples \n Seed = {Seed}')
plt.xlabel('Random Values')
plt.ylabel('Occurance of Random values')

#Calcaulted the chi sqaured values for the data in the histograms
PCRandChiSQ = chiSQ(TotalNum,PCRandHistOut[0],PCRandBinNum)
SRRandChiSQ = chiSQ(TotalNum,SFRandHistOut[0],SFRandBinNum)

#Prints out result
print(f'Chi-Squared for PCG64 for {TotalNum} samples with {PCRandBinNum - 1} degress of freedom is {np.around(PCRandChiSQ,2)}')
print(f'Chi-Squared for SFC64 for {TotalNum} samples with {SFRandBinNum - 1} degress of freedom is {np.around(PCRandChiSQ,2)}')

#Correlations
#Numpy AutoCorrelate

#Autocorrelation
PCRandAutoCorrelate = np.correlate(PCRand,PCRand, mode = 'full')
SFRandAutoCorrelate = np.correlate(SFRand,SFRand, mode = 'full')

#Normalised Autocorrelation
PCRandAutoCorrelateNorm = np.correlate((PCRand-np.mean(PCRand)) / (np.std(PCRand) * len(PCRand)),(PCRand-np.mean(PCRand)) / (np.std(PCRand)),mode = 'full')
SFRandAutoCorrelateNorm = np.correlate((SFRand-np.mean(SFRand)) / (np.std(SFRand) * len(SFRand)),(SFRand-np.mean(SFRand)) / (np.std(SFRand)),mode = 'full')

Xaxis = np.arange(1,len(PCRandAutoCorrelate) + 1,1)
ShiftedXaxis = Xaxis - Xaxis[int((len(Xaxis)-1)/2)]

#'Manual' correlation using loops

PCRandLooperCorrelate = np.zeros(TotalNum)
SFRandLooperCorrelate = np.zeros(TotalNum)
Xaxis2 = np.arange(1,TotalNum + 1,1)

for i in range(0,TotalNum):
    PCRandLooperCorrelate[i] = (np.correlate(PCRand,shiftFunc(PCRand,i)) /  (np.sqrt(np.sum(PCRand**2) * np.sum(shiftFunc(PCRand,i)**2))))
    SFRandLooperCorrelate[i] = (np.correlate(SFRand,shiftFunc(SFRand,i)) /  (np.sqrt(np.sum(SFRand**2) * np.sum(shiftFunc(SFRand,i)**2))))

#All np.correalte arrays share the same index where they reach their maximum
#The purpose of this code block is to remove the central point of no shift as we know that the array will fully corealte with itself
IndexDelete = np.where(PCRandAutoCorrelate == max(PCRandAutoCorrelate))[0][0]
ShiftedXaxis = np.delete(ShiftedXaxis,IndexDelete)
PCRandAutoCorrelate = np.delete(PCRandAutoCorrelate,IndexDelete)
SFRandAutoCorrelate = np.delete(SFRandAutoCorrelate,IndexDelete)
PCRandAutoCorrelateNorm = np.delete(PCRandAutoCorrelateNorm,IndexDelete)
SFRandAutoCorrelateNorm = np.delete(SFRandAutoCorrelateNorm,IndexDelete)

#I have used a mixture of regular and subplots depending on what would work best

#The data is plotted
Figure, ((SubPlot1,SubPlot2),(SubPlot3,SubPlot4)) = plt.subplots(2,2,figsize=(15,10), constrained_layout=False, sharex='col', sharey='row')
Figure.suptitle(f"Correlation of data \n Seed = {Seed}")

SubPlot1.title.set_text('BitGenrator PCG64 Correlation output')
SubPlot1.plot(ShiftedXaxis,PCRandAutoCorrelate)

SubPlot2.title.set_text('BitGenrator SFC64 Correlation output')
SubPlot2.plot(ShiftedXaxis,SFRandAutoCorrelate)

SubPlot3.title.set_text('BitGenrator PCG64 Correlation output, normalised')
SubPlot3.scatter(ShiftedXaxis,PCRandAutoCorrelateNorm, s = 1)

SubPlot4.title.set_text('BitGenrator SFC64 Correlation output, normalised')
SubPlot4.scatter(ShiftedXaxis,SFRandAutoCorrelateNorm, s = 1)

SubPlot1.set_ylabel('Correlation')
SubPlot3.set_ylabel('Correlation (Normalised)')

SubPlot3.set_xlabel('X-shift')
SubPlot4.set_xlabel('X-shift')

SubPlot1.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot2.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot3.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot4.grid(b=True, which='both', linestyle='-', linewidth='0.5')

plt.show()

plt.figure(3)
plt.plot(Xaxis2,PCRandLooperCorrelate)
plt.title(f'BitGenrator PCG64 Correlation output with manual shift apllied \n Seed = {Seed}')
plt.xlabel('x-shift')
plt.ylabel('Correlation')

plt.figure(4)
plt.plot(Xaxis2,SFRandLooperCorrelate)
plt.title(f'BitGenrator SFC64 Correlation output with manual shift apllied \n Seed = {Seed}')
plt.xlabel('x-shift')
plt.ylabel('Correlation')

plt.figure(5)
plt.title(f'BitGenrator PCG64 Lag plot with {TotalNum} samples \n Seed = {Seed}')
plt.xlabel('Random Values')
plt.ylabel(f'Random values shifted by + {ShiftVal}')
plt.scatter(PCRand,shiftFunc(PCRand,ShiftVal), s = 1)

plt.figure(6)
plt.title(f'BitGenrator SCF64 Lag plot with {TotalNum} samples \n Seed = {Seed}')
plt.xlabel('Random Values')
plt.ylabel(f'Random values shifted by + {ShiftVal}')
plt.scatter(SFRand,shiftFunc(SFRand,ShiftVal), s = 1)

#Tasks 2-4

#Create a timeaxis for the particles
Timeaxis = np.arange(0,TotalEvents + 1,1)

#Get random particle positions
PCRandBox = PCG64.random(TotalParticles)
SFRandBox = SFC64.random(TotalParticles)

#These matricies will be used to store the location of the particles
PCRandParticleLocY = np.zeros((len(PCRandBox),len(Timeaxis)))
PCRandParticleLocYBox2 = np.zeros((len(PCRandBox),len(Timeaxis)))

SFRandParticleLocY = np.zeros((len(SFRandBox),len(Timeaxis)))
SFRandParticleLocYBox2 = np.zeros((len(SFRandBox),len(Timeaxis)))

#This populates the previous matrices with the location of the particles
#The SFC64 random numbers are given a +1 increase to put it starting in another partition this was in case I decided to try out some methods of data plotting. I did not do anything with this, but I saw no reason to go back and change to +1 gain
for i in range(0,len(PCRandBox)):
    PCRandParticleLocY[i][0] = PCRandBox[i]
    PCRandParticleLocYBox2[i][0] = PCRandBox[i]
    SFRandParticleLocY[i][0] = (SFRandBox[i] + 1)
    SFRandParticleLocYBox2[i][0] = (SFRandBox[i] + 1)

#Probability for task 4
P = 0.75

for i in range(1,len(Timeaxis)):

    #Picks a random particle from each partition
    Index1 = PCG64.integers(0,TotalParticles,1)[0]
    Index2 = SFC64.integers(0,TotalParticles,1)[0]

    Index3 = PCG64.integers(0,TotalParticles,1)[0]
    Index4 = SFC64.integers(0,TotalParticles,1)[0]

    #Stores the current y-coordinates of the selected particle
    Particle1 = PCRandParticleLocY[Index1][i-1]
    Particle2 = SFRandParticleLocY[Index2][i-1]

    Particle3 = PCRandParticleLocY[Index1][i-1]
    Particle4 = SFRandParticleLocY[Index2][i-1]

    #Moves the random particle in each partition to the oppostie parition
    if Particle1 <= 1:
        Particle1 = Particle1 + 1
    else:
        Particle1 = Particle1 - 1

    if Particle2 >= 1:
        Particle2 = Particle2 - 1
    else:
        Particle2 = Particle2 + 1

    #Picks a random particle in each partition and prepares it to move it to the opposite partition
    if Particle3 <= 1:
        #A second check occurs to see if it makes the transition, if it does it will cross the partition
        if PCG64.random(1)[0] < P:
            Particle3 = Particle3 + 1
    else:
        #A second check occurs to see if it makes the transition, if it does it will cross the partition
        if PCG64.random(1)[0] >= P:
            Particle3 = Particle3 - 1

    #This does mean that it is possible for no transition to occur in any given 'timeframe'

    if Particle4 >= 1:
        if SFC64.random(1)[0] < P:
            Particle4 = Particle4 - 1
    else:
        if PCG64.random(1)[0] >= P:
            Particle4 = Particle4 + 1

    #I understand this is not the most efficient or elegant method to do this, but this is the only one that would work
    #This updates the next column in the matrix with the current column’s data
    for k in range(0,len(PCRandBox)):
        PCRandParticleLocY[k][i] = PCRandParticleLocY[k][i-1]
        SFRandParticleLocY[k][i] =  SFRandParticleLocY[k][i-1]
        PCRandParticleLocYBox2[k][i] = PCRandParticleLocYBox2[k][i-1]
        SFRandParticleLocYBox2[k][i] = SFRandParticleLocYBox2[k][i-1]

    #These input the selected particle’s new location into the matrices
    PCRandParticleLocY[Index1][i] = Particle1
    SFRandParticleLocY[Index2][i] = Particle2

    PCRandParticleLocYBox2[Index3][i] = Particle3
    SFRandParticleLocYBox2[Index4][i] = Particle4

#The data is plotted
Figure, ((SubPlot1,SubPlot2),(SubPlot3,SubPlot4)) = plt.subplots(2,2,figsize=(15,10), constrained_layout=False, sharex='col', sharey='row')
Figure.suptitle(f"Position of Select Particles in A Box \n Seed = {Seed}")

SubPlot1.title.set_text('Example particles from random selection box (PCG64)')
SubPlot1.hlines(1, 0, TotalEvents, color = 'black')
SubPlot1.plot(Timeaxis, PCRandParticleLocY[0],   color = 'blue', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot1.plot(Timeaxis, PCRandParticleLocY[TotalParticles-1],   color ='red', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot1.plot(Timeaxis, PCRandParticleLocY[int((TotalParticles-1)/2)],   color = 'orange', marker = 'x', linestyle = 'dotted', markersize = 1)

SubPlot2.title.set_text('Example particles from random selection box (SCF64)')
SubPlot2.hlines(1, 0, TotalEvents, color = 'black')
SubPlot2.plot(Timeaxis, SFRandParticleLocY[0],   color = 'blue', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot2.plot(Timeaxis, SFRandParticleLocY[TotalParticles-1],   color ='red', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot2.plot(Timeaxis, SFRandParticleLocY[int((TotalParticles-1)/2)],   color = 'orange', marker = 'x', linestyle = 'dotted', markersize = 1)

SubPlot3.title.set_text('Example particles from 75/25 bias box (PCG64)')
SubPlot3.hlines(1, 0, TotalEvents, color = 'black')
SubPlot3.plot(Timeaxis, PCRandParticleLocYBox2[0],   color = 'blue', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot3.plot(Timeaxis, PCRandParticleLocYBox2[TotalParticles-1],   color ='red', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot3.plot(Timeaxis, PCRandParticleLocYBox2[int((TotalParticles-1)/2)],   color = 'orange', marker = 'x', linestyle = 'dotted', markersize = 1)

SubPlot4.title.set_text('Example particles from 75/25 bias box (SCF64)')
SubPlot4.hlines(1, 0, TotalEvents, color = 'black')
SubPlot4.plot(Timeaxis, SFRandParticleLocYBox2[0],   color = 'blue', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot4.plot(Timeaxis, SFRandParticleLocYBox2[TotalParticles-1],   color ='red', marker = 'x', linestyle = 'dotted', markersize = 1)
SubPlot4.plot(Timeaxis, SFRandParticleLocYBox2[int((TotalParticles-1)/2)],   color = 'orange', marker = 'x', linestyle = 'dotted', markersize = 1)

SubPlot1.set_ylabel('Particle Postion')
SubPlot3.set_ylabel('Particle Postion')

SubPlot3.set_xlabel('TimeStep')
SubPlot4.set_xlabel('TimeStep')

SubPlot1.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot2.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot3.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot4.grid(b=True, which='both', linestyle='-', linewidth='0.5')

plt.show()

#These arrays and following loop will count how many particles are in their respective starting partition
#Box 1 corresponds to the starting Box
PCRandBox1Count = np.zeros(len(Timeaxis))
SFRandBox1Count = np.zeros(len(Timeaxis))
PCRandBox1Count75 = np.zeros(len(Timeaxis))
SFRandBox1Count75 = np.zeros(len(Timeaxis))

for i in range(0,len(Timeaxis)):
    PCRandBox1Count[i] = len(np.where(PCRandParticleLocY[:,i] < 1)[0])
    SFRandBox1Count[i] = len(np.where(SFRandParticleLocY[:,i] > 1)[0])
    PCRandBox1Count75[i] = len(np.where(PCRandParticleLocYBox2[:,i] < 1)[0])
    SFRandBox1Count75[i] = len(np.where(SFRandParticleLocYBox2[:,i] > 1)[0])

#The data is plotted
Figure, ((SubPlot1,SubPlot2),(SubPlot3,SubPlot4)) = plt.subplots(2,2,figsize=(15,10), constrained_layout=False, sharex='col', sharey='row')
Figure.suptitle(f"Particles In A Box \n Seed = {Seed}")

SubPlot1.title.set_text('Particles present in their paritions from random selection box (PCG64)')
SubPlot1.plot(Timeaxis,PCRandBox1Count, label = 'Starting Parition')
SubPlot1.plot(Timeaxis,(TotalParticles - PCRandBox1Count), label = 'Other Parition')

SubPlot2.title.set_text('Particles present in their paritions from random selection box (SCF64)')
SubPlot2.plot(Timeaxis,SFRandBox1Count, label = 'Starting Parition')
SubPlot2.plot(Timeaxis,(TotalParticles - SFRandBox1Count), label = 'Other Parition')

SubPlot3.title.set_text('Particles present in their paritions from 75/25 bias box (PCG64)')
SubPlot3.plot(Timeaxis,PCRandBox1Count75, label = 'Starting Parition')
SubPlot3.plot(Timeaxis,(TotalParticles - PCRandBox1Count75), label = 'Other Parition')
SubPlot3.hlines(750, 0, TotalEvents, color = 'black', linestyle="dotted")
SubPlot3.hlines(250, 0, TotalEvents, color = 'red', linestyle="dotted")

SubPlot4.title.set_text('Particles present in their paritions from 75/25 bias box (SCF64)')
SubPlot4.plot(Timeaxis,SFRandBox1Count75, label = 'Starting Parition')
SubPlot4.plot(Timeaxis,(TotalParticles - SFRandBox1Count75), label = 'Other Parition')
SubPlot4.hlines(750, 0, TotalEvents, color = 'black', linestyle="dotted")
SubPlot4.hlines(250, 0, TotalEvents, color = 'red', linestyle="dotted")

SubPlot1.set_ylabel('Number of Particles')
SubPlot3.set_ylabel('Number of Particles')

SubPlot3.set_xlabel('TimeStep')
SubPlot4.set_xlabel('TimeStep')

SubPlot1.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot2.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot3.grid(b=True, which='both', linestyle='-', linewidth='0.5')
SubPlot4.grid(b=True, which='both', linestyle='-', linewidth='0.5')

SubPlot1.legend(loc = 'upper right', fontsize = '10')
SubPlot2.legend(loc = 'upper right', fontsize = '10')
SubPlot3.legend(loc = 'upper right', fontsize = '10')
SubPlot4.legend(loc = 'upper right', fontsize = '10')

plt.show()


from scipy.io.wavfile import read
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import argrelextrema

def crossCorr(x, y):

    corr_length = len(x) + len(y) - 1
    corr = np.zeros(corr_length)
    padded_x = np.hstack((np.zeros((len(y)-1)), x, np.zeros(len(y) - 1)))

    for n in range(corr_length):
        corr[n] = np.dot(padded_x[n:n+len(y)], y)

    norm_corr = corr/np.max(corr)

    return norm_corr

def loadSoundFile(filename):
    _, audio = read(filename)

    if (audio.shape[1] > 1):
        return audio[:, 0]
    else:
        return audio

def findSnarePosition(snareFilename, drumloopFilename):

    snare = loadSoundFile(snareFilename)
    drums = loadSoundFile(drumloopFilename)

    corr = crossCorr(drums, snare)

    # Assumed a threshold of 0.8 would be a safe value after looking at the plot from Q.1
    corr_thresholded = np.where(corr >0.8, corr, 0)

    pos = argrelextrema(corr_thresholded, np.greater)

    # Subtracting the padding added in the beginning
    pos = np.ndarray.tolist(pos[0] - (len(snare) - 1))

    return pos

if __name__ == "__main__":
    loop = loadSoundFile("./drum_loop.wav")
    snare = loadSoundFile("./snare.wav")

    corr = crossCorr(loop, snare)
    lag = range(-len(snare)+1, len(loop))

    pos = findSnarePosition("./snare.wav", "./drum_loop.wav")

    plt.plot(lag, corr)
    plt.xlabel("Lag (In samples)")
    plt.ylabel("Normalized Correlation Coefficient")
    plt.title("Cross Correlation between Drum loop and Snare")
    plt.show()


#!/usr/bin/env python
"""
Calculates cost including cross-entropy (logistic regression cost)

Reference
---------
https://en.wikipedia.org/wiki/Cross_entropy

__author__ = "Hide Inada"
__copyright__ = "Copyright 2018, Hide Inada"
__license__ = "The MIT License"
__email__ = "hideyuki@gmail.com"
"""
import os
import logging

import numpy as np

log = logging.getLogger(__name__)
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))

class CostFunction():
    MEAN_SQUARED_ERROR = 1
    CROSS_ENTROPY = 2

    @staticmethod
    def mean_cross_entropy(y, y_hat, epsilon=1e-10):
        """
        Calculates mean cross-entropy (logistic regression cost)

        Paramters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions
        epsilon: float
            A padding to ensure {y_hat: 0 < y_hat < 1}

        Returns
        -------
        out: float
            Mean cost
        """
        m = y.shape[0]
        return (1.0 / m) * CostFunction.cross_entropy(y, y_hat, epsilon).sum()

    @staticmethod
    def cross_entropy(y, y_hat, epsilon=1e-10):
        """
        Calculates cross-entropy (logistic regression cost) for each prediction

        Parameters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions
        epsilon: float
            A padding to ensure {y_hat: 0 < y_hat < 1}

        Returns
        -------
        out: ndarray
            Cost
        """
        # Tweak y_hat to avoid 0 or 1 which cannot be passed to the cost function
        y_hat_adj = np.clip(y_hat, a_min=epsilon, a_max=(1 - epsilon))

        cost = -y * np.log(y_hat_adj) - (1.0 - y) * np.log(1.0 - y_hat_adj)

        return cost

    @staticmethod
    def d_cross_entropy(y, y_hat, epsilon=1e-10):
        """
        Calculates the partial derivative of cross-entropy (logistic regression cost) for each prediction with respect to
        y hat.

        Parameters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions

        Returns
        -------
        out: ndarray
            Derivative of cost for each prediction
        """

        y_hat_adj = np.clip(y_hat, a_min=epsilon, a_max=(1 - epsilon))
        #y_hat_adj = y_hat # commenting out due to an error found in SGD
        d = - ( (y / y_hat_adj) - (1.0 - y) / (1.0 - y_hat_adj) )
        return d

    @staticmethod
    def mean_squared_error(y, y_hat, divide_by_two_m=False):
        """
        Calculates mean squared error for each sample data

        Paramters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions
        divide_by_two_m: bool
            Divide by the dataset size x 2 instead of the dataset size

        Returns
        -------
        out: float
            Mean squared error
        """
        m = y.shape[0]
        if divide_by_two_m:
            m *= 2
        return (1 / m) * CostFunction.squared_error(y, y_hat).sum()

    @staticmethod
    def squared_error(y, y_hat):
        """
        Calculates squared error for each prediction

        Parameters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions

        Returns
        -------
        out: ndarray
            Cost
        """
        cost = (y_hat - y) ** 2

        return cost

    @staticmethod
    def d_squared_error(y, y_hat):
        """
        Calculates the partial derivative of squared error cost for each prediction with respect to
        y hat.

        Parameters
        ---------
        y: ndarray
            Ground-truth
        y_hat: ndarray
            Predictions

        Returns
        -------
        out: ndarray
            Derivative of cost for each prediction
        """
        d = 2 * (y_hat - y)
        return d


from mpi4py import MPI
import time
import math
import numpy as np
t0 = time.time()
from sympy import *
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
nprocs = comm.Get_size()
nsteps=10000000

x=symbols('x')

if rank == 0:

    sendbuf = np.arange(1,nsteps,dtype=np.float64)

    ave, res = divmod(nsteps, nprocs)
    counts = np.array([ave + 1 if p < res else ave for p in range(nprocs)])
    starts = np.array([sum(counts[:p]) for p in range(nprocs)])

else:
    sendbuf = None
    data = None
    counts = np.empty(nprocs, dtype=np.int)
    starts=np.empty(nprocs, dtype=np.int)

comm.Bcast(counts,root=0)
comm.Bcast(starts,root=0)

recvbuf = np.zeros(counts[rank])

partial_pi = np.zeros(nprocs)

t0 = time.process_time()
partial_pi[rank]= 4* integrate(1/(1+(x)**2),(x,starts[rank]/nsteps,(starts[rank]+counts[rank])/nsteps))
t1 = time.process_time()
print(partial_pi[rank])

counts=np.ones(nprocs)
pi_value= np.zeros(nprocs)
starts=np.array(list(range(nprocs)))

comm.Gatherv(partial_pi[rank], [pi_value, counts, starts, MPI.DOUBLE], root=0)
if rank == 0:
    print("Computed pi value is: {}".format(sum(pi_value)))
    print('*'*50)
    print('pi computed in {:.3f} sec'.format(t1 - t0))

    print('*'*50)
    print('error is {}'.format(abs(sum(pi_value)- math.pi)))


# ---------------------------------------------------------------
# Imports
# ---------------------------------------------------------------

import scipy as sp

# ---------------------------------------------------------------
# Interface
# ---------------------------------------------------------------

def split(s, split_by_char):
    """Splits the string into substring, splitting by the split-by character

    e.g. "for-the-win" would be split into:
    - "for"
    - "the"
    - "win"
    if '-' was used as a split-by character

    :returns the list of items"""
    if len(split_by_char) != 1:
        raise Exception("The split-by argument must be a single character")

    items = []
    buffer = []
    for c in s:
        if c == split_by_char:
            items.append(''.join(buffer))
            buffer.clear()
            continue
        buffer.append(c)
    items.append(''.join(buffer))

    return items

def read_matrix():
    """Reads in a matrix from standard input - one row at a time - until an empty row is encountered

    :returns the matrix as a numpy.ndarray
    """
    print("Input matrix rows (terminate with empty row):")
    rows = []
    s = input()
    while s != "":
        row = [float(i) for i in split(s, ' ')]
        rows.append(row)
        s = input()

    m = len(rows[0])
    if any(len(row) != m for row in rows):
        raise Exception("Each row must have equal length!")

    mat = sp.matrix(rows)
    print("Read in this matrix:\n" + str(mat) + "\n")
    return mat

def multiply(m_a, m_b):
    """Multiplies the two matrices and returns the resulting matrix"""
    if m_a.shape[1] != m_b.shape[0]:
        raise Exception("The number of columns of matrix A must be the same as the number of rows of matrix B!")

    n = m_a.shape[0]
    m = m_a.shape[1]
    l = m_b.shape[1]

    m_c = sp.zeros((n, l))
    for i in range(n):  # each row of A...
        for j in range(l):  # ...multiplied with each column of B
            # the multiplication itself now
            entry_i_j = 0
            for k in range(m):
                entry_i_j += m_a[i, k]*m_b[k, j]
            m_c[i, j] = entry_i_j

            # concise way: entry_i_j = sum(m_a[i, k]*m_b[k, j] for k in range(m))

    return m_c

# ---------------------------------------------------------------
# Main
# ---------------------------------------------------------------

def main():
    m_a = read_matrix()
    m_b = read_matrix()
    m_c = multiply(m_a, m_b)
    print("Multiplication is:\n" + str(m_c))

if __name__ == "__main__":
    main()


import streamlit as st
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.figure_factory as ff

@st.cache
def load_time_series_data():
    """
    ランダムに時系列データを生成する。
    """
    chart_data = pd.DataFrame(
        np.random.randn(20, 3),
        columns=['a', 'b', 'c']
    )
    return chart_data

@st.cache
def load_iris_data():
    """
    データ読み込み, cacheにして最適化を行う
    """
    iris_data = load_iris()
    df = pd.DataFrame(iris_data.data, columns=iris_data.feature_names)
    labels = iris_data.target_names[iris_data.target]
    return df, labels

def show_heatmap(df: 'pd.DataFrame'):
    """
    各特徴の相関ヒートマップをみる
    """
    fig, ax = plt.subplots(figsize=(10,10))
    sns.heatmap(df.corr(), annot=True, ax=ax)
    st.pyplot(fig)

def show_distplot(df: 'pd.DataFrame', labels: 'np.ndarray'):
    """
    それぞれの特徴について、各ラベルごとの分布を見る
    """
    for column in df.columns:
        st.write(f'- {column}')
        series = df[column]
        target_names = list(set(labels))
        hist_data = [series[labels == name] for name in target_names]
        fig = ff.create_distplot(
            hist_data, target_names, bin_size=[.1, .25, .5])
        st.plotly_chart(fig, use_container_width=True)

@st.cache(allow_output_mutation=True)
def fit_transform_pca(df: 'pd.DataFrame'):
    """
    主成分分析した結果の第二主成分ベクトルを可視化した結果を得る
    """
    pca = PCA(n_components=2)
    X = pca.fit_transform(df)
    return X

def show_scatter2d(X: 'np.ndarray', labels: 'np.ndarray'):
    fig, ax = plt.subplots(figsize=(10,10))
    target_names = list(set(labels))
    for i, target in enumerate(target_names):
        X_ = X[labels == target]
        x = X_[:, 0]
        y = X_[:, 1]
        ax.scatter(x, y, cmap=[i]*len(X_), label=target)

    ax.legend()
    st.pyplot(fig)

def main():
    st.title('Visualization app')

    st.write('時系列データをline plot')
    chart_data = load_time_series_data()
    st.line_chart(chart_data)

    st.write('### アイリスデータを見ていく')
    df, labels = load_iris_data()

    st.write('DataFrameの表示')
    st.write(df)

    st.write('特徴ごとの相関のHeatMap表示')
    show_heatmap(df)

    st.write('ラベルごとの特徴の分布をみる')
    show_distplot(df, labels)

    st.write('主成分分析をして、2次元にマッピングする')
    df_pca = fit_transform_pca(df)
    show_scatter2d(df_pca, labels)

if __name__ == '__main__':
    main()


"""
Functions for calculating the statistical significant differences
between two dependent or independent correlation coefficients.
The Fisher and Steiger method is adopted from the R package
http://personality-project.org/r/html/paired.r.html
and is described in detail in the book 'Statistical Methods for Psychology'
The Zou method is adopted from
http://seriousstats.wordpress.com/2012/02/05/comparing-correlations/
Credit goes to the authors of above mentioned packages!
Author: Philipp Singer (www.philippsinger.info)

------------------------------------------------------------
README.md:
CorrelationStats
This Python script enables you to compute statistical significance tests on
both dependent and independent correlation coefficients. For each case two methods to
choose from are available.

For details, please refer to: http://www.philippsinger.info/?p=347
#copied from on 4/24/2015 from
https://github.com/psinger/CorrelationStats/blob/master/corrstats.py
"""

__author__ = "psinger"
from math import atanh
from typing import Optional, Tuple

import numpy as np
from scipy.stats import norm, t

def rz_ci(r: float, n: int, conf_level: float = 0.95) -> np.ndarray:
    zr_se = pow(1 / (n - 3), 0.5)
    moe = norm.ppf(1 - (1 - conf_level) / float(2)) * zr_se
    zu = atanh(r) + moe
    zl = atanh(r) - moe
    return np.tanh((zl, zu))

def rho_rxy_rxz(rxy: float, rxz: float, ryz: float) -> float:
    num = (ryz - 1 / 2.0 * rxy * rxz) * (
        1 - pow(rxy, 2) - pow(rxz, 2) - pow(ryz, 2)
    ) + pow(ryz, 3)
    den = (1 - pow(rxy, 2)) * (1 - pow(rxz, 2))
    return num / float(den)

def dependent_corr(
    xy: float,
    xz: float,
    yz: float,
    n: int,
    twotailed: bool = True,
    conf_level: float = 0.95,
    method: str = "steiger",
) -> Tuple[float, float]:
    """
    Calculates the statistic significance between two dependent correlation coefficients
    :param xy: correlation coefficient between x and y
    :param xz: correlation coefficient between x and z
    :param yz: correlation coefficient between y and z
    :param n: number of elements in x, y and z
    :param twotailed: whether to calculate a one or two tailed test, only works for 'steiger' method
    :param conf_level: confidence level, only works for 'zou' method
    :param method: defines the method uses, 'steiger' or 'zou'
    @return: t and p-val
    """
    if method != "steiger" and "zou":
        raise Exception("Wrong method!")

    if method == "steiger":
        d = xy - xz
        determin = 1 - xy * xy - xz * xz - yz * yz + 2 * xy * xz * yz
        av = (xy + xz) / 2
        cube = (1 - yz) * (1 - yz) * (1 - yz)

        t2 = d * np.sqrt(
            (n - 1) * (1 + yz) / ((2 * (n - 1) / (n - 3)) * determin + av * av * cube)
        )
        p = 1 - t.cdf(abs(t2), n - 2)

        if twotailed:
            p *= 2

        output1 = t2
        output2 = p
    elif method == "zou":
        L1 = rz_ci(xy, n, conf_level=conf_level)[0]
        U1 = rz_ci(xy, n, conf_level=conf_level)[1]
        L2 = rz_ci(xz, n, conf_level=conf_level)[0]
        U2 = rz_ci(xz, n, conf_level=conf_level)[1]
        rho_r12_r13 = rho_rxy_rxz(xy, xz, yz)
        lower = (
            xy
            - xz
            - pow(
                (
                    pow((xy - L1), 2)
                    + pow((U2 - xz), 2)
                    - 2 * rho_r12_r13 * (xy - L1) * (U2 - xz)
                ),
                0.5,
            )
        )
        upper = (
            xy
            - xz
            + pow(
                (
                    pow((U1 - xy), 2)
                    + pow((xz - L2), 2)
                    - 2 * rho_r12_r13 * (U1 - xy) * (xz - L2)
                ),
                0.5,
            )
        )
        output1 = lower
        output2 = upper

    return (output1, output2)

def independent_corr(
    xy: float,
    ab: float,
    n: int,
    n2: Optional[int] = None,
    twotailed: bool = True,
    conf_level: float = 0.95,
    method: str = "fisher",
) -> Tuple[float, float]:
    """
    Calculates the statistic significance between two independent correlation coefficients
    :param xy: correlation coefficient between x and y
    :param ab: correlation coefficient between a and b
    :param n: number of elements in xy
    :param n2: number of elements in ab (if distinct from n)
    :param twotailed: whether to calculate a one or two tailed test, only works for 'fisher' method
    :param conf_level: confidence level, only works for 'zou' method
    :param method: defines the method uses, 'fisher' or 'zou'
    return z and p-val
    """
    if n2 is None:
        n2 = n

    if not method == "fisher" and "zou":
        raise Exception("Wrong method!")

    if method == "fisher":
        xy_z = 0.5 * np.log((1 + xy) / (1 - xy))
        xz_z = 0.5 * np.log((1 + ab) / (1 - ab))

        se_diff_r = np.sqrt(1 / (n - 3) + 1 / (n2 - 3))
        diff = xy_z - xz_z
        z = abs(diff / se_diff_r)
        p = 1 - norm.cdf(z)
        if twotailed:
            p *= 2
        output1 = z
        output2 = p
    elif method == "zou":
        L1 = rz_ci(xy, n, conf_level=conf_level)[0]
        U1 = rz_ci(xy, n, conf_level=conf_level)[1]
        L2 = rz_ci(ab, n2, conf_level=conf_level)[0]
        U2 = rz_ci(ab, n2, conf_level=conf_level)[1]
        lower = xy - ab - pow((pow((xy - L1), 2) + pow((U2 - ab), 2)), 0.5)
        upper = xy - ab + pow((pow((U1 - xy), 2) + pow((ab - L2), 2)), 0.5)
        output1 = lower
        output2 = upper

    return output1, output2


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

def solve_sir(S_i=210_000_000., I_i=2200., R_i=100., beta=0.16, mu=14, dias=365, N=24):
    """Plot a solution to the SIR differential equations."""

    def sir_deriv(s_i_r, t0, beta=beta, mu=mu):
        """Compute the time-derivative of a Lorenz system."""
        s, i, r = s_i_r
        n = s + i + r
        return [
            -beta * i * s/n,
            beta * i * s/n - i/mu,
            i/mu
        ]

    t = np.linspace(0,dias,int(N*dias))
    s_i_r_t = integrate.odeint(sir_deriv, (S_i, I_i, R_i), t)
    plt.plot(t, s_i_r_t)
    plt.show()
    print("R_0 = "+str(beta*mu))
    return t, s_i_r_t


import numpy as np
import matplotlib.pyplot as plt
from time import process_time
import scipy.stats as stat
from numba import jit

@jit(nopython=True)             #high performance Python compiler
def RNG(seed,m):                #define MCG generator
    n=[np.uint32(seed), np.uint32(seed*m)]
    while n[len(n)-1] != seed:
        h=np.uint32(m*n[len(n)-1])
        n.append(h)
    n.pop()                     #Since the loop appends also the seed after all the sequence, I delete the last item of the list
    return n

seed=987654321          #seed
m=663608941             #multiplier
n_bins=100              #number of bins

#compute random numbers through MCG, count the CPU time of the process
t_start=process_time()
x=np.array(RNG(seed,m))
t_stop=process_time()

print("Elapsed CPU time in seconds: %u \n sequence lenght: %u"%(t_stop-t_start, len(x)))

#compute Chi2 of the distribution with all the entries
hist, bin_edges= np.histogram(x/x.max(), bins=n_bins)
chi2, p_value=stat.chisquare(hist, len(x)/n_bins)

#plot the histogram with all the entries
plt.figure()
plt.hist(x/x.max(), color='orange', bins=n_bins, histtype='step', density=False)
plt.xlim(0, 1)
plt.ticklabel_format(axis="y", style="sci", scilimits=(0,0), useMathText=True)
plt.ylim(0, 1.6*10**7)
plt.xlabel('Extracted random numbers', fontsize=10)
plt.ylabel('Counts', fontsize=10)
plt.fill_between(np.linspace(0, 1, n_bins), hist, color='orange', alpha=0.2)

print('Chi2=%.2e and p=%.2f for all entries'%(chi2,p_value))

#define distributions with the first 10^3 and 10^6 elements
rng_1 = x[:10**3]
rng_2 = x[:10**6]

#compute the Chi2 of the distributions with 10^3 and 10^6 entries
hist1, bin_edges1= np.histogram(rng_1, bins=n_bins)
hist2, bin_edges2= np.histogram(rng_2, bins=n_bins)
chi2_1, p_val_1= stat.chisquare(hist1, len(rng_1)/n_bins)
chi2_2, p_val_2= stat.chisquare(hist2, len(rng_2)/n_bins)

#plot the distribution with 10^3 entries
plt.figure()
plt.hist(rng_1, color='purple', bins=n_bins, histtype='step', density=False)
plt.xlim(0, 1)
plt.ticklabel_format(axis="y", style="sci", scilimits=(0,0), useMathText=True)
plt.xlabel('Extracted random numbers', fontsize=10)
plt.ylabel('Counts', fontsize=10)
plt.fill_between(np.linspace(0, 1, n_bins), hist1, color='purple', alpha=0.2)

print('Chi2=%.2f and p=%.2f for 1000 entries'%(chi2_1,p_val_1))

#plot the distribution with 10^6 entries
plt.figure()
plt.hist(rng_2, color='blue', bins=n_bins, histtype='step', density=False)
plt.xlim(0, 1)
plt.ticklabel_format(axis="y", style="sci", scilimits=(0,0), useMathText=True)
plt.ylim(0, 1.2*10**4)
plt.xlabel('Extracted random numbers', fontsize=10)
plt.ylabel('Counts', fontsize=10)
plt.fill_between(np.linspace(0, 1, n_bins), hist2, color='blue', alpha=0.2)

print('Chi2=%.2f and p=%.2f for 1000000 entries'%(chi2_2,p_val_2))


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

def heavyside(x):
    return 1 if x>0 else 0

def laing_chow(y, t, I1, I2):
    """Implements eqs (1-4) from Liang and chow in the format required by scipy.integrate.odeint.
        y is assumed to be a list [u1, a1, u2, a2]
    """
    # set parameter by default
    alpha, beta = 0.2, 0.4
    g1 = g2 = 1
    phi, tau = 0.4, 20

    # get values from input vector
    u1, a1, u2, a2 = y

    # update equations as in the paper
    u1dot = -u1 + heavyside(alpha * g1 * u1 - beta * g2 * u2 - a1 + I1)
    u2dot = -u2 + heavyside(alpha * g2 * u2 - beta * g1 * u1 - a2 + I2)
    a1dot = 1 / tau * (-a1 + phi * heavyside(alpha * g1 * u1 - beta * g2 * u2 - a1 + I1))
    a2dot = 1 / tau * (-a2 + phi * heavyside(alpha * g2 * u2 - beta * g1 * u1 - a2 + I2))

    # return in vector format (as a list)
    return [u1dot, a1dot, u2dot, a2dot]

def plot_results(sol, t):
    # get the solution and reproduce the figure in the paper
    u1, a1, u2, a2 = sol.T

    plt.figure(figsize=(18, 8))
    plt.suptitle("Solution for the reduced model (1) through (4)", fontsize=16)

    plt.subplot(111)
    plt.plot(t, u1, 'b')
    plt.plot(t, a1, 'g')
    plt.ylim((-.2, 1.2))
    plt.plot(t, u2, 'r')
    plt.plot(t, a2, 'y')
    plt.ylim((-.2, 1.2))
    plt.legend([r"$u_1$", r"$a_1$", r"$u_2$", r"$a_2$"], fontsize=15)
    plt.xlabel("Time", fontsize=15)

def dominance_durations(x1, x2, t):
    """Calculates the dominant durations of x1 over x2"""

    # get the dominance arrays: logical array
    dom = (x1 - x2) > .1

    # find the shifts by taking the derivative
    idx = np.where(np.diff(dom))[0]

    # add shifts at the beginning and end to compensate the diff operation
    shifttimes = np.zeros(len(idx) + 2)
    shifttimes[-1] = max(t)

    # add the shift times into the array
    shifttimes[1:-1] = t[idx]

    # who dominated first?
    x1start = 0 if x1[0] > x2[0] else 1

    return np.diff(shifttimes)[x1start::2]

x0 = [1, 0.1, 0, 0.25]
I1, I2 = 0.43, 0.5
t = np.linspace(0,500,5001)
sol = odeint(laing_chow, x0, t, args=(I1, I2))
plot_results(sol, t)
# plt.show()

dominance_durations(sol[:, 2], sol[:, 0], t)


import sklearn.decomposition as deco
import numpy as np
import matplotlib.pyplot as plt
from database_actions import get_known_encodings
from sklearn import manifold

# Load the data and define the necessary variables
X, y = get_known_encodings()
X = np.transpose(X)
n_samples, n_features = X.shape
n_neighbors = 21

# Plot the dimensionality reduction frameworks.
def plot_2D(X, title=None):
    X = (X - np.mean(X, 0)) / np.std(X, 0)

    plt.figure()
    scatter_x = X[:, 0]
    scatter_y = X[:, 1]
    group = map(int, y)
    cdict = {1: 'red', 2: 'blue', 3: 'green', 4: 'black', 5: 'orange'}

    ax = plt.axes()
    for g in np.unique(group):
        ix = np.where(group == g)
        scatter_x[ix], scatter_y[ix]
        ax.scatter(scatter_x[ix], scatter_y[ix], c=cdict[g], label=g, s=100)
    ax.legend()
    plt.xlabel("X")
    plt.ylabel("Y")

    if title is not None:
        plt.title(title)

# Initiate the list of algorithms
algorithms = ["x_pca", "x_iso", "x_lle", "x_mlle", "x_hes", "x_ltsa", "x_mds", "x_se", "x_tsne"]

algorithmDef = {
    "x_pca": deco.PCA(n_components = 12).fit(X).transform(X),
    "x_iso": manifold.Isomap(n_neighbors, n_components=2).fit_transform(X),
    "x_lle": manifold.LocallyLinearEmbedding(n_neighbors, n_components=2, method='standard').fit_transform(X),
    "x_mlle": manifold.LocallyLinearEmbedding(n_neighbors, n_components=2, method='modified').fit_transform(X),
    "x_hes": manifold.LocallyLinearEmbedding(n_neighbors, n_components=2, method='hessian').fit_transform(X),
    "x_ltsa": manifold.LocallyLinearEmbedding(n_neighbors, n_components=2, method='ltsa').fit_transform(X),
    "x_mds": manifold.MDS(n_components=2, n_init=1, max_iter=100).fit_transform(X),
    "x_se": manifold.SpectralEmbedding(n_components=2, random_state=0, eigen_solver="arpack").fit_transform(X),
    "x_tsne": manifold.TSNE(n_components=2, init='pca', random_state=0).fit_transform(X)
}

# Define the titles
title = {
    "x_pca": "Principal Components projection of the digits",
    "x_iso": "Isomap projection of the digits",
    "x_lle": "Locally Linear Embedding of the digits",
    "x_mlle": "Modified Locally Linear Embedding of the digits",
    "x_hes": "Hessian Locally Linear Embedding of the digits",
    "x_ltsa": "Local Tangent Space Alignment of the digits",
    "x_mds": "MDS embedding of the digits",
    "x_se": "Spectral embedding of the digits",
    "x_tsne": "t-SNE embedding of the digits"
}

# Plot the data points for which different algorithms are applied
for algo in algorithms:
    plot_2D(algorithmDef[algo], title[algo])
    plt.show()


import numpy as np
from scipy.spatial.distance import cdist

class KMeans:
    def __init__(
            self,
            k: int,
            metric: str = "euclidean",
            tol: float = 1e-6,
            max_iter: int = 100):
        """
        inputs:
            k: int
                the number of centroids to use in cluster fitting
            metric: str
                the name of the distance metric to use
            tol: float
                the minimum error tolerance from previous error during optimization to quit the model fit
            max_iter: int
                the maximum number of iterations before quitting model fit
        """
        if k <= 0:
            raise ValueError("K must be greater than 0!")
        # Pass the input variables as attributes of the KMeans class
        self.k = k
        self.max_iter = max_iter
        self.tol = tol
        self.metric = metric
        self.error = np.inf

        # Build empty lists for holding the clusters and centroids
        self.clusters = [[] for _ in range(self.k) ] # Holds indices
        self.centroids = [] # Holds samples

    def fit(self, mat: np.ndarray):
        """
        fits the kmeans algorithm onto a provided 2D matrix

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features
        """
        # Get the dimensions of the input feature matrix
        self.mat = mat
        self.n_observations, self.n_features = mat.shape

        # Initialize random centroids and create an object to hold previous centroids as we update them through the fitting process
        centroids = np.random.rand(self.k, self.n_features)
        prev_centroids = centroids.copy()

        # This loop continues until one of two events: we reach the user-input maximum iterations for the fitting process, or we reach convergence (i.e.: the MSE for the current centroids and the previous centroids is within the user-input tolerable threshold)
        for i in range(self.max_iter):

            dist = np.linalg.norm(self.mat - centroids[0,:],axis=1).reshape(-1,1)
            for classification in range(1,self.k):
                dist = np.append(dist,np.linalg.norm(self.mat - centroids[classification,:],axis=1).reshape(-1,1),axis=1) # Calculate the Euclidean distance from each point to each centroid

            classes = np.argmin(dist,axis=1)  # Class label will be the minimum distance here. In other words, the centroid that the point is closest to

            # Update centroid positions
            for classification in set(classes):
                centroids[classification,:] = np.mean(self.mat[classes == classification,:],axis=0) # Mean position of the cluster gives the new centroid

            current_error = np.mean(np.square(dist))

            if np.isclose(self.error, current_error, self.tol): # If the MSEs for the current and previous centroids (respectively) are within the tolerable threshold, we've reached convergence and break the loop
                self.error = current_error # Store the current MSE as an attribute for the get_error method
                break

            self.error = current_error # Store the current MSE so that we can compare it to the MSE for the next centroids in the following interation. This way we also get an MSE for a model that reaches the maximum number of iterations

        self.centroids = centroids # Store the current centroids so that they can be accessed with the get_centroids method, as well as for the predict method

    def predict(self, mat: np.ndarray) -> np.ndarray:
        """
        predicts the cluster labels for a provided 2D matrix

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features

        outputs:
            np.ndarray
                a 1D array with the cluster label for each of the observations in `mat`
        """
        dist = np.linalg.norm(self.mat - self.centroids[0,:], axis = 1).reshape(-1,1) # Once again calculate euclidean distance from each point to each centroid
        for classification in range(1, self.k):
            dist = np.append(dist, np.linalg.norm(self.mat - self.centroids[classification, :], axis = 1).reshape(-1,1), axis = 1)

        self.classes = np.argmin(dist, axis = 1) # Take the index of the minimum distance in the dist array. This will be the classification/label for the data point

        return self.classes # Return the labels

    # Last two methods are pretty self-explanatory (no pun intended), they simply return the final MSE and centroids for the trained model
    def get_error(self) -> float:
        """
        returns the final mean-squared error of the fit model

        outputs:
            float
                the mean-squared error of the fit model
        """
        return self.error

    def get_centroids(self) -> np.ndarray:
        """
        returns the centroid locations of the fit model

        outputs:
            np.ndarray
                a `k x m` 2D matrix representing the cluster centroids of the fit model
        """
        return self.centroids


import math
import numpy as np

def _get_rotate_info(angle, cx, cy, h, w):
    import math
    import numpy as np
    angle = math.radians(angle)
    cos = math.cos(angle)
    sin = math.sin(angle)
    M = np.array([[cos, sin], [-sin, cos]])
    cos = abs(cos)
    sin = abs(sin)
    nw = w * cos + h * sin
    nh = w * sin + h * cos
    return M, nw, nh

def rotate_points(points, angle, cx, cy, h, w):
    import numpy as np
    M, nw, nh = _get_rotate_info(angle, cx, cy, h, w)
    points = np.array(points)
    original_shape = points.shape
    points = points.reshape((-1, 2))
    points = M.dot((points - np.array([cx, cy])).T).T + np.array([nw // 2, nh // 2])
    points = points.reshape(original_shape)
    return points

def polar2cartesian(points, from_degree=False):
    points = np.array(points)
    shape = points.shape
    points = points.reshape((-1, 2))
    batch_angle = points[:, 1:2]
    if from_degree:
        batch_angle = np.radians(batch_angle)
    batch_angle -= np.pi
    batch_x = points[:, 0:1] * np.cos(batch_angle)
    batch_y = points[:, 0:1] * np.sin(batch_angle)
    points = np.concatenate((batch_x, batch_y), axis=1)
    points = points.reshape(shape)
    return points

def cartesian2polar(points, to_degree=False):
    points = np.array(points)
    shape = points.shape
    points = points.reshape((-1, 2))
    batch_radius = np.expand_dims(np.sqrt(np.square(points).sum(axis=1)), axis=-1)
    batch_angle = np.expand_dims(np.arctan2(points[:, 1], points[:, 0]), axis=-1) + np.pi
    if to_degree:
        batch_angle = np.degrees(batch_angle)
    points = np.concatenate((batch_radius, batch_angle), axis=1)
    points = points.reshape(shape)
    return points

def bounding_rect(points):
    points = np.array(points)
    l = np.min(points[:, 0])
    r = np.max(points[:, 0])
    t = np.min(points[:, 1])
    b = np.max(points[:, 1])
    return np.array([l, t, r, b])

def pad_quad(quad, pad_size=5, pad_ratio=None, limits=None):
    import math
    from math import inf
    limits = limits or (-inf, -inf, inf, inf)

    def dist(p1, p2):
        return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

    def mean(li):
        return sum(li) / len(li)

    def minus(la, lb):
        return [x - y for x, y in zip(la, lb)]

    def add(la, lb):
        return [x + y for x, y in zip(la, lb)]

    def get_pad_params(pad_size, pad_ratio, quad):
        # l, t, r, b = box
        p0, p1, p2, p3 = quad
        bh = mean([dist(p0, p3), dist(p1, p2)])
        bw = mean([dist(p0, p1), dist(p3, p2)])
        if pad_ratio is not None:
            if isinstance(pad_ratio, (tuple, list)):
                if len(pad_ratio) == 2:
                    pad_l = pad_r = int(min(bh, bw) * pad_ratio[0])
                    pad_t = pad_b = int(min(bh, bw) * pad_ratio[1])
                else:
                    assert len(pad_ratio) == 4
                    pad_l = int(min(bh, bw) * pad_ratio[0])
                    pad_t = int(min(bh, bw) * pad_ratio[1])
                    pad_r = int(min(bh, bw) * pad_ratio[2])
                    pad_b = int(min(bh, bw) * pad_ratio[3])
            else:
                pad_l = pad_t = pad_r = pad_b = int(min(bh, bw) * pad_ratio)
        else:
            if isinstance(pad_size, (tuple, list)):
                if len(pad_size) == 2:
                    pad_l = pad_r = pad_size[0]
                    pad_t = pad_b = pad_size[1]
                else:
                    assert len(pad_size) == 4
                    pad_l, pad_t, pad_r, pad_b = pad_size
            else:
                pad_l = pad_t = pad_r = pad_b = pad_size
        return pad_l, pad_t, pad_r, pad_b

    pad_l, pad_t, pad_r, pad_b = get_pad_params(pad_size, pad_ratio, quad)
    p0, p1, p2, p3 = quad
    p0 = add(p0, [-pad_l, -pad_t])
    p1 = add(p1, [pad_r, -pad_t])
    p2 = add(p2, [pad_r, pad_b])
    p3 = add(p3, [-pad_l, pad_b])

    return limit_quad([p0, p1, p2, p3], limits=limits)

def points_distance(p0,p1):
    return np.sqrt(np.sum(np.square(np.array(p0)-np.array(p1))))
def points_center(points):
    return np.array(points).mean(axis=0)
def limit_point(p, limits):
    if limits is None: return p
    if len(limits) == 2:
        ml, mt = 0, 0
        mr, mb = limits
    else:
        assert len(limits) == 4
        ml, mt, mr, mb = limits
    x, y = p
    x = max(ml, min(mr, x))
    y = max(mt, min(mb, y))
    return [x, y]

def limit_quad(quad, limits=None):
    quad = [limit_point(p, limits) for p in quad]
    return quad

def organize_quad_points(points):
    points = np.array(points)
    center = points.mean(axis=0)
    angles = np.arctan2(center[1] - points[:, 1], points[:, 0] - center[0]) * 180 / np.pi
    ids = np.argsort(angles)[::-1]
    polygon = points[ids]
    return polygon

def organize_polygon_points(points):
    points = np.array(points)
    center = points.mean(axis=0)
    angles = np.arctan2(center[1] - points[:, 1], points[:, 0] - center[0]) * 180 / np.pi
    ids = np.argsort(angles)[::-1]
    polygon = points[ids]
    return polygon

def polygon_length(points):
    assert len(points) >= 3
    points=list(points)
    points+=[points[0]]
    dist=0
    for i in range(len(points)-1):
        p1=points[i]
        p2=points[i+1]
        dist+=np.sqrt(np.sum(np.square(np.array(p1)-np.array(p2))))
    return dist

def calc_polygon_area(points):
    area = 0
    q = points[-1]
    for p in points:
        area += p[0] * q[1] - p[1] * q[0]
        q = p
    return abs(area / 2)

def calc_quad_angle(quad):
    '''cal angle of a quad'''
    p0, p1, p2, p3 = quad

    def center(p1, p2):
        x1, y1 = p1
        x2, y2 = p2
        return (x1 + x2) / 2, (y1 + y2) / 2

    x1, y1 = center(p0, p3)
    x2, y2 = center(p1, p2)
    angle = math.atan2(y1 - y2, x2 - x1) * 180 / math.pi
    return angle


from sympy import Matrix, Piecewise, Symbol

from piecewise_function import PiecewiseFunction

def parameterize_path(path):
    """
    Represent the given joint-space path as a function q = f(s).

    To make this useful as a first step to creating smooth trajectories, we consruct the
    parameterization such that it has a few important properties:

    1. s = 0 is the start point of the path in joint space.
    2. As s increases, we move along the path monononically.
    3. The length of path traversed for a given increase in s is constant.

    Out of convenience, we choose a parameterization such that the value of s is equal
    to the length of path travesed. This "length" is in N-dimensional joint space, but
    otherwise is the same as we would compute a path length.

    Put another way, the path length from s=0 to s=S is equal to the integral from 0 to S
    of the norm of the derivative of the parameterized path function w.r.t. the variable s.
    """
    s = Symbol('s')
    boundaries = [0.0]
    functions = []
    # q0 and q1 are succesive joint space posions in the path. "boundaries" are the values of the
    # independent variable (often time) at which we switch from one function to the next in our
    # piecewise representation.
    for q0, q1 in zip(path[:-1], path[1:]):
        q0 = Matrix(q0)
        q1 = Matrix(q1)
        s0 = boundaries[-1]
        length = (q1 - q0).norm()
        s1 = s0 + length
        direction = (q1 - q0) / length
        boundaries.append(float(s1))
        functions.append(q0 + direction * s)
    return PiecewiseFunction(boundaries, functions, s)


import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
from statsmodels.tsa.stattools import acf, pacf

matplotlib.rc('xtick', labelsize=40)
matplotlib.rc('ytick', labelsize=40)

sns.set(style="whitegrid", color_codes=True)

# xma = np.random.randint(1,50000,100)
xma = np.random.normal(0, 25, 1000)

# lets create a MA series having mean 2 and of order 2
y5 = 2 + xma + 0.8*np.roll(xma, -1) + 0.6*np.roll(xma, -2)  # + 0.6 *np.roll(xma,-3)

plt.figure(figsize=(16, 7))

# Plot ACF:
plt.subplot(121)
plt.plot(xma)
plt.subplot(122)
plt.plot(y5)
plt.show()

# calling acf function from stattools
lag_acf = acf(y5, nlags=50)

# Plot ACF:
plt.figure(figsize=(16, 7))
plt.plot(lag_acf, marker="o")
plt.axhline(y=0, linestyle='--', color='gray')
plt.axhline(y=-1.96/np.sqrt(len(y5)), linestyle='--', color='gray')
plt.axhline(y=1.96/np.sqrt(len(y5)), linestyle='--', color='gray')
plt.title('Autocorrelation Function')
plt.xlabel('number of lags')
plt.ylabel('correlation')
plt.tight_layout()
plt.show()

# calling pacf function from stattools
lag_pacf = pacf(y5, nlags=50, method='ols')

# Plot PACF
plt.figure(figsize=(16, 7))
plt.plot(lag_pacf, marker="o")
plt.axhline(y=0, linestyle='--', color='gray')
plt.axhline(y=-1.96/np.sqrt(len(y5)), linestyle='--', color='gray')
plt.axhline(y=1.96/np.sqrt(len(y5)), linestyle='--', color='gray')
plt.title('Partial Autocorrelation Function')
plt.xlabel('number of lags')
plt.ylabel('correlation')
plt.tight_layout()
plt.show()


"""
Contains all metrics used for model results comparisons
"""

# main imports
import numpy as np

def coefficient_of_determination(y, predicted):
    """Returns the coefficient of determination between `y` and `y_predicted` values

    Args:
        y: array of reference values
        predicted: array of predicted values

    Returns:
        coefficient of determination

    Example:

    >>> from ipfml import metrics
    >>> import numpy as np
    >>> y = np.arange(10)
    >>> predicted = np.arange(10) * 1.05
    >>> coeff = metrics.coefficient_of_determination(y, predicted)
    >>> int(coeff)
    1
    """

    y = np.asarray(y)
    predicted = np.asarray(predicted)

    y_mean = y.mean()

    numerator_sum = 0
    denominator_sum = 0

    for id_val, val in enumerate(y):
        numerator_sum += (predicted[id_val] - y_mean) * (predicted[id_val] -
                                                         y_mean)
        denominator_sum += (val - y_mean) * (val - y_mean)

    return numerator_sum / denominator_sum


#importing libraries
import pickle
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import style
import math
from statistics import mean
style.use('fivethirtyeight')

# =============================================================================
# #please change the dataset here by uncommenting the lines
# =============================================================================
f = open('data1_new.pkl','rb')
#f = open('data2_new.pkl','rb')
#f = open('data3_new.pkl','rb')
data1 = pickle.load(f)
f.close()
da = np.asarray(data1)
X = da[:,0]
Y = da[:,1]

#taking mean of the data
X_bar = mean(X)
Y_bar = mean(Y)
delta_X = X - X_bar
delta_Y = Y - Y_bar

variance_X = (np.sum(delta_X**2))/(X.shape[0]-1)
variance_Y = (np.sum(delta_X**2))/(Y.shape[0]-1)

delta_XY = np.multiply(delta_X,delta_Y)
covarience_XY = np.sum(delta_XY)/(X.shape[0]-1)
covariance_matrix = np.array([[variance_X, covarience_XY], [covarience_XY, variance_Y]])
slope = np.sum((delta_X)*(delta_Y))/np.sum(delta_X**2)
intercept = mean(Y)-slope*mean(X)
plt.scatter(X,Y, color = 'blue')
plt.plot(X, slope*X+intercept,color = 'red', label = 'line fit without RANSAC')
plt.axis([-150,150,-100,100])
plt.show()

#writing the function for line fitting
def line_fitting(X_and_Y_values):
    X = X_and_Y_values[:,0]
    Y = X_and_Y_values[:,1]
    m = (Y[1]-Y[0])/X[1]-X[0]
    c = Y[1] - m*X[1]
    return m,c

# calculating the distance from the line
def dist_from_line(points,slope,intercept):
    m = slope
    c = intercept
    distance=0
    distance = (abs(c+(m*points[0])-points[1]))/np.sqrt(1 + m**2)
    return distance

#initializing parameters
threshold_distance = 10
best_a = np.array([])
global_inliers = 0

#for loop for number of iterations
for count in range(500):
    local_inliers = 0
    random_points = np.random.choice(da.shape[0],2,replace = False)
    initial_two_points = da[random_points,:]
    m,c = line_fitting(initial_two_points)

    #calculating the distance of all the points from the line and sorting them
    for distance in da:
        d = dist_from_line(distance,m,c)
        if d<= threshold_distance:
            local_inliers += 1

    #updating the number of inliers
    if global_inliers <= local_inliers:
        global_inliers = local_inliers
        best_a = initial_two_points

#print("no. of inliers: "+ str(global_inliers))
best_fit_slope, best_fit_intercept = line_fitting(best_a)
y = (best_fit_slope*X + best_fit_intercept)
plt.plot(X,y,color = 'green', label = 'line fit with RANSAC')
plt.show()


import numpy as np

class Prediction:

    @staticmethod
    def softmax(x):
        '''Compute softmax values for each value in x.'''
        return np.exp(x) / np.sum(np.exp(x), axis=0)

    @classmethod
    def find_weighted_averages(cls, data, window=2):
        ''' Given an array of arrays, calculates the averages along the 0 axis for
            for the past few elements (default 2) weighted by a softmax function,
            with the heaviest weights at the end of the window.
            'window' must be an integer between 1 and the length of the enclosing array.
            Returns a numpy array.
        '''
        result = []
        weights = cls.softmax(np.array(list(range(window))))

        for i in range(len(data)):

            if (i >= window-1):
                # Use the full window previously defined if possible
                avg = np.average(data[i-(window-1):i+1], axis=0, weights=weights)
            else:
                # Otherwise, too close to an edge so recalculate weights for smaller window
                alt_weights = cls.softmax(np.array(list(range(i+1))))
                avg = np.average(data[0:i+1], axis=0, weights=alt_weights)

            result.append(avg)

        return result

    @classmethod
    def predict_next_values(cls, data, window=2):
        ''' Predict the next set of numbers by applying the last weighted avg of the diffs to the last data set '''

        # If empty array, just return it
        if (len(data) == 0):
            return data

        # If there's only one element, return that element as the prediction
        if (len(data) == 1):
            return data[0]

        # Otherwise perform the weighted average of the diffs
        diffs = np.diff(data, axis=0)
        wavgs = cls.find_weighted_averages(diffs, window=window)
        return np.add(wavgs[-1], data[-1])


import numpy as np

def main():

    # given alpha value
    alpha = 0.85

    # read text from given data
    lines = read_data("data.txt")

    # get node and edge
    first_line = lines[0]
    node = int(first_line[0:first_line.index('\t')])
    edge = int(first_line[first_line.index('\t')+1:first_line.index('\n')])

    # get edges in matrix form
    edge_mat = get_edge_mat(lines[1:])

    # get adjacency matrix
    adj_mat = get_adj_mat(node, edge_mat)

    # get transition probability matrix
    tran_prob_mat = get_prob_mat(adj_mat)

    # integrate teleporting (smoothing)
    mat_with_teleport = get_mat_with_teleport(tran_prob_mat,alpha,node)

    # get pagerank values
    init_distribution = (np.ones(node)/node).tolist()
    pagerank_vals = get_pagerank(mat_with_teleport, init_distribution)
    print(pagerank_vals)

def read_data(file_name):
    f = open(file_name, 'r') # open file_name
    lines = f.readlines()    # store data into lines
    f.close()                # close file_name
    return lines             # return data

def get_edge_mat(data):
    tmp_matrix = []          # initialize tmp_matrix
    num_of_spaces = data[0].strip().count(' ')    # count # of spaces after stripping blanks on sides
    for datum in data:
        n = int(datum.strip()[0:datum.strip().find(' ')]) # store node in n
        e = int(datum.strip()[num_of_spaces:])            # store edge in e
        tmp = [n,e]                               # store [node,edge] in tmp
        tmp_matrix.append(tmp)                    # add [node,edge] to tmp_matrix

    return tmp_matrix

def get_adj_mat(num_of_node, edge_matrix):    # get adjacency matrix
    adj_matrix = np.zeros((num_of_node,num_of_node)) # get matrix of zeros
    for i in edge_matrix:
        adj_matrix[i[0]-1,i[1]-1] = adj_matrix[i[0]-1,i[1]-1] + 1 # add 1 to places that have edge(s)
    return adj_matrix

def get_prob_mat(adj_matrix):
    prob_matrix = []
    for i in adj_matrix:
        if (sum(i)!=0):     # calculate # of edges and if not zero
            prob_matrix.append(i/sum(i))  # divide all elements by total # and add to prob_matrix
        else:               # if there are no edges at all
            prob_matrix.append(i/1)       # then divide all elements(0's) by 1 and add to prob_matrix
    prob_matrix = np.array(prob_matrix)   # make prob_matrix into numpy array
    return prob_matrix

def get_mat_with_teleport(prob_mat, alpha, node):
    prob_mat = prob_mat + ((1-alpha)/node) # add (.15/node) to each element for teleporting
    prob_mat = prob_mat / (1+(1-alpha))    # divide each element by (1.15)
    return prob_mat

def get_pagerank(tr_matrix, initial_distribution):
    vector_array = []             # initialize vector_array as empty vector
    vector_array.append(initial_distribution)   # add initial_distribution vector into vector_array
    vector_array.append((np.dot(initial_distribution,tr_matrix).tolist())) # add dot product result
    cnt = 0                       # initialize cnt to count # of iterations needed to getpagerank

    # execute matrix multiplication until x is the SAME (x=xP)
    while(vector_array[-2]!=vector_array[-1]):
        vector_array.append((np.dot(vector_array[-1],tr_matrix)).tolist())
        cnt = cnt + 1
    print('Number of iterations: %d'% cnt)
    print('Pagerank for each page:')
    return vector_array[-1]

if __name__ == "__main__":
    main()


import sympy as sym
import numpy as np
import copy

#Defining Coordinates
x = [sym.symbols('t'), sym.symbols('r'), sym.symbols('theta'), sym.symbols('phi')]

#Defining Constants
G, M = sym.symbols('G, M')

#Defining the Metric Tensor (g_i_j)
g = [[-1*(1-2*G*M/x[1]), 0, 0, 0],
     [0, 1/(1-2*G*M/x[1]), 0, 0],
     [0, 0, x[1]**2, 0],
     [0, 0, 0, x[1]**2 *sym.sin(x[2])**2]]

print('Metric (g_i_j): \n', np.array(g), '\n')

#Calculating the Inverse Metric Tensor (g^i^j)
g_inv = sym.Matrix(g).inv()

print('Inverse Metric (g^i^j): \n', np.array(g_inv), '\n')

#Defining the Christoffel Pseudo-Tensor (gamma^i_j_k)
gamma = []
for i in range(4):
    m_temp = []
    for j in range(4):
        temp = []
        for k in range(4):
            temp.append(0)
        m_temp.append(temp)
    gamma.append(m_temp)

#Calculating the Christoffel Pseudo-Tensor (gamma^i_j_k)
for i in range(4):
    for j in range(4):
        for k in range(4):
            for s in range(4):
                gamma[i][j][k] += 0.5*g_inv[i, s]*(sym.diff(g[k][s], x[j])+sym.diff(g[j][s], x[k])-sym.diff(g[j][k], x[s]))
            gamma[i][j][k] = sym.simplify(gamma[i][j][k]) #Expression Simplification

print('Christoffel Pseudo-Tensor (gamma^i_j_k): \n', np.array(gamma), '\n')

#Defining the Riemann Curvature Tensor (R^i_j_k_w)
R = []
for i in range(4):
    b_temp = []
    for j in range(4):
        m_temp = []
        for k in range(4):
            temp = []
            for w in range(4):
                temp.append(0)
            m_temp.append(temp)
        b_temp.append(m_temp)
    R.append(b_temp)

#Defining the Riemann Curvature Tensor (R_i_j_k_w)
R_ = copy.deepcopy(R)

#Defining the Riemann Curvature Tensor (R^i^j^k^w)
Ru = copy.deepcopy(R)

#Calculating the Riemann Curvature Tensor (R^i_j_k_w)
for i in range(4):
    for j in range(4):
        for k in range(4):
            for w in range(4):
                R[i][j][k][w] = sym.diff(gamma[i][j][k], x[w]) - sym.diff(gamma[i][j][w], x[k])
                for s in range(4):
                    R[i][j][k][w] += gamma[s][j][k]*gamma[i][w][s] - gamma[s][j][w]*gamma[i][k][s]
                R[i][j][k][w] = sym.simplify(R[i][j][k][w]) #Expression Simplification

print('Riemann Curvature Tensor (R^i_j_k_w): \n', np.array(R), '\n')

#Calculating the Riemann Curvature Tensor (R_i_j_k_w)
for i in range(4):
    for j in range(4):
        for k in range(4):
            for w in range(4):
                for s in range(4):
                    R_[i][j][k][w] += g[i][s]*R[s][j][k][w]
                R_[i][j][k][w] = sym.simplify(R_[i][j][k][w]) #Expression Simplification

print('Riemann Curvature Tensor (R_i_j_k_w): \n', np.array(R_), '\n')

#Calculating the Riemann Curvature Tensor (R^i^j^k^w)
for i in range(4):
    for j in range(4):
        for k in range(4):
            for w in range(4):
                for s1 in range(4):
                    for s2 in range(4):
                        for s3 in range(4):
                            Ru[i][j][k][w] += g_inv[j, s1]*g_inv[k, s2]*g_inv[w, s3]*R[i][s1][s2][s3]
                Ru[i][j][k][w] = sym.simplify(Ru[i][j][k][w]) #Expression Simplification

print('Riemann Curvature Tensor (R^i^j^k^w): \n', np.array(Ru), '\n')

#Defining the Ricci Tensor (R_i_j)
r = []
for i in range(4):
    temp = []
    for j in range(4):
        temp.append(0)
    r.append(temp)

#Calculating the Ricci Tensor (R_i_j)
for i in range(4):
    for j in range(4):
        for s1 in range(4):
            for s2 in range(4):
                r[i][j] += g_inv[s1, s2]*R_[s1][i][s2][j]
            r[i][j] = sym.simplify(r[i][j]) #Expression Simplification

print('Ricci Tensor (R_i_j): \n', np.array(r), '\n')

#Defining & Calculating the Scalar Curvature (R)
K = 0
for s1 in range(4):
    for s2 in range(4):
        K += g_inv[s1, s2]*r[s1][s2]
K = sym.simplify(K) #Expression Simplification

print('Scalar Curvature (R): \n', K, '\n')

#Defining & Calculating the Kretschmann Scalar (R_i_j_k_w*R^i^j^k^w):
RR = 0
for s1 in range(4):
    for s2 in range(4):
        for s3 in range(4):
            for s4 in range(4):
                RR += R_[s1][s2][s3][s4]*Ru[s1][s2][s3][s4]
RR = sym.simplify(RR) #Expression Simplification

print('Kretschmann Scalar (R_i_j_k_w*R^i^j^k^w): \n', RR, '\n')


#Developer : Vinay Venkatesh
#Date : 2/20/2021

import matplotlib.pyplot as plt
import numpy as np
from sympy import *

def constant(c):

    '''
    A constant function is a function whose value is the same for every input value.
    With a constant function, for any two points in the interval, a change in x results in a zero change in f(x).

    Learn More: https://www.varsitytutors.com/hotmath/hotmath_help/topics/constant-function
    '''

    fig = plt.figure()
    # Hold activation for multiple lines on same graph
    # Set x-axis range
    ax = fig.add_subplot(1, 1, 1)
    ax.spines['left'].set_position('center')
    ax.spines['bottom'].set_position('center')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')
    plt.xlim((-1 * (c + c), c + c))
    # Set y-axis range
    plt.ylim((-1 * (c + c), c + c))
    # Draw lines to split quadrants

    plt.axhline(y = c)

    plt.title('Constant Graph')

    plt.show()

def linear(m, b):

  '''
  A linear function is a function whose graph is a straight line.
  A linear function has one independent variable and one dependent variable.
  The independent variable is x and the dependent variable is y.
  The inputs, m and b stand for the slope and y intercept.

  The difference between `linear()` and `psflinear()` is the forms of the linear functions.
  `psflinear()` is meant to be in a y - y1 = m(x - x1) form and `linear()` is meant to be in a y = mx+b form.

  Learn More: https://www.mathsisfun.com/algebra/linear-equations.html
  '''

  #y = mx + b
  x = np.linspace(-5,5,100)
  y = m*x+b
  plt.plot(x, y, '-r', label=f'y={m}x+{b}')
  plt.title(f'Linear Graph')
  plt.xlabel('x', color='#1C2833')
  plt.ylabel('y', color='#1C2833')
  plt.legend(loc='upper left')
  plt.grid()
  plt.show()

  x, y = symbols('x y')

  equation = Eq(y, m*x+b)

  # Use sympy.subs() method
  result = solve(equation.subs(y, 0))

  for i in range(0, len(result)):
    result[i] = round(result[i].simplify().evalf(),3)

  print(f"Slope: {m}")
  print(f"Y-Intercept: (0, {b})")
  print(f"X-Intercept: ")
  for i in range(0, len(result)):
    print(f"({result[i]}, 0)")

def psflinear(y1, m, x1):

  '''
  A linear function is a function whose graph is a straight line.
  A linear function has one independent variable and one dependent variable.
  The independent variable is x and the dependent variable is y.
  The inputs, y1 and m and x1 stand for a y coordinate, slope, and x coordinate.

  The difference between `psflinear()` and `linear()` is the forms of the linear functions.
  `psflinear()` is meant to be in a y - y1 = m(x - x1) form and `linear()` is meant to be in a y = mx+b form.

  Learn More: https://www.mathsisfun.com/algebra/linear-equations.html
  '''

  #y - y1 = m(x - x1)
  x = np.linspace(-5,5,100)
  y = m * (x - x1) + y1
  plt.plot(x, y, '-r', label=f'y-{y1}={m}(x-{x1})')
  plt.title(f'Linear Graph')
  plt.xlabel('x', color='#1C2833')
  plt.ylabel('y', color='#1C2833')
  plt.legend(loc='upper left')
  plt.grid()
  plt.show()

  x, y = symbols('x y')

  equation = Eq(y, m*(x-x1)+y1)

  # Use sympy.subs() method
  result = solve(equation.subs(y, 0))

  for i in range(0, len(result)):
    result[i] = round(result[i].simplify().evalf(),3)

  print(f"Slope: {m}")
  print(f"Y-Intercept: (0, {y1})")
  print(f"X-Intercept: ")
  for i in range(0, len(result)):
    print(f"({result[i]}, 0)")


from scipy import interpolate

# 1. Cubic Spline Interpolation

def cspline_interp(a,b,c):

    coefficients = interpolate.splrep(b, c)
    result = interpolate.splev(a, coefficients)

    return result

# 2. 1st Derivative of the Interpolated Cubic Spline

def cspline_deriv(a, b, c):

    coefficients = interpolate.splrep(b, c)
    result = interpolate.splev(a, coefficients, der=1)

    return result

# 2. Iterative Solver using a simplified Golden Search Algorithm

    # The maxima minima between which the solution is searched must be clearly defined

def itr_sol_search(func_name, var_min, var_max):

    while True:

        var_avg = (var_min + var_max)/2

        solution = func_name(var_avg)

        if  ( round(solution,4) == 0 ):
            break
        elif ( solution > 0 ):
            var_max = var_avg
        elif ( solution < 0 ):
            var_min = var_avg

    return var_avg


'''
bsplinebasis.py
Set up bspline basis

31-Mar-2021: First written

vision is to follow Vasiliev's technique for deriving radial basis functions using spline representation

built on scipy's implementation, but may use the naive implementation
as well.

TODO
1. investigate loss functions
2. investigate penalised fitting

'''

import numpy as np

from scipy.interpolate import BSpline

def B(x, k, i, t):
    '''
    the pedagogical example from https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.BSpline.html

    '''
    if k == 0:
        return 1.0 if t[i] <= x < t[i+1] else 0.0
    if t[i+k] == t[i]:
        c1 = 0.0
    else:
        c1 = (x - t[i])/(t[i+k] - t[i]) * B(x, k-1, i, t)
    if t[i+k+1] == t[i+1]:
        c2 = 0.0
    else:
        c2 = (t[i+k+1] - x)/(t[i+k+1] - t[i+1]) * B(x, k-1, i+1, t)
    return c1 + c2

def bspline(x, t, c, k):
    n = len(t) - k - 1
    assert (n >= k+1) and (len(c) >= n)
    return sum(c[i] * B(x, k, i, t) for i in range(n))


import cvxpy as cp
import numpy as np

def sdp(rho0, rho1):
    n = len(rho0)
    t = 0.5  # Since t is arbitrary, change to whatever you like.
    C = np.zeros((2 * n, 2 * n))
    try:
        C[0:n, 0:n] = (1 - t) * rho1.T
        C[n:2 * n, n:2 * n] = t * rho0.T
    except TypeError:
        print("TypeError: can't multiply sequence by non-int of type 'float'.\nTry checking input states. Input states must be square matrices!")
        return False
    A = []
    b = []

    A0 = np.zeros((2 * n, 2 * n))
    A0[0:n, 0:n] = rho1.T
    A0[n:2 * n, n:2 * n] = -1 * (rho0.T)
    A.append(A0)
    b.append(0)
    for i in range(n):
        for j in range(n):
            E = np.zeros((2 * n, 2 * n))
            E[i][j] = 1
            E[n + i][n + j] = 1
            A.append(E.T)
            b.append(1 if i == j else 0)

    # Define and solve the CVXPY problem.
    # Create a symmetric matrix variable.
    X = cp.Variable((2 * n, 2 * n), symmetric=True)
    # The operator >> denotes matrix inequality.
    constraints = [X >> 0]
    constraints += [
        cp.trace(A[i] @ X) == b[i] for i in range(n**2 + 1)
    ]
    prob = cp.Problem(cp.Minimize(cp.trace(C @ X)),
                      constraints)
    prob.solve()

    # DEBUGGING.
    # print(A)
    # print(b)

    # Print result.
    print("Input rho_0 is:")
    print(rho0)
    print("Input rho_1 is:")
    print(rho1)
    '''
    Change desired decimal places for solution here
    '''
    value = round(prob.value, ndigits=5)
    solution = np.around(X.value, decimals=3)
    print("The optimal value (min_pwc) is", value)
    print("A solution X (composed of P0 and P1) is")
    print(solution)
    print("P0 is:")
    print(solution[0:n, 0:n])
    print("P1 is:")
    print(solution[n:2 * n, n:2 * n])

if __name__ == "__main__":
    '''
    Change rho_0 and rho_1 here
    '''
    rho0 = np.array([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
    rho1 = np.array([[0.25, 0, 0, 0], [0, 0.25, 0, 0],
                     [0, 0, 0.25, 0], [0, 0, 0, 0.25]])
    sdp(rho0, rho1)


import numpy as np

input_size = 2 # number of features
lr = 0.1
epochs = 10

X = np.array([
              [1,2],
              [-1,2],
              [0,-1],
             ])

W = np.array([1.0,-0.8]) # initialize weights

y = np.array([
	      1,
              -1,
	      -1
	      ])

def activation_function(z):
	if z >= 0:
		return 1
	else:
		return -1

for epoch in range(epochs):
	n = 0 # number of correct classifications counter
	print('EPOCH# ' + str(epoch))
	for i in range(y.shape[0]):
		x = X[i]
		z = np.dot(W,x)
		a = activation_function(z)
		error = y[i] - a

		if error == 0:
			n = n + 1

		delta_w = lr * error * x

		if y[i] != a:
			W = W + delta_w # update the weight vector
		else:
			W = W

	if n == 3:
		print('Congratulations! The perceptron converged successfully.')
		print('Those are the weights:')
		print(W)
		break


import numpy as np

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

def gradient(x, y, w):
    """MSE gradient."""
    y_hat = x @ w
    error = y - y_hat
    gradient = -(1.0 / len(x)) * 2 * x.T @ error
    mse = (error ** 2).mean()
    return gradient, mse

def gradient_descent(
    x,
    y,
    w,
    alpha,
    tolerance: float = 2e-5,
    max_iterations: int = 1000,
    verbose: bool = False,
    print_progress: int = 10,
    history: bool = False,
):
    """MSE gradient descent."""
    iterations = 1
    if verbose:
        print(f"Iteration 0.", "Weights:", [f"{_:.2f}" for _ in w])
    if history:
        ws = []
        mses = []
    while True:
        g, mse = gradient(x, y, w)
        if history:
            ws.append(list(w))
            mses.append(mse)
        w_new = w - alpha * g
        if sum(abs(w_new - w)) < tolerance:
            if verbose:
                print(f"Converged after {iterations} iterations!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        if iterations % print_progress == 0:
            if verbose:
                print(
                    f"Iteration {iterations}.",
                    "Weights:",
                    [f"{_:.2f}" for _ in w_new],
                )
        iterations += 1
        if iterations > max_iterations:
            if verbose:
                print(f"Reached max iterations ({max_iterations})!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        w = w_new
    if history:
        w = w_new
        _, mse = gradient(x, y, w)
        ws.append(list(w))
        mses.append(mse)
        return ws, mses

def stochastic_gradient_descent(
    x,
    y,
    w,
    alpha,
    tolerance: float = 2e-5,
    max_iterations: int = 1000,
    verbose: bool = False,
    print_progress: int = 10,
    history: bool = False,
    seed=None,
):
    """MSE stochastic gradient descent."""
    if seed is not None:
        np.random.seed(seed)
    iterations = 1
    if verbose:
        print(f"Iteration 0.", "Weights:", [f"{_:.2f}" for _ in w])
    if history:
        ws = []
        mses = []
    while True:
        i = np.random.randint(len(x))
        g, mse = gradient(x[i, None], y[i, None], w)
        if history:
            ws.append(list(w))
            mses.append(mse)
        w_new = w - alpha * g
        if sum(abs(w_new - w)) < tolerance:
            if verbose:
                print(f"Converged after {iterations} iterations!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        if iterations % print_progress == 0:
            if verbose:
                print(
                    f"Iteration {iterations}.",
                    "Weights:",
                    [f"{_:.2f}" for _ in w_new],
                )
        iterations += 1
        if iterations > max_iterations:
            if verbose:
                print(f"Reached max iterations ({max_iterations})!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        w = w_new
    if history:
        w = w_new
        _, mse = gradient(x, y, w)
        ws.append(list(w))
        mses.append(mse)
        return ws, mses

def minibatch_gradient_descent(
    x,
    y,
    w,
    alpha,
    batch_size,
    tolerance: float = 2e-5,
    max_iterations: int = 1000,
    verbose: bool = False,
    print_progress: int = 10,
    history: bool = False,
    seed=None,
):
    """MSE stochastic gradient descent."""
    if seed is not None:
        np.random.seed(seed)
    iterations = 1
    if verbose:
        print(f"Iteration 0.", "Weights:", [f"{_:.2f}" for _ in w])
    if history:
        ws = []
        mses = []
    while True:
        i = np.random.choice(
            range(len(x)), batch_size, replace=False
        )  # no replacement
        g, mse = gradient(x[i], y[i], w)
        if history:
            ws.append(list(w))
            mses.append(mse)
        w_new = w - alpha * g
        if sum(abs(w_new - w)) < tolerance:
            if verbose:
                print(f"Converged after {iterations} iterations!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        if iterations % print_progress == 0:
            if verbose:
                print(
                    f"Iteration {iterations}.",
                    "Weights:",
                    [f"{_:.2f}" for _ in w_new],
                )
        iterations += 1
        if iterations > max_iterations:
            if verbose:
                print(f"Reached max iterations ({max_iterations})!")
                print("Final weights:", [f"{_:.2f}" for _ in w_new])
            break
        w = w_new
    if history:
        w = w_new
        _, mse = gradient(x, y, w)
        ws.append(list(w))
        mses.append(mse)
        return ws, mses