import matplotlib.pyplot as plt
import numpy as np

def f(x,y):
	return 4.0*x

def Euler1(f,x_start,y_start,h,x_max):
	x_array, y_array, dydx_array = [], [], []
	while abs(x_start)<abs(x_max):
		dydx = f(x_start,y_start)
		x_array.append(x_start);y_array.append(y_start);dydx_array.append(dydx)
		y_start = y_start + h*dydx
		x_start = x_start + h
	return x_array, y_array

it = ['blue','green','yellow']

x_start , y_start, h, x_max = 0.0, 0.0, 0.01, 10.0
xx = np.arange(0.0,10.0,0.5)
i = 0
h = 0.01
while h<1.05:
	x_start = 0
	y_start = 0
	Xarr , Yarr = Euler1(f,x_start,y_start,h,x_max)
	#print(Xarr)
	#print(Yarr)
	tt = "h=%.4f"%(h)
	plt.plot(Xarr,Yarr,it[i],label=tt)
	h += 0.5
	i += 1
plt.plot(xx,2*xx**2,'red',label="Exact Solution")
plt.legend(loc='best',prop={'size':12})
plt.xlabel("x");plt.ylabel("y")
plt.title("Comparison between true and Euler solution:")
plt.show()


# Stochastic processes course 2018/2019-2 @ ELTE - Pál Balázs
# Assignment 4: Random-walk simulation with Monte Carlo method
## 03.11.2019

## Random-Walk on a 1D lattice with equilibrium:
#
# Initially a particle rests on a 1D lattice with lattice constant $a$ in one of its grid points.
# It is also attached with a rubber rope with $D$ spring constant to the $x=0$ point. In every step,
# the particle moves in the positive, or negative direction, always jumping between discrete, adjacent
# lattice points. This means that at each step it will jump to one of the grid points adjacent to its
# current position.
# At every step, we calculate the difference of the potential energy between the origin and the end
# point of a single jump. If $\Delta E < 0$, then we execute the step in the simulation.
# If $\Delta E > 0$, we choose a random value $X$, where $X \in \left[ 0; 1 \right]$.
# If $X < e^{- \beta \Delta E}$ we also execute the step, else we do nothing, leave the particle
# unmoved and continue with the next step in the simulation.
#
# Characteristic parameters of the system (for me):
#
# $$
# \beta D a^{2}
# =
# \frac{D a^{2}}{k_{B} T}
# =
# \left\{ 0.06,\ 0.24,\ 0.52,\ 1.35 \right\} \quad - \quad \text{Pál Balázs}
# $$

################
### NEW CELL ###
################

import numpy as np
import matplotlib.pyplot as plt
import sys
import os
import random
import seaborn as sns

sns.set_style(style='whitegrid')

################
### NEW CELL ###
################

# Boltzmann constant
k_B = 1.380648e-23

# Arbitrary spring constant of the rubber band, fixing the moving particle
D = 1e-03

# Arbitrary lattice constant of the grid
# Distance between discrete points in the 1D lattice
# a = 5 * 1e-10 approximatly corresponds to real materials
a = 5 * 1e-10

# Scale factor for the microscopic system
# beta = 1/(k_B * T)
beta_1 = 0.06 / (D * a**2)
print('At first case T = {0} K'.format(1 / (beta_1 * k_B)))
beta_2 = 0.24 / (D * a**2)
print('At second case T = {0} K'.format(1 / (beta_2 * k_B)))
beta_3 = 0.52 / (D * a**2)
print('At third case T = {0} K'.format(1 / (beta_3 * k_B)))
beta_4 = 1.35 / (D * a**2)
print('At fourth case T = {0} K'.format(1 / (beta_4 * k_B)))

################
### NEW CELL ###
################

## 1.: Choose direction for the 1D random-walk on the X-axis
def choose_sign():
    return -1 if random.random() < 0.5 else 1

################
### NEW CELL ###
################

### Testing sign choosing function
### Means of random +1 and -1 values have an expected value of 0
nrows = 5
ncols = 5
picsize = 30
titlesize = 20
axislabelsize = 18
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(picsize,picsize))

n = 1000

for k in range(0,nrows):
    for j in range(0,ncols):
        indeces = [i for i in range(0,n)]
        randoms = [choose_sign() for i in range(0,n)]
        means = [np.mean(randoms[:(i+1)]) for i in range(0,n)]

        axes[j][k].plot(indeces, means, c='red')

        axes[j][k].set_ylim(-1,1)

        axes[j][k].set_xlabel('Number of +/- flips [n]', fontsize=axislabelsize)
        axes[j][k].set_ylabel('Mean of +/- values after n flip', fontsize=axislabelsize)

        axes[j][k].axhline(y=0, color='green')

fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

## 2.: $E_{n}$ energy of the current energy level
def E(energy_level, a, D):
    return 1/2 * D * a**2 * energy_level**2

################
### NEW CELL ###
################

## 3.: Step with the simulation
def simulation_step(beta, start, steps):

    # Chronologies for reached energy levels' indeces and actual energies in order
    energy_levels_chronology = np.zeros((steps+1), dtype='int')
    energy_chronology = np.zeros((steps+1))
    # Starting position on X axis
    energy_levels_chronology[0] = start
    energy_chronology[0] = E(energy_level = energy_levels_chronology[0],
                             a = a,
                             D = D)

    for current_step in range(0,steps):
        # Choose a direction for the 1D random-walk on the X-axis
        # +1 return value indicates stepping in the positive (right) direction
        # -1 return value indicates steping in the negative (left) direction
        direction = choose_sign()

        # Calculate energy difference between the jump's origin and end points
        E_1 = E(energy_level = energy_levels_chronology[current_step],
                a = a,
                D = D)

        E_2 = E(energy_level = energy_levels_chronology[current_step] + direction * 1,
                a = a,
                D = D)

        delta_E = E_2 - E_1

        # Update chronology of reached energy levels' indeces
        # If delta_E <= 0, then execute the step
        if(delta_E <= 0):
            energy_levels_chronology[current_step+1] = energy_levels_chronology[current_step] + direction * 1

        # If delta_E > 0, choose a random number X, between 0 and 1
        # If X < e^(-beta * delta_E), then execute the step
        # If not, then leave the particle at its current place and continue with the next step
        else:
            if(random.random() < np.exp(- beta * delta_E)):
                energy_levels_chronology[current_step+1] = energy_levels_chronology[current_step] + direction * 1
            else:
                energy_levels_chronology[current_step+1] = energy_levels_chronology[current_step]

        # Update chronology of actual reached energy levels
        energy_chronology[current_step+1] = E(energy_level = energy_levels_chronology[current_step+1],
                                              a = a,
                                              D = D)

    return energy_levels_chronology, energy_chronology

################
### NEW CELL ###
################

## Run functions with arbitrary initial conditons
steps = 10000
start = -250

betas = {'1': beta_1,
         '2': beta_2,
         '3': beta_3,
         '4': beta_4
        }

# Dicts for storing the energy levels' indeces and actual energies for various betas
energy_levels_chronology = {}
energy_chronology = {}

# Run the simulation
for i in range(0,len(betas)):
    energy_levels_chronology[str(i+1)], energy_chronology[str(i+1)] = simulation_step(beta=betas[str(i+1)],
                                                                                start=start,
                                                                                steps=steps)

step_indeces = np.asarray([i for i in range(0,steps+1)])

################
### NEW CELL ###
################

## Plotting propagation of the particle throughout energy levels
nrows = 2
ncols = 2
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20,20))

for i in range(0,nrows):
    for j in range(0,ncols):

        axes[i][j].axhline(y=0, color='green', label='Equilibrium')
        axes[i][j].plot(step_indeces, energy_levels_chronology[str(i*nrows + j + 1)], c='red', label='Discrete energy levels of the particle')

        axes[i][j].set_title('Discrete energy level propagation at $T =${0:.3f} K'.format(1 / (betas[str(i*nrows + j + 1)] * k_B)), fontsize=50/ncols)

        axes[i][j].set_xlabel('Number of steps [$N$]', fontsize=40/ncols)
        axes[i][j].set_ylabel('Energy level [$n$]', fontsize=40/ncols)

        axes[i][j].tick_params(axis='both', which='major', labelsize=30/ncols)

        axes[i][j].legend(fontsize=30/ncols)

fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

nrows = 2
ncols = 2
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20,20))

for i in range(0,nrows):
    for j in range(0,ncols):

        axes[i][j].axhline(y=0, color='green', label='Equilibrium')
        axes[i][j].plot(step_indeces, energy_chronology[str(i*nrows + j + 1)], c='red', label='Energy of the particle')

        axes[i][j].set_title('Actual energies $T =${0:.3f} K'.format(1 / (betas[str(i*nrows + j + 1)] * k_B)), fontsize=50/ncols)

        axes[i][j].set_xlabel('Time [$N \\cdot \\tau$]', fontsize=40/ncols)
        axes[i][j].set_ylabel('Energy [J]', fontsize=40/ncols)

        axes[i][j].tick_params(axis='both', which='major', labelsize=30/ncols)
        axes[i][j].yaxis.get_offset_text().set_fontsize(25/ncols)

        axes[i][j].legend(fontsize=30/ncols)
fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

## Test various initial conditions for one choosen $T$ temperature
### The choosen $T$ will be here $T = \frac{1}{\beta_{1} k_{B}} = 301.791\ \text{K}$
steps = 10000
choosen_beta = beta_1

# Dicts for storing the energy levels' indeces and actual energies for various betas
energy_levels_chronology_fixedT = {}
energy_chronology_fixedT = {}

# Run the simulation
for i in range(0,25):
    start = -250 + i * 20
    energy_levels_chronology_fixedT[str(i+1)], energy_chronology_fixedT[str(i+1)] = simulation_step(beta=choosen_beta,
                                                                                start=start,
                                                                                steps=steps)

step_indeces = np.asarray([i for i in range(0,steps+1)])

################
### NEW CELL ###
################

nrows = 5
ncols = 5
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(50,50))

titlesize=25
axislabelsize=18

for i in range(0,nrows):
    for j in range(0,ncols):

        axes[i][j].axhline(y=0, color='green', label='Equilibrium')
        axes[i][j].plot(step_indeces, energy_levels_chronology_fixedT[str(i*nrows + j + 1)], c='red', label='Discrete energy levels of the particle')

        axes[i][j].set_title('Discrete energy level propagation at $T =${0:.3f} K\nStarting point at level {1}'.format(1 / (choosen_beta * k_B),
                                                                                -250 + (i*nrows + j) * 20),
                             fontsize=titlesize)

        axes[i][j].set_xlabel('Number of steps [$N$]', fontsize=axislabelsize)
        axes[i][j].set_ylabel('Energy level [$n$]', fontsize=axislabelsize)

        axes[i][j].tick_params(axis='both', which='major', labelsize=18)

        axes[i][j].legend(fontsize=15)

fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

nrows = 5
ncols = 5
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(50,50))

titlesize=22
axislabelsize=19

for i in range(0,nrows):
    for j in range(0,ncols):

        axes[i][j].axhline(y=0, color='green', label='Equilibrium')
        axes[i][j].plot(step_indeces, energy_chronology_fixedT[str(i*nrows + j + 1)], c='red', label='Energy of the particle')

        axes[i][j].set_title('Discrete energy levels at $T =${0:.3f} K\nStarting point at level {1}'.format(1 / (choosen_beta * k_B),
                                                                                -250 + (i*nrows + j) * 20),
                             fontsize=titlesize)

        axes[i][j].set_xlabel('Number of steps [$N$]', fontsize=axislabelsize)
        axes[i][j].set_ylabel('Energy [J]', fontsize=axislabelsize)

        axes[i][j].tick_params(axis='both', which='major', labelsize=15)
        axes[i][j].yaxis.get_offset_text().set_fontsize(10)

        axes[i][j].legend(fontsize=15)

fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

## Examine $\left< n \right>$ and $\left< n^{2} \right>$ expected values
steps = 10000
start = -250
runs = 25

# Dicts for storing the energy levels' indeces and actual energies for various betas
energy_levels_chronology_expected = {}
energy_chronology_expected = {}

