"""
Example 3 - Logistic Regression without regularization

NOTE: The example and sample data is being taken from the "Machine Learning course by Andrew Ng" in Coursera.

Problem:
  Suppose that you are the administrator of a university department and
  you want to determine each applicant’s chance of admission based on their
  results on two exams. You have historical data from previous applicants
  that you can use as a training set for logistic regression. For each training
  example, you have the applicant’s scores on two exams and the admissions
  decision.
  Your task is to build a classification model that estimates an applicant’s
  probability of admission based the scores from those two exams.

  The file 'data/ex1data1.txt' contains the dataset for our
  Logistic regression problem.
"""

# initial imports

import numpy as np
from matplotlib import pyplot as plt

plt.ion()
from models.data_preprocessing import add_bias_unit, map_feature, feature_normalize
from models.logistic_regression import cost_function, predict, gradient_descent, gradient_function, sigmoid
from models.plotter import plot_decision_boundary

data = np.loadtxt('data/ex2data1.txt', delimiter=',')
X = data[:, :-1]
y = data[:, -1, np.newaxis]

"""
==================== Part 1: Plotting ====================
  We start the exercise by first plotting the data to understand the
  the problem we are working with.
"""
print('Plotting data with + indicating (y = 1) examples and o indicating (y = 0) examples.')
"""
Example plotting for multiple markers
x = np.array([1,2,3,4,5,6])
y = np.array([1,3,4,5,6,7])
m = np.array(['o','+','+','o','x','+'])

unique_markers = set(m)  # or yo can use: np.unique(m)

for um in unique_markers:
    mask = m == um
    # mask is now an array of booleans that van be used for indexing
    plt.scatter(x[mask], y[mask], marker=um)
"""
fig, ax = plt.subplots()
y_slim = y.ravel()
# plotting y=1 values
ax.scatter(x=X[y_slim == 1, 0], y=X[y_slim == 1, 1], marker='+', color='black', s=50)
# plotting y=0 values
# X[y_slim == 0, 0] is logical indexing with rows with y=0 only
ax.scatter(x=X[y_slim == 0, 0], y=X[y_slim == 0, 1], marker='o', color='y', s=25)

# labels
ax.set_xlabel('Exam 1 score')
ax.set_ylabel('Exam 2 score')

# Specified in plot order
ax.legend(['Admitted', 'Not admitted'])

# ============ Part 2: Compute Cost and Gradient ============
# initial sizes
m, n = X.shape

# adding bias unit
X = add_bias_unit(X)

# Initialize fitting parameters
initial_theta = np.zeros([n + 1, 1])

# Compute and display initial cost and gradient
cost = cost_function(initial_theta, X, y, regularized=False)
grad = gradient_function(initial_theta, X, y, regularized=False)
print('Cost at initial theta (zeros): {}'.format(cost))
print('Expected cost (approx): 0.693')
print('Gradient at initial theta (zeros): ')
print(grad)
print('Expected gradients (approx):\n -0.1000\n -12.0092\n -11.2628\n')

# =========== Performing gradient descent================
# from models.data_preprocessing import feature_normalize
X_norm, mu, sigma = feature_normalize(X[:, 1:])
X_norm = add_bias_unit(X_norm)

from scipy.optimize import minimize

# theta_history = np.array([]).reshape([0, n+1])
theta_history = []

def cg(abc, *args):
    theta_history.append(abc)

initial_theta = np.zeros(n + 1)
op_result = minimize(fun=cost_function, x0=initial_theta, jac=gradient_function, args=(X, y, 0.01, False), method='cg', callback=cg)

# cost = cost_function(op_result.x,X,y, regularized=False)
print('Cost at theta found by Gradient descent: {}'.format(op_result.fun))
print('Expected cost (approx): 0.203')
print('theta: {}'.format(op_result.x))
print('Expected theta (approx):\n')
print(' -25.161\n 0.206\n 0.201\n')

# converting theta_history into J_history
J_history = (np.array(theta_history[::-1]) @ op_result.x)

# plot J_history
fig1, ax1 = plt.subplots()
ax1.plot(range(J_history.size), J_history)
ax1.set_xlabel('Iterations')
ax1.set_ylabel('Cost')

theta = op_result.x[:, np.newaxis]
plot_decision_boundary(theta, X, y, sigmoid, 0.1, fig, ax)

# ============== Part 4: Predict and Accuracies ==============
#  After learning the parameters, we are going to use it to predict the outcomes
#  on unseen data. In this part, we will use the logistic regression model
#  to predict the probability that a student with score 45 on exam 1 and
#  score 85 on exam 2 will be admitted.
#
#  Furthermore, we will compute the training and test set accuracies of
#  our model.

#  Predict probability for a student with score 45 on exam 1
#  and score 85 on exam 2

prob = sigmoid(np.array([1, 45, 85]) @ theta)
print('For a student with scores 45 and 85, we predict an admission probability of {}'.format(prob))
print('Expected value: 0.775 +/- 0.002')

# Compute accuracy on our training set
p = predict(X, theta)

print('Train Accuracy: {}'.format(np.mean(p == y) * 100))
print('Expected accuracy (approx): 89.0\n')

# ===============End================
plt.ioff()
plt.show()


# coding: utf-8

# # Introdução

# A transformação para domínios alternativos ao tempo contínuo ou discreto pode provê características importantes para análise do sinal. Em particular a transformação para o dominío da frequência utilizando a transformada de Fourier visa exibir comportamentos relativos as parte que compoem o sinal.
#
# Qualquer sinal pode ser representado como uma soma infinita de senos e cossenos, tais componentes são chamadas de harmônicos. Então, uma análise em dominio da frequência pela transformada de fourier (também chamada de análise espectral) induz ao projetista identificar de forma direta as componentes e frequências presentes naquele sinal. Esta é uma forma de ver as menores partes presentes em um sinal.
#
# Em relação as imagens o processamento desta transformada é traduzida matematicamente pela aplicação da seguinte transfomada no sinal:
#
# $$F[m,n] = \frac{1}{UV} \int_o^U \int_o^V F(u,v) e^{j2\pi(um.x0 + un.y0} du dv$$

# ## Implementação e discussões

# In[1]:

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

# - Abrir imagem:

# In[2]:

img = np.array(cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE))
rows, cols = img.shape

# - Aplicar a transformada discrea de Fourier:

# In[3]:

img_dft = np.fft.fft2(img)

# - Trazer a componente DC para o centro da imagem, transladando n/2 nas duas direções:

# In[4]:

img_dft_shift = np.fft.fftshift(img_dft)

# - Calcular a magnitude a partir da componente real e imaginária:

# In[5]:

img_dft_mag = np.abs(img_dft_shift)

# In[6]:

plt.figure(2,figsize=(10,9))
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title("Imagem original")
plt.axis('OFF')

plt.subplot(122)
plt.imshow(20*np.log(img_dft_mag), 'gray')
plt.title("Espectro em frequência")
plt.axis('OFF')

plt.show()

# - Cálculo da inversa:

# In[7]:

img_idft = np.fft.ifft2(img_dft)
img_inversa = np.abs(img_idft)

plt.figure(2)
plt.imshow(img_inversa, 'gray')
plt.title("Imagem após IDFT")
plt.axis('OFF')

plt.show()

# ## Filtragem na frequência

# ### Filtro passa-alta:

# - Criar máscara Laplaciana

# In[8]:

def laplacianKernel(h1, h2):

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

    sizeKernel = np.array([h1,h2])

    kernel = -1*np.ones(sizeKernel)

    center = np.uint8(kernel.shape[0]/2)

    kernel[center,center] = (-1*np.sum(kernel)) -1

    return kernel

def gaussianKernel(h1, h2):

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

    ## Returns a normalized 2D gauss kernel array for general purporses

    x, y = np.mgrid[0:h2, 0:h1]
    x = x-h2/2
    y = y-h1/2
    sigma = 2
    g = np.exp( -( x**2 + y**2 ) / (2*sigma**2) )
    return g / g.sum()

filterKernel = gaussianKernel(rows,cols)

# In[9]:

filter_dft = np.fft.fft2(filterKernel)
filter_dft_shift = np.fft.fftshift(filter_dft)
filter_dft_mag = np.abs(filter_dft_shift)

filter_dft_mag = 1-filter_dft_mag

plt.figure(3+1)
plt.imshow(filter_dft_mag, 'gray')
plt.title("Espectro em frequência do filtro Gaussiano com spread 2")
plt.show()

# - Filtragem na frequência

# In[10]:

filter_img = img_dft_shift * filter_dft_mag
filter_img_mag = np.abs(filter_img)

#img_back = np.fft.fftshift(np.fft.ifft2(filter_img))

img_back = np.fft.ifft2(filter_img)

img_back_mag = np.abs(img_back)

plt.figure(5, figsize=(12,12))
plt.subplot(221)
plt.imshow((img), 'gray')
plt.title("Imagem original")

plt.subplot(222)
plt.imshow(20*np.log(img_dft_mag), 'gray')
plt.title("Espectro da imagem original")

plt.subplot(223)
plt.imshow(img_back_mag, 'gray')
plt.title("Imagem após filtragem na frequência")

plt.subplot(224)
plt.imshow(20*np.log(filter_img_mag), 'gray')
plt.title("Especto da imagem após filtragem na frequência")

plt.show()

# O filtro escolhido foi o filtro Gaussiano inverso, que funciona de forma análoga ao traidcional: atenua baixas frequências e permite a passagem de altas frequências.
#
# O efeito na imagem é um ressalto das regiões de transições rápidas (altas frequências) promovendo um ressalto das bordas em uma imagem. Comparando os espectros é possivel notar que as regiões de constância em intensidade de cor na imagem (baixas frequências), representadas pela maior parte de informação no espectro, sofrem atenuações e o resultado é um novo espectro com um aspecto limpo, exaltando apenas componentes de alta frequência e menos presente na imagem.

# # Conclusões

# A análise de imagens no domínio da frequência é uma ferramenta que provê análises visuais perante a imagem, exibe padrões de comportamento das cores na imagem, podem resultar em identificação de componentes como ruído ou objetos ao olhar para o espectro.
#
# Há a possibilidade de realizar filtragem no domínio da frequência, assim no domínio temporal, é um processa mais rápido pois envolve uma multiplica simples ponto a ponto, talvez o grande parte do custo computacional em executar tal técnica está em realizar a transformada direta e inversa de Fourier.

PDI - Fourier e Wavelet/01 - Fourier/codes/Python/T14/T14+-+Code.py

PDI - Fourier e Wavelet/01 - Fourier/codes/Python/src/T14+-+Code.py


"""

 Created by akiselev on 2019-07-22

mean

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

import numpy

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

print numpy.mean(my_array, axis = 0)        #Output : [ 2.  3.]
print numpy.mean(my_array, axis = 1)        #Output : [ 1.5  3.5]
print numpy.mean(my_array, axis = None)     #Output : 2.5
print numpy.mean(my_array)                  #Output : 2.5

By default, the axis is None. Therefore, it computes the mean of the flattened array.

var

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

import numpy

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

print numpy.var(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.var(my_array, axis = 1)         #Output : [ 0.25  0.25]
print numpy.var(my_array, axis = None)      #Output : 1.25
print numpy.var(my_array)                   #Output : 1.25

By default, the axis is None. Therefore, it computes the variance of the flattened array.

std

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

import numpy

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

print numpy.std(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.std(my_array, axis = 1)         #Output : [ 0.5  0.5]
print numpy.std(my_array, axis = None)      #Output : 1.11803398875
print numpy.std(my_array)                   #Output : 1.11803398875

By default, the axis is None. Therefore, it computes the standard deviation of the flattened array.

Task

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

1.The mean along axis 1
2.The var along axis 0
3.The std along axis None
"""

import numpy
n,m = map(int, input().split())
numpy.set_printoptions(sign=' ')
arr = numpy.array([input().split() for _ in range(n)], float)
print (numpy.mean(arr, axis = 1))
print (numpy.var(arr, axis = 0))
print(numpy.around( numpy.std(arr), 12))


# -*- coding: utf-8 -*-
"""
@author: datamonday
"""
import math
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler

#################### 说明 ##########################
# 为了可读性, 所有numpy和math库的函数均使用.x()调用, #
# 这种方式在实际测试中相比直接函数x()调用耗时        #
####################################################

# =========================== Data Pre-processing =========================== #

def nomalize(X, axis=-1, order=2):
    """
    归一化 (Normalize) the dataset X. data range [0, 1].
    Notes：这种方式是参考库的作者实现方式
    ----
    param X: np.array.
    param axis: axis=0(1) means columns(rows), axis=-1 means last dim.
    param order: order=2 means l2 norm.
    """
    # 针对矩阵每个行向量求向量的l2范数
    l2 = np.atleast_1d(np.linalg.norm(X, ord=order, axis=axis))
    # 将范数为零的元素置1，防止下一步除法操作溢出（NaN）
    l2[l2 == 0] = 1

    # 返回归一化的值，各api作用详见ipynb
    return X / np.expand_dims(l2, axis=axis)

def standardize(X):
    """
    标准化 (Standardize) the dataset X. mean=0, std=1.
    ----
    param X: np.array
    """
    X_std = X
    # 按列（axis=0）计算均值和标准差
    mean = X.mean(axis=0)
    std = X.std(axis=0)
    for col in range(np.shape(X)[1]):
        if std[col]:
            X_std[:, col] = (X_std[:, col] - mean[col]) / std[col]

    return X_std

def normalize_sk(X, feature_range=(0,1)):
    """
    使用 sklearn的库函数实现归一化，方便反归一化操作.
    """
    scaler = MinMaxScaler(feature_range=feature_range)
    print("Your input data type is: ", type(X))
    minmax_X = scaler.fit_transform(X)

    return scaler, minmax_X

def standardize_sk(X):
    """
    使用 sklearn的库函数实现标准化，方便反标准化操作.
    """
    scaler = StandardScaler()
    print("Your input data type is: ", type(X))
    standard_X = scaler.fit_transform(X)

    return scaler, standard_X

# ========================== Statistical Calculate ========================== #
def mean_squared_error(y_true, y_pred):
    """
    Returns the mean squared error between y_true and y_pred
    """
    mse = np.mean(np.power(y_true - y_pred, 2))

    return mse

def calculate_variance(X):
    """
    Return the variance of the features in dataset X
    """
    mean = np.ones(np.shape(X)) * X.mean(axis=0)
    # 样本数
    n_samples = np.shape(X)[0]
    # np.diag() 函数返回方阵的第k条对角线的元素, k=0表示主对角线
    variance = (1 / n_samples) * np.diag((X - mean).T.dot(X - mean), k=0)

    return variance

