#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()


#!/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()


"""
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 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()


#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)")


'''
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


import numpy as np

def QRDecomposition(A):
	n = np.shape(A)[0] #pegando o tamanho das linhas de A
	m = np.shape(A)[1] #pegando o tamanho das colunas de A
	Q = np.zeros((n,m)) #declarando a matriz Q
	R = np.zeros((m,m)) #declarando a matriz R

	for j in range(0, m):
		A_column = A[:, j] #pegando as colunas da matriz A
		V = np.zeros(n) #declarando o vetor V
		V = A_column #V igual a coluna j de A
		for i in range (0, j):
			R[i,j] = Q[:,i].dot(A_column) #fazendo o calculo do R[i,j] = coluna i de Q * coluna j de A ( i != j)
			V -= (Q[:,i].dot(A_column))*Q[:,i] #fazendo o calculo de V
		R[j,j] = np.linalg.norm(V) # R[j,j] = norma da coluna j de A (i == j)
		Q[:,j] = V/np.linalg.norm(V) #normalizando V e atribuindo a coluna j de Q

	return Q, R

def decompLU(A):
	L = np.eye(np.shape(A)[0]) #matriz L com a dimensao de A com a diagonal principla igual a 1
	U = np.zeros(np.shape(A)) #matriz U full zero
	lim = np.shape(A)[1] #pegando o tamanho da coluna de A (matriz quadrada)
	sum = 0

	for i in range(0, lim):
		for j in range(0, lim):
			sum = 0
			#Parte do somatorio de U que vai de 0 a i-1 e acumula L[i][k]*U[k][j] sendo k a variavel iteradora
			for k in range (0, i):
				sum += L[i,k]*U[k,j]
			U[i,j] = A[i,j] - sum #atribuindo o valor de U[i][j]

		for j in range (0, lim):
			sum = 0
			#Parte do somatorio de L que vai de 0 a i-1 e acumula L[j][k]*U[k][i] sendo k a variavel iteradora
			for k in range (0, i):
				sum+= L[j,k]*U[k,i]
			L[j,i] = (A[j,i] - sum)/U[i,i] #atribuindo o valor L[i][j]

	return L, U #retornando o resultado

def solveLU(L, U, b):
	#Resolvendo o sistema A*x = b
	#Como A = L*U, entao (L*U)*x = b
	#Vamos fazer L*(U*x) = b e U*x=y, portanto L*y=b
	#Logo, para encontrarmos o resultado resolvemos o sistema U*x = y

	y = np.zeros(np.shape(b)) #matriz coluna y com a mesma dimensao de b
	x = np.zeros(np.shape(b)) #matriz coluna x (resultado do sistema) com a mesma dimensao de b

	for i in range (0, np.shape(b)[0]): #resolvendo o sistema  L*y=b
		for j in range (0,i):
			y[i] -= y[j]*L[i,j]
		y[i]+=b[i]

	for i in range (np.shape(b)[0]-1, -1, -1): #resolvendo o sistema U*x=b
		for j in range (np.shape(b)[0]-1, i, -1):
			x[i] -= x[j]*U[i,j]
		x[i]+=y[i]
		x[i]/=U[i,i]

	return x #retornando o resultado

def LeastSquaresQR(x, y, n):
	#Realizar a resolucao do sistema R*alpha = (Q^T)*y
	V = np.vander(x,n) #criando a matriz de vandermonde V
	(Q,R) = QRDecomposition(V) #fazendo a decomposicao V = Q*R
	b = np.transpose(Q).dot(y) #criando a matriz b = (Q^T)*y
	(L,U) = decompLU(R) #fazendo a decomposicao LU de R
	alpha = solveLU(L,U,b) #resolvendo o sistema usando a decomposicao LU (R*alpha = b / L*U*alpha = b)
	return alpha[::-1] #retornando o vetor ao contrario

x = [-1.0, 0.0, 1.0, 2.0];
y = [0.0, -1.0, 0.0, 7.0];
print(x)
print(y)

alpha = LeastSquaresQR(x, y, 2+1) #como queremos uma aproximacao para um polinomio do segundo grau o n = 2+1

print(alpha)


# -----------------------------------------------------------------------------
# From https://en.wikipedia.org/wiki/Minkowski–Bouligand_dimension:
#
# In fractal geometry, the Minkowski–Bouligand dimension, also known as
# Minkowski dimension or box-counting dimension, is a way of determining the
# fractal dimension of a set S in a Euclidean space Rn, or more generally in a
# metric space (X, d).
# -----------------------------------------------------------------------------

# From https://github.com/rougier/numpy-100 (#87)
# From https://gist.github.com/rougier/e5eafc276a4e54f516ed5559df4242c0

import numpy as np

def boxcount(Z, k):
    """
    returns a count of squares of size kxk in which there are both colours (black and white), ie. the sum of numbers
    in those squares is not 0 or k^2
    Z: np.array, matrix to be checked, needs to be 2D
    k: int, size of a square
    """
    S = np.add.reduceat(
        np.add.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0),
        np.arange(0, Z.shape[1], k), axis=1)  # jumps by powers of 2 squares

    # We count non-empty (0) and non-full boxes (k*k)
    return len(np.where((S > 0) & (S < k * k))[0])

def boxcount_grayscale(Z, k):
    """
    find min and max intensity in the box and return their difference
    Z - np.array, array to find difference in intensities in
    k - int, size of a box
    """
    S_min = np.fmin.reduceat(
        np.fmin.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0),
        np.arange(0, Z.shape[1], k), axis=1)
    S_max = np.fmax.reduceat(
        np.fmax.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0),
        np.arange(0, Z.shape[1], k), axis=1)
    return S_max - S_min

def fractal_dimension(Z, threshold=0.9):
    """
    calculate fractal dimension of an object in an array defined to be above certain threshold as a count of squares
    with both black and white pixels for a sequence of square sizes. The dimension is the a coefficient to a poly fit
    to log(count) vs log(size) as defined in the sources.
    :param Z: np.array, must be 2D
    :param threshold: float, a thr to distinguish background from foreground and pick up the shape, originally from
    (0, 1) for a scaled arr but can be any number, generates boolean array
    :return: coefficients to the poly fit, fractal dimension of a shape in the given arr
    """
    # Only for 2d image
    assert (len(Z.shape) == 2)

    # Transform Z into a binary array
    Z = (Z < threshold)

    # Minimal dimension of image
    p = min(Z.shape)

    # Greatest power of 2 less than or equal to p
    n = 2 ** np.floor(np.log(p) / np.log(2))

    # Extract the exponent
    n = int(np.log(n) / np.log(2))

    # Build successive box sizes (from 2**n down to 2**1)
    sizes = 2 ** np.arange(n, 1, -1)

    # Actual box counting with decreasing size
    counts = []
    for size in sizes:
        counts.append(boxcount(Z, size))

    # Fit the successive log(sizes) with log (counts)
    coeffs = np.polyfit(np.log(sizes), np.log(counts), 1)
    return -coeffs[0]

def fractal_dimension_grayscale(Z):
    """
    works the same as fractal_dimension() just does not look at counts and does not require a binary arr rather is looks
    at intensities (hence can be used for a grayscale image) and returns fractal dimensions D_B and D_M (based on sums
    and means), as described in https://imagej.nih.gov/ij/plugins/fraclac/FLHelp/Glossary.htm#grayscale
    :param Z: np. array, must be 2D
    :return: D_B and D_M fractal dimensions based on polyfit to log(sum) or log(mean) resp. vs log(sizes)
    """
    # Only for 2d image
    assert (len(Z.shape) == 2)

    # Minimal dimension of image
    p = min(Z.shape)

    # Greatest power of 2 less than or equal to p
    n = 2 ** np.floor(np.log(p) / np.log(2))

    # Extract the exponent
    n = int(np.log(n) / np.log(2))

    # Build successive box sizes (from 2**n down to 2**1)
    sizes = 2 ** np.arange(n, 1, -1)

    # Actual box counting with decreasing size
    i_difference = []
    for size in sizes:
        i_difference.append(boxcount_grayscale(Z, size))

    # D_B
    d_b = [np.sum(x) for x in i_difference]

    # D_M
    d_m = [np.mean(x) for x in i_difference]

    # Fit the successive log(sizes) with log (sum)
    coeffs_db = np.polyfit(np.log(sizes), np.log(d_b), 1)
    # Fit the successive log(sizes) with log (mean)
    coeffs_dm = np.polyfit(np.log(sizes), np.log(d_m), 1)

    return -coeffs_db[0], -coeffs_dm[0]

def fractal_dimension_grayscale_DBC(Z):
    """
    Differential box counting method with implementation of appropriate box height selection.
    :param Z: 2D np.array
    :return: fd for a grayscale image
    """
    # Only for 2d image
    assert (len(Z.shape) == 2)

    # Minimal dimension of image
    p = min(Z.shape)

    # Greatest power of 2 less than or equal to p
    n = 2 ** np.floor(np.log(p) / np.log(2))

    # Extract the exponent
    n = int(np.log(n) / np.log(2))

    # Build successive box sizes (from 2**n down to 2**1)
    sizes = 2 ** np.arange(n, 1, -1)

    # get the mean and standard deviation of the image intensity to rescale r
    mu = np.mean(Z)
    sigma = np.std(Z)

    # total number of gray levels, used for computing s'
    G = len(np.unique(Z))

    # scaled scale of each block, r=size(s)
    # TODO -- when to rescale, what should a be
    # when to rescale -- either always or when the pixels in the selected box don't fall in +- 1 std, so far always
    a = 1
    r_prime = sizes / (1 + 2 * a * sigma)

    # Actual box counting with decreasing size
    i_difference = []
    for size in sizes:
        # rescale
        n_r = np.ceil(boxcount_grayscale(Z, size) / r_prime)
        # if max==min per the box, replace the 0 result with 1
        n_r[n_r == 0] = 1
        i_difference.append(n_r)

    # contribution from all boxes
    N_r = [np.sum(x) for x in i_difference]

    # Fit the successive log(sizes) with log (sum)
    coeffs = np.polyfit(np.log(sizes), np.log(N_r), 1)

    return -coeffs[0]


#!/usr/bin/python
#-*- encoding: utf-8 -*-
"""
Copyright (c) 2015 @myuuuuun
Released under the MIT license.
"""

import math
import numpy as np
import functools
import matplotlib.pyplot as plt
import matplotlib.cm as cm
EPSIRON = 1.0e-8

# P0〜P_(length-1)までのルジャンドル多項式の, xにおける値の配列を返す
def legendre(x, length):
    values = [1, x]
    for i in range(2, length):
        v = ((2*i-1)*x*values[i-1] - (i-1) * values[i-2]) / i
        values.append(v)

    return values

# P0〜P_(length-1)までのチェビシェフ多項式の, xにおける値の配列を返す
def chebyshev(x, length):
    values = []
    for i in range(length):
        v = np.cos(i * np.arccos(x))
        values.append(v)

    return values

if __name__ == '__main__':
    # 共通設定
    length = 6
    x_list = np.arange(-0.99, 1.00, 0.01)
    f_matrix = np.zeros((length, 199), dtype=float)

    # legendre
    """
    for i, x in enumerate(x_list):
        values = legendre(x, length)
        for j in range(length):
            f_matrix[j][i] = values[j]

    fig, ax = plt.subplots()
    plt.title("Legendre多項式")

    plt.xlabel("x")
    plt.ylabel("f")
    plt.xlim(-1, 1)
    for j in range(length):
        plt.plot(x_list, f_matrix[j], color=cm.gist_earth(j/length), label='P{0}'.format(j))
    plt.legend()
    plt.show()
    """

    """
    # chebyshev
    for i, x in enumerate(x_list):
        values = chebyshev(x, length)
        for j in range(length):
            f_matrix[j][i] = values[j]

    fig, ax = plt.subplots()
    plt.title("Chebyshev多項式")

    plt.xlabel("x")
    plt.ylabel("f")
    plt.xlim(-1, 1)
    for j in range(length):
        plt.plot(x_list, f_matrix[j], color=cm.gist_earth(j/length), label='P{0}'.format(j))
    plt.legend()
    plt.show()
    """


from pprint import pprint
from numpy import array, zeros, diag, diagflat, dot

def jacobi(A,b,N=25,x=None):
    """Solves the equation Ax=b via the Jacobi iterative method."""
    # Create an initial guess if needed
    if x is None:
        x = zeros(len(A[0]))

    # Create a vector of the diagonal elements of A
    # and subtract them from A
    D = diag(A)
    R = A - diagflat(D)

    # Iterate for N times
    for i in range(N):
        x = (b - dot(R,x)) / D
    return x

A = array([[2.0,1.0],[5.0,7.0]])
b = array([11.0,13.0])
guess = array([1.0,1.0])

sol = jacobi(A,b,N=25,x=guess)

print("A:")
pprint(A)

print("b:")
pprint(b)

print("x:")
pprint(sol)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, date

sample_size = 500
sigma_e = 3.0             # true value of parameter error sigma
random_num_generator = np.random.RandomState(0)
x = 10.0 * random_num_generator.rand(sample_size)
e = random_num_generator.normal(0, sigma_e, sample_size)
y = 1.0 + 2.0 * x +  e          # a = 1.0; b = 2.0; y = a + b*x

import tensorflow as tf

tf.reset_default_graph()            # reset graph
x_plus_i = np.c_[np.ones((500,1)), x]
X = tf.constant(x_plus_i, dtype=tf.float64, name='X')
# keep y as our original dataset; make a copy instead
y_copy = tf.constant(y.reshape(-1,1), dtype=tf.float64, name='y')
X_T = tf.transpose(X)
# Normal equation
beta = tf.matmul(tf.matmul(tf.matrix_inverse(tf.matmul(X_T, X)), X_T), y_copy)

with tf.Session() as sess:
    beta_value = sess.run(beta)
    print('pamameters: %.7f, %.7f' % (beta_value[0], beta_value[1]))

import tensorflow as tf

tf.reset_default_graph()
batch_size = 50
n_batches = int(500/batch_size)
n_epochs = 1000
learing_rate = 0.01

# define the graph
w = tf.Variable(tf.truncated_normal([1], mean=0.0, stddev=1.0, dtype=tf.float64, name='slope'))
b = tf.Variable(tf.zeros(1, dtype=tf.float64), name='intercept')
x_ph = tf.placeholder(tf.float64, shape=(None, 1), name='x')
y_ph = tf.placeholder(tf.float64, shape=(None, 1), name='y')
y_pred = tf.add(b, tf.multiply(w, x_ph), name='prediction')
error = y_pred - y_ph
mse = tf.reduce_mean(tf.square(error), name='mse')
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learing_rate)
training_op = optimizer.minimize(mse)

# record to TensorBoard
from datetime import datetime
now = datetime.now().strftime('%Y%m%d%H%M%S')
logdir = 'tf_logs/run-{}'.format(now)
mse_summary = tf.summary.scalar('MSE', mse)
file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())

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

    for epoch in range(n_epochs):
        for batch_index in range(n_batches):
            # Mini-batch gradient descent
            x_batch = x[batch_index*batch_size:(batch_index+1)*batch_size].reshape(-1,1)
            y_batch = y[batch_index * batch_size:(batch_index + 1) * batch_size].reshape(-1, 1)
            sess.run(training_op, feed_dict={x_ph: x_batch, y_ph: y_batch})

            summary_str = mse_summary.eval(feed_dict={x_ph: x_batch, y_ph: y_batch})
            step = epoch *  n_batches + batch_index
            file_writer.add_summary(summary_str, step)

        w_val, b_val = sess.run([w, b])
        print('epoch {}: slope {}, intercept {}'.format(epoch, w_val[0], b_val[0]))

file_writer.close()


import scipy.stats
from math import fabs, sqrt, log, erf
import numpy as np
# from ..exts.statistics import NormalDist

def z_test(y1, y2, n1, n2):
    # two-tailed
    p1 = y1.mean()
    p2 = y2.mean()
    n1 = n1.mean().round()
    n2 = n2.mean().round()
    se = np.sqrt(p1*(1-p1)/n1 + p2*(1-p2)/n2)
    z = (p1 - p2) / se
    return z, scipy.stats.norm.sf(abs(z))*2

def calc_prob_overlapping(means, variances):
    sigma1, sigma2 = np.sqrt(variances)
    mu1, mu2 = means
    ### the bug causing entry had sigma1 == sigma2 and mu1 == mu2
    #print("sigma1: ",sigma1,"sigma2: ",sigma2)
    #print("mu1: ",mu1,"mu2: ",mu2)
    if sigma1 != sigma2 and mu1 != mu2:
        return NormalDist(mu=mu1, sigma=sigma1).overlap(NormalDist(mu=mu2, sigma=sigma2))
    else:
        return np.nan,[np.nan,np.nan,np.nan,np.nan]

class NormalDist(object):
    "Modified from the library statistics.py (Authors)(https://github.com/python/cpython/blob/3.8/Lib/statistics.py) which is under GPL-compatible license."
    def __init__(self, mu=0.0, sigma=1.0):
        self._mu = mu
        self._sigma = sigma

    def cdf(self, x):
        return 0.5 * (1.0 + erf((x - self._mu) / (self._sigma * sqrt(2.0))))

    def overlap(self, another):
        """
        Compute the overlapping coefficient (OVL) between two normal distributions.
        Measures the agreement between two normal probability distributions.
        Returns a value between 0.0 and 1.0 giving the overlapping area in
        the two underlying probability density functions.
        """
        # See: "The overlapping coefficient as a measure of agreement between
        # probability distributions and point estimation of the overlap of two
        # normal densities" -- Henry F. Inman and Edwin L. Bradley Jr
        # http://dx.doi.org/10.1080/03610928908830127
        X, Y = self, another
        if (Y._sigma, Y._mu) < (X._sigma, X._mu):  # sort to assure commutativity
            X, Y = Y, X
        X_var, Y_var = X.variance, Y.variance

        dv = Y_var - X_var
        dm = fabs(Y._mu - X._mu)
        if not dv:
            return 1.0 - erf(dm / (2.0 * X._sigma * sqrt(2.0)))
        a = X._mu * Y_var - Y._mu * X_var
        b = X._sigma * Y._sigma * sqrt(dm**2.0 + dv * log(Y_var / X_var))
        x1 = (a + b) / dv # intersection point 1
        x2 = (a - b) / dv # intersection point 2

        p_overlap = 1.0 - (fabs(Y.cdf(x1) - X.cdf(x1)) + fabs(Y.cdf(x2) - X.cdf(x2)))
        list_cdf_at_intersections = [X.cdf(x1),Y.cdf(x1),X.cdf(x2),Y.cdf(x2)]

        return p_overlap,list_cdf_at_intersections

    @property
    def mean(self):
        "Arithmetic mean of the normal distribution."
        return self._mu

    @property
    def stdev(self):
        "Standard deviation of the normal distribution."
        return self._sigma

    @property
    def variance(self):
        "Square of the standard deviation."
        return self._sigma ** 2.0


""" Provides core functions for converting between coordinate systems