# Run the simulation
for j in range(0,len(betas)):
    for i in range(0,runs):
        (energy_levels_chronology_expected[str(j*runs + i + 1)],
        energy_chronology_expected[str(j*runs + i + 1)]) = simulation_step(beta=betas[str(j+1)],
                                                                                start=start,
                                                                                steps=steps)

# Storages for mean data of n and n^2 values for various T temperatures
# These dictionaries store means for every runs, for every T temperature (len == runs * len(betas))
temp_means_levels_expected = {}
temp_means_levels_expected_squared = {}
temp_means_energy_expected = {}
temp_means_energy_expected_squared = {}
# Calculate means of x and x^2 displacements
for j in range(0,len(betas)*runs):
    temp_means_levels_expected[str(j+1)] = np.asarray([np.mean(energy_levels_chronology_expected[str(j+1)][:(i+1)]) for i in range(0, steps+1)])
    temp_means_levels_expected_squared[str(j+1)] = np.asarray([np.mean((energy_levels_chronology_expected[str(j+1)][:(i+1)])**2) for i in range(0, steps+1)])

    temp_means_energy_expected[str(j+1)] = np.asarray([np.mean(energy_chronology_expected[str(j+1)][:(i+1)]) for i in range(0, steps+1)])
    temp_means_energy_expected_squared[str(j+1)] = np.asarray([np.mean((energy_chronology_expected[str(j+1)][:(i+1)])**2) for i in range(0, steps+1)])

# Storages for means of the above data
# These dictionaries stores the mean of N runs for various T temperature (len == len(betas))
means_levels_expected = {}
means_levels_expected_squared = {}
means_energy_expected = {}
means_energy_expected_squared = {}
# Temporary storages for means of each previous runs, when iterating throug them
temp_mean_levels = np.zeros((len(temp_means_levels_expected['1'])))
temp_mean_levels_squared = np.zeros((len(temp_means_levels_expected_squared['1'])))
temp_mean_energy = np.zeros((len(temp_means_energy_expected['1'])))
temp_mean_energy_squared = np.zeros((len(temp_means_energy_expected_squared['1'])))
# Calculate overall means for each betas (or temperatures)
for i in range(0,len(betas)):
    for j in range(0,runs):
        temp_mean_levels += temp_means_levels_expected[str(i*runs + j + 1)]
        temp_mean_levels_squared += temp_means_levels_expected_squared[str(i*runs + j + 1)]
        temp_mean_energy += temp_means_energy_expected[str(i*runs + j + 1)]
        temp_mean_energy_squared += temp_means_energy_expected_squared[str(i*runs + j + 1)]

    means_levels_expected[str(i+1)] = temp_mean_levels/runs
    means_levels_expected_squared[str(i+1)] = temp_mean_levels_squared/runs
    means_energy_expected[str(i+1)] = temp_mean_energy/runs
    means_energy_expected_squared[str(i+1)] = temp_mean_energy_squared/runs

    # Reset the temporary arrays for the next beta (or temperature)
    temp_mean_levels = np.zeros((len(temp_means_levels_expected['1'])))
    temp_mean_levels_squared = np.zeros((len(temp_means_levels_expected_squared['1'])))
    temp_mean_energy = np.zeros((len(temp_means_energy_expected['1'])))
    temp_mean_energy_squared = np.zeros((len(temp_means_energy_expected_squared['1'])))

step_indeces = np.asarray([i for i in range(0,steps+1)])

################
### NEW CELL ###
################

nrows = 6
ncols = 4
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(30,45))

titlesize = 20
axislabelsize = 15

# Initialize graphs with equilibrium line and axis subtitles
for i in range(0,nrows):
    for j in range(0,ncols):
        axes[i][j].axhline(y=0, color='green', label='Equilibrium')

        axes[i][j].set_xlabel('Number of steps [$N$]', fontsize=axislabelsize)

        axes[i][j].tick_params(axis='both', which='major', labelsize=12)
        axes[i][j].yaxis.get_offset_text().set_fontsize(10)