def calculate_std_dev(X):
    """
    Calculate the standard deviations of the features in dataset X
    """
    std_dev = np.sqrt(calculate_variance(X))

    return std_dev

def euclidean_distance(x1, x2):
    """
    Calculates the l2 distance between two vectors
    """
    # 实现1: numpy
    # return np.sqrt(np.sum(np.square(x1 - x2) ) )

    # 实现2: scipy
    # return pdist(np.vstack([x1, x2]), metric="euclidean")

    # 实现3: Python原生模块
    distance = 0
    for i in range(len(x1)):
        distance += pow((x1[i] - x2[i]), 2)

    return math.sqrt(distance)

def calculate_softmax(X, axis=1):
    """
    Calculate the softmax of input vector X.
    softmax(y_i) = \frac{e^{y_i}}{\sum_j e^{y_i}}
    ----------
    Parameters
    ----------
    X : np.array
    axis : int, optional
        axis=1 means calculate by rows. The default is 1.

    Returns softmax(X)
    -------
    """
    # 输入矩阵每行的最大值
    row_max = X.max(axis=axis)
    # 增加维度, 否则无法运算
    row_max = row_max.reshape(-1, 1)
    # 每行元素均减去最大值, 防止溢出
    X = X - row_max
    # 分子
    X_exp = np.exp(X)
    # 分母, keep_dim保持输入的维度数量不变(尺寸可能变化)
    X_sum = np.sum(X_exp, axis=axis, keep_dim=True)

    return X_exp / X_sum

def calculate_entropy(y):
     """
     Calculate the entropy of label array y.
     ----------
     Parameters
     ----------
     y : the sample labels.
     cross entropy = -\sum_{i=1}^{k} c_i * log(p_i)
     """
     # 换底公式
     log2 = lambda x: math.log(x) / math.log(2)
     # 所有标签
     unique_labels = np.unique(y)
     # 熵
     entropy = 0
     for label in unique_labels:
         count = len(y[y == label])
         # 某一类的概率
         p = count / len(y)
         # 交叉熵计算公式
         entropy += -p * log2(p)

     return entropy

def calculate_covariance_matrix(X, Y=None):
    """
    Calculate the covariance matrix for the dataset X
    """
    if not Y:
        Y = X
    n_samples = np.shape(X)[0]
    # 方差
    covariance_matrix = (1 / n_samples) * (X - X.mean(axis=0)).T.dot(Y - Y.mean(axis=0))

    return np.array(covariance_matrix, dtype=float)

def calculate_correlation_matrix(X, Y=None):
    """
    Calculate the correlation matrix for the dataset X
    ----------

    """
    if not Y:
        Y = X
    n_samples = np.shape(X)[0]
    # 方差
    # 相关系数计算公式的分子, 协方差Cov(X, Y)
    covariance = (1 / n_samples) * (X - X.mean(axis=0)).T.dot(Y - Y.mean(axis=0))
    # 相关系数的分母 方差开根号 sqrt(D(X)), sqrt(D(Y))
    std_dev_X = np.expand_dims(calculate_std_dev(X), 1)
    std_dev_Y = np.expand_dims(calculate_std_dev(Y), 1)
    # 相关系数计算公式
    correlation_matrix = np.divide(covariance, std_dev_X.dot(std_dev_Y.T))

    return np.array(correlation_matrix, dtype=float)

# ============================ Evaluation Metrics =========================== #
def accuray_score(y_true, y_pred):
    """
    Compare y_true to y_pred and return the accuracy
    """
    try:
        accuracy = np.sum(y_true == y_pred, axis=0) / len(y_true)
    except ValueError as e:
        print("Please ensure input params must not be zero or None!", e)
    else:
        return accuracy

def precision_score():
    pass

def recall_score():
    pass

def f_score():
    pass


import numpy as np
from HW2.tools import norm_p

def steepest_descent_direction(x):
    return -1 * f_provider.grad(*x)

def newton_direction(x):
    h_i = np.linalg.inv(f_provider.hessian(*x))
    g = f_provider.grad(*x)
    return -1 * np.dot(h_i, g)

last_BFGS_H, last_step_length, last_x = [None] * 3
def BFGS_direction(x):
    global last_BFGS_H, last_step_length, last_x

    if last_BFGS_H is None:
        H = np.linalg.inv(f_provider.hessian(*x))
    else:
        last_p = -1 * np.dot(last_BFGS_H, f_provider.grad(*x))
        x_kPlus1 = last_x
        s = last_step_length * last_p
        x_k = x_kPlus1 - s
        y = f_provider.grad(*x_kPlus1) - f_provider.grad(*x_k)
        # print("-----------", s,"============", y)
        m = 1 / np.dot(s, y)
        s = s[:, None]  # shape: (size of x,) -> (size of x,1)
        y = y[:, None]
        s_yT = np.dot(s, y.T)
        y_sT = np.dot(y, s.T)
        s_sT = np.dot(s, s.T)
        mL = np.identity(np.shape(x)[0]) - m * s_yT
        mR = np.identity(np.shape(x)[0]) - m * y_sT
        rL = np.dot(np.dot(mL, last_BFGS_H), mR)
        rR = m * s_sT
        H = rL + rR

    last_BFGS_H = H
    return -1 * np.dot(H, f_provider.grad(*x))

def first_wolf_condition(x, p, alpha, c):
    """
    sufficient decrease condition
    """
    new_x = x + alpha * p
    return f_provider.f(*new_x) <= f_provider.f(*x) + c * alpha * np.dot(p, f_provider.grad(*x))

def backtracking_line_length(x, p):
    alpha, m, c = 1, .5, .5  # parameters
    while not first_wolf_condition(x, p, alpha, c):
        alpha = alpha * m
        if alpha < TOLERANCE:
            return alpha
    return alpha

TOLERANCE, MAX_STEP = .001, 10000
def line_search(x, function_provider, descent_direction_algorithm=steepest_descent_direction):
    global f_provider, last_BFGS_H, last_x, last_step_length
    f_provider = function_provider
    last_BFGS_H = None
    step = 0
    while norm_p(f_provider.grad(*x)) >= TOLERANCE and step < MAX_STEP:
        p = descent_direction_algorithm(x)
        alpha = backtracking_line_length(x, p)
        x = x + alpha * p
        last_x, last_step_length = x, alpha
        step += 1
        # print('{}. new x: {} using alpha={}'.format(step, x, alpha))
    print('number of steps: ', step)
    return x


# from __future__ import division

#-------------------------------------------------------
#  Yu. Eidelman (start at 06/05/2017):
#
# This is original script mcool2.py
# with my insertions for debugging and understanding
#-------------------------------------------------------

import os, sys
import numpy as np
import math
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import LogNorm
import matplotlib as mpl

from scipy.constants import pi
from scipy.constants import speed_of_light as clight
from scipy.constants import epsilon_0 as eps0
from scipy.constants import mu_0 as mu0
from scipy.constants import elementary_charge as qe
from scipy.constants import electron_mass as me
from scipy.constants import proton_mass as mp
from scipy.constants import Boltzmann as kB

fourPiEps0 = 4 * pi * eps0
invFourPiEps0 = 1 / fourPiEps0

print '\nStart...'
print '\nElementary charge (C) = %e' % qe
print '\nElectron mass (kg) = %e' % me
print '\nProton mass (kg) = %e' % mp
print '\nepsilon_0 = %e' % eps0
print '\nclight = %e' % clight
print '\nmu0 = %e' % mu0
print '\nBoltzmann = %e' % kB
print '\nfourPiEps0 = %e' % fourPiEps0
print '\ninvFourPiEps0 = %e' % invFourPiEps0

"""
Elementary charge (C) = 1.602177e-19
Electron mass (kg) = 9.109383e-31
Proton mass (kg) = 1.672622e-27
epsilon_0 = 8.854188e-12
clight = 2.997925e+08
mu0 = 1.256637e-06
Boltzmann = 1.380649e-23
fourPiEps0 = 1.112650e-10
"""

""" reset some default options """
np.set_printoptions(linewidth=96)

""" indexing """
(Ix, Ipx, Iy, Ipy, Iz, Ipz) = range(6)

""" prefixes """
(femto, pico, nano, micro, milli, one, kilo, mega, giga, tera, peta) = \
    10. ** np.asarray(range(-15, 15+1, 3))

"""
We define the ion charge and mass here as global parameters.
We do the same for the magnetic field strength $B$ and the
thermal velocity $v_\text{th}$.

Then we compute various related derived quantities.

In [6]:
"""

"""
angular frequency of Larmor rotations
NB: This is a *signed* quantity, which means
that for electrons, say, you must set Z = -1.
"""
def omega_Larmor(mass, B, Z = 1):
    return Z * qe * B / mass

Z_ion = 1
M_ion = mp
B_mag = 1. # Tesla
e_temp = 300. # Kelvin

N_gyro = 100 # a somewhat arbitrary choice, range [100, 160]

""" derived quantities """
V_th = math.sqrt(2 * kB * e_temp / me)
rho_gc = me * V_th / (qe * B_mag)
rho_gc_micro=rho_gc/micro
Omega_e = omega_Larmor(me, B_mag, Z = -1)
frequency=Omega_e / (2 * pi) / giga
T_e = (2 * pi) / abs(Omega_e)
T_e_nano = T_e / nano
T_intxn = N_gyro * T_e
T_intxn_nano = T_intxn / nano

print 'V_th = %e ' % V_th
print 'rho_gc (A) = %e '  % rho_gc_micro
print 'Omega_e (s^(-1)) = %e ' %  Omega_e
print 'frequency (GHz) = %e '  % frequency
print 'T_e (ns) = %e '  % T_e_nano
print 'T_intxn (ns) = %e '  % T_intxn_nano

"""
V_th =  95361.4171888
rho_gc / mkm =  0.542189740332
Omega_e / s^(-1) =  -1.7588200236e+11
frequency / GHz =  -27.9924900765
T_e / ns =  0.0357238672682
T_intxn / ns =  3.57238672682
"""

"""
Two-body Magnetized Collisions

The Hamiltonian for a two-body interaction between an ion and a magnetized electron is $$ \vphantom{\Big]} H(\vQion, \vPion, \vQe, \vPe) = H_0(\vPion, \Qe{y}, \vPe)

+ H_\text{C}(\vQion, \vQe)

$$ where $$$$\begin{align} H_0(\vPion, \Qe{y}, \vPe) &= \frac{1}{2\Mion}\bigl(\Pion{x}^2 + \Pion{y}^2 + \Pion{z}^2\bigr) + \frac{1}{2\Me}\bigl((\Pe{x} + e B \Qe{y})^2 + \Pe{y}^2 + \Pe{z}^2\bigr),\\[1ex] H_\text{C}(\vQion, \vQe) &= -\frac{Ze^2}{4\pi\varepsilon_0} \big/ {\sqrt{(\Qion{x}-\Qe{x})^2 + (\Qion{y}-\Qe{y})^2 + (\Qion{z}-\Qe{z})^2}}, \end{align}$$

$$1ex] $$ and $$$ denotes the elementary quantum of charge.

The simplest second-order scheme for integrating this system uses
a split-operator approach: We approximate the total map $\Map{M}$ for a
time step of size $h$ by the symmetric form $$ \vphantom{\Big]} \Map{M}(h) \approx \Map{M}_0(h/2) \Map{M}_C(h) \Map{M}_0(h/2) $$ where $\Map{M}_0$ and $\Map{M}_C$ are the exact maps for the Hamiltonians $H_0$
and $H_C$ respectively. The map $\Map{M}_0$ is a simple linear map. The map
$\Map{M}_C$ generates a nonlinear kick of both ion and electron momenta.
Hamiltonians for Two-body Magnetized Collisions
In [5]:

Hamiltonian for free ion and electron in a magnetic field, under
the assuption that the ion is unaffected by that magnetic field.

Arguments:
  z_i (ndArray): 6 x N array of canonical coordinates
                 and conjugate momenta for the ions
  z_e (ndArray): 6 x N array of canonical coordinates
                 and conjugate momenta for the electrons
  In both of the above arrays, the six phase-space variables
  are given in the order(x, px, y, py, z, pz)

Return:
  the total 'free' energy of each ion-electron pair

"""
def H_twobody_0(z_i, z_e):
    ham_i = ((z_i[Ipx,:] ** 2 + z_i[Ipy,:] ** 2 + z_i[Ipz,:] ** 2)
             / (2 * M_ion))
    ham_e = ((z_e[Ipx,:] + (-qe) * B_mag * z_e[Iy,:]) ** 2
             + z_e[Ipy,:] ** 2 + z_e[Ipz,:] ** 2) / (2 * me)
    return ham_i + ham_e

"""
Hamiltonian for the interaction of each ion-electron pair.
"""
def H_twobody_C(z_i, z_e):
    g_ie = -(Z_ion * qe ** 2) / (4 * pi * eps0)
    intxn = g_ie / np.sqrt(
        + (z_i[Ix,:] - z_e[Ix,:]) ** 2
        + (z_i[Iy,:] - z_e[Iy,:]) ** 2
        + (z_i[Iz,:] - z_e[Iz,:]) ** 2)
    return intxn

"""
Total Hamiltonian for each ion-electron pair.
"""
def H_twobody(z_i, z_e):
    ham_0 = H_twobody_0(z_i, z_e)
    ham_C = H_twobody_C(z_i, z_e)
    return ham_0 + ham_C

"""
Maps for Two-body Magnetized Collisions
In [6]:

define transfer maps for ions and electrons
There are three maps to define here: one each
for ions and electrons under H_0, and another
"""

""" matrix for a linear drift """
def MatD(mass, h):
    Mdrift = np.identity(6)
    for i in (Ix, Iy, Iz):
        Mdrift[i, i + 1] = h / mass
    return Mdrift

""" matrix for linear electron dynamics in a solenoidal field """
def MatK0_e(h):
    mw = me * Omega_e
    wh = Omega_e * h
    cwh = math.cos(wh)
    swh = math.sin(wh)
    cwh1m = 2 * math.sin(wh / 2) ** 2  # 1 - cos(a) = 2 sin^2(a / 2)
    MK0 = np.identity(6)
    MK0[Iy,  Iy ] = cwh
    MK0[Ipy, Ipy] = cwh
    MK0[Iy,  Ipy] = swh / mw
    MK0[Ipy, Iy ] = -mw * swh
    MK0[Iz,  Ipz] = h / me
    MK0[Ix,  Ipx] = swh / mw
    MK0[Ix,  Iy ] = swh
    MK0[Ix,  Ipy] = cwh1m / mw
    MK0[Iy,  Ipx] = -cwh1m / mw
    MK0[Ipy, Ipx] = -swh
    return MK0

