import numpy as np

class LinearRegressor:
    """
    A linear regressor optimized through gradient descent
    """

    def __init__(self, dat, theta=None):
        # number of samples and features in the dataset
        self.num_samples = dat.shape[0]
        self.num_features = dat.shape[1]

        # For feature normalization
        self.mu = None
        self.sigma = None

        # row for the bias terms
        biases = np.ones(self.num_samples)
        normalized_features = self.feature_normalize(dat[:, :-1])
        self.x = np.column_stack((biases, normalized_features))
        self.y = np.reshape(dat[:, -1], (self.num_samples, 1))

        self.theta = theta if theta else np.zeros((self.num_features, 1))

    def feature_normalize(self, x):
        """
        Normalize the features in x
        After normalization the mean value of each feature is 0, and the standard deviation is 1

        Arguments:
            x {np.ndarray} -- An N x K dimensional numpy array of data samples
        Returns:
            {np.ndarray} -- An N x K dimensional numpy array of normalized data samples
        """

        # Initialize normalization params on training data set
        if self.mu is None or self.sigma is None:
            self.mu = np.mean(x, axis=0)
            self.sigma = np.std(x, axis=0)
            zero_mask = self.sigma == 0
            self.sigma += zero_mask  # ensure no division by zero (if == 0, set = 1)

        return (x - self.mu) / self.sigma

    def gradient_descent(self, alpha, num_iters, l1, l2):
        """ Perform gradient descent to fit theta to the data x, given y

        Arguments:
            alpha {float} -- the learning rate
            num_iters {int} -- the number of iterations to perform
            l1 {float} -- the l1 regularisation constant for gradient calculations
            l2 {float} -- the l2 regularisation constant for gradient calculations
        """

        scalar = alpha / len(self.x)
        for i in range(num_iters):
            gradients = self.x.transpose().dot((self.x.dot(self.theta) - self.y))
            gradients[1:, :] += l1 * np.sign(self.theta[1:, :])  # l1 regularisation
            gradients[1:, :] += 2 * l2 * self.theta[1:, :]  # l2 regularisation
            self.theta -= scalar * gradients

    def compute_cost(self, l1, l2):
        """ Compute the cost for linear regression

        Returns:
            {float} -- the mean squared error of this linear model
            l1 {float} -- the l1 regularisation constant for cost calculations
            l2 {float} -- the l2 regularisation constant for cost calculations
        """

        diffs = self.x.dot(self.theta) - self.y
        loss = 1 / (2 * len(self.y)) * sum(diffs * diffs)

        loss += l1 * np.sign(self.theta).sum()  # l1 regularisation
        loss += l2 * np.square(self.theta).sum()  # l2 regularisation

        return loss

    def predict(self, x):
        """ Predict the output of the linear model against sample data using learned parameters

        Arguments:
            x {np.ndarray} -- An N x K dimensional numpy array of data samples to predict
        Returns:
            {[float]} -- a list of the predicted outputs of the model
        """

        return x.dot(self.theta)

def example_main(dat):
    # Hyper-parameters
    alpha = 0.1
    num_iters = 1000
    l1 = 0.01
    l2 = 0.001

    regressor = LinearRegressor(dat)
    print("Starting Cost:", regressor.compute_cost(l1, l2))
    print("Training...")
    regressor.gradient_descent(alpha=alpha, num_iters=num_iters, l1=l1, l2=l2)
    print("Final Cost:", regressor.compute_cost(l1, l2))
    print("Final Theta:\n", regressor.theta)

if __name__ == "__main__":
    print("Beginning Univariate Linear Regression:\n")
    univariate_dat = np.loadtxt("../../datasets/regression/linear_univariate.txt", delimiter=",")
    example_main(univariate_dat)

    print("\nBeginning Multivariate Linear Regression:\n")
    multivariate_dat = np.loadtxt("../../datasets/regression/linear_multivariate.txt", delimiter=",")
    example_main(multivariate_dat)


from functools import partial

import chaospy as cp
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from integration_algorithms import monte_carlo_naive_one
from integration_algorithms import quadrature_gauss_legendre_one
from integration_algorithms import quadrature_newton_trapezoid_one
from integration_problems import problem_kinked
from integration_problems import problem_smooth

def plot_gauss_legendre_weights(deg):
    xevals, weights = np.polynomial.legendre.leggauss(deg)

    fig, ax = plt.subplots()

    ax.bar(xevals, weights, width=0.02)
    ax.set_ylabel("Weight")
    ax.set_xlabel("Node")
    ax.set_xlim([-1, 1])
    plt.show()

def plot_benchmarking_exercise():
    xvals = np.linspace(-1, 1, 10000)

    fig, (ax1, ax2) = plt.subplots(2)
    ax1.plot(xvals, problem_smooth(xvals), label=r"$e^-x$")
    ax1.legend()

    ax2.plot(xvals, problem_kinked(xvals), label=r"$\sqrt{|x|}$")
    ax2.legend()

def plot_naive_monte_carlo(num_nodes):
    fig, ax = plt.subplots(figsize=(4, 4))
    x, y = np.hsplit(np.random.uniform(size=num_nodes * 2).reshape(num_nodes, 2), 2)
    ax.scatter(x, y)
    ax.get_yticklabels()[0].set_visible(False)
    ax.set_ylim(0, 1)
    ax.set_xlim(0, 1)

def plot_quasi_monte_carlo(num_points):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))

    distribution = cp.J(cp.Uniform(0, 1), cp.Uniform(0, 1))
    samples = distribution.sample(num_points, rule="halton")
    x, y = np.hsplit(samples.T, 2)

    ax1.get_yticklabels()[0].set_visible(False)
    ax1.scatter(x, y)
    ax1.set_ylim(0, 1)
    ax1.set_xlim(0, 1)
    ax1.set_title("Halton")

    samples = distribution.sample(num_points, rule="sobol")
    x, y = np.hsplit(samples.T, 2)
    ax2.get_yticklabels()[0].set_visible(False)
    ax2.scatter(x, y)
    ax2.set_ylim(0, 1)
    ax2.set_xlim(0, 1)

    ax2.set_title("Sobol")

def plot_naive_monte_carlo_error(max_nodes):

    index = pd.Index(np.linspace(5, max_nodes, dtype=int), name="Nodes")
    df_results = pd.DataFrame(columns=["Trapezoid", "Gauss", "Naive", "Truth"], index=index)

    p_trapezoid = partial(quadrature_newton_trapezoid_one, problem_smooth, -1, 1)
    p_gauss = partial(quadrature_gauss_legendre_one, problem_smooth, -1, 1)
    p_naive = partial(monte_carlo_naive_one, problem_smooth, -1, 1)

    df_results.loc[:, "Truth"] = np.exp(1) - np.exp(-1)
    for nodes in df_results.index.get_level_values("Nodes"):
        df_results.loc[nodes, "Trapezoid"] = np.abs(p_trapezoid(nodes))
        df_results.loc[nodes, "Gauss"] = np.abs(p_gauss(nodes))
        df_results.loc[nodes, "Naive"] = np.abs(p_naive(nodes))

    fig, ax = plt.subplots()
    for column in df_results.columns:
        ax.plot(df_results.index.get_level_values("Nodes"), df_results[column], label=column)
    ax.set_xlabel("Number of nodes")
    ax.set_ylabel("Error")
    ax.legend()

def plot_naive_monte_carlo_randomness():

    grid = range(10)
    yvals = list()
    for seed in grid:
        rslt = monte_carlo_naive_one(problem_smooth, a=-1, b=1, n=50, seed=seed)
        yvals += [np.abs(rslt - 2.3504023872876028)]

    fig, ax = plt.subplots()
    ax.scatter(grid, yvals)
    ax.set_xlabel("Seed")
    ax.set_ylabel("Error")


from typing import Tuple
import numpy as np

def stencil_weights(d: int, i_min: int, i_max: int)->Tuple[np.ndarray, int]:
    """
    Generates weights c_k for the finite difference stencil.

    Args:
        d (int): order of the derivative
        i_min (int): number of stencil points to the left of the point of the derivation
        i_max (int): number of stencil points to the right of the point of the derivation

    Returns:
        Tuple[np.ndarray, int]: returns a tuple of the stencli weight vector c_k (1d numpy array) and the largest of the two values i_min and i _max
    """
    A = []
    b = []
    di = i_max-i_min + 1
    for n in range(di):
        A.append(np.array([1 if (i == 0) & (n == 0) else i**n for i in range(i_min, i_max + 1)]))
        b.append(0 if n != d else 1)

    A = np.array(A)
    b = np.array(b)
    i0 = np.max(np.abs([i_max, i_min]))
    return np.linalg.solve(A, b)*np.math.factorial(d), i0

def stencil_matrix_pure(c: np.ndarray, i0: int, y_axes=0)-> np.ndarray:
    """
    Generates a matrix that corresponds to a FDM stencil of a derivative in only one direction. This matrix can be convolved with the target field to take the derivative.
    Args:
        c (np.ndarray): stencil vector
        i0 (int): number of points on the largest side of the stencil
        y_axes (int, optional): Transposes the stencil matrix if the derivation is i y direction. Stencil vectors for x and y axes are the same but their stencil matrices are transposedversions of each other. Defaults to 0.

    Returns:
        np.ndarray: _description_
    """
    n = c.size
    o = np.zeros(n)
    o[i0] = 1
    M = np.outer(o, c)

    if y_axes:
        return M.transpose()
    else:
        return M

def stencil_matrix_mixed(*c_s: np.ndarray):
    return np.outer(*c_s)


#!/usr/bin/python3
# -*- coding: utf-8 -*-

import numpy as np

###### list

##
a = [1, 2, 3]
b = [4, 5, 6]

a, b
# Out[2]: ([1, 2, 3], [4, 5, 6])

##
a * b
# TypeError: can't multiply sequence by non-int of type 'list'

##
np.multiply(a, b)
# Out[4]: array([ 4, 10, 18])

##
np.dot(a, b)
# Out[5]: 32

###### ndarray

##
c = np.asarray(a)
d = np.asarray(b)

c, d
# Out[6]: (array([1, 2, 3]), array([4, 5, 6]))

##
c * d
# Out[7]: array([ 4, 10, 18])

##
np.multiply(c, d)
# Out[8]: array([ 4, 10, 18])

##
np.dot(c, d)
# Out[9]: 32

###### constant

##
a * 3
# Out[10]: [1, 2, 3, 1, 2, 3, 1, 2, 3]

##
c * 3
# Out[11]: array([3, 6, 9])

##
np.multiply(a, 3)
# Out[6]: array([3, 6, 9])

###### mixture

##
np.dot(c, a) # = np.dot(a, c)
# Out[14]: 14 = 1*1 + 2*2 + 3*3

##
np.multiply(c, a)
# Out[16]: array([1, 4, 9])

###### T

## 1-D
c, c.T, c.reshape((len(c), -1))
# Out[16]: (array([1, 2, 3]), array([1, 2, 3]), array([[1],
#         [2],
#         [3]]))

## 2-D
e = np.array([[1,2,3], [4,5,6]])
e, e.T, e.reshape(len(e[0]), len(e))
# Out[18]: (array([[1, 2, 3],
#         [4, 5, 6]]), array([[1, 4],
#         [2, 5],
#         [3, 6]]), array([[1, 2],
#         [3, 4],
#         [5, 6]]))

## Mul
t = 3
x = np.array([t])
y = np.array([[t]])
f = c.reshape((len(c)), -1)
x, y, f
# Out[23]: (array([3]), array([[3]]), array([[1],
#         [2],
#         [3]]))

##
f * t, f * x, f * y
# Out[25]: (array([[3],
#         [6],
#         [9]]), array([[3],
#         [6],
#         [9]]), array([[3],
#         [6],
#         [9]]))