# Mean of occupied discrete energy levels (this is the <n> expected value for displacement)
for j in range(0,4):
    axes[0][j].plot(step_indeces, means_levels_expected[str(j+1)],
                    c='red', label='Mean displacement')
    axes[0][j].set_title('Mean of occupied discrete\nenergy levels ($\\left< n \\right>$) at $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                         fontsize=titlesize)

    axes[0][j].set_ylabel('Energy level [$n$]', fontsize=axislabelsize)

# Mean of occupied discrete energy levels squared (this is the <n^2> expected value for displacement squared)
for j in range(0,4):
    axes[1][j].plot(step_indeces, means_levels_expected_squared[str(j+1)],
                      c='red', label='Mean displacement squared')

    axes[1][j].set_title('Mean of occupied discrete\nenergy levels squared ($\\left< n^2 \\right>$) at $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                           fontsize=titlesize)

    axes[1][j].set_ylabel('Energy level$^{2}$ [n$^{2}$]', fontsize=axislabelsize)

# Mean of occupied discrete energy levels (this is the <x> expected value for displacement)
for j in range(0,4):
    axes[2][j].plot(step_indeces, means_levels_expected[str(j+1)] * a,
                    c='red', label='Mean displacement')
    axes[2][j].set_title('Mean of displacements ($\\left< x \\right>$)\nat $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                         fontsize=titlesize)

    axes[2][j].set_ylabel('Displacement [$a \\cdot n$]', fontsize=axislabelsize)

# Mean of occupied discrete energy levels squared (this is the <x^2> expected value for displacement squared)
for j in range(0,4):
    axes[3][j].plot(step_indeces, means_levels_expected_squared[str(j+1)] * a**2,
                      c='red', label='Mean displacement squared')

    axes[3][j].set_title('Mean of displacements squared ($\\left< x^2 \\right>$)\nat $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                           fontsize=titlesize)

    axes[3][j].set_ylabel('Displacement$^{2}$ [$a^{2} \\cdot n^{2}$]', fontsize=axislabelsize)

# Mean of actual occupied energies
for j in range(0,4):
    axes[4][j].plot(step_indeces, means_energy_expected[str(j+1)],
                      c='red', label='Mean energy')

    axes[4][j].set_title('Mean of occupied energies ($\\left< E \\right>$)\nat $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                           fontsize=titlesize)

    axes[4][j].set_ylabel('Energy [J]', fontsize=axislabelsize)

# Mean of actual occupied energies squared
for j in range(0,4):
    axes[5][j].plot(step_indeces, means_energy_expected_squared[str(j+1)],
                      c='red', label='Mean energy squared')

    axes[5][j].set_title('Mean of occupied energies\nsquared ($\\left< E^2 \\right>$) at $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                           fontsize=titlesize)

    axes[5][j].set_ylabel('Energy$^{2}$ [J$^{2}$]', fontsize=axislabelsize)

# Add legends on graphs
for i in range(0,nrows):
    for j in range(0,ncols):
        axes[i][j].legend(fontsize=15)

fig.tight_layout()
plt.show()

################
### NEW CELL ###
################

## Oscillation of $\left< x^{2} \right> - \left< x \right>^{2}$ around equilibrium
#
# We can get $\left< x \right>^{2} = a^{2} \cdot \left< n \right>^{2}$ and similarly
# $\left< x^{2} \right> = a^{2} \cdot \left< n^{2} \right>$.

oscillation_levels = {}
oscillation_energy = {}
for i in range(0, len(means_energy_expected)):
    oscillation_levels[str(i+1)] = means_levels_expected_squared[str(i+1)] * a**2 - means_levels_expected[str(i+1)]**2 * a**2
    oscillation_energy[str(i+1)] = means_energy_expected_squared[str(i+1)] - means_energy_expected[str(i+1)]**2

################
### NEW CELL ###
################

nrows = 2
ncols = 4
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(40,20))

titlesize = 25
axislabelsize = 20

# Initialize graphs with equilibrium line and axis subtitles
for i in range(0,nrows):
    for j in range(0,ncols):
        axes[i][j].axhline(y=0, color='green', label='Equilibrium')

        axes[i][j].set_xlabel('Number of steps', fontsize=axislabelsize)

        axes[i][j].tick_params(axis='both', which='major', labelsize=15)
        axes[i][j].yaxis.get_offset_text().set_fontsize(12)

# Oscillation of energy levels (this is the <x^2> - <x>^2 expected value)
for j in range(0,4):
    axes[0][j].plot(step_indeces, oscillation_levels[str(j+1)],
                    c='red', label='$\\left< x^{2} \\right> - \\left< x \\right>^{2}$')

    axes[0][j].set_title('Oscillation of $\\left< x^2 \\right> - \\left< x \\right>^2$ at $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                         fontsize=titlesize)

    axes[0][j].set_ylabel('Displacement$^{2}$ [$a^{2} \\cdot n^{2}$]', fontsize=axislabelsize)

# Mean of actual occupied energies (this is the <E^2> - <E>^2 expected value)
for j in range(0,4):
    axes[1][j].plot(step_indeces, oscillation_energy[str(j+1)],
                      c='red', label='$\\left< E^{2} \\right> - \\left< E \\right>^{2}$')

    axes[1][j].set_title('Oscillation of $\\left< E^2 \\right> - \\left< E \\right>^2$ at $T = ${0:.3f}'.format(1 / (betas[str(j+1)] * k_B)),
                           fontsize=titlesize)

    axes[1][j].set_ylabel('Energy$^{2}$ [J$^{2}$]', fontsize=axislabelsize)

# Add legends on graphs
for i in range(0,nrows):
    for j in range(0,ncols):
        axes[i][j].legend(fontsize=15)

fig.tight_layout()
plt.show()

Lecture 4/Homework 4/original sent file/Pal_Balazs_04_code.py


__author__ = 'BisharaKorkor'

import numpy as np
from math import exp, pow, sqrt, pi, fmod

def movingaverage(a, w):
    """ An array b of length len(a)-w is returned where b_n = (a_n + a_n-1 + ... + a_n-w)/w """
    return [np.mean(a[i:i+w]) for i in range(len(a)-w)]

def gaussiankernel(sigma, width):
    """Generates gaussian kernel"""
    #  tmp is a non-normalized gaussian kernel
    tmp = [exp(-pow((width/2 - i) / sigma, 2)/2)/(sigma * sqrt(2 * pi)) for i in range(width)]
    #  compute sum for normalization
    s = np.sum(tmp)
    #  return the normalized kernel
    return [i / s for i in tmp]

def movingbaseline(array, width):
    """ Each array value is assigned to be it's value divided by the average of the preceding width (inclusive)
    elements"""
    mva = movingaverage(array, width)
    return [array[i+width]/mva[i] for i in range(len(mva))]

def exponentialsmoothing(array, alpha):

    sa = [array[0]]  #smoothed array

    for i in range(len(array)):
        sa += [alpha * array[i] + (1-alpha) * sa[i]]

    del sa[0]
    return sa

def histogramfrom2Darray(array, nbins):
    """
    Creates histogram of elements from 2 dimensional array

    :param array: input 2 dimensional array

    :param nbins: number of bins so that bin size = (maximum value in array - minimum value in array) / nbins
        the motivation for returning this array is for the purpose of easily plotting with matplotlib

    :return: list of three elements:
            list[0] = length nbins list of integers, a histogram of the array elements
            list[1] = length nbins list of values of array element types, values of the lower end of the bins
            list[2] = [minimum in list, maximum in list]
        this is just good to know sometimes.
    """

    #find minimum
    minimum = np.min(array)
    #find maximu
    maximum = np.max(array)

    #compute bin size
    binsize = (maximum - minimum) / nbins

    #create bin array
    bins = [minimum + binsize * i for i in range(nbins)]
    histo = [0 for b in range(nbins)]

    for x in array:
        for y in x:
            #find the lower end of the affiliated bin
            ab = y - (minimum + fmod(y - minimum, binsize))
            histo[int(ab/binsize)-1] += 1

    return [histo, bins, [minimum, maximum]]

def sum_of_subset(array, x, y, dx, dy):
    summ = 0  # summ because sum is native
    for ix in range(x, x + dx):
        for iy in range(y, y + dy):
            summ += array[ix][iy]
    return summ

def subset(array, x, y, dx, dy):
    ss = []
    for ix in range(x, x + dx):
        for iy in range(y, y + dy):
            ss.appen(array[ix][iy])
    return ss


from sympy import *

# A psychologist contends that the number of facts of a certain type that are remembered after t hours is given by the following function.

t = symbols( 't' )
F = 86*t / ( 99*t - 86 )
dF = diff( F, t )

# Find the rate of change at which the number of facts remembered is changing after 1 hour and after 10 hours.

# The rate of change after 1 hour is about
one_hr = dF.subs( { t: 1 } )
# (Round to the nearest integer as needed)
round( one_hr )

# The rate of change after 10 hours is about
ten_hr = dF.subs( { t: 10 } ).evalf()
# (Round to the nearest integer as needed)
round( ten_hr )


import math
import numbers
import random
import numpy as np
import unittest
from unittest import TestCase

from util.stats import RunningStats

class TestRunningStats(TestCase):
    def test_accumulative_diff(self):
        rs = RunningStats()
        rs.push(1987698.987)
        self.assertEqual(0, rs.accumulative_diff())

        rs.push(1987870.987)
        self.assertEqual(172, rs.accumulative_diff())

        rs.push(1987990.987)
        self.assertEqual(292, rs.accumulative_diff())

        rs.push(1988200.987)
        self.assertEqual(502, rs.accumulative_diff())

        rs.push(1988000.987)
        self.assertEqual(302, rs.accumulative_diff())

        rs = RunningStats()
        [rs.push(n) for n in [1, 2, 4, 7, 0]]
        self.assertEqual(np.diff([1, 2, 4, 7, 0]).sum(), rs.accumulative_diff())

        rs = RunningStats()
        values = np.random.uniform(-100, 100, 100)
        [rs.push(n) for n in values]
        self.assertTrue(math.isclose(np.diff(values).sum(), rs.accumulative_diff(), rel_tol=0, abs_tol=0.001))

    def test_slope(self):
        rs = RunningStats()
        self.assertEqual(0, rs.slope())

        rs.push(10)
        self.assertEqual(10, rs.slope())

        rs.push(3)
        self.assertEqual(-7, rs.slope())

    def test_push(self):
        rs = RunningStats()
        rs.push(0)

        self.assertEqual(1, rs.n())
        self.assertEqual(0, rs.curr())
        self.assertEqual(0, rs.last())

        rs.push(10)
        self.assertEqual(2, rs.n())
        self.assertEqual(10, rs.curr())
        self.assertEqual(0, rs.last())

    def test_median(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]

        self.assertEqual(np.median(values), rs.median())

    def test_mad(self):
        rs = RunningStats()
        self.assertTrue(math.isnan(rs.median()))

        rs = RunningStats()
        values = [1, 2]
        random.shuffle(values)
        [rs.push(n) for n in values]
        self.assertEqual(1.5, rs.median())
        self.assertEqual(0.5, rs.mad())

        rs = RunningStats()
        values = [2, 2]
        random.shuffle(values)
        [rs.push(n) for n in values]
        self.assertEqual(2, rs.median())
        self.assertEqual(0, rs.mad())

        rs = RunningStats()
        values = [1, 2, 3]
        random.shuffle(values)
        [rs.push(n) for n in values]
        self.assertEqual(2, rs.median())
        self.assertEqual(1, rs.mad())

        rs = RunningStats()
        values = [1, 1, 2, 2, 4, 6, 9]
        random.shuffle(values)
        [rs.push(n) for n in values]
        self.assertEqual(2, rs.median())
        self.assertEqual(1, rs.mad())

    def test_mean(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]
        self.assertTrue(math.isclose(values.mean(), rs.mean(), rel_tol=0, abs_tol=0.001))

    def test_exp_mean(self):
        self.fail()

    def test_variance(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]
        self.assertTrue(math.isclose(values.var(), rs.variance(), rel_tol=0, abs_tol=0.001), f'{values.var()}, {rs.variance()}',  )

    def test_standard_deviation(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]

        self.assertEqual(values.std(), rs.standard_deviation())

    def test_max(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]

        self.assertEqual(values.max(), rs.max())

    def test_min(self):
        rs = RunningStats()
        values = np.random.uniform(-100, 100, 10)
        [rs.push(n) for n in values]

        self.assertEqual(values.min(), rs.min())

    def test_t_test(self):
        self.fail()

    def test_serialize(self):
        rs = RunningStats()

        [rs.push(n) for n in [1, 1, 2, 2, 4, 6, 9]]

        for key, value in rs.serialize().items():
            self.assertIsInstance(key, str)
            self.assertIsInstance(value, numbers.Number)

if __name__ == '__main__':
    unittest.main()


# numexpr is a package which allows for direct-access to C-speed operations without
# intermediate object generation. Pandas methods eval() and query() are dependent on it

# this chapter covers the pandas eval() and query() operations, as per usual,
# the basics, good rules-of-thumb, and coventions generally followed.

### Motivating query() and eval() --- Compound Expressions (and numexpr demonstration):
# Recall Vectorized operations in NP and PD:
import numpy as np
rng = np.random.RandomState(42)
x = rng.rand(1000000)
y = rng.rand(1000000)
%timeit x + y           # recall anaconda/ipython magics

%timeit np.fromiter((xi + yi for xi, yi in zip(x, y)), dtype=x.dtype, count=len(x))

# however, our vectorized ops are less efficient for compound expressions:
mask = (x > 0.5) & (y < 0.5)
# this is because NP evals each subexpression seperately, roughly, doing this...
tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2
# ... allocating our intermediate steps in memory, introducing the same inefficiency as before

import numexpr
mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
np.allclose(mask, mask_numexpr)

### pandas.eval() for Efficient Operations
# eval() in PD uses string expressions to compute operations using DataFrames, efficiently.
import pandas as pd
nrows, ncols = 100000, 100
rng = np.random.RandomState(42)
df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols))
                      for i in range(4))

%timeit df1 + df2 + df3 + df4

%timeit pd.eval('df1 + df2 + df3 + df4')

### Operations supported by pd.eval()
# demonstration of supported ops as of PD v0.16 in pd.eval()
df1, df2, df3, df4, df5 = (pd.Dataframe(rng.randint(0, 1000, (100, 3)))
                           for i in range(5))

## Arithmetic Operators
# All arithmetic operators are supported
result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
np.allclose(result1, result2)

## Comparison Operators
# all comparison operators, including chained expressions:
result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval('df1 < df2 <= df3 != df4')
np.allclose(result1, result2)

## Bitwise Operators
# supports the & and | bitwise operators
result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
np.allclose(result1, result2)

# also supports the use of literal "and" and "or" in Bool expressions
result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')
np.allclose(result1, result3)

## Other Attributes and Indices
# pd.eval() supports access to:
    # object attributes     via     obj.attr    syntax
    # index  attributes     via     obj[index]  syntax
result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')
np.allclose(result1, result2)

## Other Operations
# otherwise not mentioned ops [function calls, conditional statements, loops]
# are all NOT implemented within pd.eval() - you would need full numexpr lib

### DataFrame.eval() for Column-Wise Operations
# pd.eval() is top-level, DFs have their own eval() which is similar
# benefit: columns can be referred to by name
df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
df.head()

result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = pd.eval("df.A + df.B) / (df.C - 1)")
np.allclose(result1, result2)
# even MORE concise: treat col names as variables!
result3 = df.eval('(A + B) / (C - 1)')
np.allclose(result1, result3)

## Assignment in DataFrame.eval()
# create column "D" as result of arithmetic operations on A,B, & C
df.eval('D = (A + B) / C', inplace=True)
df.head()
# re-assign existing column "D" (in same way)
df.eval('D = (A - B) / C', inplace=True)
df.head()

## Local variables in DataFrame.eval()
# adding @ sign; df.eval() will plug local Python vars, not DF cols
column_mean = df.mean(1)
result1 = df['A' + column_mean]
result2 = df.eval('A + @column_mean')
np.allclose(result1, result2)

### DataFrame.query() Method
# DataFrame has another method on evaluated strings called query()
# This is both a more efficient and easy-to-comprehend op to masking.
result1 = df[(df.A < 0.5) & (df.B < 0.5)]
result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
np.allclose(result1, result2)

# Do not express using DataFrame.eval() syntax - use query() method. (?)
result2 = df.query('A < 0.5 and B < 0.5')
np.allclose(result1, result2)

# query also supports @ flag to mark local vars
Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
np.allclose(result1, result2)

### Performance Tradeoffs: When to use these functions
# Two considerations: Computation Time, Memory Use
# recall: all compound expressions involving NP arrs or PD DFs creates tmp arrs

# For example, these two blocks are performing roughly equivalent work:
x = df[(df.A < 0.5) & (df.B < 0.5)]

tmp1 = df.A < 0.5
tmp2 = df.B < 0.5
tmp3 = tmp1 & tmp2
x = df[tmp3]

# if the size of your tmp DataFrames are significant compared to system RAM
# (several GBs) then you would want to use eval() or query() exprs.

# recall: can get approximate size of arr in bytes with:
df.values.nbytes

# sometimes eval() is faster even without maxing-out system memory.
# You'd want to compare temp arr size to the size of L1 or L2 CPU caches on your system
# primarily though, it's about preventing memory utilization issues.

# For more performance notes, see referenced documentation:
    # General index, lots of performance-specific tutorials
    # http://pandas-docs.github.io/pandas-docs-travis/

# Cython extension notes (for use with ipython)
# http://pandas-docs.github.io/pandas-docs-travis/user_guide/enhancingperf.html
%load_ext Cython


'''
Python program to get all strobogrammatic numbers that are of length

A strobogrammatic number is a number whose numeral is
rotationally symmetric, so that it appears the same when rotated
180 degrees. In other words, the numeral looks the same rightside
up and upside down (e.g., 69, 96, 1001).
For example,
Given n = 2, return ["11", "69", "88", "96"].
Given n = 3, return ['818', '111', '916', '619', '808', '101', '906', '609',
'888', '181', '986', '689']

'''

#https://github.com/keon/algorithms/blob/master/math/generate_strobogrammtic.py

from numpy import result_type

def gen_strobogrammatic (n):
    """
    :type n: int
    :rtype: List [str]
    """

    result = helper (n,n)
    return result

def helper (n, length):
    if n == 0:
        return [""]
    if n == 1:
        return ["1", "0", "8"]
    middles = helper (n-2, length)
    result = []
    for middle in middles:
        if n != length:
            result.append ("0" + middle + "0")
        result.append ("8" + middle + "8")
        result.append ("1" + middle + "1")
        result.append ("9" + middle + "6")
        result.append ("6" + middle + "9")
    return result
print ("n = 2: \n" , gen_strobogrammatic(2))
print ("n = 3: \n" , gen_strobogrammatic(3))
print ("n = 4: \n" , gen_strobogrammatic (4) )


from __future__ import division

import numpy as np
from scipy.optimize import fsolve

def dahlquist(_, x, lam):
    """
    dahlquist test equation ode.
    :param _: place holder for time, not used
    :param x: x value
    :param lam: lambda
    :return: slope dx/dt
    """
    dx = lam * x
    return dx

def dahlquist_ref(t, x0, lam):
    """
    reference solution for dahlquist test equation ode. x' = lam*x -> y = x0 * exp(lam*t)
    :param t: time
    :param x0: initial value
    :param lam: lambda
    :return: samples of reference solution for time t
    """
    x_ref = np.exp(lam * t) * x0  # analytical solution of the dahlquist test equation
    return x_ref

def definition_area(t, x):
    """
    for the initial value x0 = 1 this ODE only has a solution for x in (-sqrt(2),sqrt(2)). Therefore the ode is only
    defined in a certain area.
    :param t: time
    :param x: x value
    :return: slope dx/dt
    """
    dx = t * x ** 2
    return dx

def definition_area_ref(t, x0):
    """
    reference solution for ode with respricted definition area.
    :param t: time
    :param x0: initial value
    :return: samples of reference solution for time t
    """
    x_ref = 1. / (1. / x0 - 1. / 2. * (t ** 2))  # analytical solution of this ODE
    return x_ref

def logistic_equation(_, x, k, g):
    """
    ode for the logistic equation
    :param _: place holder for time, not used
    :param x: x value
    :param k: slope of logistic equation
    :param g: upper bound of logistic equation
    :return: slope dx/dt
    """
    dx = k * x * (g - x)
    return dx

def logistic_equation_ref(t, x0, k, g):
    """
    reference solution for logistic equation ode
    :param t: time
    :param x0: initial value
    :param k: slope of logistic equation
    :param g: upper bound of logistic equation
    :return: samples of reference solution for time t
    """
    if 0 != x0:
        x_ref = g * 1 / (1 + np.exp(-k * g * t) * (g / x0 - 1))
    else:
        x_ref = 0
    return x_ref

def oscillator_equation(_, x, omega):
    """
    two dimensionaly ode describing the harmonic oszillator
    :param _: place holder for time, not used
    :param x: x value
    :param omega: frequency of oszillation
    :return: slope dx/dt
    """
    A = np.array([[0, 1], [-omega ** 2, 0]])
    dx = np.dot(A, x)
    return dx

def oscillator_equation_ref(t, x0, omega, v0=0):
    """
    reference solution for two dimensional ode describing the harmonic oszillator
    :param t: time
    :param x0: initial displacements
    :param omega: frequency of oszillation
    :param v0: initial velocity
    :return: samples of reference solution (only displacement) for time t
    """
    x = x0 * np.exp(1j * omega * t) + v0 * np.exp(-1j * omega * t)
    return np.real(x)

def ref_sol(f_ref, x0, t_min = 0, t_max = 1, n_samples = 1000):
    """
    computes samples of the reference solution for a given timespan
    :param f_ref: reference solution function handle
    :param x0: initial value of ode
    :param t_min: starting time
    :param t_max: end time
    :param n_samples: number of samples to be produced
    :return: tuple of time and x value samples of the reference solution
    """
    t_ref = np.linspace(t_min, t_max, n_samples)
    x_ref = f_ref(t_ref, x0)
    return t_ref, x_ref

def expl_euler(f, x0, h, timespan):
    """
    explicit euler solver. Computes the solution for a given ode using explicit euler scheme.
    :param f: function handle for ode
    :param x0: initial value
    :param h: constant step size
    :param timespan: integration time
    :return: numerical solution in time and x
    """
    n = int(np.ceil(timespan / h))
    t = np.empty(n + 1)
    x = np.empty([x0.shape[0], n + 1])

    t[0] = 0
    x[:, 0] = x0
    for k in range(n):
        dx = f(t[k], x[:, k])
        t[k + 1] = (k + 1) * h
        x[:, k + 1] = x[:, k] + dx * h

    return t, x

def impl_euler(f, x0, h, timespan):
    """
    implicit euler solver. Computes the solution for a given ode using implicit euler scheme.
    :param f: function handle for ode
    :param x0: initial value
    :param h: constant step size
    :param timespan: integration time
    :return: numerical solution in time and x
    """
    n = int(np.ceil(timespan / h))
    t = np.empty(n + 1)
    x = np.empty([x0.shape[0], n + 1])

    t[0] = 0
    x[:, 0] = x0
    for k in range(n):
        t[k + 1] = (k + 1) * h
        try:
            x[:, k + 1] = fsolve(lambda arg: x[:, k] - arg + h * f(t[k + 1], arg), x[:, k])
        except RuntimeError:
            print("newton did not converge!")
            for k in range(k, n):
                t[k + 1] = (k + 1) * h
            break
    return t, x

def impl_midpoint(f, x0, h, timespan):
    """
    implicit midpoint rule solver. Computes the solution for a given ode using the implicit midpoint rule scheme.
    :param f: function handle for ode
    :param x0: initial value
    :param h: constant step size
    :param timespan: integration time
    :return: numerical solution in time and x
    """
    n = int(np.ceil(timespan / h))
    t = np.empty(n + 1)
    x = np.empty([x0.shape[0], n + 1])

    t[0] = 0
    x[:, 0] = x0
    for k in range(n):
        t[k + 1] = (k + 1) * h
        try:
            dx_left = f(t[k], x[:, k])
            x[:, k + 1] = fsolve(lambda arg: x[:, k] - arg + h / 2 * (f(t[k + 1], arg) + dx_left), x[:, k])
        except RuntimeError:
            print("newton did not converge!")
            for k in range(k, n):
                t[k + 1] = (k + 1) * h
            break
    return t, x


import numpy as np

def perceptron(X, y):
    '''
    PERCEPTRON Perceptron Learning Algorithm.

       INPUT:  X: training sample features, P-by-N matrix.
               y: training sample labels, 1-by-N row vector.

       OUTPUT: w:    learned perceptron parameters, (P+1)-by-1 column vector.
               iter: number of iterations

    '''
    P, N = X.shape
    w = np.zeros((P + 1, 1))
    iters = 0
    MAX_ITERS = 1e4
    # YOUR CODE HERE

    # begin answer
    while(iters < MAX_ITERS):
        done = True
        for i in range(N):
                # for every sample
                iters += 1
                xi = np.hstack((np.ones((1)), X[:,i]))
                xi = xi.reshape(xi.shape[0], 1)
                tmp = np.matmul(w.T, xi)
                yi_hat = np.sign(tmp)
                if(yi_hat[0][0] != y[0][i]):
                        done = False
                        w = w + xi*y[0][i]
        if(done == True):
                # ok
                break
    # end answer

    return w, iters


import numpy as np
import pdb

def p1(range_max, factor_list):
    """Problem 1
    If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

    Find the sum of all the multiples of 3 or 5 below 1000.

    """
    range_min = 0

    sum_multiples = 0

    for num in range(range_max):
        if num % factor_list[0] == 0:
            sum_multiples = sum_multiples + num
        elif num % factor_list[1] == 0:
            sum_multiples = sum_multiples + num

            # print(num)

    print("Summation of multiples is: %g" % sum_multiples)

    return sum_multiples

def p2(max):
    """Problem 2
    Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:

    1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

    By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
    """

    fib_num = [1,2]
    sum_even = 0
    while fib_num[-1] < max:
        # check if next fibonacci number is even
        if (fib_num[-1] % 2) == 0:
            sum_even = sum_even + fib_num[-1]

        next_num = fib_num[-2]+fib_num[-1]

        fib_num.append(next_num)

    print("Sum of even Fibonacci numbers less than %f is %g" % (max, sum_even))

    return sum_even
def prime_sieve_erathosthenes(n):
    """Sieve of Erathosthenes

    Output the list of all primes less than n
    """

    not_prime = []
    prime = []

    if n < 2:
        print("No primes less than 2")
        return 1

    for i in range(2, n+1):
        if i not in not_prime:
            prime.append(i)
            for j in range(i*i,n+1, i):
                not_prime.append(j)

    return prime

def gen_primes(n):
    """Yield primes less than n
    """
    D = {}
    q = 2
    while q < n:
        if q not in D:
            yield q

            D[q * q] = [q]
        else:
            for p in D[q]:
                D.setdefault(p + q, []).append(p)
            del D[q]

        q += 1

def primesfrom2to(num):
    """ Input n>=6, Returns a array of primes, 2 <= p < n """

    sieve = np.ones(num//3 + (num%6==2), dtype=np.bool)
    for i in range(1,int(num**0.5)//3+1):
        if sieve[i]:
            k=3*i+1|1
            sieve[       k*k//3     ::2*k] = False
            sieve[k*(k-2*(i&1)+4)//3::2*k] = False
    return np.r_[2,3,((3*np.nonzero(sieve)[0][1:]+1)|1)]

def primesfrom3to(n):
    """ Returns a array of primes, 3 <= p < n """
    sieve = numpy.ones(n/2, dtype=numpy.bool)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = False
    return 2*numpy.nonzero(sieve)[0][1::]+1

def p3(num):
    """Problem 3
    The prime factors of 13195 are 5, 7, 13 and 29.

    What is the largest prime factor of the number 600851475143 ?
    """
    if num < 2:
        print("No primes less than 2")
        return []
    largest_factor = 0
    for p in gen_primes(num//2 + 1):
        if num % p == 0:
            # this is a prime factor
            if p > largest_factor:
                largest_factor = p

    return largest_factor


from typing import Callable, Any

import numpy as np

def root(f: Callable[[float], float], x0: float, df: Callable[[float], float] = None,
         epsilon: float = 10e-9, delta: float = 10e-6, stop: int = 10e3, xn1: float = None, progress=False):
    """
    find a root of the function, using the newton's method if derivative is available, or the secant method if not.

    :param f: univariate function
    :param df: derivative of f(x)
    :param x0: initial guess for the solution of f(x) = 0
    :param epsilon: largest permissible value of abs(f(x)) when solution is found
    :param delta: largest permissible distance between two consecutive approximations when solution is found
    :param stop: maximum iterations
    :param xn1: one of the first initial guesses needed to initialize secant method
    :param progress: whether to print the convergence progress

    :return: a root
    """
    if df is None:
        # secant method
        if xn1 is None:
            xn1 = x0 + 0.1
        new, old = x0, xn1
        fnx, fox = f(new), f(old)
        while (abs(fnx > epsilon) or abs(new - old) > delta) and stop > 0:
            if progress:
                print(new)
            oldest = old
            old = new
            new = old - fnx * (old - oldest) / (fnx - fox)
            fnx, fox = f(new), f(old)
            stop -= 1
    else:
        # newton method
        new, old = x0, x0 + 1
        fx, dfx = f(new), df(new)
        while (abs(fx > epsilon) or abs(new - old) > delta) and stop > 0:
            old = new
            new = old - fx / dfx
            fx, dfx = f(new), df(new)
            stop -= 1
            if progress:
                print(new)
    return new

def integrate(f: Callable[[float], float], a: float, b: float, epsilon: float = 10e-8, stop: int = 10e3):
    """
    calculate the definite integral of a function

    :param f: function
    :param a: left endpoint
    :param b: right endpoint
    :param epsilon: maximum tolerance
    :param stop: maximum iterations

    :return: approximated definite integral with error less than epsilon
    """
    old = simpsons(f, a, b, 4)
    new = simpsons(f, a, b, 8)
    n = 8
    while abs(old - new) > epsilon and stop > 0:
        old = new
        n *= 2
        new = simpsons(f, a, b, n)
        stop -= 1
    return new

def simpsons(f: Callable[[Any], Any], a: float, b: float, n: int):
    """
    approximate the definite integral of a function using simpson's method

    :param f: function
    :param a: left endpoint
    :param b: right endpoint
    :param n: partitions

    :return: approximated definite integral
    """
    x = np.linspace(a, b, n + 1)
    y = f(x)
    s = np.sum(y[0:-1:2] + y[2::2] + 4 * y[1::2])
    return s * (b - a) / (3 * n)


import numpy as np
import timeit
import warnings

class KMeansBase:
    def __init__(self, data, k):
        self.data = data
        self.k = k

    def cluster(self):
        return self._lloyds_iterations()

    def _initial_centroids(self):
        # get the initial set of centroids
        # get k random numbers between 0 and the number of rows in the data set
        centroid_indexes = np.random.choice(range(self.data.shape[0]), self.k, replace=False)
        # get the corresponding data points
        return self.data[centroid_indexes, :]

    def _lloyds_iterations(self):
        #warnings.simplefilter("error")
        centroids = self._initial_centroids()
        #print('Initial Centroids:', centroids)

        stabilized = False

        j_values = []
        iterations = 0
        while (not stabilized) and (iterations < 1000):
            print ('iteration counter: ', iterations)
            try:
                # find the Euclidean distance between a center and a data point
                # centroids array shape = k x m
                # data array shape = n x m
                # In order to broadcast it, we have to introduce a third dimension into data
                # data array becomes n x 1 x m
                # now as a result of broadcasting, both array sizes will be n x k x m
                data_ex = self.data[:, np.newaxis, :]
                euclidean_dist = (data_ex - centroids) ** 2
                # now take the summation of all distances along the 3rd axis(length of the dimension is m).
                # This will be the total distance from each centroid for each data point.
                # resulting vector will be of size n x k
                distance_arr = np.sum(euclidean_dist, axis=2)

                # now we need to find out to which cluster each data point belongs.
                # Use a matrix of n x k where [i,j] = 1 if the ith data point belongs
                # to cluster j.
                min_location = np.zeros(distance_arr.shape)
                min_location[range(distance_arr.shape[0]), np.argmin(distance_arr, axis=1)] = 1

                # calculate J
                j_val = np.sum(distance_arr[min_location == True])
                j_values.append(j_val)

                # calculates the new centroids
                new_centroids = np.empty(centroids.shape)
                for col in range(0, self.k):
                    if self.data[min_location[:, col] == True,:].shape[0] == 0:
                        new_centroids[col] = centroids[col]
                    else:
                        new_centroids[col] = np.mean(self.data[min_location[:, col] == True, :], axis=0)

                # compare centroids to see if they are equal or not
                if self._compare_centroids(centroids, new_centroids):
                    # it has resulted in the same centroids.
                    stabilized = True
                else:
                    centroids = new_centroids
            except:
                print ('exception!')
                continue
            else:
                iterations += 1

        print ('Required ', iterations, ' iterations to stabilize.')
        return iterations, j_values, centroids, min_location

    def _compare_centroids(self, old_centroids, new_centroids, precision=-1):
        if precision == -1:
            return np.array_equal(old_centroids, new_centroids)
        else:
            diff = np.sum((new_centroids - old_centroids)**2, axis=1)
            if np.max(diff) <= precision:
                return True
            else:
                return False

    def initCost(self):
        t = timeit.Timer(lambda: self._initial_centroids())
        return t.timeit(number=10)


# %%
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import fetch_openml, make_swiss_roll
from sklearn.decomposition import PCA, IncrementalPCA, KernelPCA
from sklearn.linear_model import LogisticRegression
from sklearn.manifold import LocallyLinearEmbedding, TSNE
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline

# %%
np.random.seed(4)

# %%
m = 60
w1, w2 = 0.1, 0.3
noise = 0.1

# %%
angles = np.random.rand(m) * 3 * np.pi / 2 - 0.5
X = np.empty((m, 3))
X[:, 0] = np.cos(angles) + np.sin(angles) / 2 + noise * np.random.randn(m) / 2
X[:, 1] = np.sin(angles) * 0.7 + noise * np.random.randn(m) / 2
X[:, 2] = X[:, 0] * w1 + X[:, 1] * w2 + noise * np.random.randn(m)

# %%
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]
print(f"Shapes: U: {U.shape}, s: {s.shape}, Vt: {Vt.shape}")

# %%
m, n = X_centered.shape

S = np.zeros(X_centered.shape)
S[:n, :n] = np.diag(s)

# %%
print(f"X_centered = U @ S @ Vt? {np.allclose(X_centered, U @ S @ Vt)}")

# %%
W2 = Vt.T[:, :2]
X2D_v1 = X_centered.dot(W2)

# %%
pca = PCA(n_components=2)
X2D = pca.fit_transform(X)
print(f"X2d == X2d_v1? {np.allclose(np.abs(X2D), np.abs(X2D_v1))}")
# print(f"Components:\n{pca.components_.T}")
# print(f"First component: {pca.components_.T[:, 0]}")
print(f"First component: {pca.components_[0]}")
print(f"Explained variance: {pca.explained_variance_}")
print(f"Explained variance ratio: {pca.explained_variance_ratio_}")

# %%
X_train, X_val = train_test_split(X, test_size=0.2)

# %%
pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1

# %%
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_train)
print(f"Explained variance ratio: {pca.explained_variance_ratio_} "
      f"({np.sum(pca.explained_variance_ratio_):.3f})")

# %%
if "mnist" in globals():
    mnist = globals()["mnist"]
else:
    mnist = fetch_openml("mnist_784", version=1, as_frame=False)
    mnist.target = mnist.target.astype(np.uint8)

# %%
X = mnist["data"]
y = mnist["target"]

X_train, X_test, y_train, y_test = train_test_split(X, y)

# %%
pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1

# %%
plt.figure(figsize=(6, 4), dpi=300)
plt.plot(cumsum, linewidth=3)
plt.axis([0, 400, 0, 1])
plt.xlabel("Dimensions")
plt.ylabel("Explained Variance")
plt.plot([d, d], [0, 0.95], "k:")
plt.plot([0, d], [0.95, 0.95], "k:")
plt.plot(d, 0.95, "ko")
plt.grid(True)
plt.show()

# %%
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_train)
X_recovered = pca.inverse_transform(X_reduced)

# %%
print(f"Components: {pca.components_.shape[0]}")

# %%
def plot_digits(instances, images_per_row=5, **options):
    size = 28
    images_per_row = min(len(instances), images_per_row)
    n_rows = (len(instances) - 1) // images_per_row + 1
    n_empty = n_rows * images_per_row - len(instances)
    padded_instances = np.concatenate([instances, np.zeros((n_empty, size * size))], axis=0)
    image_grid = padded_instances.reshape((n_rows, images_per_row, size, size))
    big_image = image_grid.transpose(0, 2, 1, 3).reshape(n_rows * size, images_per_row * size)
    plt.imshow(big_image, cmap="binary", **options)
    plt.axis("off")

# %%
# noinspection PyShadowingNames
def plot_recovered(X_recovered):
    plt.figure(figsize=(7, 4), dpi=300)
    plt.subplot(121)
    plot_digits(X_train[::2100])
    plt.title("Original")
    plt.subplot(122)
    plot_digits(X_recovered[::2100])
    plt.title("Compressed")

# %%
plot_recovered(X_recovered)
plt.show()

# %%
pca = PCA(n_components=32)
X_reduced = pca.fit_transform(X_train)
X_recovered = pca.inverse_transform(X_reduced)

# %%
plot_recovered(X_recovered)
plt.show()

# %%
rnd_pca = PCA(n_components=154, svd_solver="randomized")
X_reduced = rnd_pca.fit_transform(X_train)
X_recovered = rnd_pca.inverse_transform(X_reduced)

# %%
plot_recovered(X_recovered)
plt.show()

# %%
n_batches = 100
inc_pca = IncrementalPCA(n_components=154)

# %%
for X_batch in np.array_split(X_train, n_batches):
    inc_pca.partial_fit(X_batch)

# %%
X_reduced = inc_pca.transform(X_train)
X_recovered = inc_pca.inverse_transform(X_reduced)

# %%
plot_recovered(X_recovered)
plt.show()

# %%
X, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)
y = t > 6.9

# %%
rbf_pca = KernelPCA(n_components=2, kernel="rbf", gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)

# %%
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap="hot")
plt.show()

# %%
clf = Pipeline([
    ("kpca", KernelPCA(n_components=2)),
    ("log_reg", LogisticRegression()),
])

# %%
param_grid = [{
    "kpca__gamma": np.linspace(0.03, 0.05, 10),
    "kpca__kernel": ["rbf", "sigmoid"]
}]

# %%
grid_search = GridSearchCV(clf, param_grid, cv=3)
grid_search.fit(X, y)

# %%
print(f"Best parameters: {grid_search.best_params_}")

# %%
rbf_pca = KernelPCA(n_components=2, kernel="rbf", gamma=0.0433, fit_inverse_transform=True)
X_reduced = rbf_pca.fit_transform(X)
X_preimage = rbf_pca.inverse_transform(X_reduced)

# %%
print(f"MSE for Kernel PCA: {mean_squared_error(X, X_preimage):.1f}")

# %%
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)
X_reduced = lle.fit_transform(X)

# %%
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap="hot")
plt.show()

# %%
tsne = TSNE(n_components=2, learning_rate="auto", init="random", n_jobs=48, random_state=42)
X_reduced = tsne.fit_transform(X)

# %%
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap="hot")
plt.show()