"""
map phase-space coordinates forward in time by amount h
based on the Hamiltonian H_0, which describes the free
motion of ions and the motion of electrons in a solenoidal
magnetic field
"""
def MapZ_0(h, z_i, z_e):
    mat = MatD(M_ion, h)
    zf_i = mat.dot(z_i)
    mat = MatK0_e(h)
    zf_e = mat.dot(z_e)
    return zf_i, zf_e

"""
map phase-space coordinates forward in time by amount h
based on the Hamiltonian H_C, which describes the collision
between a single ion-electron pair
"""
def MapZ_C(h, z_i, z_e):
    g = h * Z_ion * qe ** 2 / (4 * pi * eps0)
    dz = z_i - z_e
    denom = (dz[Ix,:] ** 2 + dz[Iy,:] ** 2 + dz[Iz,:] ** 2) ** (3/2)
    zf_i = z_i.copy()
    zf_e = z_e.copy()
    for ip in (Ipx, Ipy, Ipz):
        zf_i[ip,:] = z_i[ip,:] - g * dz[ip - 1] / denom
        zf_e[ip,:] = z_e[ip,:] + g * dz[ip - 1] / denom
    return zf_i, zf_e

def apply_MapZ_0(h, n, z_i, z_e):
    mat_i = MatD(M_ion, h)
    mat_e = MatK0_e(h)
    zf_i = [z_i]
    zf_e = [z_e]
    for i in range(n):
        z_i = mat_i.dot(z_i)
        z_e = mat_e.dot(z_e)
        zf_i.append(z_i)
        zf_e.append(z_e)
    return np.asarray(zf_i), np.asarray(zf_e)

""" second-order split-operator integration for the total Hamiltonian """
def apply_MapZ(h, n, z_i, z_e):
    hh = 0.5 * h
    mat_i = MatD(M_ion, hh)
    mat_e = MatK0_e(hh)
    zf_i = [z_i]
    zf_e = [z_e]
    for i in range(n):
        z_i = mat_i.dot(z_i)
        z_e = mat_e.dot(z_e)
        z_i, z_e = MapZ_C(h, z_i, z_e)
        z_e = mat_e.dot(z_e)
        z_i = mat_i.dot(z_i)
        zf_i.append(z_i)
        zf_e.append(z_e)
    return np.asarray(zf_i), np.asarray(zf_e)

"""
We also require the transformation to and from the coordinates $\Theta$-J:
$$ \begin{align}
\Theta &= \dotsb,
J &= p_\phi + \frac{Ze^2}{4\pi\varepsilon_0}
\frac{r_L}{\Omega} \frac{(\Qion{x}-xgc)\cos\phi-(\Qion{y}-ygc)\sin\phi}
{% \bigl[(\Qion{x}-\Qe{x})^2+(\Qion{y}-\Qe{y})^2+(\Qion{z}-\Qe{z})^2+r_L^2\bigr]^{3/2}}.
\end{align} $$
$$ \begin{align} \phi &= \dotsb, \\ p_\phi &= \dotsb. \end{align} $$
In [7]:
"""

""" convert to guiding-center coordinates """
def toGuidingCenter(z_e):
    mOmega = me * Omega_e
    zgc = z_e.copy()
    zgc[Ix,:]  = np.arctan2(z_e[Ipx,:] + mOmega * z_e[Iy,:], z_e[Ipy,:])
    zgc[Ipx,:] = (((z_e[Ipx,:] + mOmega * z_e[Iy,:]) ** 2
                   + z_e[Ipy,:] ** 2) / (2 * mOmega))
    zgc[Iy,:]  = - z_e[Ipx,:] / mOmega
    zgc[Ipy,:] = z_e[Ipy,:] + mOmega * z_e[Ix,:]
    return zgc

""" convert from guiding-center coordinates """
def fromGuidingCenter(zgc):
    mOmega = me * Omega_e
    rhoL = np.sqrt(2 * mOmega * zgc[Ipx,:]) / mOmega
    z_e = zgc.copy()
    z_e[Ix,:]  = zgc[Ipy,:] / mOmega - rhoL * np.cos(zgc[Ix,:])
    z_e[Ipx,:] = - mOmega * zgc[Iy,:]
    z_e[Iy,:]  = zgc[Iy,:] + rhoL * np.sin(zgc[Ix,:])
    z_e[Ipy,:] = mOmega * rhoL * np.cos(zgc[Ix,:])
    return z_e

""" return J(z_gc) coordinates using the (probably correct) minus sign """
def actionJ(z_i, zgc):
    g = Z_ion * qe ** 2 / (4 * pi * eps0)
    mOmega = me * Omega_e
    rhoL = np.sqrt(2 * zgc[Ipx,:] / mOmega)
    num = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) * np.cos(zgc[Ix,:])
        -  (z_i[Iy,:] - zgc[Iy,:]) * np.sin(zgc[Ix,:]))
    den = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
        +  (z_i[Iy,:] - zgc[Iy,:]) ** 2
        +  (z_i[Iz,:] - zgc[Iz,:]) ** 2 + rhoL ** 2) ** (3/2)
    return zgc[Ipx,:] + g * (rhoL / Omega_e) * num / den

"""
In [8]:
"""

""" return the Larmor radius """
def rLarmor(z_e):
    mOmega = me * Omega_e
    return np.sqrt((z_e[Ipx,:] + mOmega * z_e[Iy,:]) ** 2 + z_e[Ipy,:] ** 2) / mOmega

""" return the Larmor radius """
def rLarmor_gc(zgc):
    mOmega = me * Omega_e
    return np.sqrt(2 * mOmega * zgc[Ipx,:]) / mOmega

""" return the perturbation ratio (uses the minus sign) """
def pertubationRatio(z_i, zgc):
    mOmega = me * Omega_e
    rhoL = np.sqrt(2 * mOmega * zgc[Ipx,:]) / mOmega
    num = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) * np.cos(zgc[Ix,:])
           - (z_i[Iy,:] - zgc[Iy,:]) * np.sin(zgc[Ix,:]))
    den = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
        +  (z_i[Iy,:] - zgc[Iy,:]) ** 2
        +  (z_i[Iz,:] - zgc[Iz,:]) ** 2 + rhoL ** 2)
    return 2 * rhoL * num / den