from math import gcd
import numpy as np
from util import *

def getSafePrime(digits):
    '''
    To get the a random safe prime of the form p = 2q+1 where q is also a prime
    Implemented by Rishika Mohanta
    '''
    while True:
        p = generate_prime(digits)
        if MillerRabin_test(int((p-1)/2)):
            print("Generated Safe Prime...",end="")
            return p

def getPrimitiveRoot(p):
    '''
    To get the first primitive root mod n
    Implemented by Rishika Mohanta
    '''
    coprime_set = {num for num in range (1, p) if gcd(num, p) == 1}
    for g in range(1, p):
        generated_set = set(pow(g, powers) % p for powers in range (1, p))
        if coprime_set == generated_set:
            print(f"Generated primitive root modulo p.")
            return g

def genPublicMessage(p,g,privatekey):
    '''
    To generate DH public message
    Implemented by Ramya Narayanan
    '''
    return int(pow(g,privatekey,p))

def genSharedKey(p,publicmessage,privatekey):
    '''
    To generate DH shared key
    Implemented by Ramya Narayanan
    '''
    return int(pow(publicmessage,privatekey,p))


# -*- coding: utf8 -*-
import matplotlib.pyplot as plt
import numpy as np
import scipy.integrate
plt.style.use('labdls-dark')

def lorenz_attractor(r, t, p):
    """
    Compute the right hand side of system of ODEs for Lorenz attractor.

    Parameters
    ----------
    r : array_like, shape (3,)
        (x, y, z) position of trajectory.
    t : dummy_argument
        Dummy argument, necessary to pass function into
        scipy.integrate.odeint
    p : array_like, shape (3,)
        Parameters (s, k, b) for the attractor.

    Returns
    -------
    output : ndarray, shape (3,)
        Time derivatives of Lorenz attractor.

    Notes
    -----
    .. Returns the right hand side of the system of ODEs describing
       the Lorenz attractor.
        x' = s * (y - x)
        y' = x * (k - z) - y
        z' = x * y - b * z
    """
    # Unpack variables and parameters
    x, y, z = r
    s, p, b = p

    return np.array([s * (y - x),
                     x * (p - z) - y,
                     x * y - b * z])

# Parameters to use
p = np.array([10.0, 28.0, 8.0 / 3.0])

# Initial condition
r0 = np.array([0.1, 0.0, 0.0])

# Time points to sample
t = np.linspace(0.0, 80.0, 10000)

# Use scipy.integrate.odeint to integrate Lorentz attractor
r = scipy.integrate.odeint(lorenz_attractor, r0, t, args=(p,))

# Unpack results into x, y, z.
x, y, z = r.transpose()

# Plot the result
plt.plot(x, z, '-', linewidth=0.5)
plt.xlabel(r'$x(t)$', fontsize=18)
plt.ylabel(r'$z(t)$', fontsize=18)

#plt.savefig('lorenz.png')
plt.show()


# Plot the gamma distribution
# Based on https://github.com/probml/pmtk3/blob/master/demos/gammaPlotDemo.m

import numpy as np
import matplotlib.pyplot as plt
import pyprobml_utils as pml

from scipy.stats import gamma