import time

import numpy as np

from sklearn.base import BaseEstimator, ClassifierMixin, RegressorMixin
from sklearn.utils.validation import check_X_y, check_array, check_is_fitted
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression as LR

'''
    A simple Gaussian Bayesian Classifier
'''

class GDAClassifier(BaseEstimator, ClassifierMixin):
    def __init__(self):
        self._has_been_fit = False

        self.classes_ = None
        self.class_probabilities = None

        self.X_ = None
        self.y_ = None

        self.n_features = None
        self.n_classes = None

        self.means = None
        self.vars = None
        self.inv_vars = None
        self.normalization_constant = None

    def fit(self, X, y):
        X, y = check_X_y(X, y)

        self.classes_, new_y, self.class_probabilities = np.unique(y, return_inverse=True, return_counts=True)
        self.class_probabilities = self.class_probabilities / float(len(y))

        self.n_classes = len(self.classes_)

        self.X_ = X
        self.y_ = new_y

        n_features = X.shape[1]
        n_classes = self.classes_.shape[0]

        self.n_features = n_features
        self.n_classes = n_classes

        self.means = np.zeros((n_classes, n_features))

        vars_accum = 0
        for i in range(n_classes):
            idx = np.where(self.y_ == i)
            idx = idx[0]

            class_elements = X[idx, :]

            self.means[i, :] = np.mean(class_elements, axis=0)

            for elem in class_elements:
                diff_to_mean = elem - self.means[i, :]
                diff_to_mean = diff_to_mean[np.newaxis, :]
                vars_accum += np.dot(diff_to_mean.T, diff_to_mean)

        self.vars = vars_accum / len(y)
        self.inv_vars = np.linalg.pinv(self.vars)

        self.normalization_constant = 1.0 / ((np.linalg.det(self.vars) ** 0.5) *
                                             ((2 * np.pi) ** (self.n_features / 2)))

        return self

    def predict_likelihood(self, X):
        n_examples = X.shape[0]

        likelihood = np.zeros((n_examples, self.n_classes))

        for i in range(n_examples):
            for j in range(self.n_classes):
                diff_to_mean = X[i, :] - self.means[j, :]

                l = np.exp(-0.5 * np.dot(np.dot(diff_to_mean, self.inv_vars),
                                         diff_to_mean.T)
                           )

                l *= self.normalization_constant

                likelihood[i, j] = l

        return likelihood

    def predict_proba(self, X):
        n_examples = X.shape[0]

        probas = self.predict_likelihood(X)

        probas *= self.class_probabilities
        probas /= np.sum(probas, axis=1).reshape(n_examples, 1)

        probas[np.isnan(probas)] = 0

        return probas

    def predict(self, X):
        check_is_fitted(self, ['X_', 'y_'])

        X = check_array(X)

        probas = self.predict_proba(X)

        return self.classes_[np.argmax(probas, axis=1)]