""" return the ratio (impact parameter) / (Larmor radius) """
def impactParamOverRL(z_i, zgc):
    mOmega = me * Omega_e
    rhoL = np.sqrt(2 * mOmega * zgc[Ipx,:]) / mOmega
    b = np.sqrt((z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
               + (z_i[Iy,:] - zgc[Iy,:]) ** 2)
    return b / rhoL

"""
Hamiltonians using Guiding-center Coordinates
In [9]:

Hamiltonian for free ion and electron in a magnetic field, under
the assuption that the ion is unaffected by that magnetic field.

"""
def H_gc_0(z_i, zgc):
    ham_i = ((z_i[Ipx,:] ** 2 + z_i[Ipy,:] ** 2 + z_i[Ipz,:] ** 2)
             / (2 * M_ion))
    ham_e = Omega_e * actionJ(z_i, zgc) + zgc[Ipz,:] ** 2 / (2 * me)
    return ham_i + ham_e

"""
Hamiltonian for the ion-electron interaction in guiding-center coordinates
"""
def H_gc_C(z_i, zgc):
    g_ie = (Z_ion * qe ** 2) / (4 * pi * eps0)
    mOmega = me * Omega_e
    intxn = -g_ie / np.sqrt(
        + (z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
        + (z_i[Iy,:] - zgc[Iy,:]) ** 2
        + (z_i[Iz,:] - zgc[Iz,:]) ** 2
        + 2 * actionJ(z_i, zgc) / mOmega)
    return intxn

"""
total Hamiltonian for the ion-electron system in GC coordinates
"""
def H_gc(z_i, zgc):
    ham_0 = H_gc_0(z_i, zgc)
    ham_C = H_gc_C(z_i, zgc)
    return ham_0 + ham_C

"""
Hamiltonian for the ion-electron interaction
in guiding-center coordinates.
"""
def H_gc_Cp(z_i, zgc):
    g_ie = (Z_ion * qe ** 2) / (4 * pi * eps0)
    mOmega = me * Omega_e
    intxn = -g_ie / np.sqrt(
        + (z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
        + (z_i[Iy,:] - zgc[Iy,:]) ** 2
        + (z_i[Iz,:] - zgc[Iz,:]) ** 2
        + 2 * actionJp(z_i, zgc) / mOmega)
    return intxn

"""
total Hamiltonian for the ion-electron system in GC coordinates
"""

def H_gc_m(z_i, zgc):
    ham_0 = H_gc_0(z_i, zgc)
    ham_C = H_gc_Cm(z_i, zgc)
    return ham_0 + ham_C

"""
Maps using Guiding-center Coordinates
In [10]:
"""

"""
define transfer maps for ions and electrons
There are three maps to define here: one each
for ions and electrons under H_0, and another
for the interaction under H_c
"""

"""
Map phase-space coordinates forward in time by amount h.
This map is based on the Hamiltonian H_gc_0, which describes
the free motion of ions and the averaged motion of electrons
in a solenoidal magnetic field.
NB: We do not update the \Theta coordinate, as it does not
contribute to the dynamics of any other variables.
"""

def MapZgc_0(h, z_i, zgc):
    matD = MatD(M_ion, h)
    zf_i = matD.dot(z_i)
    zf_e = zgc.copy()
    zf_e[Iz,:] += (h / me) * zgc[Ipz,:]
    return zf_i, zf_e

"""
Map phase-space coordinates forward in time by amount h.
This map is based on the Hamiltonian H_gc_C, which describes
the collision between a single ion-electron pair in guiding-center
coordinates.
NB: We do not update the \Theta coordinate, as it does not
contribute to the dynamics of any other variables.
"""
def MapZgc_C(h, z_i, zgc):
    g_ie = Z_ion * qe ** 2 / (4 * pi * eps0)
    mOmega = me * Omega_e
    dr3 = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) ** 2
           + (z_i[Iy,:] - zgc[Iy,:]) ** 2
           + (z_i[Iz,:] - zgc[Iz,:]) ** 2
           + (2 / mOmega) * actionJ(z_i, zgc)) ** (3/2)
    Omega_gc = (g_ie / mOmega) / dr3
    S = np.sin(Omega_gc * h)
    C1 = 2 * np.sin(Omega_gc * (h / 2)) ** 2
    zf_i = z_i.copy()
    zf_e = zgc.copy()
    Dxgc = ((z_i[Ix,:] - zgc[Ipy,:] / mOmega) * C1
            + (z_i[Iy,:] - zgc[Iy,:]) * S)
    Dygc = ((z_i[Iy,:] - zgc[Iy,:]) * C1
            - (z_i[Ix,:]- zgc[Ipy,:] / mOmega) * S)
    Dpz = (Omega_gc * h) * mOmega * (z_i[Iz,:] - zgc[Iz,:])
    zf_i[Ipx,:] += mOmega * Dygc
    zf_i[Ipy,:] -= mOmega * Dxgc
    zf_i[Ipz,:] -= Dpz
    zf_e[Iy,:]  += Dygc
    zf_e[Ipy,:] += mOmega * Dxgc
    zf_e[Ipz,:] += Dpz
    return zf_i, zf_e

def apply_MapZgc_0(h, n, z_i, zgc):
    mat_i = MatD(M_ion, h)
    mat_e = np.identity(6)
    mat_e[Iz, Ipz] = h / me
    zf_i = [z_i]
    zf_e = [zgc]
    for i in range(n):
        z_i = mat_i.dot(z_i)
        zgc = mat_e.dot(zgc)
        zf_i.append(z_i)
        zf_e.append(zgc)
    return np.asarray(zf_i), np.asarray(zf_e)

def apply_MapZgc(h, n, z_i, zgc):
    hh = 0.5 * h
    mat_i = MatD(M_ion, hh)
    mat_e = np.identity(6)
    mat_e[Iz, Ipz] = hh / me
    zf_i = [z_i]
    zf_e = [zgc]
    for i in range(n):
#        z_i = mat_i.dot(z_i)
#        zgc = mat_e.dot(zgc)
        z_i = np.dot(mat_i,z_i)
        zgc = np.dot(mat_e,zgc)
        z_i, zgc = MapZgc_C(h, z_i, zgc)
#        zgc = mat_e.dot(zgc)
#        z_i = mat_i.dot(z_i)
        zgc =np.dot( mat_e,zgc)
        z_i = np.dot(mat_i,z_i)
        zf_i.append(z_i)
        zf_e.append(zgc)
    return np.asarray(zf_i), np.asarray(zf_e)

"""
Maps using the Magnus Expansion
In [11]:
"""

"""
compute $\Delta P_\text{ion}$ using the Magnus expansion
"""
def magnus_deltaPIon(h, z_i, zgc):
    g_ie = (Z_ion * qe ** 2) / (4 * pi * eps0)
    mOmega = me * Omega_e
    xgc = zgc[Ipy,:] / mOmega
    C1 = ((z_i[Ix,:] - xgc) ** 2
        + (z_i[Iy,:] - zgc[Iy,:]) ** 2
        + (z_i[Iz,:] - zgc[Iz,:]) ** 2
        + (2 / mOmega) * actionJ(z_i, zgc))
    C2 = (2 * ((z_i[Ix,:] - xgc) * z_i[Ipx,:] / M_ion
             + (z_i[Iy,:] - zgc[Iy,:]) * z_i[Ipy,:] / M_ion
             + (z_i[Iz,:] - zgc[Iz,:]) * (z_i[Ipz,:] / M_ion - zgc[Ipz,:] / me)))
    C3 = ((z_i[Ipx,:] / M_ion) ** 2
        + (z_i[Ipy,:] / M_ion) ** 2
        + (z_i[Ipz,:] / M_ion - zgc[Ipz,:] / me) ** 2)
    B = np.sqrt(C1 + (C2 + C3 * h) * h)
    Delta = 4 * C1 * C3 - C2 ** 2
    D1 = (2 * C3 * h + C2) / B - C2 / np.sqrt(C1)
    D2 = (C2 * h + 2 * C1) / B - 2 * np.sqrt(C1)
    dPx = - ((2 * g_ie / Delta) *
             ((z_i[Ix,:] - xgc) * D1 - (z_i[Ipx,:] / M_ion) * D2))
    dPy = - ((2 * g_ie / Delta) *
             ((z_i[Iy,:] - zgc[Iy,:]) * D1 - (z_i[Ipy,:] / M_ion) * D2))
    dPz = - ((2 * g_ie / Delta) *
             ((z_i[Iz,:] - zgc[Iz,:]) * D1 - (z_i[Ipz,:] / M_ion - zgc[Ipz,:] / me) * D2))
    return np.asarray(( dPx, dPy, dPz)).T

"""
In [12]:
"""

"""
For the Magnus computation to work well, the interaction must be perturbative.
This function return a value for the minimum impact parameter, above which the
interaction becomes perturbative.
"""
def minImpactParam(magB, EkOverEV, bOverRL):
    numer = 2 * EkOverEV * (invFourPiEps0 * Z_ion * me)
    denom = ((1 / bOverRL) ** 2 + (tan_alpha / (N_gyro * pi)) ** 2) * magB ** 2
    return (numer / denom) ** (1./3)

"""
Conversion from (Q,V) to (Q,P)
In [13]:
"""

"""
define matrix that transforms ion coordinate-velocity data
to canonically conjugate phase-space variables
"""

QVtoZion = np.identity(6)

for i in (Ipx, Ipy, Ipz):
    QVtoZion[i, i] = M_ion

"""
define matrix that transforms electron coordinate-velocity data
to canonically conjugate phase-space variables
NB: This depends on the local magnetic vector potential,
hence on the local magnetic field.
"""
QVtoZe = np.identity(6)
for i in (Ipx, Ipy, Ipz):
    QVtoZe[i, i] = me
QVtoZe[Ipx, Iy] = (-qe) * (-B_mag)

"""
Explore a range of values for the perturbation parameters
In [14]:
"""

Z_ion = 1
M_ion = mp
e_temp = 300. # Kelvin
B_mag = 1. # Tesla

N_gyro = 100    # enforce adequate averaging
tan_alpha = 5.0 # and an adequate opening angle

""" derived quantities """
Omega_e = omega_Larmor(me, B_mag, Z = -1)
T_e = (2 * pi) / abs(Omega_e)

"""
In [17]:
"""

bOverLarmorR = 20.0 # 20 ==> max perturbation ratio of about 0.1
EkinOverVmax = 10.0 # 10 ==> eV_closest_approach / E_kinetic = 0.1
print "minImpactParam=", minImpactParam(B_mag, EkinOverVmax, bOverLarmorR)/micro

"""
minImpactParam= 3.90333164611
"""

"""
3.90333173895

In [26]:
"""

decades = 2
pts_per_decade = 3
logBs = np.linspace(0., 1. * float(decades), num = 1 + pts_per_decade * decades, endpoint = True)
bvals = np.power(10, logBs)
impactParameterB = micro * bvals

print "impactParameterB / micro =    ", impactParameterB / micro
print "b_min / micro =", minImpactParam(B_mag, EkinOverVmax, bOverLarmorR) / micro
"""
impactParameterB / micro =     [   1.            2.15443469    4.64158883   10.           21.5443469    46.41588834  100.        ]
b_min / micro = 3.90333164611
"""

"""
b / micro =     [   1.            2.15443469    4.64158883   10.           21.5443469    46.41588834  100.        ]
b_min / micro = 3.90333173895
"""

"""
In [27]:
"""

""" (the following depend on the impact parameter) """
LarmorR = impactParameterB / bOverLarmorR  # (this version is defined positive)
V_perp = - LarmorR * Omega_e
L_intxn = tan_alpha * impactParameterB #[-1] * np.ones_like(impactParameterB)
V_z = 2 * L_intxn / (N_gyro * T_e)
T_intxn = 2 * L_intxn / V_z
print "LarmorR,V_perp,L_intxn,V_z,T_intxn =", (LarmorR,V_perp,L_intxn,V_z,T_intxn)

"""
LarmorR,V_perp,L_intxn,V_z,T_intxn = (
array([  5.00000000e-08,   1.07721735e-07,   2.32079442e-07,   5.00000000e-07,   1.07721735e-06,   2.32079442e-06,   5.00000000e-06]),
array([   8794.10043924,   18946.31505391,   40818.59840043,   87941.00439236,  189463.15053915,   408185.98400427,  879410.0439236 ]),
array([  5.00000000e-06,   1.07721735e-05,   2.32079442e-05,   5.00000000e-05,   1.07721735e-04,   2.32079442e-04,   5.00000000e-04]),
array([   2799.2491099 ,    6030.79938841,   12992.96341102,   27992.49109902,   60307.99388414,   129929.63411022,  279924.9109902 ]),
array([  3.57238660e-09,   3.57238660e-09,   3.57238660e-09,   3.57238660e-09,   3.57238660e-09,   3.57238660e-09,   3.57238660e-09]))
"""

"""
In [28]:
"""

num_steps_per_gyro = 40
delta_Te = T_e / num_steps_per_gyro
print " T_intxn / delta_Te =", T_intxn / delta_Te
"""
 T_intxn / delta_Te = [ 4000.  4000.  4000.  4000.  4000.  4000.  4000.]
 """

num_steps = int(np.floor(T_intxn[0] / delta_Te))
print "num_steps =", num_steps

"""
[ 4000.  4000.  4000.  4000.  4000.  4000.  4000.]
"""

num_steps = 4000

"""
In [29]:
"""

""" initial condition for the ion --- just one, for now """
QVion = np.array([
          ( 0.0 * rho_gc,  0.000 * V_th, 0.0 * rho_gc,  0.000 * V_th, 0.0,  0.000 * V_th)
        ]).transpose()
Zion = QVtoZion.dot(QVion)

print "Zion dimensions=",Zion.shape
print "Zion: ",Zion

"""
Zion dimensions= (6, 1)
Zion:  [[ 0.]
 [ 0.]
 [ 0.]
 [ 0.]
 [ 0.]
 [ 0.]]
 """

""" initial conditions for the electrons """

print "impactParameterB dimensions=",impactParameterB.shape
print "impactParameterB=",impactParameterB

"""
impactParameterB dimensions= (7,)
impactParameterB= [  1.00000000e-06   2.15443469e-06   4.64158883e-06   1.00000000e-05   2.15443469e-05
   4.64158883e-05   1.00000000e-04]
"""

QVelec = np.asarray([
           np.zeros(impactParameterB.shape),
           V_perp,
           impactParameterB - LarmorR,
           np.zeros(impactParameterB.shape),
           - L_intxn,
           L_intxn * abs(Omega_e) / (pi * N_gyro)
        ])
Zelec = QVtoZe.dot(QVelec)
print "Zelec dimensions=",Zelec.shape
print "QVelec: ",QVelec
print "Zelec: ",Zelec

"""
Zelec dimensions= (6, 7)
QVelec:  [[  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [  8.79410044e+03   1.89463151e+04   4.08185984e+04   8.79410044e+04   1.89463151e+05
    4.08185984e+05   8.79410044e+05]
 [  9.50000000e-07   2.04671296e-06   4.40950939e-06   9.50000000e-06   2.04671296e-05
    4.40950939e-05   9.50000000e-05]
 [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [ -5.00000000e-06  -1.07721735e-05  -2.32079442e-05  -5.00000000e-05  -1.07721735e-04
   -2.32079442e-04  -5.00000000e-04]
 [  2.79924911e+03   6.03079939e+03   1.29929634e+04   2.79924911e+04   6.03079939e+04
    1.29929634e+05   2.79924911e+05]]
Zelec:  [[  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [  1.60217657e-25   3.45178477e-25   7.43664485e-25   1.60217656e-24   3.45178477e-24
    7.43664485e-24   1.60217656e-23]
 [  9.50000000e-07   2.04671296e-06   4.40950939e-06   9.50000000e-06   2.04671296e-05
    4.40950939e-05   9.50000000e-05]
 [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [ -5.00000000e-06  -1.07721735e-05  -2.32079442e-05  -5.00000000e-05  -1.07721735e-04
   -2.32079442e-04  -5.00000000e-04]
 [  2.54994320e-27   5.49368609e-27   1.18357879e-26   2.54994320e-26   5.49368609e-26
    1.18357879e-25   2.54994320e-25]]
    1.18357879e-25   2.54994320e-25]]

"""

num_elec0 = Zelec.shape[1]
num_ion0 = Zion.shape[1]
print "num_elec0 = %d ,num_ion0=%d" % (num_elec0,num_ion0)

# num_elec0,num_ion0= 7 1

"""
=== create arrays of electrons and ions ===
Here we arrange them so that we can pair each ion with each
electron, and compute the \Delta{p} for each interaction.
"""
ZI_elec = np.hstack([Zelec for e in range(num_ion0)])
ZI_ion = Zion[:, np.arange(num_ion0 * num_elec0) // num_elec0]
print "Dimensions: ZI_elec=, ZI_ion =", (ZI_elec.shape,ZI_ion.shape)
print "ZI_elec: ",ZI_elec
print "ZI_ion: ",ZI_ion

# Dimensions ZI_elec,ZI_ion = (6, 7) (6, 7)
"""
ZI_elec:  [[  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [  1.60217657e-25   3.45178477e-25   7.43664485e-25   1.60217656e-24   3.45178477e-24
    7.43664485e-24   1.60217656e-23]
 [  9.50000000e-07   2.04671296e-06   4.40950939e-06   9.50000000e-06   2.04671296e-05
    4.40950939e-05   9.50000000e-05]
 [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
    0.00000000e+00   0.00000000e+00]
 [ -5.00000000e-06  -1.07721735e-05  -2.32079442e-05  -5.00000000e-05  -1.07721735e-04
   -2.32079442e-04  -5.00000000e-04]
 [  2.54994320e-27   5.49368609e-27   1.18357879e-26   2.54994320e-26   5.49368609e-26
    1.18357879e-25   2.54994320e-25]]
ZI_ion:  [[ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]]
"""

num_elec = ZI_elec.shape[1]
num_ion = ZI_ion.shape[1]
print "num_elec=%d, num_ion =%d",(num_elec,num_ion)

# num_elec,num_ion = 7 7

"""
In [30]:
"""

ZF_i, ZF_e = apply_MapZ(delta_Te, num_steps, ZI_ion, ZI_elec)
print "Dimensions ZF_i=, ZF_e=", (ZF_i.shape,ZF_e.shape)

# Dimensions ZF_i,ZF_e= (4001, 6, 7) (4001, 6, 7)

"""
In [31]:
"""

navg = 1 # number of gyrotron oscillations over which to average; set to 1, 2, 4, 5, or 10
ZI_elec_gc = toGuidingCenter(ZI_elec)
ZFgc_i, ZFgc_e = apply_MapZgc(navg * 40*delta_Te, num_steps//40 // navg, ZI_ion, ZI_elec_gc)
print "Dimensions ZFgc_i,ZFgc_e=", (ZFgc_i.shape,ZFgc_e.shape)

# Dimensions ZFgc_i,ZFgc_e= (101, 6, 7) (101, 6, 7)

"""
In [32]:
"""

deltaP_exp = np.array(ZF_i[-1, [Ipx, Ipy, Ipz], :] - ZF_i[0, [Ipx, Ipy, Ipz], :]).T
deltaP_avg = np.array(ZFgc_i[-1, [Ipx, Ipy, Ipz], :] - ZFgc_i[0, [Ipx, Ipy, Ipz], :]).T
deltaP_mgn = magnus_deltaPIon(T_intxn, ZI_ion, ZI_elec_gc)

print deltaP_exp
print
print deltaP_avg
print
print deltaP_mgn

"""
[[  1.59929578e-37   2.26217164e-31   3.72126900e-36]
 [  1.59938594e-38   1.05003668e-31   3.72148528e-37]
 [  1.59940499e-39   4.87386815e-32   3.72153185e-38]
 [  1.59940700e-40   2.26225216e-32   3.72154185e-39]
 [  1.59939876e-41   1.05004473e-32   3.72154360e-40]
 [  1.59935086e-42   4.87387621e-33   3.72154745e-41]
 [  1.59915588e-43   2.26225297e-33   3.72154755e-42]]

[[  1.59474700e-37   2.26056018e-31   3.72013211e-36]
 [  1.59483687e-38   1.04928867e-31   3.72034825e-37]
 [  1.59485623e-39   4.87039616e-32   3.72039482e-38]
 [  1.59486041e-40   2.26064060e-32   3.72040485e-39]
 [  1.59486130e-41   1.04929671e-32   3.72040702e-40]
 [  1.59486150e-42   4.87040420e-33   3.72040745e-41]
 [  1.59486154e-43   2.26064141e-33   3.72040784e-42]]

[[ -0.00000000e+00   1.61223622e-25   1.01895482e-40]
 [ -0.00000000e+00   3.47345764e-26   3.78365546e-41]
 [ -0.00000000e+00   7.48333763e-27  -7.02486917e-42]
 [ -0.00000000e+00   1.61223622e-27  -1.30426217e-42]
 [ -0.00000000e+00   3.47345764e-28  -4.84307898e-43]
 [ -0.00000000e+00   7.48333763e-29   4.49591627e-44]
 [ -0.00000000e+00   1.61223622e-29   3.33891116e-44]]
"""

deltaP_exp = np.array([[  1.08030578e-26,   5.72808106e-26,   1.45007842e-27], \
[  4.03458322e-28,   1.66767979e-26,   1.38486416e-27], \
[  2.76032939e-29,   6.40599505e-27,   9.05956974e-29], \
[  7.85620796e-31,   1.58638251e-27,   2.60010730e-30], \
[  1.75039675e-32,   3.47567664e-28,   5.80271684e-32], \
[  3.78408946e-34,   7.50099341e-29,   1.25468090e-33], \
[  8.15538018e-36,   1.61631831e-29,   2.70410697e-35]])

deltaP_avg = np.array([[  1.05836708e-26,   5.67504416e-26,   8.51768805e-28], \
[  4.00940540e-28,   1.66262914e-26,   1.38431451e-27], \
[  2.74641067e-29,   6.39066436e-27,   9.05285901e-29], \
[  7.81439841e-31,   1.58238970e-27,   2.59745819e-30], \
[  1.74099270e-32,   3.46684860e-28,   5.79654896e-32], \
[  3.76373889e-34,   7.48192282e-29,   1.25334141e-33], \
[  8.11151685e-36,   1.61220857e-29,   2.70121883e-35]])

deltaP_mgn = np.array([[ -0.00000000e+00,   1.61222627e-25,  -1.01894869e-40], \
[ -0.00000000e+00,   3.47345583e-26,  -3.78365367e-41], \
[ -0.00000000e+00,   7.48333795e-27,  -0.00000000e+00], \
[ -0.00000000e+00,   1.61223638e-27,  -0.00000000e+00], \
[ -0.00000000e+00,   3.47345800e-28,  -0.00000000e+00], \
[ -0.00000000e+00,   7.48333842e-29,  -4.49591691e-44], \
[ -0.00000000e+00,   1.61223639e-29,  -0.00000000e+00]])

print "deltaP_exp (again)=",deltaP_exp
print
print "deltaP_avg (again)=",deltaP_avg
print
print "deltaP_mgn (again)=",deltaP_mgn

"""
deltaP_exp (again)= [[  1.08030578e-26   5.72808106e-26   1.45007842e-27]
 [  4.03458322e-28   1.66767979e-26   1.38486416e-27]
 [  2.76032939e-29   6.40599505e-27   9.05956974e-29]
 [  7.85620796e-31   1.58638251e-27   2.60010730e-30]
 [  1.75039675e-32   3.47567664e-28   5.80271684e-32]
 [  3.78408946e-34   7.50099341e-29   1.25468090e-33]
 [  8.15538018e-36   1.61631831e-29   2.70410697e-35]]

deltaP_avg (again)= [[  1.05836708e-26   5.67504416e-26   8.51768805e-28]
 [  4.00940540e-28   1.66262914e-26   1.38431451e-27]
 [  2.74641067e-29   6.39066436e-27   9.05285901e-29]
 [  7.81439841e-31   1.58238970e-27   2.59745819e-30]
 [  1.74099270e-32   3.46684860e-28   5.79654896e-32]
 [  3.76373889e-34   7.48192282e-29   1.25334141e-33]
 [  8.11151685e-36   1.61220857e-29   2.70121883e-35]]

deltaP_mgn (again)= [[ -0.00000000e+00   1.61222627e-25  -1.01894869e-40]
 [ -0.00000000e+00   3.47345583e-26  -3.78365367e-41]
 [ -0.00000000e+00   7.48333795e-27  -0.00000000e+00]
 [ -0.00000000e+00   1.61223638e-27  -0.00000000e+00]
 [ -0.00000000e+00   3.47345800e-28  -0.00000000e+00]
 [ -0.00000000e+00   7.48333842e-29  -4.49591691e-44]
 [ -0.00000000e+00   1.61223639e-29  -0.00000000e+00]]
"""

"""
In [33]:
"""

dDeltaP_ax = deltaP_avg - deltaP_exp
dDeltaP_mx = deltaP_mgn - deltaP_exp

print "np.linalg.norm(dDeltaP_ax=",np.linalg.norm(dDeltaP_ax, axis = 1)
print "np.linalg.norm(deltaP_exp=",np.linalg.norm(deltaP_exp, axis = 1)
print "np.linalg.norm(dDeltaP_mx=",np.linalg.norm(dDeltaP_mx, axis = 1)

"""
np.linalg.norm(dDeltaP_ax= [  8.29093678e-28   5.05722047e-29   1.53314687e-29   3.99281307e-30   8.82804007e-31
   1.90705900e-31   4.10974000e-32]
np.linalg.norm(deltaP_exp= [  5.83086619e-26   1.67390626e-26   6.40669510e-27   1.58638484e-27   3.47567669e-28
   7.50099341e-29   1.61631831e-29]
np.linalg.norm(dDeltaP_mx= [  1.04511770e-25   1.81152791e-26   1.08149769e-27   2.59961603e-29   2.29993860e-31
   1.76554764e-31   4.08192098e-32]
"""

relErr_avg = (np.linalg.norm(dDeltaP_ax, axis = 1)
             / np.linalg.norm(deltaP_exp, axis = 1))
relErr_mgn = (np.linalg.norm(dDeltaP_mx, axis = 1)
             / np.linalg.norm(deltaP_exp, axis = 1))

print "relErr_avg =",relErr_avg
print
print "relErr_mgn =", relErr_mgn
print

"""
relErr_avg = [ 0.01421905  0.00302121  0.00239304  0.00251693  0.00253995  0.00254241  0.00254266]

relErr_mgn = [  1.79238841e+00   1.08221587e+00   1.68807423e-01   1.63870454e-02   6.61723976e-04
   2.35375175e-03   2.52544375e-03]
"""

eV_closest_approach = (invFourPiEps0 * Z_ion * qe ** 2 / impactParameterB)
E_kinetic_e = (me / 2) * (V_perp ** 2 + V_z ** 2)
eVcaOverEkin = eV_closest_approach / E_kinetic_e

print "eVcaOverEkin = ",eVcaOverEkin
print "log(eVcaOverEkin) = ",np.log10(eVcaOverEkin)

" Draw Figure: "
# llres = plt.figure()
# plt.loglog(eVcaOverEkin, relErr_avg, '-bo')
# plt.loglog(eVcaOverEkin, relErr_mgn, '-rs')
# plt.annotate(s="Averging",xy=(1.e-1,1.e-3),color='b')
# plt.annotate(s="Magnus",xy=(1.e-2,5.e-1),color='r')
# plt.grid(True)

# plt.xlabel("$eV_{\mathrm{ca}} / E_{\mathrm{kin}}$")
# plt.ylabel("relative error in $\Delta{P}_{\mathrm{ion}}$")

# plt.show()

x_elec=np.array(ZF_e[:,Ix,:])
y_elec=np.array(ZF_e[:,Iy,:])
z_elec=np.array(ZF_e[:,Iz,:])
print "\n\nz_elec(201)=", mega*z_elec[201,0]

vx_elec=np.array(ZF_e[:,Ipx,:])
vy_elec=np.array(ZF_e[:,Ipy,:])
vz_elec=np.array(ZF_e[:,Ipz,:])
print "Dimension vx_elex=",vx_elec.shape
print "ZF_e[0:41,Ipx,0]=",ZF_e[0:41,Ipx,0]
print "vx_elec,vy_elec=", vx_elec[0:41,0], vy_elec[0:41,6]

x_gc_elec=np.array(ZFgc_e[:,Ipx,:])/(me*Omega_e)
y_gc_elec=np.array(ZFgc_e[:,Iy,:])
z_gc_elec=np.array(ZFgc_e[:,Iz,:])
print "Dimension x_elex,x_gc_elec=",x_elec.shape,x_gc_elec.shape
print "x_gc_elec=", mega*x_gc_elec[0:10,0]
print "y_gc_elec=", mega*y_gc_elec[0:10,0]
print "z_gc_elec=", mega*z_gc_elec[0:10,0]

plt.figure(100)
plt.plot(range(40),vx_elec[1:41,0],'-r')
# xyTrac.set_aspect(1)
plt.show()

plt.figure(110)
# xyTrac.plot(vx_elec[0:41,0],vy_elec[0:41,0]-y_elec[0,0]-.5*(y_elec[20,0]-y_elec[0,0]),'-r', \
#             vx_elec[0:41,1],vy_elec[0:41,1]-y_elec[0,1]-.5*(y_elec[20,1]-y_elec[0,1]),'-b')
plt.plot(range(41),vy_elec[0:41,6],'-b')
plt.show()

import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

# xyz-track for 5 larmor radius with  indx impact factor:

indx=0
fig150=plt.figure(150)
ax150=fig150.gca(projection='3d')
ax150.plot(mega*x_elec[0:201,indx],mega*y_elec[0:201,indx],mega*z_elec[0:201,indx],'-r',linewidth=3)
plt.hold(True)
ax150.plot(mega*x_gc_elec[0:6,indx],mega*y_gc_elec[0:6,indx],mega*z_gc_elec[0:6,indx],'-b',linewidth=3)
impctPrmtr=mega*impactParameterB[indx]
plt.title('Electron: Impact Parameter = %5.3f $\mu m$' % impctPrmtr, color='m',fontsize=20)
plt.xlabel('x, $\mu m$',color='m',fontsize=20)
plt.ylabel('y, $\mu m$',color='m',fontsize=20)
ax150.set_zlabel('z, $\mu m$',color='m',fontsize=20)
ax150.text(-0.025,1,-4.425,'Larmor Circles',color='r',fontsize=16)
ax150.text(-0.0335,1,-4.83,'Larmor Center',color='b',fontsize=16)
# ax150.zaxis.label.set_color('magenta')
# ax150.zaxis.label.set_fontsize(20)
# plt.show()

indx=6
fig200=plt.figure(200)
ax200=fig200.gca(projection='3d')
ax200.plot(mega*x_elec[0:201,indx],mega*y_elec[0:201,indx],mega*z_elec[0:201,indx],'-r',linewidth=3)
plt.hold(True)
ax200.plot(mega*x_gc_elec[0:6,indx],mega*y_gc_elec[0:6,indx],mega*z_gc_elec[0:6,indx],'-b',linewidth=3)
impctPrmtr=mega*impactParameterB[indx]
plt.title('Electron: Impact Parameter = %5.3f $\mu m$' % impctPrmtr, color='m',fontsize=20)
plt.xlabel('x, $\mu m$',color='m',fontsize=20)
plt.ylabel('y, $\mu m$',color='m',fontsize=20)
ax200.set_zlabel('z, $\mu m$',color='m',fontsize=20)
ax200.text(-2.5,100,-442.5,'Larmor Circles',color='r',fontsize=16)
ax200.text(-3.35,100,-483,'Larmor Center',color='b',fontsize=16)
# ax200.zaxis.label.set_color('magenta')
# ax200.zaxis.label.set_fontsize(20)
plt.show()

# xyz-tracks for 1 larmor radius with first and second impact factor:

# fig200=plt.figure(200)
# ax200=fig.gca(projection='3d')
# ax200.plot(x_elec[0:41,0],y_elec[0:41,0]-y_elec[0,0]-.5*(y_elec[20,0]-y_elec[0,0]),z_elec[0:41,0]-z_elec[0,0],'-r')
# plt.hold(True)
# ax200.plot(x_elec[0:41,1],y_elec[0:41,1]-y_elec[0,1]-.5*(y_elec[20,1]-y_elec[0,1]),z_elec[0:41,1]-z_elec[0,1],'-b')
# plt.show()

print "End ..."

sys.exit()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Point source on a sphere
========================
An infinitesimally oscillating on the surface of a sphere. Useful where the
source is very small in comparison to the sphere.

References
----------
Chp 12, secn. 12.5, Beranek, L. L., & Mellow, T. (2012). Acoustics: sound fields and transducers.
Academic Press.

"""
import numpy as np
from sympy import symbols, legendre, lambdify, I, Sum, cos
from beamshapes.special_functions import sph_hankel2
from beamshapes.utilities import dB

k, R, n, theta, NN, kR = symbols('k R n theta NN kR')
costheta = cos(theta)
kR = k*R
# eqn. 12.46

numerator_sumterm = I**(n+1)*(2*n+1)**2*legendre(n, costheta)
denominator_sumterm = n*sph_hankel2.subs({'n':n-1,'z':kR}) - (n+1)*sph_hankel2.subs({'n':n+1,'z':kR})
sum_term = Sum(numerator_sumterm/denominator_sumterm, (n, 0, NN))
d_theta = (1/(kR**2))*sum_term

d_theta_func = lambdify([theta,k,R,NN],d_theta)

docs_for_dtheta = '''On-axis level for point on a sphere.
                 Lambdified version of :code:`d_theta` SymPy expression.

                 Parameters
                 ----------
                 theta : np.float>0
                    Elevation/azimuth off-axis angle in radians.
                 k : np.float>0
                    Wavenumber
                 R : np.float>0
                    Radius of sphere
                 NN : int
                    Number of summation steps that must be calculated.

                 Returns
                 -------
                 _ : np.float, Off-axis level at :math:`\\theta` for point-source on a sphere.
                 '''
d_theta_func.__doc__ = docs_for_dtheta

# eqn. 12.47
onaxis_num_sumterm = I**n*(2*n+1)**2
onaxissum_term = Sum(onaxis_num_sumterm/denominator_sumterm, (n,0,NN))
d_zero = (1/(kR**2))*onaxissum_term

d_zero_func = lambdify([k,R,NN],d_zero)

docs_for_dzero = '''
                 On-axis level for point on a sphere.
                 Lambdified version of :code:`d_zero` SymPy expression.

                 Parameters
                 ----------
                 k : np.float>0
                    Wavenumber
                 R : np.float>0
                    Radius of sphere
                 NN : int
                    Number of summation steps that must be calculated.

                 Returns
                 -------
                 _ : np.float,  On-axis level for point-source on a sphere.

                 '''

d_zero_func.__doc__ = docs_for_dzero

def point_source_on_a_sphere_directivity(angles, param, parallel=False):
    '''Directivity for point source on a sphere.

    Parameters
    ----------
        angles : list/np.array-like
            The angles for which the directivity is to be calculated.
            All angles in radians.

        param: dictionary
            With the following keys and entries:
                k : np.float > 0
                    Wavenumber
                R : np.float > 0
                    Radius of sphere

    Returns
    -------
    _ : None
    dtheta_by_d0 : np.array
        20log10(D_theta/D_0)

    '''
    kv, Rv = [param[each] for each in ['k','R']]
    NNv = param.get('NN', int(10+2*kv*Rv)) # trial formula - need to see if it matches w textbook

    onaxis_value = abs(d_zero_func(kv,Rv,NNv))
    offaxis_values = []
    for angle in angles:
        offaxis_values.append(abs(d_theta_func(angle, kv, Rv, NNv)))
    dtheta_by_d0 = dB(np.abs(np.array(offaxis_values))/np.abs(onaxis_value))
    return None , dtheta_by_d0


# Posterior computations for the binomial model.

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

# Given data from example.
n_1 = 10
n_2 = 20
y_1 = 6
y_2 = 10

# Parameters for Beta prior distribution.
a_1 = 1
b_1 = 1
a_2 = 1
b_2 = 1

# Parameters for Beta posterior distribution.
alpha_1 = a_1 + y_1
beta_1 = b_1 + n_1 - y_1
alpha_2 = a_2 + y_2
beta_2 = b_2 + n_2 - y_2

# Simulation to approximate the posterior
# distribution p(p_1 - p_2 | y_1, y_2) using Monte Carlo.
draws = 500000
delta = []
for r in range(draws):
    p_1 = (np.random.beta(alpha_1, beta_1))
    p_2 = (np.random.beta(alpha_2, beta_2))
    delta.append(p_1 - p_2)

# PLot the result.
sns.distplot(delta, hist=True, kde = True, color="red",
            hist_kws={'edgecolor':'black'},
            kde_kws={'linewidth': 4})
plt.show()

# Calculate the expectation for delta.
#delta_expect = 1/draws * sum(delta)
delta_expect = np.mean(delta)
print(f"Mean for p_1 - p_2 = {delta_expect}")

# Calculate a 95% posterior interval, e.g. [q_0.025, q_0.975].
delta_sorted = np.sort(delta)
lower_bound = delta_sorted[int(0.025 * draws)]
upper_bouod = delta_sorted[int(0.975 * draws)]
print(f"95% posterior interval: [{lower_bound}, {upper_bouod}]")

quantile_bounds = np.quantile(delta_sorted, [0.025, 0.975])
print(f"95% posterior interval: [{quantile_bounds[0]}, {quantile_bounds[1]}]")

# Calculate the posterior probability that p1 > p2.
delta = np.array(delta)
prob = len(delta[delta > 0]) / len(delta)
print(f"Posterior probability that p1 > p2: {prob}")


import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d, UnivariateSpline, BarycentricInterpolator

def f(x):
    return np.log(x)

def frangeAdd(x, xtend):
    xmin = np.min(x)
    xmax = np.max(x)
    add = xtend*(xmax-xmin)
    xmin -= add
    xmax += add
    return xmin, xmax

# Create "data" to be fit & save to data file
npts = 7
noiseAmp = 0.03
xdata = np.linspace(0.2, 10., npts) + 0.3*np.random.randn(npts)
xdata = np.sort(xdata)
ydata = f(xdata) * (1.0 + noiseAmp * np.random.randn(npts))
np.savetxt('logCurveData9.txt', zip(xdata, ydata), fmt='%10.2f')

# Create x array spanning data set plus 5%
xmin, xmax = frangeAdd(xdata, 0.05)
x = np.linspace(xmin, xmax, 200)

# Plot data and "fit"
plt.plot(xdata, ydata, 'or', label='data')
plt.plot(x, f(x), 'k-', label='fitting function')

# Create y array from cubic spline
f_cubic = interp1d(xdata, ydata, kind='cubic', bounds_error=False)
plt.plot(x, f_cubic(x), 'k-', label='cubic spline')

# Create y array from univariate spline
f_univar = UnivariateSpline(xdata, ydata, w=None, bbox=[xmin, xmax], k=3)
plt.plot(x, f_univar(x), label='univariate spline')

# Create y array from barycentric interpolation
f_bary = BarycentricInterpolator(xdata, ydata)
plt.plot(x, f_bary.__call__(x), label='barycentric interp')

plt.legend(loc='best')

plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Dec  2 23:25:38 2020

@author: ed203246
"""
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # nicer plots
import sklearn.metrics as metrics
import sklearn.linear_model as lm

import sklearn.datasets as datasets
from sklearn.model_selection import train_test_split

# %% Plot train/test with inreasing size

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

def fit_on_increasing_size(model):
    n_samples = 100
    n_features_ = np.arange(20, 2000, 100)
    bacc_train, bacc_test = [], []
    for n_features in n_features_:
        n_features_info = int(n_features / 10)
        X, y = datasets.make_classification(n_samples=n_samples * 2, n_features=n_features,
                                     n_informative=n_features_info, n_redundant=int(n_features_info / 2),
                                     n_classes=2,
                                     n_clusters_per_class=1,
                                     weights=None, flip_y=0.01,
                                     class_sep=.5,
                                     hypercube=True, shift=0.0, scale=1.0, shuffle=True,
                                     random_state=1)
        """
        # Sample the dataset (* 2 nb of samples)
        n_features_info = int(n_features / 10)
        np.random.seed(27)  # Make reproducible 27
        X = np.random.randn(n_samples * 2, n_features)
        beta = np.zeros(n_features)
        beta[:n_features_info] = 1
        Xbeta = np.dot(X, beta)
        eps = np.random.randn(n_samples * 2)
        proba = logistic(Xbeta + eps)
        y =  (proba >= 0.5).astype(int)
        """
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, stratify=y, random_state=42)
        # fit/predict
        mod.fit(X_train, y_train)
        y_pred_train = mod.predict(X_train)
        y_pred_test = mod.predict(X_test)
        #snr.append(Xbeta.std() / eps.std())
        bacc_train.append(metrics.balanced_accuracy_score(y_train, y_pred_train))
        bacc_test.append(metrics.balanced_accuracy_score(y_test, y_pred_test))
    return n_features_, np.array(bacc_train), np.array(bacc_test)

def plot_bacc(n_features_, bacc_train, bacc_test, xvline, ax, title):
    """
    Two scales plot. Left y-axis: train test r-squared. Right y-axis SNR.
    """
    ax.plot(n_features_, bacc_train, label="Train Acc", linewidth=2, color=sns.color_palette()[0])
    ax.plot(n_features_, bacc_test, label="Test Acc", linewidth=2, color=sns.color_palette()[1])
    ax.axvline(x=xvline, linewidth=2, color='k', ls='--')
    ax.fill_between(n_features_, bacc_test, 0.5, alpha=.3, color=sns.color_palette()[1])
    ax.fill_between(n_features_, bacc_test, bacc_train, alpha=.3, color=sns.color_palette()[0])
    ax.axhline(y=0.5, linewidth=1, color='k', ls='--')
    ax.set_ylim(0.3, 1.1)
    ax.set_ylabel("r2", fontsize=16)
    ax.legend(loc='best')
    ax.grid(True)
    ax.set_title(title, fontsize=20)

# plot
fig, axis = plt.subplots(4, 1, figsize=(9, 12), sharex=True)

# %% No regularization

#fig, axis = plt.subplots(1, 1, figsize=(9, 12), sharex=True)
#fig, axis = plt.subplots(1, 1, figsize=(9, 9), sharex=True)

#mod = lm.LogisticRegression(penalty='none')
mod = lm.LogisticRegression(penalty='l2', C=.1e16) # lambda = 1 / C!

n_features, bacc_train, bacc_test = fit_on_increasing_size(model=mod)
argmax = n_features[np.argmax(bacc_test)]
plot_bacc(n_features, bacc_train, bacc_test, argmax, axis[0], 'Regression')

# %% L2 regularization

mod = lm.LogisticRegression(penalty='l2', C=1e-2) # lambda = 1 / C!
n_features, bacc_train, bacc_test = fit_on_increasing_size(model=mod)
argmax = n_features[np.argmax(bacc_test)]
plot_bacc(n_features, bacc_train, bacc_test, argmax, axis[1], 'Ridge')

# %% L1 regularization

mod = lm.LogisticRegression(penalty='l1', C=.1, solver='saga') # lambda = 1 / C!
n_features, bacc_train, bacc_test = fit_on_increasing_size(model=mod)
argmax = n_features[np.argmax(bacc_test)]
plot_bacc(n_features, bacc_train, bacc_test, argmax, axis[2], 'Lasso')

# %% L1-L2 regularization

mod = lm.LogisticRegression(penalty='elasticnet',  C=.1, l1_ratio=0.5, solver='saga')
n_features, bacc_train, bacc_test = fit_on_increasing_size(model=mod)
argmax = n_features[np.argmax(bacc_test)]
plot_bacc(n_features, bacc_train, bacc_test, argmax, axis[3], 'ElasticNet')

plt.tight_layout()
axis[3].set_xlabel("Number of input features", fontsize=16)
#plt.savefig("/home/ed203246/git/pystatsml/images/linear_classification_penalties.png")

# %% Codes examples:

if False:
    from sklearn import datasets
    import  sklearn.linear_model as lm

    X, y = datasets.make_regression(n_features=5, n_informative=2, random_state=0)

    lr = lm.LinearRegression().fit(X, y)

    l2 = lm.Ridge(alpha=10).fit(X, y)  # lambda is alpha!
    print(l2.coef_)

    l1 = lm.Lasso(alpha=1).fit(X, y)  # lambda is alpha !
    print(l1.coef_)

    l1l2 = lm.ElasticNet(alpha=1, l1_ratio=.9).fit(X, y)


import random
import sys

import numpy as np

class KMeans:
    def __init__(self, k, max_iter=1000, epsilon=1e-5):
        self.k = k
        self.__max_iter = max_iter
        self.__epsilon = epsilon
        self.__centers = None
        self.__cluster_id = None

    # Return a list of cluster id for all samples
    def get_labels(self):
        return self.__cluster_id

    # Return a list of the centers of each cluster
    def get_centers(self):
        return self.__centers

    # Calculate the Euclidean distance for the two given points
    @staticmethod
    def __get_distance(x_1, x_2):
        return np.linalg.norm(x_1 - x_2)

    # Create k clusters with their centers
    def __init_clusters(self):
        clusters = list()
        for i in range(self.k):
            new_cluster = list()
            new_cluster.append(self.__centers[i])
            clusters.append(new_cluster)
        return clusters

    # Find the closest cluster for a single point
    def __find_closest_cluster(self, sample):
        dists = [self.__get_distance(c, sample) for c in self.__centers]
        min_dist = min(dists)
        return dists.index(min_dist)

    # Assign every sample to its proper cluster
    def __group_samples(self, clusters, x):
        for i, sample in enumerate(x):
            c_index = self.__find_closest_cluster(sample)
            self.__cluster_id[i] = c_index
            clusters[c_index].append(sample)
        return clusters

    # Recompute the center of each cluster
    def __update_centers(self, error, clusters):
        for i, c in enumerate(clusters):
            c_mean = np.mean(c, axis=0)
            error = min(error, max(abs(c_mean - self.__centers[i])))
            self.__centers[i] = c_mean
        return error

    def fit(self, x):
        error = sys.maxsize
        counter = 0
        n_sample = x.shape[0]
        self.__centers = random.sample(list(x), k=self.k)
        self.__cluster_id = np.zeros(n_sample, dtype=int)

        while error > self.__epsilon and counter < self.__max_iter:
            clusters = self.__init_clusters()
            clusters = self.__group_samples(clusters, x)
            error = self.__update_centers(error, clusters)


import numpy as np

# create vector using numpy
# 1D array is the vector
# create python list and convert into array
x = [1,2,3]
vector = np.array(x)
# array([1, 2, 3])

vector.ndim
# 1 --> 1D

vector.shape
# (3,) --> 1D array

# create "a" 1D vector between 10 to 50 with size = 5
# vector in theory, array in pratical representation
a = np.random.randint(10,50,size=5)
# print a --> array([10, 33, 28, 29, 32])

# create "b" 1D vector between 25 to 75 with size = 5
b = np.random.randint(20,75,size=5)
# print b --> array([72, 32, 50, 30, 59])

# vection dot product (element wise multiplication and summation)
# when 2 vector are dot producted, outcome is scalar
# can do using numpy in built function "dot"

vector_dot_product = a.dot(b)
# print vector_dot_product --> 5934

# LETS UNDERSTAND

x = np.arange(5,10) # array([5, 6, 7, 8, 9])
y = np.arange(5) # array([0, 1, 2, 3, 4])

# vector dot product working for x and y
# 0+6+14+24+36 = 80

vector_dot_product = x.dot(y)
# vector dot product of x and y --> 80


import numpy as np

# ReLU activation
class Activation_ReLU:
    '''ReLU activation\n
    max (0, input)
    '''

    def forward(self, inputs, training):
        '''Activation_ReLU.forward (input_data)'''
        # np.maximum takes two inputs and finds element wise maximum
        self.output = np.maximum(0, inputs)

        # Cache the input values for backprop
        self.inputs = inputs

    # Backward pass
    def backward(self, dvalues):
        '''Activation_ReLU.backward (upstream_gradient)\n
        Calculates the backward pass through the current non linearity'''
        # Since we need to modify the original variable,
        # let's make a copy of the values first
        self.dvalues = dvalues.copy()

        # Zero gradient where the input values were <= 0
        self.dvalues[self.inputs <= 0] = 0

    # Calculate prediction for outputs
    def predictions(self, outputs):
        return outputs

    def __str__(self):
        return "ReLU Activation"

# Softmax activation
class Activation_Softmax:
    '''Softmax activation'''

    # Forward Pass
    def forward(self, inputs, training):
        '''Activation_Softmax.forward (input_data)'''

        # get unnormalized probabilities
        # np.max takes one input and finds maximum from that,
        # here it takes each row of the input (i.e. neuron activations for each sample)
        # and finds max in each of the rows
        exp_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True))
        # normalize them for each sample
        probabilities = exp_values / np.sum(exp_values, axis=1, keepdims=True)

        self.output = probabilities

    # Backward pass
    def backward(self, dvalues):
        '''Activation_ReLU.backward (upstream_gradient)\n
        Calculates the backward pass through the current non linearity\n
        ---- IMPLEMENTATION TO BE UPDATED SOON---'''

        self.dvalues = dvalues.copy()

    # return predictions for outputs
    def predictions(self, outputs):
        return np.argmax(outputs, axis=1)

    def __str__(self):
        return "Softmax Activation"