Coord module provides simple module for converting between different coordinate
systems using numpy.
"""

import numpy as np

def cartesian2spherical(coords):
    """Convert Cartesian to Spherical coordinates.

    Converts an array of 3D Cartesian coordinates [x, y, z] to
    the equivalent array of Spherical coordinates [r, theta, phi].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Cartesian coordinates (x, y, z) in an
            3-dimensional space.

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Spherical coordinates in the
                order r (radial distance), theta (azimuth angle, radians)
                and phi (zenith angle, radians).
    """
    sphere = np.zeros(coords.shape)
    xy_sq = coords[:, 0]**2 + coords[:, 1]**2
    sphere[:, 0] = np.sqrt(xy_sq + coords[:, 2]**2)
    sphere[:, 1] = np.arctan2(coords[:, 1], coords[:, 0])
    sphere[:, 2] = np.arctan2(np.sqrt(xy_sq), coords[:, 2])
    return sphere

def spherical2cartesian(sphere):
    """Convert Spherical to Cartesian coordinates.

    Converts an array of Spherical coordinates [r, theta, phi] to
    the equivalent array of Cartesian coordinates [x, y, z].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Spherical coordinates in the
            order r (radial distance), theta (azimuth angle, radians)
            and theta (zenith angle, radians).

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Cartesian coordinates (x, y, z) in an
                3-dimensional space.
    """
    cart = np.zeros(sphere.shape, dtype=np.float64)
    sine_phi = np.sin(sphere[:, 2])

    cart[:, 0] = sphere[:, 0] * np.cos(sphere[:, 1]) * sine_phi
    cart[:, 1] = sphere[:, 0] * np.sin(sphere[:, 1]) * sine_phi
    cart[:, 2] = sphere[:, 0] * np.cos(sphere[:, 2])
    return cart

def spherical2cylindrical(sph):
    """Convert Spherical to Cylindrical coordinates.

    Converts an array of Spherical coordinates [r, theta, phi] to
    the equivalent array of Cylindrical coordinates [r, theta, z].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Spherical coordinates in the
            order r (radial distance), theta (azimuth angle, radians)
            and phi (zenith angle, radians).

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Cylindrical coordinates in the
                order r (radial distance), theta (azimuth angle, radians)
                and z (height).
    """
    cyl = np.zeros(sph.shape)
    cyl[:, 0] = sph[:, 0] * np.sin(sph[:, 2])
    cyl[:, 1] = sph[:, 1]
    cyl[:, 2] = sph[:, 0] * np.cos(sph[:, 2])
    return cyl

def cylindrical2spherical(cyl):
    """Convert Cylindrical to Spherical coordinates.

    Converts an array of Cylindrical coordinates [r, theta, z] to
    the equivalent array of Spherical coordinates [r, theta, phi].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Cylindrical coordinates in the
            order r (radial distance), theta (azimuth angle, radians)
            and z (height).

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Spherical coordinates in the
                order r (radial distance), theta (azimuth angle, radians)
                and phi (zenith angle, radians).
    """
    sph = np.zeros(cyl.shape)
    sph[:, 0] = np.sqrt(cyl[:, 0]**2 + cyl[:, 2]**2)
    sph[:, 1] = cyl[:, 1]
    sph[:, 2] = np.arctan2(cyl[:, 0], cyl[:, 2])
    return sph

def cartesian2cylindrical(coords):
    """Convert Cartesian to Cylindrical coordinates.

    Converts an array of 3D Cartesian coordinates [x, y, z] to
    the equivalent array of Cylindrical coordinates [r, theta, z].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Cartesian coordinates (x, y, z) in an
            3-dimensional space.

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Cylindrical coordinates in the
                order r (radial distance), theta (azimuth angle, radians)
                and z (height).
    """
    cyl = np.zeros(coords.shape)
    cyl[:, 0] = np.sqrt(coords[:, 0] ** 2 + coords[:, 1] ** 2)
    cyl[:, 1] = np.arctan2(coords[:, 1], coords[:, 0])
    cyl[:, 2] = coords[:, 2]
    return cyl

def cylindrical2cartesian(cylinder):
    """Convert Cylindrical to Cartesian coordinates.

    Converts an array of Cylindrical coordinates [r, theta, z] to
    the equivalent array of Cartesian coordinates [x, y, z].

    Parameters
    ----------
    coords: numpy.ndarray
            An m by 3 array of m Cylindrical coordinates in the
            order r (radial distance), theta (azimuth angle, radians)
            and z (height).

    Returns
    -------
    out_coords: numpy.ndarray
                Returns an m by 3 array of m Cartesian coordinates (x, y, z) in an
                3-dimensional space.
    """
    cart = np.zeros(cylinder.shape)
    cart[:, 0] = cylinder[:, 0] * np.cos(cylinder[:, 1])
    cart[:, 1] = cylinder[:, 0] * np.sin(cylinder[:, 1])
    cart[:, 2] = cylinder[:, 2]
    return cart


import numpy as np

# from numpy.linalg import det, inv

def main():

    """
    Basic matrix operations are supported by numpy.
    Addition, subtraction, multiplication, division by:
    - scalar
    - vector
    - matrix
    """

    matrix_A = np.array([[3, 9], [21, -3]])

    assert (matrix_A + 5 == np.array([[8, 14], [26, 2]])).all()
    assert (matrix_A - 1 == np.array([[2, 8], [20, -4]])).all()
    assert (matrix_A * 2 == np.array([[6, 18], [42, -6]])).all()
    assert (matrix_A / 3 == np.array([[1, 3], [7, -1]])).all()

    vector = [1, 2]
    assert (matrix_A + vector == np.array([[4, 11], [22, -1]])).all()
    assert (matrix_A - vector == np.array([[2, 7], [20, -5]])).all()
    assert (matrix_A * vector == np.array([[3, 18], [21, -6]])).all()
    assert (matrix_A / vector == np.array([[3, 4.5], [21, -1.5]])).all()

    matrix_B = np.array([[4, 9], [25, 16]])

    assert (np.sqrt(matrix_B) == np.array([[2, 3], [5, 4]])).all()

    assert (matrix_A + matrix_B == np.array([[7, 18], [46, 13]])).all()
    assert (matrix_A - matrix_B == np.array([[-1, 0], [-4, -19]])).all()
    assert (matrix_A * matrix_B == np.array([[12, 81], [525, -48]])).all()
    assert (matrix_B / matrix_A == np.array([[4 / 3, 1], [25 / 21, -16 / 3]])).all()

    """
    The transpose of a matrix is a reversal of its rows with its columns.
    Square matrices have determinants.
    The identity matrix is a square matrix with ones on the diagonal and zeros elsewhere.
    The inverse of a square matrix M is a matrix of the same size, N, such that M⋅N=I.
    """
    M = np.array([[-4, 5], [1, 7], [8, 3]])
    N = np.array([[3, -5, 2, 7], [-5, 1, -4, -3]])
    print(np.dot(M, N))

    assert (np.transpose(matrix_A) == np.array([[3, 21], [9, -3]])).all()
    assert (np.transpose(matrix_B) == np.array([[4, 25], [9, 16]])).all()

    assert np.linalg.det(matrix_A) == -198
    assert abs(np.linalg.det(matrix_B) + 161) < 0.001

    identity = np.eye(2)
    inverse = np.linalg.inv(matrix_A)
    product = np.dot(matrix_A, inverse)
    assert (product == identity).all()

if __name__ == "__main__":
    main()