## 2. Our dataset ##

import pandas

# Set index_col to False to avoid pandas thinking that the first column is row indexes (it's age).
income = pandas.read_csv("income.csv", index_col=False)
print(income.head(5))

## 3. Converting categorical variables ##

# Convert a single column from text categories into numbers.
col = pandas.Categorical.from_array(income["workclass"])
income["workclass"] = col.codes
print(income["workclass"].head(5))
cols = ['education', 'marital_status', 'occupation', 'relationship', 'race', 'sex', 'native_country','high_income']
for item in cols:
    cat = pandas.Categorical.from_array(income[item])
    income[item]=cat.codes

## 5. Performing a split ##

# Enter your code here.
private_incomes = income[income['workclass']==4]
public_incomes = income[income['workclass']!=4]

## 8. Entropy ##

import math
# We'll do the same calculation we did above, but in Python.
# Passing 2 as the second parameter to math.log will take a base 2 log.
entropy = -(2/5 * math.log(2/5, 2) + 3/5 * math.log(3/5, 2))
print(entropy)
countts = income['high_income'].value_counts()
income_entropy = -(countts[0]/len(income['high_income']) * math.log(countts[0]/len(income['high_income']),2) + countts[1]/len(income['high_income']) * math.log(countts[1]/len(income['high_income']),2))
print(income_entropy)

## 9. Information gain ##

import numpy