# Sigmoid activation
class Activation_Sigmoid:
    # Forward Pass
    def forward(self, inputs, training):
        # Save input and calcuilate/save output of the sigmoid function
        self.input = inputs
        self.output = 1 / (1 + np.exp(-inputs))

    # Backward pass
    def backward(self, dvalues):
        # d/dx(sigm(x)) = sigm(x) * [1 - sigm(x)]
        self.dvalues = dvalues * (1 - self.output) * self.output

    # Calculate predictions for outputs
    def predictions(self, outputs):
        return (outputs > 0.5) * 1

    def __str__(self):
        return "Sigmoid Activation"

# Linear activation
class Activation_Linear:

    # Forward pass
    def forward(self, inputs, training):
        # All you need to do is just cache the values
        self.input = inputs
        self.output = inputs

    # Backward pass
    def backward(self, dvalues):
        # Chain rule:
        # local Derivative = 1, upstream gradient = dvalues
        # downstream gradient = (local derivative ) * (upstream gradient)
        # self.dvalues = 1 * dvalues
        self.dvalues = dvalues.copy()

    # Calculate predictions for outputs
    def predictions(self, outputs):
        return outputs

    def __str__(self):
        return "Linear Activation"


"""Multivariable Chinese Remainder Theorem"""

from math import gcd