x = np.linspace(0, 7, 100)
aa = [1.0, 1.5, 2.0, 1.0, 1.5, 2.0]
bb = [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
#props = ['b-', 'r:', 'k-.', 'g--', 'c-', 'o-']
props = ['b-', 'r-', 'k-', 'b:', 'r:', 'k:']

for a, b, p in zip(aa, bb, props):
    y = gamma.pdf(x, a, scale=1/b, loc=0)
    plt.plot(x, y, p, lw=3, label='a=%.1f,b=%.1f' % (a, b))
plt.title('Gamma distributions')
plt.legend(fontsize=14)
pml.savefig('gammadist.pdf')
plt.show()

x = np.linspace(0, 7, 100)
b = 1
plt.figure()
for a in [1, 1.5, 2]:
    y = gamma.pdf(x, a, scale=1/b, loc=0)
    plt.plot(x, y)
plt.legend(['a=%.1f, b=1' % a for a in [1, 1.5, 2]])
plt.title('Gamma(a,b) distributions')
pml.savefig('gammaDistb1.pdf')
plt.show()

x = np.linspace(0, 7, 100)
b = 1
a = 1
rv = gamma(a, scale=1/b, loc=0)
y = rv.pdf(x)
plt.plot(x, y)
plt.axvline(1, color='r')
plt.title('Gamma(1,1) distribution')
pml.savefig('gammaDist1.pdf')
plt.show()


'''
查找 - 顺序查找和二分查找
算法： 解决问题的方法(步骤)
评价一个算法的好坏主要有两个指标：渐进时间复杂度和渐进空间复杂度，
通常一个算法很难做到时间复杂度和空间复杂度都很低(因为时间和空间是不可调和的矛盾)
表示渐进时间复杂度通常使用大O标记
O(c): 常量时间复杂度 - 哈希存储 / 布隆过滤器
O(log_2n): 对数时间复杂度 - 折半查找
O(n):线性时间复杂度 - 顺序查找
O(n * log_2 n): - 对数线性时间复杂度 - 高级排序算法 （归并排序、快速排序）
O(n ** 2): 平方时间复杂度 - 简单排序算法(冒泡排序、选择排序、插入排序)
O(n ** 3):立方时间复杂度 - Floyd算法 / 矩阵乘法运算
也称为多项式时间复杂度
O(2 ** n):几何级数时间复杂度 - 汉诺塔
O(3 ** n):几何级数时间复杂度
也称为指数时间复杂度
O(n!):阶乘时间复杂度 - 旅行经销商问题 - NP
'''

from math import log2, factorial
from matplotlib import pyplot
import numpy

def seq_search(items:list, elem) -> int:
    '''顺寻查找'''
    for index, item in enumerate(items):
        if elem == item:
            return index
    return -1

def bin_search(items, elem):
    '''二分查找'''
    start, end = 0, len(items) - 1
    while start <= end:
        mid = (start + end) // 2
        if elem > items[mid]:
            start = mid + 1
        elif elem < items[mid]:
            end = mid - 1
        else:
            return mid
    return -1

def main():
    '''主函数(程序入口)'''
    num = 6
    styles = ['r-.', 'g-*', 'b-o', 'y-x', 'c-^', 'm-+', 'k-d']
    legends = ['对数', '线性', '线性对数', '平方', '立方', '几何级数', '阶乘']
    x_data = [x for x in range(1, num + 1)]
    y_data1 = [log2(y) for y in range(1, num + 1)]
    y_data2 = [y for y in range(1, num + 1)]
    y_data3 = [y * log2(y) for y in range(1, num + 1)]
    y_data4 = [y ** 2 for y in range(1, num + 1)]
    y_data5 = [y ** 3 for y in range(1, num + 1)]
    y_data6 = [3 ** y for y in range(1, num + 1)]
    y_data7 = [factorial(y) for y in range(1, num + 1)]
    y_datas = [y_data1, y_data2, y_data3, y_data4, y_data5, y_data6, y_data7]
    for index, y_data in enumerate(y_datas):
        pyplot.plot(x_data, y_data,styles[index])
    pyplot.legend(legends)
    pyplot.xticks(numpy.arange(1, 7, step=1))
    pyplot.yticks(numpy.arange(0, 751, step=50))
    pyplot.show()

if __name__ == '__main__':
    main()


from cvxopt import matrix as cvxmat, sparse as cvxsparse, spmatrix as cvxspmatrix
from cvxopt.solvers import qp as cvxQP, options as cvxopt
import numpy as np

def quadprog(H, f, Aeq=None, beq=None, Aineq=None, bineq=None, lb=None, ub=None,abstol=None,reltol=None,maxiters=None,returnVar='yes'):
    """
    minimize:
            (1/2)*x'*H*x + f^T*x
    subject to:
            Aeq*x = beq
	    Aineq <= bineq
            lb <= x <= ub

##########################################################################################################################
#        Matlab style quadratic programming using cvxopt with equality, inequality and lower and upper bounds.           #
#                                                                                #
#             I modified slightly the code from https://gist.github.com/garydoranjr/1878742 to fit what I wanted                  #
#                                                                                #
#                     Louis-Francois Arsenault, Columbia University la2518@columbia.edu (2013-2017)                      #
##########################################################################################################################
#                                                                                #
#       INPUTS:                                                                                #
#                                                                                #
#         H     : numpy 2d array([[h_11,h_12,...,h_1n],[h_21,h_22,...,h_2n],...,[h_n1,h_n2,...,h_nn]])                   #
#		  can also be a scalar which mean H is the scalar H time the identity matrix                             #
#															 #
#         f     : numpy 1d array([f_1,f_2,f_3,...,f_n])                                                                  #
#                                                                                #
#         Aeq   : numpy 2d array([[aeq_11,aeq_12,...,aeq_1n],[aeq_21,aeq_22,...,aeq_2n],...,[aeq_p1,aeq_p2,...,aeq_pn]]) #
#                                                                                #
#         beq   : numpy 1d array([beq_1,beq_2,beq_3,...,beq_p])                                                          #
#                                                                                #
#         Aineq : numpy 2d array([[ai_11,ai_12,...,ai_1n],[ai_21,ai_22,...,ai_2n],...,[ai_l1,ai_l2,...,ai_ln]])          #
#                                                                                #
#         bineq : numpy 1d array([bi_1,bi_2,bi_3,...,bi_l])                                                              #
#                                                                                #
#         lb    : numpy 1d array([lb_1,lb_2,...,lb_n])                                                                   #
#                                                                                #
#         ub    : numpy 1d array([ub_1,ub_2,...,ub_n])                                                                   #
#                                                                                #
#	  Optionnal:													 #
#	  abstol   : absolute tolerance											 #
#	  reltol   : relative tolerance											 #
#	  maxiters : max number of iterations										 #
#															 #
##########################################################################################################################
#	Note that as long as f,beq,bineq,lb and ub are somewhat 1d like arrays like shape =  (m,1) or (1,m),		 #
#			         they will be reshaped to have the correct (m,) shape !!                                 #
##########################################################################################################################
"""
    P, q, G, h, A, b = _convert(H, f, Aeq, beq,Aineq,bineq, lb, ub)

    cvxopt['show_progress'] = False
    #Use options given. If None, use default
    if abstol != None:
	cvxopt['abstol'] = abstol
    if reltol != None:
	cvxopt['reltol'] = reltol
    if maxiters != None:
	cvxopt['maxiters'] = maxiters

    #Only returns the values
    results = cvxQP(P, q, G, h, A, b)

    #Convert back to NumPy array
    if returnVar is 'yes':
    	return np.array(results['x'])
    else:
	return results

def _convert(H, f, Aeq=None, beq=None,Aineq=None,bineq=None, lb=None, ub=None):

    #Convert everything to
    #cvxopt-style matrices

    #P and q are always given
    if isinstance(H,np.ndarray) is False:
	#H is a scalar meaning H*I, where I identity matrix
	P=H*speye(len(f))
    else:
    	P = cvxmat(H,tc='d')
    q = cvxmat(f, tc='d')

    if Aeq is None:
        A = None
	b = None #if no Aeq, no beq
    else:
	if Aeq.ndim == 1:
		Aeq.shape = (1,Aeq.size)
	elif (Aeq.shape[1]==1):
		Aeq = Aeq.transpose()
        A = cvxmat(Aeq,tc='d')
	if beq.ndim != 1:
		beq.shape = (beq.size,)
	b = cvxmat(beq,tc='d')

    if Aineq is None:
	if lb is None:
		if ub is None:
			G = None
			h = None
		else:
			if ub.ndim != 1:
		                ub.shape = (ub.size,)
			n=ub.size
			G = cvxsparse(speye(n),tc='d')
			h = cvxmat(ub,tc='d')
	else:
		if lb.ndim != 1:
                	lb.shape = (lb.size,)
		if ub is None:
			n=lb.size
			G = cvxsparse(-speye(n),tc='d')
			h = cvxmat(-lb,tc='d')
		else:
			if ub.ndim != 1:
                                ub.shape = (ub.size,)
			n = lb.size
    			G = cvxsparse([speye(n), -speye(n)],tc='d')
    			h = cvxmat(np.hstack([ub,-lb]),tc='d')
    else:
	if bineq.ndim != 1:
        	bineq.shape = (bineq.size,)
	if lb is None:
		if ub is None:
			G = cvxmat(Aineq,tc='d')
			h = cvxmat(bineq,tc='d')
		else:
			if ub.ndim != 1:
                                ub.shape = (ub.size,)
			n = ub.size
			Gt = cvxmat(Aineq,tc='d')
			G = cvxmat([Gt,speye(n)],tc='d')
			h = cvxmat(np.hstack([bineq,ub]),tc='d')
	else:
		if lb.ndim != 1:
                	lb.shape = (lb.size,)
		if ub is None:
			n = lb.size
			Gt = cvxmat(Aineq,tc='d')
                        G = cvxmat([Gt,-speye(n)],tc='d')
                        h = cvxmat(np.hstack([bineq,-lb]),tc='d')
		else:
			if ub.ndim != 1:
                                ub.shape = (ub.size,)
			n = lb.size
			Gt = cvxmat(Aineq,tc='d')
                        G = cvxmat([Gt,speye(n),-speye(n)],tc='d')
                        h = cvxmat(np.hstack([bineq,ub,-lb]),tc='d')
    return P, q, G, h, A, b

def speye(n):

#    Create a sparse identity matrix

    r = range(n)
    return cvxspmatrix(1.0, r, r)


# Question: https://projecteuler.net/problem=29

import numpy as np

N_a = 100
N_b = 100

count = np.ones((N_a+1, N_b+1), dtype = np.uint8)
count[0] = 0
count[1] = 0
count[:,0] = 0
count[:,1] = 0
for x in range(2, 11):
    #  2^{2,4,6,...100} = 4^{1,2,3,...,50}
    # ---
    #  2^{3,6,9,...,99} = 8^{1,2,3,...,33}
    #  4^{3,6,9,...,99} = 8^{2,4,6,...,66}
    # ---
    #  2^{4,8,12,...,100} = 16^{1,2,3,...,25}
    #  4^{4,8,12,...,100} = 16^{2,4,6,...,50}
    #  8^{4,8,12,...,100} = 16^{3,6,9,...,75}
    #  ---
    #  2^{5,10,15,...,100} = 32^{1,2,3,...,20}
    #  4^{5,10,15,...,100} = 32^{2,4,6,...,40}
    #  8^{5,10,15,...,100} = 32^{3,6,9,...,60}
    # 16^{5,10,15,...,100} = 32^{4,8,12,...,80}
    n = x * x
    k = 2
    while n <= N_a:
        for i in range(1, k):
            #print(x, n, list(range(i, 100//k*i+1, i)))
            count[n][i:100//k*i+1:i] = 0
        n = n * x
        k = k + 1

print(np.sum(count))
""" Doesn't work
start_from = [2] * (N_a+1)

count = 0

for i in range(2, N_a + 1):
    n = i * i
    k = 2
    while n <= N_a:
        start_from[n] = max(start_from[n], N_b // k + 1)
        n = n * i
        k = k + 1
    count = count + N_b - start_from[i] + 1
    print(i, count)

for i, j in enumerate(start_from):
    print(i, start_from[i], end = '| ')
    if i % 20 == 19:
        print()
print(count)
"""


import numpy as np

# Problem 40 : Champernowne's constant

def dn(n):
    """
    This function returns the last digit in the decimal number 0.123456...
    (loop over digits while there is n digits after the comma)
    """
    i = 0
    champ = "0."
    while len(champ) <= n+2:
        i += 1
        champ += str(i)
    dn = champ[n+1]
    return dn

def dn_liste(liste_n):
	"""
	This function return the a list with the dn value for every n in the given list.
	"""
    liste_n = np.sort(liste_n)
    liste_dn = []
    i = 0
    j = 0
    champ = "0."
    while len(champ) <= liste_n[-1]+2:
        i += 1
        champ += str(i)
        if len(champ) >= liste_n[j]+2:
            liste_dn.append(int(champ[liste_n[j]+1]))
            j += 1
    return liste_dn

def champernowne_constant_product(liste_n):
	"""
	This function returns the champernowne constant product.
	"""
    liste_dn = dn_liste(liste_n)
    product = 1
    for elt in liste_dn:
        product *= elt
    return product

print(champernowne_constant_product(
    [1, 10, 100, 1000, 10000, 100000, 1000000]))

# Result 210


import numpy as np
import pandas as pd

def annualizedRet(r, num_periods):
    '''

    @param r: series, returns
    @param num_periods: scalar, number of periods in return series
    @return: scalar, annualized return
    '''

    comp = (1 + r).prod()
    nPer = r.shape[0]
    return comp ** (num_periods / nPer) - 1

def annualizedVol(r, num_periods, downside = False):
    '''

    @param r: series, returns
    @param num_periods: scalar, number of periods in return series
    @param downside: bool, for downside std
    @return: scalar, annualized volatility
    '''

    if downside:
        semistd = r[r < 0].std()
        return semistd * (num_periods ** 0.5)
    else:
        return r.std() * (num_periods ** 0.5)

def drawdown(r: pd.Series):
    '''

    @param r: series, returns
    @return: dictionary: 'hwm':high watermark, 'drawdowns': drawdown periods
    '''

    index = 1000 * (1 + r).cumprod()
    highwatermark = index.cummax()
    drawdowns = (index - highwatermark) / highwatermark
    return pd.DataFrame(dict(hwm = highwatermark,
                             drawdowns=drawdowns))

def skewness(r):
    '''

    @param r: series, returns
    @return: scalar, third moment
    '''
    centerMoment = r - r.mean()
    sigR = r.std(ddof=0)
    exp = (centerMoment ** 3).mean()
    return exp / sigR ** 3

def kurtosis(r):
    '''

    @param r: series, returns
    @return: scalar, fourth moment
    '''

    centerMoment = r - r.mean()
    sigR = r.std(ddof=0)
    exp = (centerMoment ** 4).mean()
    return exp / sigR ** 4

def varGaussian(r, level=5, modified=False):
    '''

    @param r: series, returns
    @param level: scalar, significance level
    @param modified: bool, taylor expansion and approximation of the VAR
    @return: scalar, percentage of portfolio Value at Risk
    '''
    from scipy.stats import norm
    z = norm.ppf(level / 100)

    if modified is True:
        s = skewness(r)
        k = kurtosis(r)
        z = (z +
             (z ** 2 - 1) * s / 6 +
             (z ** 3 - 3 * z) * (k - 3) / 24 -
             (2 * z ** 3 - 5 * z) * (s ** 2) / 36
             )
    return - (r.mean() + z * r.std(ddof=0))

def sharpeRatio(r, rf, num_periods):
    '''

    @param r: series, returns
    @param rf: scalar or series, of risk-free rate proxy
    @param num_periods: scalar, number of periods
    @return: scalar, risk-adjusted return
    '''
    # convert the annual riskfree to per period
    rf = (1 + rf) ** (1 / num_periods) - 1
    excessRets = r - rf
    annExcessRets = annualizedRet(excessRets, num_periods)
    annVol = annualizedVol(r, num_periods)
    return annExcessRets / annVol

def sortinoRatio(r,rf, num_periods):
    '''

    @param r: series, returns
    @param rf: scalar or series, of risk-free rate proxy
    @param num_periods: scalar, number of periods
    @return: scalar, risk-adjusted return
    '''

    rf = (1 + rf) ** (1 / num_periods) - 1
    excessRets = r - rf
    annExcessRets = annualizedRet(excessRets, num_periods)
    anndownsideVol = annualizedVol(r, num_periods, downside=True)
    return annExcessRets / anndownsideVol

def summary_stats(r, riskFree=0, periodsInYear=252):
    '''

    @param r: series, return
    @param riskFree: scalar or series, of risk-free rate proxy
    @param num_periods: scalar, number of periods
    @param title: string, title of the returned df
    @return: DataFrame of summary statistics
    '''

    if not isinstance(r,pd.DataFrame):
        r = pd.DataFrame(r)

    annR = r.aggregate(annualizedRet, num_periods= periodsInYear)
    annVol = r.aggregate(annualizedVol, num_periods= periodsInYear)
    dd = r.aggregate(lambda r: drawdown(r).drawdowns.min())
    skew = r.aggregate(skewness)
    kurt = r.aggregate(kurtosis)
    modVar = r.aggregate(varGaussian, level=5, modified=True)
    sharpe = r.aggregate(sharpeRatio, rf=riskFree, num_periods = periodsInYear)
    sortino = r.aggregate(sortinoRatio, rf = riskFree, num_periods = periodsInYear)

    stats = pd.DataFrame({
        'Annualized Returns': annR*100,
        'Annualized Volatility':  annVol*100,
        'Sharpe Ratio': sharpe,
        'Sortino Ratio': sortino,
        'Max Drawdown':  dd*100,
        'Skewness': skew,
        'Kurtosis': kurt,
        'Cornish Fisher adj. VAR 5%': modVar*100,
    })

    #formatting
    stats['Annualized Returns'] = stats['Annualized Returns'].map('{:,.2f}%'.format)
    stats['Annualized Volatility'] = stats['Annualized Volatility'].map('{:,.2f}%'.format)
    stats['Sharpe Ratio'] = stats['Sharpe Ratio'].map('{:,.2f}'.format)
    stats['Sortino Ratio'] = stats['Sortino Ratio'].map('{:,.2f}'.format)
    stats['Max Drawdown'] = stats['Max Drawdown'].map('{:,.2f}%'.format)
    stats['Skewness'] = stats['Skewness'].map('{:,.2f}'.format)
    stats['Kurtosis'] = stats['Kurtosis'].map('{:,.2f}'.format)
    stats['Cornish Fisher adj. VAR 5%'] = stats['Cornish Fisher adj. VAR 5%'].map('{:,.2f}%'.format)

    return stats.T


import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse import spdiags
from scipy.sparse import linalg as lg

# Quadratic function
class Quad(object):
    def __init__(self, Q, p):
        self.Q = Q
        self.p = p

    def grad_func(self, x):
        df = np.dot(self.Q, x) - self.p
        return df

# Solver
class CG_Solve(object):

    def __init__(self, A, b, x_0, TOL, MAXIT):
        self.func = Quad(A, b)
        self.x = x_0
        self.TOL = TOL
        self.MAXIT = MAXIT
        self.g = self.func.grad_func(self.x)
        self.d = - self.g
        self.k = 1
        self.res = [np.linalg.norm(self.g)]

    def calc(self, ):
        while self.res[-1] > self.TOL and self.k < self.MAXIT:
            z = np.dot(self.func.Q, np.copy(self.d))
            alpha = np.linalg.norm(self.g) ** 2 / np.dot(self.d.T, z)
            self.x += alpha * self.d
            self.g += alpha * z
            res = np.linalg.norm(self.g)
            self.res.append(res)
            beta = (self.res[-1] / self.res[-2]) ** 2
            self.d = - self.g + beta * self.d
            self.k += 1
        return self.res, self.k, self.x

# Generate Matrix
class gen_mat(object):

    def __init__(self, n):
        self.n = n

    def gen_diag(self, ):
        data = np.array([- np.ones(self.n), 2 * np.ones(self.n), - np.ones(self.n)])
        diags = np.array([-1,0,1])
        M = spdiags(data, diags, self.n, self.n).toarray()
        return M

def main(n):
    mat = gen_mat(n)
    run = CG_Solve(mat.gen_diag(), -np.ones(n),np.zeros(n), 1e-5, 2000)
    sci = lg.cg(mat.gen_diag(), -np.ones(n), np.zeros(n), 1e-5, 2000)
    result = run.calc()
    print('result :', result[2])
    print('result_sci :', sci[0])
    plt.semilogy(range(0, result[1]), np.log(result[0]), label='CG')
    plt.legend()
    plt.ylabel('Residuals')
    plt.xlabel('Iterations')
    plt.show()

if __name__ == '__main__':
    main(25)


# Library of Gaussian Mixture Models
# To-do: convert library into class
# Author: Gerardo Durán-Martín

import superimport

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

def plot_mixtures(X, mu, pi, Sigma, r, step=0.01, cmap="viridis", ax=None):
    ax = ax if ax is not None else plt.subplots()[1]
    colors = ["tab:red", "tab:blue"]
    x0, y0 = X.min(axis=0)
    x1, y1 = X.max(axis=0)
    xx, yy = np.mgrid[x0:x1:step, y0:y1:step]
    zdom = np.c_[xx.ravel(), yy.ravel()]

    Norms = [multivariate_normal(mean=mui, cov=Sigmai)
             for mui, Sigmai in zip(mu, Sigma)]

    for Norm, color in zip(Norms, colors):
        density = Norm.pdf(zdom).reshape(xx.shape)
        ax.contour(xx, yy, density, levels=1,
                    colors=color, linewidths=5)

    ax.scatter(*X.T, alpha=0.7, c=r, cmap=cmap, s=10)
    ax.set_xlim(x0, x1)
    ax.set_ylim(y0, y1)

def compute_responsibilities(k, pi, mu, sigma):
    Ns = [multivariate_normal(mean=mu_i, cov=Sigma_i) for mu_i, Sigma_i in zip(mu, sigma)]
    def respons(x):
        elements = [pi_i * Ni.pdf(x) for pi_i, Ni in zip(pi, Ns)]
        return elements[k] / np.sum(elements, axis=0)
    return respons

def e_step(pi, mu, Sigma):
    responsibilities = []
    for i, _ in enumerate(mu):
        resp_k = compute_responsibilities(i, pi, mu, Sigma)
        responsibilities.append(resp_k)
    return responsibilities

def m_step(X, responsibilities, S=None, eta=None):
    N, M = X.shape
    pi, mu, Sigma = [], [], []
    has_priors = eta is not None
    for resp_k in responsibilities:
        resp_k = resp_k(X)
        Nk = resp_k.sum()
        # mu_k
        mu_k = (resp_k[:, np.newaxis] * X).sum(axis=0) / Nk
        # Sigma_k
        dk = (X - mu_k)
        Sigma_k = resp_k[:, np.newaxis, np.newaxis] * np.einsum("ij, ik->ikj", dk, dk)
        Sigma_k = Sigma_k.sum(axis=0)
        if not has_priors:
            Sigma_k = Sigma_k / Nk
        else:
            Sigma_k = (S + Sigma_k) / (eta + Nk + M + 2)

        # pi_k
        pi_k = Nk / N

        pi.append(pi_k)
        mu.append(mu_k)
        Sigma.append(Sigma_k)
    return pi, mu, Sigma

def gmm_log_likelihood(X, pi, mu, Sigma):
    likelihood =  0
    for pi_k, mu_k, Sigma_k in zip(pi, mu, Sigma):
        norm_k = multivariate_normal(mean=mu_k, cov=Sigma_k)
        likelihood += pi_k * norm_k.pdf(X)
    return np.log(likelihood).sum()

def apply_em(X, pi, mu, Sigma, threshold=1e-5, S=None, eta=None):
    r = compute_responsibilities(0, pi, mu, Sigma)(X)
    log_likelihood = gmm_log_likelihood(X, pi, mu, Sigma)
    hist_log_likelihood = [log_likelihood]
    hist_coeffs = [(pi, mu, Sigma)]
    hist_responsibilities = [r]

    while True:
        responsibilities = e_step(pi, mu, Sigma)
        pi, mu, Sigma = m_step(X, responsibilities, S, eta)
        log_likelihood = gmm_log_likelihood(X, pi, mu, Sigma)

        hist_coeffs.append((pi, mu, Sigma))
        hist_responsibilities.append(responsibilities[0](X))
        hist_log_likelihood.append(log_likelihood)

        if np.abs(hist_log_likelihood[-1] / hist_log_likelihood[-2] - 1) < threshold:
            break
        results = {
            "coeffs": hist_coeffs,
            "rvals": hist_responsibilities,
            "logl": hist_log_likelihood
        }
    return results


from math import pi, cos, sin
import numpy as np
import random
from matplotlib import pyplot as plt, colors as mcol
from pymicro.view.vol_utils import compute_affine_transform

colors = 'brgmk'
my_gray = (0.8, 0.8, 0.8)
random.seed(13)
n = 5
ref_points = np.empty((n, 2))
for i in range(n):
    ref_points[i, 0] = random.randint(0, 10)
    ref_points[i, 1] = random.randint(0, 10)
    print(ref_points[i])
    plt.plot(ref_points[i, 0], ref_points[i, 1], 'o', color=colors[i], markersize=10, markeredgecolor='none',
             label='reference points' if i == 0 else '')
plt.grid()
plt.axis([0, 20, 0, 20])
plt.xlabel('X coordinate')
plt.ylabel('Y coordinate')
plt.legend(numpoints=1)
plt.savefig('pointset_registration_1.png', format='png')

# compute the affine transform by composing R, S and T
s = 1.4
angle = 22 * pi / 180.
tx, ty = 5, 3
S = np.array([[s, 0.0, 0.0],
              [0.0, s, 0.0],
              [0.0, 0.0, 1.0]])
R = np.array([[cos(angle), -sin(angle), 0.0],
              [sin(angle), cos(angle), 0.0],
              [0.0, 0.0, 1.0]])
T = np.array([[1.0, 0.0, tx],
              [0.0, 1.0, ty],
              [0.0, 0.0, 1.0]])
A = np.dot(T, np.dot(S, R))
print('full affine transform:\n{:s}'.format(A))

# transform the points
tsr_points = np.empty_like(ref_points)
for i in range(n):
    p = np.dot(A, [ref_points[i, 0], ref_points[i, 1], 1])
    tsr_points[i, 0] = p[0]
    tsr_points[i, 1] = p[1]
    # tsr_points[i] = T[:2] + np.dot(np.dot(S[:2, :2], R[:2, :2]), ref_points[i])
    print(tsr_points[i])
    plt.plot(tsr_points[i, 0], tsr_points[i, 1], 's', color=colors[i], markersize=10, markeredgecolor='none',
             label='transformed points' if i == 0 else '')
# overwrite reference points in light gray
plt.plot(ref_points[:, 0], ref_points[:, 1], 'o', color=my_gray, markersize=10, markeredgecolor='none',
         label='reference points' if i == 0 else '')
# draw dashed lines between reference and transformed points
for i in range(n):
    plt.plot([ref_points[i, 0], tsr_points[i, 0]], [ref_points[i, 1], tsr_points[i, 1]], '--', color=colors[i])
plt.legend(numpoints=1)
plt.savefig('pointset_registration_2.png', format='png')

# compute the affine transform from the point set
translation, transformation = compute_affine_transform(ref_points, tsr_points)
invt = np.linalg.inv(transformation)
offset = -np.dot(invt, translation)
ref_centroid = np.mean(ref_points, axis=0)
tsr_centroid = np.mean(tsr_points, axis=0)
new_points = np.empty_like(ref_points)
for i in range(n):
    new_points[i] = ref_centroid + np.dot(transformation, tsr_points[i] - tsr_centroid)
    print('point %d will move to (%3.1f, %3.1f) to be compared with (%3.1f, %3.1f)' % (
        i, new_points[i, 0], new_points[i, 1], ref_points[i, 0], ref_points[i, 1]))
    plt.plot(new_points[i, 0], new_points[i, 1], 'x', color=colors[i], markersize=12,
             label='new points' if i == 0 else '')
plt.legend(numpoints=1)
plt.savefig('pointset_registration_3.png', format='png')
plt.show()


import numpy as np

def weights(N,p):
    """ Compute the weights for all (uniform) N-point
        finite difference approximations to the pth derivative """

    M = np.array([[ (i**j) for i in range(1-N,N) ] \
                           for j in range(N)])
    G = np.diag(np.cumprod([1]+[j for j in range(1,N)]))
    return np.flipud([np.linalg.solve(M[:,k:(k+N)],G[p]) for k in range(N)])

def stencil_weights( N, p=1, tol=1e-13 ):
    W = weights(N,p)
    d = int(N//2)
    h = [k-d for k in range(N)]
    h,w = zip(*[ (h[k],w) for k,w in enumerate(W[d]) if abs(w)>tol ])
    return h,w

def check_derivative(fun,exact,x,v,delta,order=1):

    import numpy as np
    from numpy.linalg import norm

    d,w = stencil_weights(order+1)

    phi = lambda h : fun(x+h*v)
    df = lambda h : sum( fun(x+j*h*v)*w[k] for k,j in enumerate(d) )/h

    if isinstance(delta,np.ndarray):
        assert np.all(np.isreal(delta))
        return np.array([ norm(df(h)-exact) for h in delta ])

    elif hasattr(delta,'__getitem__') and hasattr(delta,'__len__'):
        assert np.all([np.isreal(delta[k]) for k in range(len(delta))])
        return [ norm(df(h)-exact) for h in delta ]

    elif np.isscalar(delta):
        assert np.isreal(delta)
        return norm(df(h)-exact)

    else:
        raise TypeError("Finite difference step delta must either be a real-valued scalar, an array-like container of real scalars, or an iterable that yields real scalars.")


"""
 NAME:
    LUMDIST

 PURPOSE:
    Calculate luminosity distance (in Mpc) of an object given its redshift
 EXPLANATION:
    The luminosity distance in the Friedmann-Robertson-Walker model is
    taken from  Caroll, Press, and Turner (1992, ARAA, 30, 499), p. 511
    Uses a closed form (Mattig equation) to compute the distance when the
    cosmological constant is zero.   Otherwise integrates the function using
    QSIMP.
 CALLING SEQUENCE:
    result = lumdist(z, [H0 = , k = , Omega_M =, Lambda0 = , q0 = ,/SILENT])

 INPUTS:
    z = redshift, positive scalar or vector

 OPTIONAL KEYWORD INPUTS:
    /SILENT - If set, the program will not display adopted cosmological
        parameters at the terminal.
    H0: Hubble parameter  in km/s/Mpc, default is 70

        No more than two of the following four parameters should be
        specified.   None of them need be specified -- the adopted defaults
        are given.
    k - curvature constant, normalized to the closure density.   Default is
        0, indicating a flat universe
    Omega_m -  Matter density, normalized to the closure density, default
        is 0.3.   Must be non-negative
    Lambda0 - Cosmological constant, normalized to the closure density,
        default is 0.7
    q0 - Deceleration parameter, numeric scalar = -R*(R'')/(R')^2, default
        is -0.55

 OUTPUTS:
    The result of the function is the luminosity distance (in Mpc) for each
    input value of z.

 EXAMPLE:
    (1) Plot the distance of a galaxy in Mpc as a function of redshift out
        to z = 5.0, assuming the default cosmology (Omega_m=0.3, Lambda = 0.7,
        H0 = 70 km/s/Mpc)

        IDL> z = findgen(50)/10.
        IDL> plot,z,lumdist(z),xtit='z',ytit='Distance (Mpc)'

        Now overplot the relation for zero cosmological constant and
        Omega_m=0.3
        IDL> oplot,z,lumdist(z,lambda=0,omega=0.3),linestyle=1
 COMMENTS:
    (1) Integrates using the IDL Astronomy Version procedure QSIMP.    (The
    intrinsic IDL QSIMP function is not called because of its ridiculous
    restriction that only scalar arguments can be passed to the integrating
    function.)
    (2) Can fail to converge at high redshift for closed universes with
    non-zero lambda.   This can presumably be fixed by replacing QSIMP with
    an integrator that can handle a singularity
 PROCEDURES CALLED:
    COSMO_PARAM, QSIMP
 REVISION HISTORY:
    Written   W. Landsman        Raytheon ITSS       April 2000
    Avoid integer overflow for more than 32767 redshifts  July 2001
    Use double precision J. Moustakas/W. Landsman April 2008
    Convert to python S. Koposov                        2010
"""

from numpy import array, ndarray, sqrt, sin, sinh, maximum
from cosmo_param import cosmo_param
from scipy.integrate import quad
from math import sqrt as msqrt

def ldist(z, q0=None, lambda0=None):

   term1 = (1. + z) ** 2
   term2 = 1. + 2. * (q0 + lambda0) * z
   term3 = z * (2. + z) * lambda0
   denom = (term1 * term2 - term3)
   if denom>0:
      out = 1. / msqrt(denom) # since the function is used with scalar arguments
								  # I use math.sqrt instead of numpy.sqrt for
                                  # performance reasons
   else:
      out = 0.
   return out

def lumdist(z, h0=None, k=None, lambda0=None, omega_m=None, q0=None, silent=None):
   '''Syntax: result = lumdist(z, H0 = ,k=, Lambda0 = ])
   Returns luminosity distance in Mpc'''

   scal=False
   scalret = lambda x : x[0] if scal else x

   if isinstance(z, list):
      z = numpy.array(z)
   elif isinstance(z, ndarray):
      pass
   else:
      scal = True
      z = array([z])
   n = len(z)

   omega_m, lambda0, k, q0 = cosmo_param(omega_m, lambda0, k, q0)

   # Check keywords
   c = 2.99792458e5                  #  speed of light in km/s
   if h0 is None:
      h0 = 70
   if not silent:
      print 'LUMDIST: H0:', h0, ' Omega_m:', omega_m, ' Lambda0', lambda0, ' q0: ', q0, ' k: ', k#, format='(A,I3,A,f5.2,A,f5.2,A,f5.2,A,F5.2)'

   # For the case of Lambda = 0, we use the closed form from equation 5.238 of
   # Astrophysical Formulae (Lang 1998).   This avoids terms that almost cancel
   # at small q0*z better than the more familiar Mattig formula.
   #
   if lambda0 == 0:
      denom = sqrt(1 + 2 * q0 * z) + 1 + q0 * z
      dlum = (c * z / h0) * (1 + z * (1 - q0) / denom)
      return scalret(dlum)

      # For non-zero lambda
   else:
      dlum = z * 0.0
      for i in range(n):
         if z[i] <= 0.0:
            dlum[i] = 0.0
         else:
            lz = quad(ldist, 0, z[i], args=(q0, lambda0))
            dlum[i] = lz[0]

      if k > 0:
         dlum = sinh(sqrt(k) * dlum) / sqrt(k)
      else:
         if k < 0:
            dlum = maximum(sin(sqrt(-k) * dlum) / sqrt(-k), 0)
      return scalret(c * (1 + z) * dlum / h0)


import cocos.numerics as cn
from cocos.numerics.numerical_package_selector import \
    select_num_pack_by_dtype_from_iterable

import cocos.device as cd

from cocos.symbolic import (
    LambdifiedMatrixExpression, \
    find_length_of_state_vectors
)
import numpy as np
import sympy as sym
import time

sym.init_printing()

# define symbolic arguments to the function
x1, x2, x3, t = sym.symbols('x1, x2, x3, t')
argument_symbols = (x1, x2, x3)

# define a function f: R^3 -> R^3
g = sym.Function('g')
f = sym.Matrix([[x1 + x2], [(g(t) * x1 + x3) ** 2], [sym.exp(x1 + x2 + g(t))]])
print("defining the vector valued function f(x1, x2, x3) as")
print(f)
print()

# Compute the Jacobian symbolically
jacobian_f = f.jacobian([x1, x2, x3])
print("symbolically derived Jacobian:")
print(jacobian_f)
print()

def numeric_time_function(t: float):
    return np.log(t)

def jacobian_direct(t, x1, x2, x3):
    g = numeric_time_function
    state_vectors = (x1, x2, x3)
    R = find_length_of_state_vectors(state_vectors)
    num_pack = select_num_pack_by_dtype_from_iterable(state_vectors)
    result = num_pack.zeros((R, 3, 3))

    result[:, 0, 0] = 1.0
    result[:, 0, 1] = 1.0
    result[:, 0, 2] = 0.0

    result[:, 1, 0] = 2.0 * g(t) * (g(t) * x1 + x3)
    result[:, 1, 1] = 0.0
    result[:, 1, 2] = 2.0 * (g(t) * x1 + x3)

    result[:, 2, 0] = num_pack.exp(x1 + x2 + g(t))
    result[:, 2, 1] = num_pack.exp(x1 + x2 + g(t))
    result[:, 2, 2] = 0.0

    return result

# Convert the symbolic array expression to an object that can evaluated
# numerically on the cpu or gpu.
jacobian_f_lambdified \
    = LambdifiedMatrixExpression(
        argument_symbols=argument_symbols,
        time_symbol=t,
        symbolic_matrix_expression=jacobian_f,
        symbolic_time_function_name_to_numeric_time_function_map={'g': numeric_time_function})

# Define a 3 dimensional vector X = (x1, x2, x3) = (1, 2, 3)
X_gpu = cn.array([[1], [2], [3]])

# Numerically evaluate the Jacobian at X = (1, 2, 3)
print("numerical Jacobian at X = (1, 2, 3)")
print(jacobian_f_lambdified.evaluate(X_gpu.transpose(), t=1.0))
print(jacobian_direct(t=1.0, x1=1.0, x2=2.0, x3=3.0))

# Compare the performance on cpu and gpu by numerically evaluating the Jacobian
# at n different vectors (x1, x2, x3)
n = 10000000
print(f'evaluating Jacobian at {n} vectors\n')

X_gpu = cn.random.rand(n, 3)
X_cpu = np.array(X_gpu)

# evaluate on gpu
tic = time.time()
# jacobian_f_numeric_gpu = jacobian_f_lambdified.evaluate(X_gpu, t=1.0)
# jacobian_f_numeric_gpu = \
#     (jacobian_f_lambdified
#      .evaluate_with_dictionary(
#         symbolic_to_numeric_parameter_map={x1: X_gpu[:, 0],
#                                            x2: X_gpu[:, 1],
#                                            x3: X_gpu[:, 2]},
#         t=1.0))
jacobian_f_numeric_gpu = \
    (jacobian_f_lambdified
     .evaluate_with_kwargs(x1=X_gpu[:, 0],
                           x2=X_gpu[:, 1],
                           x3=X_gpu[:, 2],
                           t=1.0))
cd.sync()
time_gpu = time.time() - tic
print(f'time on gpu: {time_gpu}')

tic = time.time()
jacobian_f_numeric_gpu_direct = \
    jacobian_direct(t=1.0,
                    x1=X_gpu[:, 0],
                    x2=X_gpu[:, 1],
                    x3=X_gpu[:, 2])
cd.sync()
time_gpu_direct = time.time() - tic
print(f'time for direct computation on gpu: {time_gpu_direct}')

print(f'numerical results from gpu match results from direct computation: '
      f'{np.allclose(jacobian_f_numeric_gpu_direct, jacobian_f_numeric_gpu)}')

# evaluate on cpu
tic = time.time()
# jacobian_f_numeric_cpu = jacobian_f_lambdified.evaluate(X_cpu, t=1.0)
# jacobian_f_numeric_cpu = \
#     (jacobian_f_lambdified
#      .evaluate_with_dictionary(
#         symbolic_to_numeric_parameter_map={x1: X_cpu[:, 0],
#                                            x2: X_cpu[:, 1],
#                                            x3: X_cpu[:, 2]},
#         t=1.0))
jacobian_f_numeric_cpu = \
    (jacobian_f_lambdified
     .evaluate_with_kwargs(x1=X_cpu[:, 0],
                           x2=X_cpu[:, 1],
                           x3=X_cpu[:, 2],
                           t=1.0))
time_cpu = time.time() - tic
print(f'time on cpu: {time_cpu}')

tic = time.time()
jacobian_f_numeric_cpu_direct = \
    jacobian_direct(t=1.0,
                    x1=X_cpu[:, 0],
                    x2=X_cpu[:, 1],
                    x3=X_cpu[:, 2])
time_cpu_direct = time.time() - tic
print(f'time for direct computation on cpu: {time_cpu_direct}')

print(f'numerical results from cpu match results from direct computation: '
      f'{np.allclose(jacobian_f_numeric_cpu_direct, jacobian_f_numeric_cpu)}')

# output performance gain on gpu
print(f'speedup on gpu vs cpu: {time_cpu / time_gpu}\n')

# Verify that the results match
print(f'numerical results from cpu and gpu match: '
      f'{np.allclose(jacobian_f_numeric_gpu, jacobian_f_numeric_cpu)}')


#! /usr/bin/env python
import argparse
from scipy.stats import binom
import matplotlib.pyplot as plt
from scipy.stats import norm
import os
import math
import numpy as np

def testScipy(N, p, outdir):
    fig, ax = plt.subplots(1, 1)
    x = list(range(0,N+1))
    ax.plot(x, binom.pmf(x, N, p), 'bo', ms=8, label='binom pmf')
    ax.plot(x, binom.cdf(x, N, p), 'ro', ms=8, label='binom cdf')
    ax.plot(x, binom.sf(x, N, p), 'go', ms=8, label='binom sf')
    ax.legend(loc='best', frameon=False)
    plt.savefig(outdir + "/distributions.png")

def main(n, p_0, alpha=1/10, outdir='out'):
    testScipy(n, p_0, outdir)
    k_cutoff = binom.ppf(alpha, n, p_0) - 1
    print('k*: {}'.format(k_cutoff))
    print('P(binom < k*): {}'.format(binom.cdf(k_cutoff, n, p_0)))

    fig, ax = plt.subplots(1, 1)
    x = list(range(0,n+1))
    ax.plot(x, binom.cdf(x, n, p_0), 'ro', ms=5)
    ax.plot([0,n], [alpha,alpha], 'g')
    ax.plot([k_cutoff,k_cutoff], [0,1], 'b')
    # TODO alpha and k*
    plt.savefig(outdir + "/{}_{}.png".format(n, p_0))

if __name__ == "__main__":
    main(20, 1/2)
    main(100, 1/7)


import numpy as np                  # NumPy library for arrays
import matplotlib.pyplot as plt     # Matplotlib library for plotting

tmax = 10.0                         # Time to perform integration to
dt = 0.01                           # Timestep
time = np.arange(0.0, tmax, dt)     # Initialise the time array; from t = 0 -> t = tmax, in steps of size dt

# Set the constants and initial conditions in the equation to solve; du/dt = -lambda * (u - sin(t))
lambd = 100.0                       # Set lambda as a constant (= 100)
u = np.zeros(len(time))             # Initialise the array; u = 0 for each timestep
u[0] = 1.0                          # Intial condition, u(0) = 1

for i in range(len(time)-1):       # Loop over i in 0,1,2,..,(total number of timesteps - 1)
    # We solve for u;
    # du/dt = -lambda * (u - sin(t))
    # So,
    # u(i+1) = u(i) + du/dt * dt

    dudt = -lambd * (u[i] - np.sin(time[i]))    # We use the numpy sine function (np.sin)
    u[i+1] = u[i]  + dt * dudt

    plt.cla()                       # Clear the axis to prevent repeatedly plotting on top of old lines
    plt.xlabel('t')                # x-label for the plot
    plt.ylabel('u')                # y-label for the plot
    plt.plot(time,u,label='numerical solution')   # Plot our numerical solution for the current timestep
    plt.plot(time,np.sin(time),ls='dashed',label=r'$\sin t$') # Plot sin(t) for the current timestep as reference
                                                              # with a dashed linestyle (ls='dashed')
    plt.title(f'time={time[i]:.2f}')# Update the title as the current time to two decimal places
    plt.legend()                    # Display the legend, where the labels are set in the two lines above
    plt.show(block = False)         # Tell matplotlib to show the plot (block=False allows the code to continue)
    plt.pause(0.02)                 # Pause for a short time for animatio


import math
import time

import numpy as np

def calc_pearson(matrix, method='default', zero_diag=True):
    """Calculate the pearson correlation between nodes

    Params:
        matrix: data of shape [N, T], N is node num, T is sample num
        method: method used, default for manually calculation,
            numpy for numpy implementation
        zero_diag:
                if zero the self correlation value (in diagonal position)
    """
    # 一行是一个数据
    if method == 'numpy':
        res = np.corrcoef(np.array(matrix))
        if zero_diag:
            for i in range(res.shape[0]):
                res[i, i] = 0.0
        res = res.tolist()
    else:
        nrows = len(matrix)
        ncols = len(matrix[0])
        n = ncols * 1.0
        res = [[0 for i in range(nrows)] for j in range(nrows)]
        for i in range(nrows):
            idx = i + 1
            for j in range(idx, nrows):
                a = b = c = f = e = 0
                for k in range(0, ncols):
                    a += matrix[i][k] * matrix[j][k]  # sigma xy
                    b += matrix[i][k]  # sigma x
                    c += matrix[j][k]  # sigma y
                    e += matrix[i][k] * matrix[i][k]  # sigma xx
                    f += matrix[j][k] * matrix[j][k]  # sigma yy

                para1 = a
                para2 = b * c / n
                para3 = e
                para4 = b * b / n
                para5 = f
                para6 = c * c / n

                r1 = para1 - para2
                r2 = (para3 - para4) * (para5 - para6)
                r2 = math.sqrt(r2)
                r = 1.0 * r1 / r2
                res[i][j] = res[j][i] = r * 1.00000
        # w = Workbook()
        # # Write to a new Sheet
        # ws = w.create_sheet("relation"+str(ela) + ".xlsx")  #Create table sheet
        # for i in range(nrows):
        #     for j in range(nrows):
        # 	    ws.cell(row = i+1, column = j+1).value = res[i][j]
        if not zero_diag:
            for i in range(nrows):
                for j in range(nrows):
                    res[i][j] = 1.0
    return res

if __name__ == "__main__":
    np.random.seed(42)
    a = np.random.rand(100, 100)
    tic = time.time()
    b_numpy = calc_pearson(a, 'numpy')
    time_numpy = time.time() - tic
    tic = time.time()
    b_default = calc_pearson(a, 'default')
    time_default = time.time() - tic
    print('{:^10}Default time:{}'.format('', time_default))
    print('{:^10}Numpy   time:{}'.format('', time_numpy))
    print('{:^10}Distance    :{}'.format(
        '', np.linalg.norm(np.array(b_numpy)-np.array(b_default))))
    print('Result numpy  :', b_numpy[0][:10])
    print('Result default:', b_default[0][:10])


#setup: import numpy as np ; np.random.seed(0); N = 500000 ; X, Y = np.random.rand(N), np.random.rand(N)
#run: lstsqr(X, Y)
#from: http://nbviewer.ipython.org/github/rasbt/One-Python-benchmark-per-day/blob/master/ipython_nbs/day10_fortran_lstsqr.ipynb

#pythran export lstsqr(float[], float[])
import numpy as np
def lstsqr(x, y):
    """ Computes the least-squares solution to a linear matrix equation. """
    x_avg = np.average(x)
    y_avg = np.average(y)
    dx = x - x_avg
    dy = y - y_avg
    var_x = np.sum(dx**2)
    cov_xy = np.sum(dx * dy)
    slope = cov_xy / var_x
    y_interc = y_avg - slope*x_avg
    return (slope, y_interc)


#runas import numpy as np ; N = 10000 ; a = np.arange(float(N)); b = 0.1; c =1.1; log_likelihood(a, b, c)
#from http://arogozhnikov.github.io/2015/09/08/SpeedBenchmarks.html
import numpy

#pythran export log_likelihood(float64[], float64, float64)
def log_likelihood(data, mean, sigma):
    s = (data - mean) ** 2 / (2 * (sigma ** 2))
    pdfs = numpy.exp(- s)
    pdfs /= numpy.sqrt(2 * numpy.pi) * sigma
    return numpy.log(pdfs).sum()


import numpy as npv
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def fit_plane_svd(data):
    """
    Fit plane using SVD, ax + by + cz + d = 0
    :param data: 3D points
    :return: normalized direction [a, b, c]
    """
    # if don't remove center, don't changed the value (a,b,c)
    avg = data.mean(axis=0)
    centered_data = data - avg
    cal_data = np.c_[centered_data, np.ones(len(data))]
    u, s, vh = np.linalg.svd(cal_data)

    # params
    abc = vh[-1, :-1]
    abc = abc / np.linalg.norm(abc)
    d = -np.dot(abc, avg)
    return np.hstack((abc, d))

def plane_fitting_example():
    """example for 3D line fitting"""
    print(util.Section('3D Plane Fitting'))

    # generate 3D points data, ax + by + cz + d = 0
    print(util.Section('Generate 3D Points Data'))
    # generate simulated data
    params = np.array([1.0, 2.0, 3.0, 4.0])
    x, y = np.meshgrid(np.arange(-10, 10, 1), np.arange(-10, 10, 1))
    z = -(params[0] * x + params[1] * y + params[3]) / params[2]
    plane_data = np.c_[x.flatten(), y.flatten(), z.flatten()]
    print(f'abcd = {params}')
    print(f'normalized abcd = {params / np.linalg.norm(params)}')

    # plane fitting
    print(util.Section('Generate 3D Points Data'))
    params_svd = fit_plane_svd(plane_data)
    print(f'SVD, abcd = {params_svd}')
    print(f'SVD, normalized abcd = {params_svd / np.linalg.norm(params_svd)}')

    # generate plot data
    x_plot, y_plot = np.meshgrid(np.arange(-10, 10, 0.01), np.arange(-10, 10, 0.01))
    z_plot = -(params_svd[0] * x_plot + params_svd[1] * y_plot + params_svd[3]) / params_svd[2]
    # plot
    fig = plt.figure('3D Plane Fitting')
    ax = fig.add_subplot(111, projection='3d')
    ax.plot(x.flatten(), y.flatten(), z.flatten(), 'k.', label='Measurement')
    ax.plot_surface(x_plot, y_plot, z_plot, alpha=0.2, color='r', label='SVD Fitting')
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title('3D Plane Fitting')
    plt.show(block=True)

if __name__ == '__main__':
    plane_fitting_example()


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

D=list(range(1,9))          #list with the dimesions of the integral

#function to be integrated
def g(x):
    g=np.e**(-x)
    return g

#analytic result of the integral as a function of dimensionality
def I_analytic(D):
    I= (1- (1/np.e))**D
    return I

N=[65536, 256**2, 40**3, 16**4, 9**5, 6**6, 5**7, 4**8]   #number of extractions = number of cells

#Compute the integral with Monte Carlo integration (hit or miss)
I_mc=[]
for d in range(0, len(D)):
    s=1
    for j in range(0,D[d]):
        I=0
        for i in range(0,N[d]):
            x=random.random()
            h=g(x)
            I+=h
        s=s*I/N[d]
    I_mc.append(s)
I_mc=np.array(I_mc)

#number of counts in mid-point summation per each dimensionality
def k(d):
    k=round((N[d])**(1./D[d]))
    return k

#Compute the integral with mid-point summation:
I_mid=[]
for d in range(0, len(D)):
    n = k(d)
    h = 1/(k(d)-1)
    x = np.linspace(0., 1., n)
    I = h * sum(g((x[:n-1] + x[1:])/2))
    I_mid.append(I**D[d])

#plot of the result for Monte Carlo vs. mid-point integration
D=np.array(D)
plt.figure()
plt.plot(D, abs(I_analytic(D)-I_mc), 'ro', label='$Monte\ Carlo$')
plt.plot(D, abs(I_analytic(D)-I_mid), 'b*', label='$Mid-point$')
plt.ylabel('$|I_{analytic}-I_{numerical}|$', fontsize=14)
plt.xlabel('$Dimensionality$', fontsize=14)
plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.yscale('log')
plt.legend(loc='lower right')
plt.ylim(10**(-12),10**(-2) )


import matplotlib
matplotlib.rcParams = matplotlib.rc_params_from_file('../../matplotlibrc')

import numpy as np
from numpy.linalg import solve
from solution import cheb
import matplotlib.pyplot as plt
import math

def deriv_matrix_exercise1():

    def f(x):
        return np.exp(x)*np.cos(6.*x)

    def fp(x):
        return np.exp(x)*(np.cos(6.*x) - 6.*np.sin(6.*x))

    N = 10
    D,x = cheb(N)
    f_exact, fp_exact = f(x), fp(x)
    xx = np.linspace(-1,1,200)
    plt.plot(xx,fp(xx),'-k') # Exact Derivative

    uu = (np.poly1d(np.polyfit(x, D.dot(f(x) ), N)))(xx)

    plt.plot(x,D.dot(f(x)),'*r')	# Approximation to derivative at grid points
    plt.plot(xx,uu,'-r')			# Approximation to derivative at other values
    plt.savefig('equally_spaced_points.pdf')
    plt.clf()
    print "max error = \n", np.max(np.abs( D.dot(f(x)) - fp(x)   ))

def exercise2():
    # Solves the Poisson boundary value problem u''(x) = f(x), u(-1) = u(1) = 0
    def f(x):
        return np.exp(2.*x) #np.exp(4.*x)

    def anal_sol(x):
        # return (np.exp(4.*x) - np.sinh(4.)*x - np.cosh(4.))/16.
        return (np.exp(2.*x) - np.sinh(2.)*x - np.cosh(2.))/4.

    N = 3
    D, x = cheb(N)

    A = np.dot(D, D)[1:N:1,1:N:1]
    u = np.zeros(x.shape)
    u[1:N] = solve(A, f(x[1:N:1]))

    xx = np.linspace(-1, 1, 50)
    uu = (np.poly1d(np.polyfit(x, u, N)))(xx)
    print "Max error is ", np.max(np.abs(uu - anal_sol(xx)))

    plt.plot(x, u, '*r')
    plt.plot(xx, uu, '-r')
    plt.plot(xx, anal_sol(xx), '-k')
    plt.axis([-1.,1.,-2.5,.5])
    plt.savefig('chebyshev_points.pdf')
    plt.clf()

def nonzeroDirichlet():
    # Solves the Poisson boundary value problem
    # u''(x) + u'(x) = f(x),
    # u(-1) = a
    # u(1) = b
    a, b = 2, -1

    def f(x):
        return -(b-a)/2.*np.ones(x.shape)+ np.exp(3.*x)

    def G(x):
        return (b-a)/2.*np.ones(x.shape) *x + (b+a)/2.*np.ones(x.shape)

    def anal_sol(x):
        # out = ( np.exp(-x)/( 12.*(-1 +np.exp(1))*(1 + np.exp(1))*np.exp(3))  *
        # (-12.*a*np.exp(x+3.)+12.*a*np.exp(4) + 12.*b*np.exp(x+5)-12*b*np.exp(4) -
        #   np.exp(4*x+3) + np.exp(4*x+5) - np.exp(x+8 )+ np.exp(x)- np.exp(1) + np.exp(7)
        #   )
        # 	  )
        N, B = np.array([[1.,np.exp(1)],[1,np.exp(-1)]]), np.array([[a-np.exp(-3)/12.], [b-np.exp(3)/12.]])
        C = solve(N,B)
        return C[0] + C[1]*np.exp(-x) + np.exp(3.*x)/12.

    N = 7
    D, x = cheb(N)

    A = np.dot(D, D)[1:N:1,1:N:1]
    u = np.zeros(x.shape)
    u[1:N] = solve(A + D[1:N:1,1:N:1], f(x[1:N:1]))

    xx = np.linspace(-1, 1, 50)
    uu = (np.poly1d(np.polyfit(x,u,N)))(xx)
    print "Max error is ", np.max(np.abs(uu + G(xx) - anal_sol(xx)))

    # plt.plot(x,u+G(x), '*k')
    # plt.plot(xx,uu+G(xx), '-r')
    plt.plot(xx, anal_sol(xx))
    plt.xlabel('$x$')
    plt.ylabel('$u$')
    # plt.axis([-1.,1.,-2.5,.5])
    plt.savefig('nonzeroDirichlet.pdf')
    plt.clf()

if __name__ == "__main__":
    deriv_matrix_exercise1()
    exercise2()
    nonzeroDirichlet()


'''
Austin Griffith
bsoptions.py

Provides an analytical solution to options via the Black Scholes methodology.
'''

import numpy as np
from scipy.stats import norm

class EuroOptions:
    '''

    The Black Scholes value of a European Call / Put option
    Payoffs are of the form :
    C = max(S - K, 0)
    P = max(K - S, 0)

    The delta value of a European Call / Put option measures sensitivity
    of option value with respect to change in underlying asset price,
    calculated from the derivative dV/dS
    Deltas are of the form :
    C = e**-qT N(d1)
    P = -e**-qT N(-d1)

    The gamma value of a European option measures sensitivity of option
    delta with respect to change in underlying asset price,
    calculated from the derivative d2C/dS2
    Gamma is of the form :
    G = e**(-qT) N'(d1) / S vol sqrt(T)

    The rho value of a European Call / Put option measures sensitivity
    of option value with respect to change in interest rate over the life
    of the option, calculated from derivative dV/dr
    Rhos are of the form :
    C = e**-rT K T N(d2)
    P = -e**-rT K T N(-d2)

    The theta value of a European Call / Put option, measures sensitivity
    of option value with respect to change in time,
    calculated from derivative dV/dT

    The vega value of a European Call / Put option measures sensitivity
    of option value with respect to change in underlying asset volatility,
    calculated from derivative dV/dσ
    Vega is of the form :
    V = S e**(-qT) N'(d1) sqrt(T)

    Parameters
    ----------
    spot : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying s
        Spot value of underlying asset at current time, t
    strike : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying k
        Strike value of option, determined at initiation
    riskfree : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying r
        Risk free interest rate, implied constant till expiration
    tau : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying T
        Time till expiration for option
    vol : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying vol
        Volatility of underlying, implied constant till expiration in Black
        Scholes model
    div : number of any type (int, float8, float64 etc.) or numpy array of any type
        should the user wish to have a list of values output with varying q
        Continuous dividend payout, as a percentage

    Notes
    -----
    All parameters can be individual values.
    Only one of these parameters can be a numpy.array, otherwise there will be
    a dimension mismatch.

    '''

    def __init__(self, spot, strike, riskfree, tau, vol, div):
        self.s = spot
        self.k = strike
        self.r = riskfree
        self.T = tau
        self.vol = self.vol
        self.q = div

        self.d1, self.d2 = self._euroD()
        self.optionleg, self.strikeleg = self._euroParity()

    def EuroPrice(self):
        '''

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Euro call and put prices, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        call = max(self._euroPriceCall(), 0)
        put = max(self._euroPricePut(), 0)
        return([call,put])

    def EuroCall(self):
        '''

        Returns
        -------
        data : dictionary float or numpy.array values
            Euro call price and greeks, labeled according to the respective value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        price = max(self._euroPriceCall(), 0)
        delta = self._euroDeltaCall()
        gamma = self._euroGamma()
        vega = self._euroVega()
        theta = self._euroThetaCall()
        rho = self._euroRhoCall()

        eur = [price, delta, gamma, vega, theta, rho]
        label = ['price','delta','gamma','vega','theta','rho']
        data = dict(zip(label, eur))
        return(data)

    def EuroPut(self):
        '''

        Returns
        -------
        data : dictionary float or numpy.array values
            Euro put price and greeks, labeled according to the respective value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        price = max(self._euroPricePut(), 0)
        delta = self._euroDeltaPut()
        gamma = self._euroGamma()
        vega = self._euroVega()
        theta = self._euroThetaPut()
        rho = self._euroRhoPut()

        eur = [price, delta, gamma, vega, theta, rho]
        label = ['price','delta','gamma','vega','theta','rho']
        data = dict(zip(label, eur))
        return(data)

    def _euroRhoCall(self):
        r = self.strikeleg*self.T*norm.cdf(self.d2)
        return(r)

    def _euroRhoPut(self):
        r = -1*self.strikeleg*self.T*norm.cdf(-1*self.d2)
        return(r)

    def _euroThetaCall(self):
        t = self._euroDerivTheta() - self.strikeleg*norm.cdf(self.d2) + \
            self.optionleg*norm.cdf(self.d1)
        return(t)

    def _euroThetaPut(self):
        t = self._euroDerivTheta() + self.strikeleg*norm.cdf(-1*self.d2) - \
            self.optionleg*norm.cdf(-1*self.d1)
        return(t)

    def _euroDerivTheta(self):
        dt = -0.5*np.exp(-1*self.q*self.T)*norm.pdf(self.d1)*self.s*self.vol / np.sqrt(self.T)
        return(dt)

    def _euroPriceCall(self):
        p = self.strikeleg*norm.cdf(-1*self.d2) - self.optionleg*norm.cdf(-1*self.d1)
        return(p)

    def _euroPricePut(self):
        p = self.optionleg*norm.cdf(self.d1) - self.strikeleg*norm.cdf(self.d2)
        return(p)

    def _euroD(self):
        d1 = (np.log(self.s / self.k) + \
              (self.r - self.q + 0.5*self.vol*self.vol)*self.T) / self.vol*np.sqrt(self.T)
        d2 = d1 - self.vol*np.sqrt(self.T)
        return(d1, d2)

    def _euroParity(self):
        o_leg = np.exp(-1*self.q*self.T)*self.s
        k_leg = np.exp(-1*self.r*self.T)*self.k
        return(o_leg, k_leg)

    def _euroGamma(self):
        g = np.exp(-1*self.q*self.T)*norm.pdf(self.d1) / (self.s*self.vol*np.sqrt(self.T))
        return(g)

    def _euroVega(self):
        v = self.s*np.exp(-1*self.q*self.T)*norm.pdf(self.d1)*np.sqrt(self.T)
        return(v)

    def _euroDeltaCall(self):
        ed = np.exp(-1*self.q*self.T)*norm.cdf(self.d1)
        return(ed)

    def _euroDeltaPut(self):
        ed = -1*np.exp(-1*self.q*self.T)*norm.cdf(-1*self.d1)
        return(ed)

class ExoticOptions:

    @staticmethod
    def AsianGeometric(s, k, r, T, vol, q):
        '''

        Calculate the Black Scholes value of Geometric Average Asian Call / Put
        option with a fixed strike
        Payoffs are of the form :
        C = max(AVG_geo - K, 0)
        P = max(K - AVG_geo, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Asian call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        a = 0.5*(r - q - vol*vol/6)
        volG = vol/np.sqrt(3)

        d1 = (np.log(s/k) + (a + 0.5*volG*volG)*T) / (volG*np.sqrt(T))
        d2 = d1 - volG*np.sqrt(T)

        option = s*np.exp((a - r)*T)
        strike = k*np.exp(-r*T)

        put = max(strike*norm.cdf(-d2) - option*norm.cdf(-d1), 0)
        call = max(option*norm.cdf(d1) - strike*norm.cdf(d2), 0)
        return([call,put])

    @staticmethod
    def AsianArithmetic(s, k, r, T, vol, q):
        '''

        Calculate the Black Scholes value of Arithmetic Average Asian Call / Put
        option with a fixed strike
        Payoffs are of the form :
        C = max(AVG_arithmetic - K, 0)
        P = max(K - AVG_arithmetic, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        * want r > q, else the natural logarithm has a chance of breaking

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Asian call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        m1 = s*(np.exp((r - q)*T) - 1) / ((r - q)*T)
        m2l = 2*s*s*np.exp((2*r - 2*q + vol*vol)*T) / ((r - q +vol*vol)*T*T*(2*r - 2*q + vol*vol))
        m2r = (2*s*s / ((r-q)*T*T))*((1/(2*(r-q) + vol*vol)) -
            np.exp((r-q)*T)/(r - q - vol*vol))
        m2 = m2l + m2r

        volA = np.sqrt(np.log(m2/(m1*m1)) / T)

        d1 = (np.log(m1/k) + 0.5*volA*volA*T) / (volA*np.sqrt(T))
        d2 = d1 - volA*np.sqrt(T)

        call = np.exp(-r*T)*(m1*norm.cdf(d1) - k*norm.cdf(d2))
        put = np.exp(-r*T)*(k*norm.cdf(-d2) - m1*norm.cdf(-d1))
        return([call,put])

    @staticmethod
    def Power(s, k, r, T, vol, q, n):
        '''

        Calculate the Black Scholes value of a traditional Power Call / Put option
        with a fixed strike
        Payoffs are of the form :
        C = max(S**n - K, 0)
        P = max(K - S**n, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage
        n : number of any type (int, float8, float64 etc.)
            Power to which the underlying spot is raised at payoff

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array (not including 'n'),
        otherwise there will be a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Power call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value, other than 'n', is numpy.array, then output
            will be numpy.array.

        '''
        d1 = (np.log(s/np.power(k,1/n)) + (r - q + vol*vol*(n - 0.5))*T) / (vol*np.sqrt(T))
        d2 = d1 - n*vol*np.sqrt(T)

        option = np.exp(T*(n-1)*(r + 0.5*n*vol*vol))*np.power(s,n)
        strike = k*np.exp(-r*T)

        put = strike*norm.cdf(-d2) - option*norm.cdf(-d1)
        call = option*norm.cdf(d1) - strike*norm.cdf(d2)
        return([call,put])

    @staticmethod
    def PowerStrike(s, k, r, T, vol, q, n):
        '''

        Calculate the Black Scholes value of Power Call / Put option with a
        fixed strike to the power n
        Payoffs are of the form :
        C = max(S**n - K**n, 0)
        P = max(K**n - S**n, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage
        n : number of any type (int, float8, float64 etc.)
            Power to which the underlying spot is raised at payoff

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array (not including 'n'),
        otherwise there will be a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Power call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value, other than 'n', is numpy.array, then output
            will be numpy.array.

        '''
        d1 = (np.log(s/np.power(k,1/n)) + (r - q + vol*vol*(n - 0.5))*T) / (vol*np.sqrt(T))
        d2 = d1 - n*vol*np.sqrt(T)

        option = np.exp(T*(n-1)*(r + 0.5*n*vol*vol))*np.power(s,n)
        strike = np.power(k,n)*np.exp(-r*T)

        put = strike*norm.cdf(-d2) - option*norm.cdf(-d1)
        call = option*norm.cdf(d1) - strike*norm.cdf(d2)
        return([call,put])

    @staticmethod
    def Margrabe(s, s2, T, vol, vol2, q, q2, corr):
        '''

        Calculate the Black Scholes value of the Margrabe Option
        Payoff is of the form :
        O = max(S_1 - S_2, 0)

        Parameters
        ----------
        s1 and s2 : number of any type (int, float8, float64 etc.), numpy array of
            any type should the user wish to have a list of values output with
            varying s
            Spot value of underlying assets 1 and 2 at current time, t
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol1 and vol2 : number of any type (int, float8, float64 etc.), numpy array
            of any type should the user wish to have a list of values output with
            varying vol
            Volatility of underlying for assets 1 and 2, implied constant till
            expiration in Black Scholes model
        q1 and q2 : number of any type (int, float8, float64 etc.), numpy array of
            any type should the user wish to have a list of values output with
            varying q
            Continuous dividend payout for assets 1 and 2, as a percentage
        corr : number of any type (int, float8, float64 etc.), numpy array of any
            type should the user wish to have a list of values output with varying
            corr
            Correlation between the motion of the underlying (relationship between
            the Weiner process of asset 1 and 2)

        Notes
        -----
        All parameters can be individual values.
        At most, only one pair of these parameters can be a numpy.array,
        otherwise there will be a dimension mismatch.

        Returns
        -------
        price : float or numpy.array value
            Margrabe price, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one pair of input values are a numpy.array,
            then output will be numpy.array.

        '''
        volMix = np.sqrt(vol*vol + vol2*vol2 - vol*vol2*corr)
        d1 = (np.log(s/s2) + (q2 - q + 0.5*(volMix**2))*T) / (volMix*np.sqrt(T))
        d2 = d1 - volMix*np.sqrt(T)

        option = np.exp(-q*T)*s*norm.cdf(d1)
        option2 = np.exp(-q2*T)*s2*norm.cdf(d2)

        price = option - option2
        return(price)

    @staticmethod
    def Lookback(s, M, r, T, vol, q):
        '''

        Calculate the Black Scholes value of floating strike
        Lookback Call / Put option
        Payoffs are of the form :
        C = S_T - min(m,m_T)
        P = max(M,M_T) - S_T
        where 'm' is the current minimum, or starting strike at initiation, and
        'm_T' is the minimum over the remaining life of the option
        similarly, 'M' is the current maximum, or starting strike at initiation, and
        'M_T' is the maximum over the remaining life of the option

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        M : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined by minimum value of underlying
            over the life of the option
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Lookback call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        B = 2*(r - q) / (vol*vol)
        x = (np.log(s/M) + (r - q - 0.5*vol*vol)*T) / (vol*np.sqrt(T))
        y = (-np.log(s/M) - (r - q + 0.5*vol*vol)*T) / (vol*np.sqrt(T))

        option = s*np.exp(-q*T)
        minimum = M*np.exp(-r*T)
        left = np.exp(-r*T)*np.power(s/M,-B)
        right = np.exp(-q*T)

        call = (option*norm.cdf(x + vol*np.sqrt(T)) -
            minimum*norm.cdf(x) +
            (s/B)*(left*norm.cdf(y + B*vol*np.sqrt(T)) -
                right*norm.cdf(y)))
        put = (-option*norm.cdf(-x - vol*np.sqrt(T)) +
            minimum*norm.cdf(-x) -
            (s/B)*(left*norm.cdf(-y - B*vol*np.sqrt(T)) -
                right*norm.cdf(-y)))
        return([call,put])

class BarrierOptions:
    '''

    Provides an analytical solution to barrier options using
    the Black Scholes methodology. Due to the underlying calculus'
    assumptions, the risk-free rate and implied volatility of the
    underlying are held constant from t0 to t1.

    f(u) : the density function of the natural logarithm of
        the risk-neutral underlying asset return
    g(u) : the density function of the natural logarithm of
        the risk-neutral underlying asset return when the
        underlying asset price starts above/below the barrier
        crosses the barrier but ends up above/below the barrier
        at expiration

    The _I functions are set values used as the analytical
    solution to the barrier options. Since a barrier option
    is marked by a set of logic conditions being met, these
    can be manipulated via an alpha and beta scalar to account
    for each barrier option's requirements. Each barrier is
    a linear combination of this set of analytical solutions.

    I1 - I2 : call payoff integrated over f(u) between the
        strike and barrier
    I1 - I3 : call payoff integrated over the probability density
        of the terminal asset price conditional on NOT crossing
        the barrier
    I2 - I4 :
    I3 : call payoff integrated over g(u) conditional on crossing
        the barrier
    I4 : call payoff integrated over the density function of
        the natural logarithm under risk-neutral assumptions
        between the barrier and infinity
    I5 : rebate for "In" options
    I6 : reabte for "Out" options

    Parameters
    ----------
    spot : number of any type (int, float8, float64 etc.)
        Spot value of underlying asset at current time, t
    strike : number of any type (int, float8, float64 etc.)
        Strike value of option, determined at initiation
    riskfree : number of any type (int, float8, float64 etc.)
        Risk free interest rate, implied constant till expiration
    barrier : number of any type (int, float8, float64 etc.)
        Barrier value of option, determined at initiation
    tau : number of any type (int, float8, float64 etc.)
        Time till expiration for option, can be interpreted as 'T - t' should
        the option already be initiated, and be 't' time from time = 0
    vol : number of any type (int, float8, float64 etc.)
        Volatility of underlying, implied constant till expiration in Black
        Scholes model
    div : number of any type (int, float8, float64 etc.)
        Continuous dividend payout, as a percentage
    rebate : number of any type (int, float8, float64 etc.)
        Rebate of barrier option, if there is no rebate provision, set = 0
        Default value is 0

    '''
    def __init__(self, spot, strike, riskfree, barrier, tau, vol, div, rebate=0):
        self.s = spot
        self.k = strike
        self.r = riskfree
        self.Z = barrier
        self.T = tau
        self.vol = vol
        self.q = div
        self.R = rebate

        self.L = self._lambdaDrift()

    def _lambdaDrift(self):
        '''

        Lambda constant calculated from the risk-netural
        drift of the underlying

        Returns
        -------
        l : float
            The lambda constant used in each of barrier option
            analytical solutions

        '''
        m = self._mu()
        l = 1 + (m / (self.vol*self.vol))
        return(l)

    def _mu(self):
        '''

        The underlying's risk-netural drift term,
        referred to as 'Mu'

        Returns
        -------
        mu : float
            The drift term used in the barrier option
            analytical solutions

        '''
        mu = (self.r - self.q - self.vol*self.vol*0.5)
        return(mu)

    def _x1val(self):
        x = np.log(self.s / self.Z) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(x)

    def _xval(self):
        x = np.log(self.s / self.k) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(x)

    def _y1val(self):
        y = np.log(self.Z / self.s) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(y)

    def _yval(self):
        y = np.log(np.square(self.Z) / (self.s*self.k)) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(y)

    def _zval(self):
        z = np.log(self.Z / self.s) / (self.vol*np.sqrt(self.T)) + self._bval()*self.vol*np.sqrt(self.T)
        return(z)

    def _aval(self):
        a = self._mu() / (self.vol*self.vol)
        return(a)

    def _bval(self):
        b = np.sqrt(self._mu()**2 + 2*self.r*self.vol*self.vol) / (self.vol*self.vol)
        return(b)

    def _I1(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I1 partial analytical solution for the barrier option

        '''
        xval = self._xval()
        partial = alpha*self.s*norm.cdf(alpha*xval) - alpha*self.k*np.exp(-1*self.r*self.T)*norm.cdf(alpha*xval - alpha*self.vol*np.sqrt(self.T))
        return(partial)

    def _I2(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I2 partial analytical solution for the barrier option

        '''
        xval = self._x1val()
        partial = alpha*self.s*norm.cdf(alpha*xval) - alpha*self.k*np.exp(-1*self.r*self.T)*norm.cdf(alpha*xval - alpha*self.vol*np.sqrt(self.T))
        return(partial)

    def _I3(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I3 partial analytical solution for the barrier option

        '''
        yval = self._yval()
        partial = alpha*self.s*np.power(self.Z / self.s, 2*self.L)*norm.cdf(beta*yval) - \
            alpha*self.k*np.exp(-1*self.r*self.T)*np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*yval - beta*self.vol*np.sqrt(self.T))
        return(partial)

    def _I4(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I4 partial analytical solution for the barrier option

        '''
        yval = self._y1val()
        partial = alpha*self.s*np.power(self.Z / self.s, 2*self.L)*norm.cdf(beta*yval) - \
            alpha*self.k*np.exp(-1*self.r*self.T)*np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*yval - beta*self.vol*np.sqrt(self.T))
        return(partial)

    def _I5(self, beta : int):
        x = self._x1val()
        y = self._y1val()
        partial = self.R*np.exp(-1*self.r*self.T) * \
            (norm.cdf(beta*x - beta*self.vol*np.sqrt(self.T)) - \
             np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*y - beta*self.vol*np.sqrt(self.T)))
        return(partial)

    def _I6(self, beta : int):
        a = self._aval()
        b = self._bval()
        z = self._zval()
        partial = self.R * (np.power(self.Z / self.s, a - b)*norm.cdf(beta*z) - \
                            np.power(self.Z / self.s, a - b)*norm.cdf(beta*z - 2*beta*b*self.vol*np.sqrt(self.T)))
        return(partial)

    def DownOutPut(self):
        '''

        Calculate the Down-and-Out PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = 1

        if self.k > self.Z and self.s >= self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I3(a,b) - self._I4(a,b) + self._I6(b)
        elif self.k < self.Z and self.s >= self.Z:
            price = self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownOutCall(self):
        '''

        Calculate the Down-and-Out CALL option, for any barrier

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = 1

        if self.k > self.Z and self.s >= self.Z:
            price = self._I1(a,b) - self._I3(a,b) + self._I6(b)
        elif self.k < self.Z and self.s >= self.Z:
            price = self._I2(a,b) - self._I4(a,b) + self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpOutCall(self):
        '''

        Calculate the Up-and-Out CALL option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = -1

        if self.k > self.Z and self.s <= self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I3(a,b) - self._I4(a,b) + self._I6(b)
        elif self.k < self.Z and self.s <= self.Z:
            price = self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpOutPut(self):
        '''

        Calculate the Up-and-Out PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = -1

        if self.k < self.Z and self.s <= self.Z:
            price = self._I1(a,b) - self._I3(a,b) + self._I6(b)
        elif self.k > self.Z and self.s <= self.Z:
            price = self._I2(a,b) - self._I4(a,b) + self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownInCall(self):
        '''

        Calculate the Down-and-In CALL option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = 1

        if self.k > self.Z:
            price = self._I3(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I4(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownInPut(self):
        '''

        Calculate the Down-and-In PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = 1

        if self.k > self.Z:
            price = self._I2(a,b) - self._I3(a,b) + self._I4(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I1(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpInCall(self):
        '''

        Calculate the Up-and-In Call option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = -1

        if self.k > self.Z:
            price = self._I1(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I2(a,b) - self._I3(a,b) + self._I4(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpInPut(self):
        '''

        Calculate the Up-and-In Put option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = -1

        if self.k > self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I4(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I3(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

if __name__ == '__main__':
    opt1 = [1, 2, 0.02, 1.5, 5, 0.05, 0.01, 0.01]
    bro = BarrierOptions(*opt1)
    a = bro.DownInCall()