def calc_entropy(column):
    """
    Calculate entropy given a pandas Series, list, or numpy array.
    """
    # Compute the counts of each unique value in the column.
    counts = numpy.bincount(column)
    # Divide by the total column length to get a probability.
    probabilities = counts / len(column)

    # Initialize the entropy to 0.
    entropy = 0
    # Loop through the probabilities, and add each one to the total entropy.
    for prob in probabilities:
        if prob > 0:
            entropy += prob * math.log(prob, 2)

    return -entropy

# Verify our function matches our answer from earlier.
entropy = calc_entropy([1,1,0,0,1])
print(entropy)

information_gain = entropy - ((.8 * calc_entropy([1,1,0,0])) + (.2 * calc_entropy([1])))
print(information_gain)
income_entropy = calc_entropy(income["high_income"])

median_age = income["age"].median()

left_split = income[income["age"] <= median_age]
right_split = income[income["age"] > median_age]

age_information_gain = income_entropy - ((left_split.shape[0] / income.shape[0]) * calc_entropy(left_split["high_income"]) + ((right_split.shape[0] / income.shape[0]) * calc_entropy(right_split["high_income"])))

## 10. Finding the best split ##

def calc_information_gain(data, split_name, target_name):
    """
    Calculate information gain given a dataset, column to split on, and target.
    """
    # Calculate original entropy.
    original_entropy = calc_entropy(data[target_name])

    # Find the median of the column we're splitting.
    column = data[split_name]
    median = column.median()

    # Make two subsets of the data based on the median.
    left_split = data[column <= median]
    right_split = data[column > median]

    # Loop through the splits, and calculate the subset entropy.
    to_subtract = 0
    for subset in [left_split, right_split]:
        prob = (subset.shape[0] / data.shape[0])
        to_subtract += prob * calc_entropy(subset[target_name])

    # Return information gain.
    return original_entropy - to_subtract

# Verify that our answer is the same as in the last screen.
print(calc_information_gain(income, "age", "high_income"))

columns = ["age", "workclass", "education_num", "marital_status", "occupation", "relationship", "race", "sex", "hours_per_week", "native_country"]
information_gains = []
for item in columns:
    info = calc_information_gain(income,item,'high_income')
    information_gains.append(info)
highest_gain_index = information_gains.index(max(information_gains))
highest_gain = columns[highest_gain_index]

Decision Trees/Introduction to Decision Trees-137.py


#!/usr/bin/env python3

import numpy as np

# 1
myarray = np.array([1, 22.4, 5, 35, 4, 6.7, 3, 8, 40])

print(myarray.ndim)
print(myarray.shape)
print(myarray.size)
print(myarray.dtype)

# 2
mymatrix = np.array([['a', 'b'], ['c', 'd'], [3, 3]])

print(mymatrix.ndim)
print(mymatrix.shape)
print(mymatrix.size)
print(mymatrix.dtype)

# 3
array_arange = np.arange(3, 10)
array_linspace = np.linspace(3, 10)
array_rand = np.random.rand(1, 3)

print(array_arange)
print(array_linspace)
print(array_rand)

# 4
matrix_zeros = np.zeros((3, 3))
matrix_ones = np.ones((3, 3))
matrix_eye = np.eye(3)
matrix_rand = np.random.rand(3, 3)

print(matrix_zeros)
print(matrix_ones)
print(matrix_eye)
print(matrix_rand)

# 5
sevens_array = np.ones(20) * 7
reshaped_sevens = sevens_array.reshape(4, 5)
print(reshaped_sevens)

# 6
random_matrix = np.random.rand(6, 6)
print(random_matrix[0, 0])
print(random_matrix[0:2])
print(random_matrix[4:6])
print(random_matrix[2:4])
print(random_matrix[5:6])
print(random_matrix[2:4, 2:4])

for i in range(0, random_matrix[0].size):
    print(random_matrix[:, i].sum())
    print(random_matrix[:, i].mean())
    print(random_matrix[:, i].max())
    print(random_matrix[:, i].min())


from __future__ import annotations

import numpy as np

from edutorch.typing import NPArray

from .module import Module

class SpatialGroupNorm(Module):
    def __init__(self, num_features: int, G: int, eps: float = 1e-5) -> None:
        super().__init__()
        self.eps = eps
        self.G = G

        self.gamma = np.zeros(num_features)
        self.beta = np.zeros(num_features)
        self.set_parameters("gamma", "beta")

    def forward(self, x: NPArray) -> NPArray:
        """
        Computes the forward pass for spatial group normalization.
        In contrast to layer normalization, group normalization splits each entry
        in the data into G contiguous pieces, which it then normalizes independently.
        Per feature shifting and scaling are then applied to the data, in a manner
        identical to that of batch normalization and layer normalization.

        Inputs:
        - x: Input data of shape (N, C, H, W)
        - gamma: Scale parameter, of shape (C,)
        - beta: Shift parameter, of shape (C,)
        - G: Integer mumber of groups to split into, should be a divisor of C
        - gn_param: Dictionary with the following keys:
        - eps: Constant for numeric stability

        Returns a tuple of:
        - out: Output data, of shape (N, C, H, W)
        - cache: Values needed for the backward pass
        """
        N, C, H, W = x.shape
        self.gamma = self.gamma.reshape((1, C, 1, 1))
        self.beta = self.beta.reshape((1, C, 1, 1))

        x = x.reshape(N * self.G, -1).T
        sample_mean = np.mean(x, axis=0)
        sample_var = np.var(x, axis=0)
        v = np.sqrt(sample_var + self.eps)
        x_hat = (x - sample_mean) / v
        x_hat = x_hat.T.reshape(N, C, H, W)
        out = self.gamma * x_hat + self.beta

        self.cache = (x_hat, v)

        return out

    def backward(self, dout: NPArray) -> tuple[NPArray, ...]:
        """
        Computes the backward pass for spatial group normalization.

        Inputs:
        - dout: Upstream derivatives, of shape (N, C, H, W)
        - cache: Values from the forward pass

        Returns a tuple of:
        - dx: Gradient with respect to inputs, of shape (N, C, H, W)
        - dgamma: Gradient with respect to scale parameter, of shape (C,)
        - dbeta: Gradient with respect to shift parameter, of shape (C,)
        """
        x_hat, v = self.cache

        N, C, H, W = dout.shape
        dx_hat = dout * self.gamma
        dgamma = np.sum(dout * x_hat, axis=(0, 2, 3), keepdims=True)
        dbeta = np.sum(dout, axis=(0, 2, 3), keepdims=True)

        x_hat = x_hat.reshape(N * self.G, -1).T
        dx_hat = dx_hat.reshape(N * self.G, -1).T
        dx = (
            dx_hat - np.mean(dx_hat, axis=0) - x_hat * np.mean(dx_hat * x_hat, axis=0)
        ) / v
        dx = dx.T.reshape(N, C, H, W)

        return dx, dgamma, dbeta


import math
import numpy as np
import itertools

def approximate_equilibrium(row_payoff, col_payoff, epsilon, max_k=30):
    """
    Returns an tuple representing a strategy pair that is an epsilon-approximate
    Nash equilibrium solution based on the given payoff matrices.
    """
    # Do some error checking
    if isinstance(row_payoff, list) and isinstance(col_payoff, list):
        row_payoff = np.array(row_payoff)
        col_payoff = np.array(col_payoff)

    if row_payoff.shape != col_payoff.shape:
        raise ValueError("Payoff matrices differ in shape!")

    row_shape = row_payoff.shape
    if len(row_shape) != 2 or row_shape[0] != row_shape[1]:
        raise ValueError("Payoff matrix is not a square matrix")

    # Calculate our parameters
    n = row_shape[0]
    k = math.ceil((12 * math.log(n)) / (epsilon * epsilon))
    print("Chose k = ", k)

    # Normalize our payoff matrices and epsilon
    row_payoff = normalize(row_payoff)
    col_payoff = normalize(col_payoff)

    if max_k is not None and k > max_k:
        print(f"k={k} has too long of an expected runtime. Switching to k={max_k}")
        k = max_k

    # Calculate the k multisets
    indices = np.arange(n)
    row_multisets = itertools.product(indices, repeat=k)
    col_multisets = itertools.product(indices, repeat=k)

    # Iterate over the (k^2) strategies profiles
    for row_multiset in row_multisets:
        for col_multiset in col_multisets:

            # Convert the pair of multisets into mixed strategies
            row_strategy = _to_mixed_strategy(row_multiset, k, n)
            col_strategy = _to_mixed_strategy(col_multiset, k, n)

            # Spend O(n)  to check whether our solution is correct
            if _is_approximate(row_strategy, col_strategy, row_payoff, epsilon, n):
                if _is_approximate(col_strategy, row_strategy, col_payoff.T, epsilon, n):
                    return (row_strategy, col_strategy)

    # Should never happen
    return None

def _is_approximate(row_strategy, col_strategy, payoff, epsilon, n, debug=False):
    """
    Returns True if the given k-uniform mixed strategy is an epsilon-approximate solution.
    Returns False otherwise.
    """
    col = np.dot(payoff, col_strategy)
    row_reward = np.inner(row_strategy, col)

    deviations = np.identity(n)
    pure_rewards = np.inner(deviations, col)

    if debug:
        print(pure_rewards - row_reward)

    return np.all(np.abs(pure_rewards - row_reward) <= epsilon)

def normalize(matrix):
    """
    Returns the given numpy array with values scaled to [0, 1]
    """
    min_val = np.min(matrix)
    max_val = np.max(matrix)
    scaling_factor = (max_val - min_val)

    normalized = (matrix - min_val) / scaling_factor
    return normalized

def _to_mixed_strategy(multiset, k, size):
    """
    Returns a k-uniform mixed strategy based on the given multiset
    """
    # Convert to numpy array for speed
    multiset = np.array(multiset)

    # Track the probability of events
    values, alpha = np.unique(multiset, return_counts=True)

    # Convert to mixed strategy
    strategy = np.zeros(size)
    strategy[values] = alpha / k

    return strategy

def main():
    print("Try running the test function instead!")

if __name__ == '__main__':
    main()


import numpy as np

## Mean ##
data = np.array([])
mean = np.mean(data)

## Standard Deviation ##
data = np.array([])
std = np.std(data)

## Variance ##
data = np.array([])
var = np.std(data)**2

## Sample estimator of standard deviation ##
data = np.array([])

def seStd(data):
    n = 0
    for e in data:
        n += (e-np.mean(data))**2
    n = n/(len(data)-1)
    n = np.sqrt(n)
    return n

samstd = seStd(data)

## Standard Error of mean ##
def calculateStandardErrorMean(data):
    return np.std(data)/np.sqrt(len(data))

## Confidence interval for population mean (95%) ##
def calculateConfidenceIntervalMean(data):
    return f"{np.mean(data)} +/- {1.96*(np.std(data)/np.sqrt(len(data)))}"

## Standard Error of proportion ##
# p = proportion
def calculateStandardErrorProportion(data,p):
    return np.sqrt(p*(1-p)/len(data))

## Confidence interval for proportion (95%) ##
# p = proportion
# n = sample size
def calculateConfidenceIntervalProportion(p,n):
    return f"{p} +/- {1.96*np.sqrt(p*(1-p)/n)}"

## Agresti-Coull interval for proportion (95%) ##
# p = proportion
# n = sample size
def calculateAgrestiCoullInterval(p,n):
    p2 = (p*n+2)/(n+4)
    n2 = n+4
    x = (1.96*np.sqrt(p2*(1-p2)/n2))
    return f"{p2} +/- {x}"

## Sample size for mean (95%) ##
# s = Expected sample variance
# e = Desired margin of error (eg. 0.05 as 5% margin of error)
def calculateSampleSizeMean(s,e):
    return (1.96**2)*(s**2/e**2)

## Sample size for proportion (95%) ##
# p = Expected proportion (Worst case scenaria: 0.5)
# e = Desired margin of error (eg. 0.05 as 5% margin of error)
def calculateSampleSizeProportion(p,e):
    return (1.96**2)*(p*(1-p)/e**2)