import numpy as np

def egcd(a, m):
    """Extended GCD"""
    if a == 0:
        return (m, 0, 1)
    g, y, x = egcd(m % a, a)
    return (g, x - (m // a) * y, y)

def modinv(a, m):
    """Find Modular Inverse"""
    amodm = a % m
    g, x, _ = egcd(amodm, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

def pivot(A, m):
    """Finds the pivot of A and m"""
    length = len(A)
    result = [0] * length
    for i in range(length):
        for j in range(length):
            if gcd(A[i][j], m[i]) == 1:
                result[i] = j
    return result

def is_sol(A, x, b, m):
    """Checks if Ax = b mod m"""
    ax_b = np.matmul(np.array(A), np.array(x)) - np.array(b)
    for i, mod in enumerate(m):
        if ax_b[i] % mod != 0:
            return False
    return True

def mcrt(A, b, m):
    """Returns for x in Ax = b mod m"""
    eqn_cnt = len(A)
    piv = pivot(A, m)
    x = [0] * eqn_cnt
    m_prod = 1

    for i in range(eqn_cnt):
        tot = sum(A[i][k] * x[k] for k in range(eqn_cnt))
        tmp = (modinv(m_prod * A[i][piv[i]], m[i]) * (b[i] - tot)) % m[i]
        x[piv[i]] = x[piv[i]] + tmp * m_prod
        m_prod *= m[i]

    return x


import numpy as np

def premier(n):

    if n == 1 or n == 2:

        return True

    if n%2 == 0:

        return False

    r = n**0.5

    if r == int(r):

        return False

    for x in range(3, int(r), 2):

        if n % x == 0:

            return False

    return True

def pgcd(a,b):

    while (b>0):

        r=a%b

        a,b=b,r

    return a

def pgcde(a, b):

    r = a
    u = 1
    v = 0
    rp = b
    up = 0
    vp = 1

    while rp != 0:
        q = r//rp
        rs, us, vs = r, u, v
        r, u, v = rp, up, vp
        rp, up, vp = (rs - q*rp), (us - q*up), (vs - q*vp)

    return (r, u, v)

def key():

    p = np.random.choice(1000,1)
    q = np.random.choice(1000,1)

    while premier(p) is False:
        p = np.random.choice(1000,1)

    while premier(q) is False:
        q = np.random.choice(1000,1)

    n = p*q
    m = (p-1)*(q-1)

    r = 10
    d = 0
    while r != 1 or d <= 2 or d >= m:
        c = np.random.choice(1000,1)
        r, d, v = pgcde(c,m)

    n, c, d = int(n), int(c), int(d)

    return {"priv":(n,c), "pub":(n,d)}


from sympy import *
from sympy.abc import S,I,t,b,g

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

b=1
g=1
def dx_dt(x,t):
    return [ -b*x[0]+g*x[1] , b*x[0]-g*x[1] ]
#trayectorias en tiempo hacia adelante
ts=np.linspace(0,10,500)
ic=np.linspace(20000,100000,3)
for r in ic:
    for s in ic:
        x0=[r,s]
        xs=odeint(dx_dt,x0,ts)
        plt.plot(xs[:,0],xs[:,1],"-", color="orangered", lw=1.5)
#trayectorias en tiempo hacia atras
ts=np.linspace(0,-10,500)
ic=np.linspace(20000,100000,3)
for r in ic:
    for s in ic:
        x0=[r,s]
        xs=odeint(dx_dt,x0,ts)
        plt.plot(xs[:,0],xs[:,1],"-", color="orangered", lw=1.5)
#etiquetas de ejes y estilo de letra
plt.xlabel('S',fontsize=20)
plt.ylabel('I',fontsize=20)
plt.tick_params(labelsize=12)
plt.ticklabel_format(style="sci", scilimits=(0,0))
plt.xlim(0,100000)
plt.ylim(0,100000)
#campo vectorial
X,Y=np.mgrid[0:100000:15j,0:100000:15j]
u=-b*X+g*Y
v=b*X-g*Y
pl.quiver(X,Y,u,v,color='dimgray')
plt.savefig("SIS.pdf",bbox_inches='tight')
plt.show()


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

# code starts here
df = pd.read_csv(path)

# print(df.head())

#Calculate the probability p(A)for the event that fico credit
fico_score = df[df['fico']>700]
p_a = len(fico_score)/len(df['fico'])

#Calculate the probabilityp(B) for the event that purpose == 'debt_consolation'
p_b = len(df[df['purpose']=='debt_consolidation'])/len(df['purpose'])

#
df1 = df[df['purpose']=='debt_consolidation']

#probability of a and b
p_a_and_b = len(df[(df['purpose']=='debt_consolidation') & (df['fico']>700)])/len(df1)

print(p_a_and_b)

#Calculate the probablityp(B|A) for the event purpose == 'debt_consolidation' given 'fico' credit score is greater than 700
#probability of a given b

p_a_b = p_a_and_b/p_b

print(p_a_b)
print(p_a)

if (p_a_b == p_a):
    result= True
else:
    result= False

print(result)
# code ends here

# --------------
# code starts here

#Let's calculate the bayes theorem for the probability of credit policy is yes and the person is given the loan.

#Calculate the probability p(A) for the event that paid.back.loan == Yes
prob_lp = len(df[df['paid.back.loan']=='Yes'])/len(df['paid.back.loan'])

#Calculate the probability p(B) for the event that credit.policy == Yes
prob_cs = len(df[df['credit.policy']=='Yes'])/len(df['credit.policy'])

#
new_df = df[df['paid.back.loan']=='Yes']

#joint probability of both the conditions satisfied
# prob_lp_and_cs = len(df[(df['paid.back.loan']=='Yes') & (df['credit.policy']=='Yes')])/len(new_df)

##Calculate the probablityp(B|A) for the event paid.back.loan == 'Yes' given credit.policy == 'Yes'
prob_pd_cs = new_df[new_df['credit.policy']=='Yes'].shape[0]/new_df.shape[0]

##Calculate the conditional probability using the Bayes theorm
p_cs_pd = (prob_pd_cs * prob_lp)/prob_cs

bayes = p_cs_pd

print(bayes)
#
# code ends here

# --------------
# code starts here
#Let's visualize the bar plot for the purpose and again using condition where

#Visualize the bar plot for the feature purpose
fig = plt.figure(figsize=(15,50))
ax_1 = plt.subplot(3,1,1)
ax_2 = plt.subplot(3,1,2)
ax_3 = plt.subplot(3,1,3)

df['purpose'].value_counts().plot(kind='bar',ax=ax_1)

#Visualize the bar plot for the feature purpose where paid.back.loan == No
df1 = df[df['paid.back.loan']=='No']
df1['purpose'].value_counts().plot(kind='bar',ax= ax_2)

# lets check for the percentage of defaulters in each category
df2 = df1['purpose'].value_counts()/df['purpose'].value_counts()
df2.round(2).value_counts().plot(kind='bar',ax=ax_3)
# code ends here

# --------------
# code starts here
import seaborn as sns
#Let's plot the histogram for visualization of the continuous variable.

fig,(ax_1,ax_2) = plt.subplots(2,1,figsize=(20,20))

inst_median = df['installment'].median()

inst_mean = df['installment'].mean()

#plotting a histogram for the distribution
ax_1.hist(df['installment'],color='c',edgecolor='k',alpha=0.65,normed=True)

#marking mean and median on the histogram plot
ax_1.axvline(inst_mean,color='k',linestyle='dashed',linewidth=2)
ax_1.axvline(inst_median,color='r',linestyle='dashed',linewidth=2)

##drawing density plot over the histogram
sns.kdeplot(df['installment'],color='black',label='Frequency',ax=ax_1)

##making the tick size bigger
plt.setp(ax_1.get_xticklabels(),fontsize=14)
plt.setp(ax_1.get_yticklabels(),fontsize=14)

#plotting the histogram for log.annual.inc
ax_2.hist(df['log.annual.inc'],color='b',edgecolor='k',alpha=0.65,normed=True)
plt.setp(ax_2.get_xticklabels(),fontsize=14)
plt.setp(ax_2.get_yticklabels(),fontsize=14)

#display the plot
plt.show()
# code ends here


"""
Code to draw plot for the documentation.

This plots an example of stable steady state strategies in an asymmetric game.

The code should match the reference code in the documentation.
"""
import matplotlib.pyplot as plt
import numpy as np

import nashpy as nash

A = np.array([[2, 2], [3, 4]])
B = np.array([[4, 3], [3, 2]])
game = nash.Game(A, B)
x0 = np.array([0.9, 0.1])
y0 = np.array([0.3, 0.7])
xs, ys = game.asymmetric_replicator_dynamics(x0=x0, y0=y0)

plt.figure(figsize=(13, 5))
plt.subplot(1, 2, 1)
plt.plot(xs)
plt.title("Probability distribution of strategies over time for row player")
plt.xlabel("Timepoints")
plt.ylabel("Probability")
plt.legend([f"$s_{0}$", f"$s_{1}$"])
plt.subplot(1, 2, 2)
plt.plot(ys)
plt.title("Probability distribution of strategies over time for column player")
plt.xlabel("Timepoints")
plt.ylabel("Probability")
plt.legend([f"$s_{0}$", f"$s_{1}$"])

plt.savefig("main.svg", transparent=True)


import math

import numpy as np
from numpy import ndarray
from pandas.core.frame import DataFrame
from pandas.core.series import Series
from sklearn.base import ClusterMixin
from sklearn.cluster import DBSCAN

PI = math.pi
TWO_PI = PI * 2

class DBACAN(ClusterMixin):
    def __init__(self, eps: float, min_samples: int, degrees=False) -> None:
        '''
        # Parameters
        - eps (float): The maximum angle between two samples for one to be considered as in the neighborhood of the other.
        - min_samples (int): The number of samples (or total weight) in a neighborhood for a point to be considered as a core point.
        - degrees (bool): True if eps is in degrees, false if in radians.
        This includes the point itself.
        '''
        self.degrees = degrees
        if self.degrees:
            eps = math.radians(eps)

        self.dbscan = DBSCAN(eps=eps, min_samples=min_samples, metric='precomputed')

    @staticmethod
    def _get_distance_matrix(X: ndarray) -> ndarray:
        return abs((((X - X.transpose()) + PI) % TWO_PI) - PI)

    def fit_predict(self, X, y=None):
        if isinstance(X, list):
            X = np.array(X)
        if isinstance(X, (DataFrame, Series)):
            X = X.to_numpy()
        if self.degrees:
            X = np.radians(X)

        X = np.reshape(X, (len(X), 1))
        dist_matrix = self._get_distance_matrix(X)

        return self.dbscan.fit_predict(dist_matrix)

# angles = [0, 0, 0, 90, 90, 90, 180, 180, 180, 270, 270, 270]
# result = DBACAN(20, 3, True).fit_predict(angles)
# print(result)


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

class KMeans:
    def __init__(
            self,
            k: int,
            metric: str = "euclidean",
            tol: float = 1e-6,
            max_iter: int = 100,
            seed: int = 42):
        """
        inputs:
            k: int
                the number of centroids to use in cluster fitting
            metric: str
                the name of the distance metric to use
            tol: float
                the minimum error tolerance from previous error during optimization to quit the model fit
            max_iter: int
                the maximum number of iterations before quitting model fit
        """
        np.random.seed(seed)
        assert k > 0, "Error: The number of centroids must be greater than 0."
        assert k > 1, "Error: Setting k=1 means every point belongs to the same cluster."
        self.k = k
        self.metric = metric
        self.tol = tol
        self.max_iter = max_iter

    def _check_input_mat(self, mat: np.ndarray) -> np.ndarray:
        """
        Check if the number of centroids are less than the number of observations in the input matrix.
        Check if the input matrix is one dimensional

        inputs:
            mat: np.ndarray
        outputs:
            mat: np.ndarray
        """
        assert self.k < mat.shape[0], "Error: The number of centroids must be less than the number of observations."

        if mat.ndim == 1:
            print("Warning: Reshaping 1D numpy array (-1,1).")
            print("Warning: Consider an alternative algorithm like KDE for one dimensional data.")
            mat = mat.reshape(-1,1)

        return mat

    def _find_nearest_centroids(self,  mat: np.ndarray) -> np.ndarray:
        """
        Find the nearest centroids for each point.

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

        output:
            np.ndarray
                A 1D array specifying the class labels

        References:
        -----------
        1. https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
        2. https://www.askpython.com/python/examples/k-means-clustering-from-scratch
        """
        self._distances = cdist(mat, self._centroids, self.metric)
        return np.argmin(self._distances, axis=1)

    def _calculate_mse(self, mat: np.ndarray) -> float:
        """
        Calculate the mean squared error (mse) of the centroid distances.

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features
        """
        points_to_centroid = np.array(list(map(lambda label: self._centroids[label], self._labels)))
        distances = np.diag(cdist(mat, points_to_centroid, metric=self.metric))
        distances_squared = np.square(distances)
        mse = distances_squared.mean()
        return mse

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

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

        References
        ----------
        1. https://blog.paperspace.com/speed-up-kmeans-numpy-vectorization-broadcasting-profiling/
        """
        mat = self._check_input_mat(mat)

        # Initialize random centroids
        self._centroids = np.random.rand(self.k, mat.shape[1])
        self._labels = self._find_nearest_centroids(mat)

        # Fit model
        self._mse = []
        for iter_step in range(self.max_iter):
            for k_cluster in range(self.k):
                # Find the mean of the cluster for the new centroid
                self._centroids[k_cluster,:] = np.mean(mat[self._labels == k_cluster,:],axis = 0)

            # Find the nearest centroid for each point and compute the MSE
            self._labels = self._find_nearest_centroids(mat)
            self._mse.append(self._calculate_mse(mat))

            # Check clustering stability against previous MSE during optimization to end the model fit
            if iter_step > 0 and abs(self._mse[iter_step] - self._mse[iter_step-1]) <= self.tol:
                self._mse = self._mse[iter_step]
                break

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

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

        outputs:
            np.ndarray
                a 1D array with the cluster label for each of the observations in `mat`
        """
        test_mat = self._check_input_mat(mat)
        predicted_labels = self._find_nearest_centroids(test_mat)
        return predicted_labels

    def get_error(self) -> float:
        """
        returns the final squared-mean error of the fit model

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

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

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


# -*- coding: utf-8 -*-
import sympy as sy
from sympy import symbols, Matrix

def inv_sym_3x3(m: Matrix, as_adj_det=False):
    P11, P12, P13, P21, P22, P23, P31, P32, P33 = \
        symbols('P_{11} P_{12} P_{13} P_{21} P_{22} P_{23} P_{31} \
                P_{32} P_{33}', real=True)
    Pij = [[P11, P12, P13], [P21, P22, P23], [P31, P32, P33]]
    P = sy.Matrix(Pij)
    detP = P.det()
    adjP = P.adjugate()
    invP = adjP / detP
    subs = {s: r for s, r in zip(sy.flatten(P), sy.flatten(m))}
    if as_adj_det:
        return detP.subs(subs), adjP.subs(subs)
    else:
        return invP.subs(subs)

"""
from sympy.utilities.lambdify import lambdify
import numpy as np
f_det3x3 = lambdify([P_], detP)
f_adj3x3 = lambdify([P_], adjP)
f_inv3x3 = lambdify([P_], invP)

arr = np.eye(3) * 3
f_det3x3(arr)
f_adj3x3(arr)
f_inv3x3(arr)
"""


"""
solution for problem 3
"""

import matplotlib.pyplot as plt
import xlrd
import numpy as np
from sympy import *
from sympy.solvers import solve
from sympy import Symbol
from mvnpdf import plot2dGaussian
from gaussContour import plot2dGaussianCont

x1=Symbol('x1')
x2=Symbol('x2')

def mean_cov(dataFeatures):
    # find mean mu
    mu=(1/dataFeatures.shape[0])*dataFeatures.sum(axis=0)
    # find sigma
    xMINmu=dataFeatures-mu
    xMINmu_xMINmuTrans=np.zeros((2,2))
    for i in range(dataFeatures.shape[0]):
        xMINmu_xMINmuTrans = xMINmu_xMINmuTrans + np.dot(xMINmu[i,:].reshape((2,1)),np.transpose(xMINmu[i,:].reshape((2,1))))
    sigma=(1/dataFeatures.shape[0])*xMINmu_xMINmuTrans
    return mu,sigma

def decisionBoundary(w1_mu,w2_mu,w3_mu,w1_sigma,w2_sigma,w3_sigma):
    x=np.array([x1,x2])
    # class 1 & 2
    eq1=simplify(0.5*np.transpose(x-w1_mu).dot(np.linalg.inv(w1_sigma).dot(x-w1_mu))\
    -0.5*np.transpose(x-w2_mu).dot(np.linalg.inv(w2_sigma).dot(x-w2_mu))\
    +0.5*np.log(np.linalg.det(w1_sigma)) - 0.5*np.log(np.linalg.det(w2_sigma)))

    # class 2 & 3
    eq2=simplify(0.5*np.transpose(x-w2_mu).dot(np.linalg.inv(w2_sigma).dot(x-w2_mu))\
    -0.5*np.transpose(x-w3_mu).dot(np.linalg.inv(w3_sigma).dot(x-w3_mu))\
    +0.5*np.log(np.linalg.det(w2_sigma)) - 0.5*np.log(np.linalg.det(w3_sigma)))

    # class 3 & 1
    eq3=simplify(0.5*np.transpose(x-w3_mu).dot(np.linalg.inv(w3_sigma).dot(x-w3_mu))\
    -0.5*np.transpose(x-w1_mu).dot(np.linalg.inv(w1_sigma).dot(x-w1_mu))\
    +0.5*np.log(np.linalg.det(w3_sigma)) - 0.5*np.log(np.linalg.det(w1_sigma)))

    return eq1,eq2,eq3

def accuracy(eq1,eq2,eq3):
    truePrediction=0
    for i in range(w1_test.shape[0]):
        if eq1.subs(x1,w1_test[i][0]).subs(x2,w1_test[i][1])<0:
            truePrediction=truePrediction+1
    for i in range(w2_test.shape[0]):
        if eq2.subs(x1,w2_test[i][0]).subs(x2,w2_test[i][1])<0:
            truePrediction=truePrediction+1
    for i in range(w3_test.shape[0]):
        if eq3.subs(x1,w3_test[i][0]).subs(x2,w3_test[i][1])<0:
            truePrediction=truePrediction+1
    return truePrediction/(w1_test.shape[0]+w2_test.shape[0]+w3_test.shape[0])

def bayesian_classifier(case,w1_train,w1_test,w2_train,w2_test,w3_train,w3_test):
    #  Same covariance matrix for all the classes
    if case==1:
        _,sigma=mean_cov(np.concatenate((w1_train, w2_train, w3_train), axis=0))
        w1_mu,_=mean_cov(w1_train)
        w2_mu,_=mean_cov(w2_train)
        w3_mu,_=mean_cov(w3_train)

        print("Class w1 \n mu: ",w1_mu,"\n sigma: \n",sigma,"\n")
        print("Class w2 \n mu: ",w2_mu,"\n sigma: \n",sigma,"\n")
        print("Class w3 \n mu: ",w3_mu,"\n sigma: \n",sigma,"\n")

        eq1,eq2,eq3=decisionBoundary(w1_mu,w2_mu,w3_mu,sigma,sigma,sigma)
        acc=accuracy(eq1,eq2,eq3)
        print("Case 1 accuracy: ",acc)

        # plot2dGaussian(w1_mu,sigma,w2_mu,sigma,w3_mu,sigma)
        plot2dGaussianCont(w1_mu,sigma,w2_mu,sigma,w3_mu,sigma,eq1,eq2,eq3)

    # Different covariance matrices
    if case==2:
        w1_mu,w1_sigma=mean_cov(w1_train)
        w2_mu,w2_sigma=mean_cov(w2_train)
        w3_mu,w3_sigma=mean_cov(w3_train)
        print("Class w1 \n mu: ",w1_mu,"\n sigma: \n",w1_sigma,"\n")
        print("Class w2 \n mu: ",w2_mu,"\n sigma: \n",w2_sigma,"\n")
        print("Class w3 \n mu: ",w3_mu,"\n sigma: \n",w3_sigma,"\n")

        eq1,eq2,eq3=decisionBoundary(w1_mu,w2_mu,w3_mu,w1_sigma,w2_sigma,w3_sigma)
        acc=accuracy(eq1,eq2,eq3)
        print("Case 2 accuracy: ",acc)

        plot2dGaussian(w1_mu,w1_sigma,w2_mu,w2_sigma,w3_mu,w3_sigma)
        plot2dGaussianCont(w1_mu,w1_sigma,w2_mu,w2_sigma,w3_mu,w3_sigma,eq1,eq2,eq3)

    # Diagonal covariance matrices
    if case==3:

        w1_mu,w1_sigma=mean_cov(w1_train)
        w1_sigma=np.diag(np.diag(w1_sigma))

        w2_mu,w2_sigma=mean_cov(w2_train)
        w2_sigma=np.diag(np.diag(w2_sigma))

        w3_mu,w3_sigma=mean_cov(w3_train)
        w3_sigma=np.diag(np.diag(w3_sigma))

        print("Class w1 \n mu: ",w1_mu,"\n sigma: \n",w1_sigma,"\n")
        print("Class w2 \n mu: ",w2_mu,"\n sigma: \n",w2_sigma,"\n")
        print("Class w3 \n mu: ",w3_mu,"\n sigma: \n",w3_sigma,"\n")

        eq1,eq2,eq3=decisionBoundary(w1_mu,w2_mu,w3_mu,w1_sigma,w2_sigma,w3_sigma)
        acc=accuracy(eq1,eq2,eq3)
        print("Case 3 accuracy: ",acc)

        plot2dGaussian(w1_mu,w1_sigma,w2_mu,w2_sigma,w3_mu,w3_sigma)
        plot2dGaussianCont(w1_mu,w1_sigma,w2_mu,w2_sigma,w3_mu,w3_sigma,eq1,eq2,eq3)

# loading the data
loc = ("que3.xlsx")
wb = xlrd.open_workbook(loc)
sheet = wb.sheet_by_index(0)

data=[]
for i in range(1,1501):
    data.append([sheet.cell_value(i, 0),sheet.cell_value(i, 1)])

# list to np array
data = np.array(data)

# splitting data between 3 classes
w1=data[:500,:]
w2=data[501:1000,:]
w3=data[1001:1500,:]

# splitting test and train data
w1_train=w1[:350,:]
w1_test=w1[351:,:]
w2_train=w2[:350,:]
w2_test=w2[351:,:]
w3_train=w3[:350,:]
w3_test=w3[351:,:]

# the first parameter describes the case and can be 1/2/3
bayesian_classifier(3,w1_train,w1_test,w2_train,w2_test,w3_train,w3_test)


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

def main():
    a = np.arange(16)
    lambda_ = [1.5, 4.5]
    colours = ["#348ABD", "#A60628"]

    plt.bar(a, stats.poisson.pmf(a, lambda_[0]), color=colours[0],
            label="$\lambda = %.1f$" % lambda_[0], alpha=0.60,
            edgecolor=colours[0], lw="3")

    plt.bar(a, stats.poisson.pmf(a, lambda_[1]), color=colours[1],
            label="$\lambda = %.1f$" % lambda_[1], alpha=0.60,
            edgecolor=colours[1], lw="3")

    plt.xticks(a + 0.4, a)
    plt.legend()
    plt.ylabel("probability of $k$")
    plt.xlabel("$k$")
    plt.title("Probability mass function of a Poisson random variable; differing \
    $\lambda$ values")
    plt.show()

if __name__ == '__main__':
    main()


##1.5.1 Bringing the NumPy
import numpy as np
##1.5.2 Generating a NumPy array
x = np.array([1.0, 2.0, 3.0])
print(x)
type(x)

##1.5.3 NumPy Arithmetic Operations
x = np.array([1.0, 2.0, 3.0])
y = np.array([2.0, 4.0, 6.0])
x + y # 원소별 덧셈
x - y # 원소별 뺄셈
x * y # 원소별 곱셈
x / y # 원소별 나눗셈

x = np.array([1.0, 2.0, 3.0])
x / 2.0

##1.5.4 N-dimensional NumPy Array
A = np.array([[1, 2], [3, 4]])
print(A)
A.shape
A.dtype

C = np.array([[0, 1, 2], [3, 4, 5]])
print(C)
C.shape
C.dtype

B = np.array([[3, 0], [0, 6]])
A + B

A * B

print(A)
A * 10

##1.5.5 Broadcast in NumPy
A = np.array([[1, 2], [3, 4]])
B = np.array([10, 20])

A * B

##1.5.6 Accessing any element of NumPy array
X = np.array([[51, 55], [14, 19], [0, 4]])
print(X)

X[0]

X[0][1]
### access by for loop
for row in X:
	print(row)
### Flattening the 2-dim array X into one-dim
X = X.flatten()
print(X)

X[np.array([0, 2, 4])]
### Poking specific elements by conditional index
X > 15

X[X > 15]

#1.6 Python's Graph Drawing: matplotlib
##1.6.1 Drawing a simple graph

import numpy as np
import matplotlib.pyplot as plt

# data preparation
x = np.arange(0, 6, 0.1) # generate from 0 to 6 by 0.1 intervals
y = np.sin(x)

# drawing the graph
plt.plot(x, y)
plt.show()

##1.6.2 matplotlib.pyplot module's functions
######### Sin & Cos Graph ###########
import numpy as np
import matplotlib.pyplot as plt

# data preparation
x = np.arange(0, 6.5, 0.1) # generate from 0 to 6 by 0.1 intervals
y1 = np.sin(x)
y2 = np.cos(x)

# drawing the graph
plt.plot(x, y1, label="sin")
plt.plot(x, y2, linestyle="--", label="cos") #draw cosine function with dashed lines
plt.xlabel("x")  # name the x-axis
plt.ylabel("y")  # name the y-axis
plt.title("sin & cos") #title
plt.legend()

plt.show()

##1.6.3 Showing an image
import matplotlib.pyplot as plt
from matplotlib.image import imread

img = imread('lena.png') # Reading the image

plt.imshow(img)
plt.show()

#1.7 Ending words for further journey


#! /usr/bin/env python

"""
File: sequence_limits.py
Copyright (c) 2016 Austin Ayers
License: MIT

Course: PHYS227
Assignment: A. 1
Date: Feb 11, 2016
Email: ayers111@mail.chapman.edu
Name: Austin Ayers
Description: Determines the limit of a sequence
"""
import numpy as np
import matplotlib.pyplot as plt

def seq_a(n):
    """
    Returns an element in a sequence given the n value
    """
    return ((7.0+(1.0/(float(n)+1.0)))/(3.0-(1.0/(float(n)+1.0)**2)))

def seq_c(n):
    return np.sin(2.0**(-1 *float(n)))/(2.0**(-1 *float(n)))

def part_a():
    """
    Writes out the sequence for N = 100, and finds the value as n -> inf
    """
    sequence = []
    for i in range(0,100,2):
        print i
        print seq_a(i)
        sequence.append(seq_a(i))
    print "\n\n"
    print "The series converges to: 7/3 or 2.3333..., and a_N was: " + str(seq_a(100)) + " and the difference was: " + str((seq_a(100)-2.33333333333333333))
    return sequence

def limit(seq):
    """
    Determines if a series has a limit and returns it, if the series has no limit it outputs None
    """
    cond = True
    for i in range(1,len(seq)-1):
        if not (abs(seq[i]) - abs(seq[i+1]) < abs(seq[i-1]) - abs(seq[i])):
            print "None"
            cond = False
            break
    if(cond):
        print "The limit exists (to this algorithm's knowledge)"
    if(seq[-1] - seq[-2] < 0.01):
        return seq[-1]

def part_b():
    """
    tests limit(seq) if it works for the sequence in part a
    """
    seq_a = part_a()
    print "The limit is roughly: " + str(limit(seq_a))

def part_c():
    """
    tests limit(seq) if it works for the sequence in part c
    """
    sequence = []
    for i in range(500):
        sequence.append(seq_c(i))
    print "The limit is roughly: " + str(limit(sequence))
def sin_x(x):
    return np.sin(x)
def D(f, x, N):
    """
    takes a function f(x), a value x, and the number N and returns the sequence for 0,N
    """
    sequence = []
    for i in range(N):
        sequence.append((f(float(x)+(2.0**(-1 *float(i))))-f(x))/(2.0**(-1 *float(i))))
    return sequence

def part_d():
    seq_d = D(sin_x, 0, 80)
    print str(limit(seq_d))
    print "notice this fails because the function is oscillatory in behavior (sin(x))"
    plt.plot(range(80), seq_d, 'go')
    plt.show

def part_e():
    seq_e = D(sin_x, np.pi, 80)
    print str(limit(seq_e))
    plt.plot(range(80), seq_e, 'go')
    plt.show

def part_f():
    print "the computations for x = pi go wrong for large N because sin(pi) = 0 and 2 ** (-n) approaches 0 as well, so the numerator and denominator both go to 0 and that usually leads to problems."

def run():
    """
    Runs the entire program with parts
    """
    print "Part (a): "
    part_a()
    print
    print "Part (b): "
    part_b()
    print
    print "Part (c): "
    print
    part_c()
    print "Part (d): "
    print
    part_d()
    print "Part (e): "
    print
    part_e()
    print "Part (f): "
    print
    part_f()
    print "Finished"


# coding: utf-8

# In[1]:

import numpy as np
from math import sqrt

def getPoints(numPoints):
	randArr = np.random.uniform(-1, 1, (numPoints, 2))

	mask=[]

	for i in range(0, numPoints):
		mask.append(distanceCheck(randArr[i][1],randArr[i][0]))

	return mask

def distanceCheck(x, y):
	distance = sqrt(x**2 + y**2)
	return distance < 1 # returns false if outside circle

def truCounter(numPoints):
	truArray = getPoints(numPoints)

	numTrue = 0

	for i in range(0, numPoints):
		if truArray[i] == True:
			numTrue = numTrue + 1
	return numTrue

def estimatePi(numPoints):
	truCount = truCounter(numPoints)
	return 4 * truCount / (numPoints)

print(estimatePi(10000))