## Covariance ##
data = np.array([])
cov = np.cov(data)

## Correlation
def calculateCorrelation(data):
    temp=np.cov(data)
    x=[char for char in temp[0]]
    y=[char for char in temp[1]]
    return (x[1]/(np.sqrt(x[0])*np.sqrt(y[1])))


import numpy as np

def rotation_matrix(angle):
    """Create an array corresponding to the 2D transformation matrix
    of a rotation over `angle`.
    """
    return np.array([[np.cos(angle), -np.sin(angle)],
                     [np.sin(angle), np.cos(angle)]])

def scaling_matrix(kappa, dims=2):
    """Create a numpy array containing the
    `dims`-dimensional scaling matrix scaling the first dimension by kappa.
    """
    return np.diag([kappa]+[1]*(dims-1))

def apply_transformation_matrix(vecs, matrix):
    """Apply transformation matrix `matrix` to a list of vectors `vecs`.
    `vecs` can either be a list of vectors, or a NxM array, where N is
    the number of M-dimensional vectors.
    """
    avecs = np.asanyarray(vecs)
    return avecs @ matrix.T

def rotate(vecs, angle):
    "Rotate 2D vectors `vecs` by `angle` around the origin."
    return apply_transformation_matrix(vecs, rotation_matrix(angle))

def wrapToPi(x):
    """Wrap all values of x to the interval -pi,pi"""
    r = (x + np.pi) % (2*np.pi) - np.pi
    return r


import numpy as np
def prime_sieve(top_lim,limit=500000):
    """ Use the sieve of Erathosthenes to generate an array of primes
    arguments:
    top_lim - the highest number you'd like to generate up to
    limit - the highest number of iterations you're willing to allow (Defaut 500000)"""
    arr = np.concatenate((np.array([2]),np.arange(3,top_lim,2)))
    change=True
    min_num=2
    len_arr = 1
    # will loop up until the sqrt of your top limit
    limit_check = top_lim**0.5
    while change==True:
        # find the lowest number that you haven't already divided by
        min_num = np.min(arr[arr>min_num])
        # sieve - remove all numbers divisible or less than your min
        arr=arr[(arr%min_num!=0)|(arr<=min_num)]
        if min_num>=limit_check:
            change=False
        limit_check+=1
        if limit_check>limit:
            change=False
    # convert to np.float64
    arr=np.array(arr, dtype=np.float64)
    return arr


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, ln, pprint, oo )

# The lifetime of a printer costing $200 is exponentially distributed with mean 5 years.
# The manufacturer agrees to pay a full refund to a buyer if the printer fails during the first year following its purchase, and a one-half refund if it fails during the second year.
# If the manufacturer sells 100 printers, how much should it expect to pay in refunds?

cost = 100

mu = 3
a = 1 / mu
x = symbols( 'x' )

pdf = a * exp( -x / mu )

year_1 = integrate( pdf, ( x, 0, 1 ) ).evalf()
year_2 = integrate( pdf, ( x, 1, 2 ) ).evalf()

refund = year_1 * cost + year_2 * ( .5 * cost )
refund = refund * 100

round( refund, 2 )


import numpy as np
from tuples import Tuple
from math import sin, cos

class Matrix:
    def __init__(self, matrix):
        self.matrix = np.array(matrix)
        self.is_invertible = None

    def __eq__(self, other):
        return np.allclose(self.matrix, other.matrix, rtol=0.0001)

    def __mul__(self, other):
        if type(other) is Matrix:
            result = np.matmul(self.matrix, other.matrix)
            return Matrix(result.tolist())
        else:
            result = self.matrix.dot(other.vec)
            return Tuple(*result.tolist())

    @staticmethod
    def identity(size):
        return Matrix(np.identity(size).tolist())

    def transpose(self):
        return Matrix(np.transpose(self.matrix).tolist())

    def determinant(self):
        result = np.linalg.det(self.matrix)
        self.is_invertible = result != 0
        return result

    def slow_determinant(self):
        if self.matrix.shape == (2, 2):
            result = self.matrix[0, 0] * self.matrix[1, 1] - self.matrix[0, 1] * self.matrix[1, 0]
        else:
            result = 0
            for ic, col in enumerate(self.matrix[0]):
                result += col * self.cofactor(0, ic)

        self.is_invertible = result != 0
        return result

    def submatrix(self, row, column):
        return Matrix(np.delete(np.delete(self.matrix, row, axis=0), column, axis=1))

    def minor(self, row, column):
        return self.submatrix(row, column).determinant()

    def cofactor(self, row, column):
        factor = 1 if (row + column) % 2 == 0 else -1
        return factor * self.minor(row, column)

    def slow_inverse(self):
        determinant = self.slow_determinant()
        if not self.is_invertible:
            return None

        new = np.empty(self.matrix.shape, dtype=float)
        for row in range(self.matrix.shape[0]):
            for col in range(self.matrix.shape[1]):
                new[col, row] = self.cofactor(row, col) / determinant

        return Matrix(new.tolist())

    def inverse(self):
        return Matrix(np.linalg.inv(self.matrix).tolist())

    @staticmethod
    def translation(x, y, z):
        ident = Matrix.identity(4)
        ident.matrix[0, 3] = x
        ident.matrix[1, 3] = y
        ident.matrix[2, 3] = z
        return ident

    @staticmethod
    def scaling(x, y, z):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = x
        ident.matrix[1, 1] = y
        ident.matrix[2, 2] = z
        return ident

    @staticmethod
    def rotation_x(radians):
        ident = Matrix.identity(4)
        ident.matrix[1, 1] = cos(radians)
        ident.matrix[1, 2] = -sin(radians)
        ident.matrix[2, 1] = sin(radians)
        ident.matrix[2, 2] = cos(radians)
        return ident

    @staticmethod
    def rotation_y(radians):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = cos(radians)
        ident.matrix[0, 2] = sin(radians)
        ident.matrix[2, 0] = -sin(radians)
        ident.matrix[2, 2] = cos(radians)
        return ident

    @staticmethod
    def rotation_z(radians):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = cos(radians)
        ident.matrix[0, 1] = -sin(radians)
        ident.matrix[1, 0] = sin(radians)
        ident.matrix[1, 1] = cos(radians)
        return ident

    @staticmethod
    def shearing(xy, xz, yx, yz, zx, zy):
        ident = Matrix.identity(4)
        ident.matrix[0, 1] = xy
        ident.matrix[0, 2] = xz
        ident.matrix[1, 0] = yx
        ident.matrix[1, 2] = yz
        ident.matrix[2, 0] = zx
        ident.matrix[2, 1] = zy
        return ident


from math import log

from ..app import app

import sys
import numpy as np
from scipy.integrate import odeint

#based on https://scipython.com/blog/the-double-pendulum/

# The gravitational acceleration (m.s-2).
g = 9.81

def deriv(y, t, L1, L2, m1, m2):
    """Return the first derivatives of y = theta1, z1, theta2, z2."""
    theta1, z1, theta2, z2 = y

    c, s = np.cos(theta1-theta2), np.sin(theta1-theta2)

    theta1dot = z1
    z1dot = (m2*g*np.sin(theta2)*c - m2*s*(L1*z1**2*c + L2*z2**2) -
            (m1+m2)*g*np.sin(theta1)) / L1 / (m1 + m2*s**2)
    theta2dot = z2
    z2dot = ((m1+m2)*(L1*z1**2*s - g*np.sin(theta2) + g*np.sin(theta1)*c) +
            m2*L2*z2**2*s*c) / L2 / (m1 + m2*s**2)
    return theta1dot, z1dot, theta2dot, z2dot

def solve(L1, L2, m1, m2, tmax, dt, y0):
    t = np.arange(0, tmax+dt, dt)

    # Do the numerical integration of the equations of motion
    y = odeint(deriv, y0, t, args=(L1, L2, m1, m2))
    theta1, theta2 = y[:,0], y[:,2]

    # Convert to Cartesian coordinates of the two bob positions.
    x1 = L1 * np.sin(theta1)
    y1 = -L1 * np.cos(theta1)
    x2 = x1 + L2 * np.sin(theta2)
    y2 = y1 - L2 * np.cos(theta2)

    return theta1, theta2, x1, y1, x2, y2

@app.task
def simulate_pendulum_instance(L1, L2, m1, m2, tmax, dt, theta1_init, theta2_init):

    y0 = np.array([
        theta1_init,
        0.0,
        theta2_init,
        0.0
    ])
    #vracam i thete da znam za cega su ovi rezultati (da ih posle mogu
    #pisati u fajl)
    return theta1_init, theta2_init, solve(L1, L2, m1, m2, tmax, dt, y0)


import numpy as np

def left_most_point(points):
    '''
    Takes in the array of points, and returns the point
    furthest on the left side.
    '''
    leftMost = np.array([])
    for point in points:
        if leftMost.size == 0 or point[0] < leftMost[0]:
            leftMost = point
    return leftMost

def lr_most_point(points):
    '''
    Takes in the array of points, and returns the points
    furthest on the left and right sides as a tuple.
    '''
    leftMost  = np.array([])
    rightMost = np.array([])
    for point in points:
        if leftMost.size == 0 or point[0] < leftMost[0]:
            leftMost = point
        if rightMost.size == 0 or point[0] > rightMost[0]:
            rightMost = point
    return (leftMost, rightMost)

def dist(A, B, C):
    '''
    Returns the shortest distance between the point C,
    and the line segment connected by A and B.
    '''
    P = A - C
    Q = A - B
    magQ = np.linalg.norm(Q)

    proj = (np.dot(P, Q) / (magQ*magQ)) * Q
    return np.linalg.norm(P - proj)

def sort_by_x(points):
    '''
    Sort the array by x coordinate, or by y coordinate in case of tie.
    Returns a array of data points. (Does not modify the array)
    '''
    indices = np.lexsort((points[:,1], points[:,0]))
    return points[indices]

def cross(a, b, o):
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1]  - o[1]) * (b[0] - o[0])


from typing import List, Optional, Union

from numpy import ndarray, array, vstack, zeros, exp
from numpy.linalg import inv

from optmlstat.functions.function_base import FunctionBase

class GaussianBasisFunction(FunctionBase):
    """
    Gaussian basis function.
    We do not multiply the scaling term as in Gaussian probability density function (PDF)
    since it can be dealt with coefficients.
    """

    def __init__(
        self,
        covariance_list: Union[List[Union[ndarray, float, int]], ndarray, float, int],
        mean_array_2d: Optional[ndarray] = None,
        inverse: bool = False,
    ) -> None:
        """
        Parameters
        ----------
        covariance_list:
          List of covariance matrices
        inverse:
          If inverse is True, covariance_list is interpreted as a list of the inverses of the covariance matrices.
        """
        if isinstance(covariance_list, int):
            covariance_list = float(covariance_list)

        if isinstance(covariance_list, float):
            covariance_list = array([[covariance_list]])

        if isinstance(covariance_list, ndarray):
            covariance_list = [covariance_list]

        assert len(covariance_list) > 0

        for idx, covariance in enumerate(covariance_list):
            if isinstance(covariance, (float, int)):
                covariance_list[idx] = array([[float(covariance)]])

        assert covariance_list[0].ndim == 2, covariance_list[0].ndim
        assert covariance_list[0].shape[0] == covariance_list[0].shape[1], covariance_list[0].shape
        for covariance in covariance_list:
            assert covariance_list[0].shape == covariance.shape, (covariance_list[0].shape, covariance.shape)

        self.covariance_list: List[ndarray] = covariance_list

        self.mean_array_2d: ndarray = mean_array_2d

        if self.mean_array_2d is None:
            self.mean_array_2d = zeros((self.num_outputs, self.num_inputs))

        assert self.mean_array_2d.shape == (self.num_outputs, self.num_inputs)

        self.inverse_covariance_list: List[ndarray] = self.covariance_list
        if not inverse:
            self.inverse_covariance_list = [inv(covariance) for covariance in self.covariance_list]

    @property
    def num_inputs(self) -> Optional[int]:
        return self.covariance_list[0].shape[0]

    @property
    def num_outputs(self) -> Optional[int]:
        return len(self.covariance_list)

    @property
    def is_affine(self) -> Optional[bool]:
        return False

    @property
    def is_strictly_convex(self) -> Optional[bool]:
        return False

    @property
    def is_convex(self) -> Optional[bool]:
        return False

    def get_y_values_2d(self, x_array_2d: ndarray) -> ndarray:
        y_array_1d_list: List[ndarray] = list()

        for idx, inverse_covariance in enumerate(self.inverse_covariance_list):
            x_array_2d_ = x_array_2d - self.mean_array_2d[idx, :]
            y_array_1d_list.append(exp(-0.5 * (x_array_2d_.dot(inverse_covariance) * x_array_2d_).sum(axis=1)))

        return vstack(y_array_1d_list).T


"""
Polynomial Regression and Pipelines.

What do we do when a linear model is not the best fit for our data?
Let’s look into another type of regression model: the polynomial regression.
We Transform our data into a polynomial, then use linear regression to fit the parameter.
Then we will discuss pipelines.
Pipelines are a way to simplify your code.
Polynomial regression is a special case of the general linear regression.
This method is beneficial for describing curvilinear relationships.
What is a curvilinear relationship?
It’s what you get by squaring or setting higher-order terms of the predictor variables in the model, transforming the data.
The model can be quadratic, which means that the predictor variable in the model is squared.

"""

# First we import all the modules we need.
import pandas as pd
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

# get data file from url
# dataFile = 'https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data'

# or just from a local repository
data_file = './data/imports-85.data'

# defining headers
headers = ['symboling', 'normalized-losses', 'make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'wheel-base', 'length', 'width', 'height', 'curb-weight', 'engine-type', 'num-of-cylinders', 'engine-size', 'fuel-system', 'bore', 'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg', 'highway-mpg', 'price']

# read file into pandas dataframe
df = pd.read_csv(data_file)

# set the dataframe header
df.columns = headers

# CALCULATE A POLYNOMIAL OF 3RD ORDER
print("\n\n\n# CALCULATE A POLYNOMIAL OF 3RD ORDER\n")

x = df["price"]
y = df["engine-size"]

# create a Polynomial of 3rd order
f = np.polyfit(x,y,3)

#
p = np.poly1d(f)

# print the polynomial
print(p)
"""
OUTPUT
            3             2
-1.759e-12 x + 1.655e-07 x + 0.0002803 x + 94.56
"""

# POLYNOMIAL REGRESSION WITH THE ONE DIMENSION
print("\n\n\n# POLYNOMIAL REGRESSION WITH THE ONE DIMENSION\n")

# We use the "preprocessing" library in sci-kit-learn, to create a polynomial feature object.
pr = PolynomialFeatures(degree=2, include_bias=False)

# Then we transform the features into a polynomial feature with the “fit_transform” method.
x_polly = pr.fit_transform(df[['horsepower', 'curb-weight']])

print(x_polly)
"""
OUTPUT
[[1.1100000e+02 2.5480000e+03 1.2321000e+04 2.8282800e+05 6.4923040e+06]
 [1.5400000e+02 2.8230000e+03 2.3716000e+04 4.3474200e+05 7.9693290e+06]
 [1.0200000e+02 2.3370000e+03 1.0404000e+04 2.3837400e+05 5.4615690e+06]
 ...
 [1.3400000e+02 3.0120000e+03 1.7956000e+04 4.0360800e+05 9.0721440e+06]
 [1.0600000e+02 3.2170000e+03 1.1236000e+04 3.4100200e+05 1.0349089e+07]
 [1.1400000e+02 3.0620000e+03 1.2996000e+04 3.4906800e+05 9.3758440e+06]]
"""

# PREPROCESSING
print("\n\n\n# PREPROCESSING\n")

# We can use the preprocessing module to simplify many tasks.

# We import “StandardScaler”
from sklearn.preprocessing import StandardScaler

# We train the object
SCALE = StandardScaler()

# fit the scale object;
SCALE.fit(df[['horsepower', 'highway-mpg']])

# then transform the data into a new dataframe on array “x_scale”.
x_scale = SCALE.transform(df[['horsepower', 'highway-mpg']])

print(x_scale)
"""
OUTPUT
[[ 0.19101743 -0.54779597]
 [ 1.24419463 -0.69311506]
 [-0.029415   -0.11183871]
 [ 0.2889874  -1.27439141]
 [ 0.16652494 -0.83843415]
            ...
 [ 0.75434477 -1.12907233]
 [ 0.06855497 -0.54779597]
 [ 0.26449491 -0.83843415]]
"""

# PIPELINES
print("\n\n\n# PIPELINES\n")

"""
There are more normalization methods available in the preprocessing library, as well as other transformations.
We can simplify our code by using a pipeline library.
There are many steps to getting a prediction, for example, Normalization, Polynomial transform, and Linear regression.
We simplify the process using a pipeline.
Pipelines sequentially perform a series of transformation.
The last step carries out a prediction.
"""

# We create a list of tuples, the first element in the tuple contains the name of the estimator: model. The second element contain model constructor.
Input = [
    ('scale', StandardScaler()),
    ('polynomial', PolynomialFeatures(degree=2)),
    ('model', LinearRegression)
]

# We input the list in the pipeline constructor.
Pipe = Pipeline(Input)

print(Pipe)

# We now have a pipeline object, We can train the pipeline by applying the train method to the Pipeline object.
# Pipe.train(df['horsepower', 'curb-weight', 'engine-size', 'highway-mpg'], y)

# We can also produce a prediction as well.
# yhat = Pipe.predict(df['horsepower', 'curb-weight', 'engine-size', 'highway-mpg'])


from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, roc_curve, roc_auc_score
from sklearn.model_selection import cross_val_predict
import matplotlib.pyplot as plt
import numpy as np

# let's try to identify Iris-Virginica(targe = 2) just by using petal width feature.
# This is a basic example of single class classification

iris_np = load_iris()
X = iris_np['data'][:, 3:]  # peta; width
y = (iris_np['target'] == 2).astype(np.int)  # make the output = 1 if Iris-Virginica, else 0

# fit out model
logit = LogisticRegression()
logit.fit(X, y)

# find the decision boundary of each class(where the two lines meet)
X_test = np.linspace(0, 3, 1000).reshape(-1, 1)  # make synthetic test data. 0<=petal widht<=3
y_prob = logit.predict_proba(X_test)  # get the probabilities of each class
# plt.plot(X_test, y_prob[:, 1], 'g-', label='Iris-Virginica')
# plt.plot(X_test, y_prob[:, 0], 'b--', label='Not Iris-Virginica')
# plt.show() # uncomment to show graph

# Model accuracy

# 1) confusion matrix --> perfect predictor: all but the main diagonal are zeros
predicted = cross_val_predict(logit, X, y, cv=10)
matrix = confusion_matrix(y, predicted)  # [[95  5][ 3 47]]
# print(matrix)

# 2) Precision VS Recall
precision = precision_score(y, predicted)  # 0.9038461538461539
recall = recall_score(y, predicted)  # 0.94

# 3) F1 score or harmonic mean of precision and recall
# Combines precision and recall into a single formula. Useful when you care about
# the overall performance and not so much about them individually
f1 = f1_score(y, predicted)  # 0.9215686274509804

# 4) ROC curve ==> True positive rate VS False positive rate
def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1], [0, 1], 'k--')
    plt.axis([0, 1, 0, 1])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')

y_scores = cross_val_predict(logit, X, y, cv=10, method='decision_function')
fpr, tpr, thresholds = roc_curve(y, y_scores) # gets the True positive and false positive rate.
# plot_roc_curve(fpr, tpr) # matplot code to graph
# plt.show()

# how to compare two classifiers, use the area under the curve. area = { 1 ==> perfect, 0.5 ==> random}
area_under_roc = roc_auc_score(y, y_scores)


import numpy as np
from sklearn import datasets
import random

def kmeans(x_train, k):
    changed = True
    center = np.array(random.sample(x_train, k))
    dist = np.zeros((150, k))
    # Loop until the center won't change
    while changed:
        for i in range(k):
            C = np.tile(center[i,:], (150,1))
            dist[:,i]=np.sqrt(np.sum((x_train-C)**2,axis=1))

        labels = np.argmin(dist, axis=1)
        newcenter = np.zeros((k,4))
        for i in range(k):
            newcenter[i,:] =  np.average(x_train[labels==i,:], axis=0)
        changed = (newcenter == center).all() == False
        center = newcenter
    # Calculate the number of each group
    num_of_each_type = np.array([x_train[labels == i,:].shape[0] for i in range(k)])
    # Calcultate the total distance of the classification
    totalDistance = 0
    for i in range(k):
        #print sum(labels==i)
        newCenter = np.tile(center[i,:], (sum(labels==i),1))
        #print "dist"+str(np.sum((x_train[labels==i,:]-newCenter)**2,axis=1))
        totalDistance += np.sum((np.sum((x_train[labels==i,:]-newCenter)**2,axis=1)))
    # return type labels and total distance and number of each type
    return labels, np.sqrt(totalDistance), num_of_each_type

# Loading data
iris = datasets.load_iris()
x_train = iris.data
y_train = iris.target

k = 3
n = 10 # num of loops
labels = {}
num = {}
totalDist = np.zeros(n)
minDist = 10**6
minIndex = 0
for j in range(n):
    labels[j], totalDist[j], num[j] = kmeans(x_train, k)
    if totalDist[j] < minDist:
        minIndex = j
        minDist = totalDist[j]

# Print the classification results
print "Number of samples in each claster is " + str(num[minIndex]) + "."
#print "\t\t %-15s %-15s %-15s" %("setosa", "versicolor", "virginica")
print "\t\t %-15s %-15s %-15s" %("type0", "type1", "type2")
for i in range(k):
    a = sum((y_train[labels[minIndex]==i])==0)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    b = sum((y_train[labels[minIndex]==i])==1)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    c = sum((y_train[labels[minIndex]==i])==2)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    print "%8s\t %-15.4f %-15.4f %-15.4f" %("cluster"+str(i), a, b, c)


# A piece of wire 17 ft. long is cut into two pieces. One piece is made into a circle and the other piece is made into a square. Let the piece of length x be formed into a circle.
#
# Allow x to equal 0 or 17, so all the wire is used for the square or the circle.
# Where should the cut be made to make the sum of the areas maximum?
# (Hint: Remember to use the endpoints of a domain when looking for absolute maxima and minima.)

import math
from sympy import Derivative, symbols, solve, sympify, pi
from collections import namedtuple

Pieces = namedtuple( 'Pieces', [ 'circumference', 'side'] )

def cut_wire( l ):
	# Side, Circumference
	s, c = symbols( 's, c' )
	# V = s^2 ( square ) + pi * ( c / 2pi )^2
	A = ( s / 4 ) ** 2 + c**2 / ( 4 * math.pi )

	# c + s = l
	L = c + s - l

	# Solve for c in terms of s
	sC = solve( L, s )[ 0 ]

	# A1 = area in terms of a single variable
	A1 = A.subs( s, sC )

	# Now single variable, take The derivitave with respect to c.
	dA = Derivative( A1, c ).doit()
	slns = solve( dA )

	min_c = slns[ 0 ]
	min_s = l - min_c

	return Pieces( circumference = min_c, side = min_s )

def area_sqare( s ):
	return s**2

def area_circle( c ):
	return c**2 / ( 4 * math.pi )

if __name__ == '__main__':

	try:
		l = float( input( 'Enter the length of the wire to cut: ') )
	except ValueError:
		print( 'Error parsing values' )
	else:
		print( 'wire length: {0}\n'.format( l ) )

		pieces = cut_wire( l )
		areaCircle = area_circle( pieces.circumference )
		areaSqare = area_sqare( pieces.side )

		print( 'Mimimum Area: {0}'.format( areaCircle + areaSqare ) )
		print( 'Circle Area: {0}'.format( areaCircle ) )
		print( 'Square Area: {0}'.format( areaSqare ) )

		print( 'Maximum Area: Circle = {0} ({1} len), Square = 0, (0 len)'.format( area_circle( l ), l ) )