# Run the blending process
# Martha Grover, July 6, 2017 (MATLAB)
# John Hedengren, July 14, 2017 (Python)

import numpy as np
from scipy.integrate import odeint

# Here is the playlist for the accompanying screencasts:
# https://www.youtube.com/playlist?list=PL4xAk5aclnUhb0tM6nypIATyxPRk0fB3L

# Following on the first screencast (Blending Process: Dynamic Simulation),
# here you can simulate the nonlinear coupled set of differential equations.
# As in the second screencast (Blending Process: Steady States),
# observe here how the mass fraction in the tank
# approaches the steady state value of xbar as the system approaches long
# time.  The volume does not have a unique steady state.  Since filling a
# tank is a integrating process, it may increase or decrease without bound,
# based on the difference between the inlet and outlet mass flow rates.

# Parameters
rho = 1000   # Density [kg/m^3]

# Here we will consider x1 and x2 to remain as constants
x1 = 0.1
x2 = 0

#######################################################################
############### Define the Nonlinear Model  ###########################
#######################################################################
# Nominal inputs:
# x1: Mass fraction of Species A in Stream 1 (unitless)
# w1: Mass flow rate in Stream 1 [kg/s]
# x2: Mass fraction of Species A in Stream 2 (unitless)
# w2: Mass flow rate in Stream 2 [kg/s]
# w:  Outlet mass flow [kg/s]

def blending(z,t,u):
    # Dynamic model of the blending process
    # State variables passed in as vector z:
    V = z[0]   # Volume [m3]
    x = z[1]   # Mass fraction of Species A in the tank (unitless)
    # Inputs passed in as vector u:
    w1 = u[0]  # Mass flow rate in Stream 1 [kg/s]
    w2 = u[1]  # Mass flow rate in Stream 2 [kg/s]
    w = u[2]   # Here we set the outlet flow equal to the total inlet flow

    # Dynamic equations
    dVdt = (1.0/rho)*(w1+w2-w)                     # Change in volume
    dxdt = (1.0/(rho*V))*(w1*(x1-x)+w2*(x2-x))     # Change in mass fraction

    # Assemble the two time derivatives back into a vector to pass back
    dzdt = [dVdt,dxdt]
    return dzdt

#######################################################################
############# Dynamic Simulation of the Nonlinear System ##############
#######################################################################

# Initial conditions
V0 = 10        # Initial volume in the tank [m^3]
x0 = 0.025     # Initial mass fraction of Species A in the tank (unitless)
# Note: these are currently set at their nominal steady-state values...
# But you can try changing them to other values.
z0 = [V0,x0]  # Initial condition vector for the system

# The flow rates will be the variables in u that may change over time
w1 = 1         # w1: Mass flow rate in Stream 1 [kg/s]
w2 = 3         # w2: Mass flow rate in Stream 2 [kg/s]
w = w1 + w2    # w:  Outlet mass flow [kg/s]
u = [w1,w2,w]  # Assemble these together in a column vector u

tf = 3600      # Final simulation time [s]
t = np.linspace(0,tf,100) # points to report solution

# Simulate the blending process
z = odeint(blending,z0,t,args=(u,))

# plotting results
import matplotlib.pyplot as plt

# Plot the blending process under steady operation
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(t,z[:,0])
plt.xlabel('time t [s]')
plt.ylabel(r'$V \; [m^3]$')
plt.title('Blending process under constant inputs')
plt.subplot(2,1,2)
plt.plot(t,z[:,1])
plt.xlabel('time t [s]')
plt.ylabel('x')

# Calculate the steady-state value for x
xbar = (w1*x1 + w2*x2)/(w1+w2)
# Check to see if this matches the long-term value of x on the plot
# Since V does not have a unique steady state, specify a desired operating value
# Here it is set equal to the initial volume
Vbar = V0  # volume [m^3]

# Define ubar as the set of inputs to achieve this steady state
ubar = np.copy(u)

# Next simulate the system for the higher value of w1 = 1.1 (0.1 higher)
dw1 = 0.1
u[0] = ubar[0] + dw1

zs = odeint(blending,z0,t,args=(u,))

# Plot the blending process under a step change in w1
plt.figure(2)
plt.subplot(2,1,1)
plt.plot(t,zs[:,0])
plt.xlabel('time t [s]')
plt.ylabel(r'$V \; [m^3]$')
plt.title(r'Blending process under a step input to $w_1$')
plt.subplot(2,1,2)
plt.plot(t,zs[:,1])
plt.xlabel('time t [s]')
plt.ylabel('x')

#######################################################################
###################### Linear Approximate Model #######################
#######################################################################

# Formulate the linear approximation as a state-space system using
# deviation variables (See screencast on Blending Process: Deviation
# Variables)

# Dynamic state x: V' and x'

# Inputs u: w1', w2', w'

# Measurements y: w1', V', x'

# Define the matrices A, B, C, and D for the linearized system
# dxdt = A*x + B*u'
# y = C*x + D*u'
# See the screencasts on Linearization and State Space

# Dynamic equation: dx/dt = Ax + Bu
A = np.array([[0,0], \
              [0,1.0/(rho*Vbar)*(-w1-w2)]])
B = np.array([[1.0/rho,1.0/rho,-1.0/rho], \
              [1.0/(rho*Vbar)*(x1-xbar),1.0/(rho*Vbar)*(x2-xbar),0]])
# Measurement equation: y = Cx + Du
C = np.array([[0,0], \
              [1,0], \
              [0,1]])        # Measuring the states V and x directly
D = np.array([[1,0,0], \
              [0,0,0], \
              [0,0,0]])      # Measuring the input w directly

#######################################################################
############### Define the Linear Model ###############################
#######################################################################
def blending_linear(x,t,u):
    # State space version of the linearized model for the blending process,
    # using deviation variables
    # dx/dt = A*x + B*u
    # The measurement equation of y = C*x + D*u is not included here, since
    # it is an algebraic equation that does not need to be solved by odeint.

    # Dynamic state x
    Vp = x[0]      # V' = deviation in volume
    xp = x[1]      # x' = deviation in mass fraction

    # Input vector u
    w1p = u[0]     # w1' = deviation in mass flow rate in inlet Stream 1
    w2p = u[1]     # w2' = deviation in mass flow rate in inlet Stream 2
    wp = u[2]      # w' = deviation in mass fraction in outlet stream

    # dx/dt = Ax + Bu
    dxdt = np.dot(A,x) + np.dot(B,u)
    return dxdt

# Define input in deviation variables u'
up = np.array(u) - np.array(ubar)
# Define the deviation in initial state
# Here we will continue to use z for the state vector, to avoid confusion
# with the mass fraction of Species A which is called x
zbar = np.array([Vbar,xbar])
zp0 = z0 - zbar   # Note: We set z0 = zbar earlier, so this should be zero
# Simulate the linearized model of the blending process
zp = odeint(blending_linear,zp0,t,args=(up,))

# Plot the comparison between the nonlinear system and the linearized
# system, for a step change of 0.1 in w1
plt.figure(3)
plt.subplot(2,1,1)
plt.plot(t,zs[:,0],'r-',linewidth=2)
plt.plot(t,zp[:,0]+Vbar,'b--',linewidth=2)
plt.legend(['Nonlinear','Linearized'])
plt.xlabel('time t [s]')
plt.ylabel(r'$V \; [m^3]$')
plt.title(r'Comparison of nonlinear and linear models for a step change in $w_1$')
plt.subplot(2,1,2)
plt.plot(t,zs[:,1],'k-',linewidth=2)
plt.plot(t,zp[:,1]+xbar,'g:',linewidth=2)
plt.legend(['Nonlinear','Linearized'])
plt.xlabel('time t [s]')
plt.ylabel('mass fraction x')

# In this example, the linear model agrees pretty well with the nonlinear
# model.  The equation for volume V is linear and does not depend on the
# mass fraction x, so it agrees perfectly with the nonlinear model.
# The mass fraction prediction is not perfect, but because the step change
# is "small" (10%) the agreement is still pretty good.

# Steady-state gain of the system
K = np.dot(np.dot(C,np.linalg.pinv(-A)),B)+D

# Note: It is not possible to calculate this since A is not invertible
# This makes sense because there is no unique steady-state value for
# volume V

# Note that we did not actually use C and D in this example.  We can
# evaluate the measurements y' at each time point of our ode solution:
yp = np.zeros((np.size(ubar),np.size(t)))
yp = np.dot(C,zp.T)
for i in range(np.size(t)):
    yp[:,i] = yp[:,i] + np.dot(D,up)

# show plots
plt.show()


import numpy as np
import theano.tensor as T
import matplotlib.pyplot as plt

# MY CODE

temperature = 0.01
logits = np.linspace(-2, 2, 10).reshape([1, -1])

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

def sample_gumbel(shape, eps=1e-20):
    """ Sample from Gumbel(0, 1) """
    U = np.random.uniform(size=shape, low=0, high=1)
    return -np.log(-np.log(U + eps) + eps)

def sample_gumbel_softmax(logits, temperature):
    """ Sample from Gumbel-Softmax distribution """
    y = logits + sample_gumbel(logits.shape)
    return softmax(y / temperature)

plt.title('gumbel-softmax samples')
for i in range(100):
    plt.plot(
        range(10),
        sample_gumbel_softmax(logits,temperature=temperature),
        marker='o',
        alpha=0.25
    )

plt.ylim(0,1)
plt.show()

gsm_samples = [sample_gumbel_softmax(logits,temperature=temperature) for _ in range(500)]
plt.title('average over samples')
plt.plot(
    range(10),
    np.mean(gsm_samples, axis=0)[0],
    marker='o',
    label='gumbel-softmax average'
)
plt.plot(
    softmax(sample_gumbel_softmax(logits,temperature=temperature)[0]),
    marker='+',
    label='regular softmax'
)
plt.legend(loc='best')
plt.show()

# ---------------------------------------------------------------------

# THEIR CODE
temperature = 0.01
logits = np.linspace(-2, 2, 10)

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

def GumbelSoftmax(logits, temperature):
    uniform = np.random.uniform(size=logits.shape,low=0,high=1)
    gumbel = -np.log(-np.log(uniform + 1e-20) + 1e-20)
    return softmax((logits + gumbel) / temperature)

plt.title('gumbel-softmax samples')
for i in range(100):
    plt.plot(
        range(10),
        GumbelSoftmax(logits, temperature),
        marker='o',
        alpha=0.25
    )

plt.ylim(0,1)
plt.show()

gsm_samples = [sample_gumbel_softmax(logits,temperature=temperature) for _ in range(500)]
plt.title('average over samples')
plt.plot(
    range(10),
    np.mean(gsm_samples, axis=0)[0],
    marker='o',
    label='gumbel-softmax average'
)
plt.plot(
    softmax(sample_gumbel_softmax(logits,temperature=temperature)[0]),
    marker='+',
    label='regular softmax'
)
plt.legend(loc='best')
plt.show()


import dataload
import matplotlib.pyplot as plt
import numpy as np
from random import seed, randrange
import os, sys, glob
from abc import abstractmethod, ABC

class LINEAR_REGRESSION(object):
    def __init__(self, xs, ys):
        self.xs = xs
        self.ys = ys
        self.size = len(xs)
        if (self.size == 0):
            print("ERROR: Data size cannot be zero")
            sys.exit()
        elif (len(xs) != len(ys)):
            print("ERROR: Independent and dependent variable length is unequal")
            sys.exit()

    def RegressionPlot(self, y_predict):
        pass

class LINEAR_RSQUARE(object):
    # TODO: Use super method
    def __init__(self, xs, ys):
        self.xs = xs
        self.ys = ys
        self.size = len(xs)
        if (self.size == 0):
            print("ERROR: Data size cannot be zero")
            sys.exit()
        elif (len(xs) != len(ys)):
            print("ERROR: Independent and dependent variable length is unequal")
            sys.exit()

    def slope_intercept(self):
        n = self.size
        S_x  = sum( self.xs ); S_y  = sum( self.ys )
        S_xx = sum( self.xs**2 ); S_xy = sum( self.xs*self.ys )
        # S_yy = sum( ys**2 )

        m = (n*S_xy - S_x*S_y) / (n*S_xx - S_x**2)
        b = S_y/n - m*(S_x/n)
        return m, b

    def squared_error(self, original, estimation):
        sum_sqr_err = 0.0
        for i in range( len(original) ):
            sqr_err = (estimation[i] - original[i])**2
            sum_sqr_err += sqr_err
        return sum_sqr_err

    def rsquared(self):
        ys_original  = list(self.ys)
        m_est, b_est = self.slope_intercept()

        ys_estimation = [m_est * x + b_est for x in self.xs]
        ys_mean_line  = [np.mean(self.ys) for y in ys_original]
        SS_tot = self.squared_error(ys_original, ys_mean_line)
        SS_res = self.squared_error(ys_original, ys_estimation)
        return (1.0 - (SS_res/SS_tot))

class LINEAR_RMSE(object):
    def __init__(self, xs, ys):
        self.xs = xs
        self.ys = ys

    def predict(self, row, coeff):
        # Coefficient predicitions
        y_hat = coeff[0]
        for i in range (len(row)-1):
            y_hat += coeff[i+1] * row[i]
        return y_hat

    def SGD_coeff(self, n_epoch=10, l_rate=0.001):
        # Estimate linear regression coefficients using SGD
        train_set = np.concatenate( (self.xs, self.ys) )
        train_set = np.reshape(train_set, (len(self.xs),2), order='F')

        coef = np.zeros( len(train_set[0]) )
        for _ in range(n_epoch):
            sum_error = 0.0

            for row in train_set:
                yhat       = self.predict(row, coef)
                error      = yhat - row[-1]
                sum_error += error**2
                coef[0]   -= l_rate * error

                for i in range(len(row)-1):
                    coef[i + 1] -= l_rate * error * row[i]
            # print('>> epoch={}, lrate={}, error={}'.format(epoch, l_rate,
            # sum_error))
        m = np.array( [coef[1]] ); b = np.array( [coef[0]] )
        return m, b

    def RMSE(self, y_original, y_estimation):
        sum_error = 0.0
        for i in range(0, len(y_original)):
            error = y_estimation[i] - y_original[i]
            sum_error += error**2
        mean_error = sum_error/float( len(y_original) )
        return mean_error

if __name__ == '__main__':
    snow_data = '../data/snow/snow.csv'
    x_snow, y_snow = dataload.loadCSV(snow_data)

    linear_reg_01 = LINEAR_RSQUARE(x_snow, y_snow)
    m_snow, b_snow = linear_reg_01.slope_intercept()
    regression_line = [m_snow * x + b_snow for x in x_snow]
    rsqr = linear_reg_01.rsquared()

    print("Slope and Intercept: {} and {}".format(m_snow, b_snow) )
    print("R-square: {}".format(rsqr))
    """ Use bool flag to display graph
    plt.scatter(x_snow, y_snow, color='red')
    plt.plot(x_snow, regression_line, color='blue')
    plt.title('Plot of Snow vs Yield using Statistical Analysis')
    plt.xlabel('Snow')
    plt.ylabel('Yield')
    plt.show()
    """

    linear_reg_02 = LINEAR_RMSE(x_snow, y_snow)
    m, b = linear_reg_02.SGD_coeff(1000, 0.001)
    regressionline_02 = m * x_snow + b
    rmse = linear_reg_02.RMSE(y_snow, regressionline_02)
    print("Slope and Intercept: {} and {}".format(m, b) )
    print("RMSE: {}".format(rmse))

    """ Use bool flag to display graph
    plt.scatter(x_snow, y_snow, color='red')
    plt.plot(x_snow, regressionline_02, color='blue')
    plt.title('Plot of Snow vs Yield using Machine Learning Analysis')
    plt.xlabel('Snow')
    plt.ylabel('Yield')
    plt.show()
    """


# python3
"""Z-Y dcomposition of a unitary U."""

import cmath

from absl import app
import numpy as np
import scipy.stats

from src.lib import ops

# The "Z-Y decomposition for a single qubit" shows that
# any unitary U can be decomposed into Rz and Ry rotations
# with 4 parameters alpha, beta, gamma, delta as follows:
#
#   U = e^(i*alpha) * Rz(beta) * Ry(gamma) * Rz(delta)
#
# The question is how to find the 4 parameters for
# a given U?
#
# One answer was provided here - it solves correctly for alpha and gamma:
#    https://threeplusone.com/pubs/on_gates.pdf
#
# The approach from this paper can be found in code, here:
#    https://github.com/gecrooks/quantumflow-dev/blob/master/quantumflow/decompositions.py
#
# Another publication solves correctly for beta and delta:
#    https://quantumcomputing.stackexchange.com/questions/\
#    16256/what-is-the-procedure-of-finding-z-y-decomposition-of-unitary-matrices
#
# Let's try and implement this here!

def make_u(alpha, beta, gamma, delta):
  """Construct unitary from the 4 parameters."""

  return ((ops.RotationZ(beta) @
           ops.RotationY(gamma) @
           ops.RotationZ(delta)) *
          cmath.exp(1.0j * alpha))

def zy_decompose(umat):
  """Perform Z-Y decomposition of unitary operator in SU(2)."""

  a = umat[0][0]
  b = umat[0][1]
  c = umat[1][0]

  det = np.linalg.det(umat)
  alpha = 0.5 * np.arctan2(det.imag, det.real)

  if a >= b:
    gamma = 2 * np.arccos(abs(a))
  else:
    gamma = 2 * np.arcsin(abs(b))

  # TODO(rhundt): Handle cases with gamma very close to 0 or Pi

  beta = cmath.phase(c) - cmath.phase(a)
  delta = cmath.phase(-b) - cmath.phase(a)

  return alpha, beta, gamma, delta

def main(argv):
  if len(argv) > 1:
    raise app.UsageError('Too many command-line arguments.')

  iterations = 10000
  print(f'Perform {iterations} random Z-Y decompositions.')

  for i in range(iterations):
    #
    # Construct a random unitary operator and put into SU(2).
    #
    u = scipy.stats.unitary_group.rvs(2)
    umat = np.sqrt(1 / np.linalg.det(u)) * u

    #
    # Now decompose this operator and find the four parameters.
    #
    alpha, beta, gamma, delta = zy_decompose(umat)

    #
    # Construct another operator from these newly found
    # parameters and make sure that the resulting
    # operator matches the one from above.
    #
    unew = make_u(alpha, beta, gamma, delta)

    if not np.allclose(umat, unew, atol=1e-4):
      print(f'decomp : {i:2d}: {alpha:.3f} {beta:.3f} {gamma:.3f} {delta:.3f}')
      raise AssertionError('Z-Y decomposition failed')

  print('Success')

if __name__ == '__main__':
  app.run(main)


#!/usr/bin/env python
# coding: utf-8

import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import matrix_power
from termcolor import colored
import timeit
plt.style.use('seaborn')

def transition_matrix(N, mu_a, mu_A,
                      f, g):
     '''
    N -- population size
    mu_a, mu_A are mutation rates
    and f, g are fitnesses for
    allele a and allele A respectively.
    '''
    P = np.zeros((N+1,N+1))
    for j in range(1,N):
        sa = f*j/(f*j + g*(N-j))   # Prob of selecting an allele a
        sA = 1 - sa   # Prob of selecting an allele A
        P[j][j+1] = (sa*(1 - mu_a) + (sA*mu_A))*(N - j)/N   # Prob of birth for allele a
        P[j][j-1] = (sa*mu_a + sA*(1 - mu_A))*j/N   # Prob of death for allele a
        P[j][j] = 1 - P[j][j+1] - P[j][j-1]
    P[0][1] = mu_A
    P[N][N-1] = mu_a
    P[0][0] = 1 - P[0][1]
    P[N][N] = 1 - P[N][N-1]
    return P

def fixation_prob(N, f, g, x0, k):
    '''
    Returns the probability of
    fixation at state N, starting at x0.
    '''
    P = transition_matrix(N, 0, 0, f, g)
    estimate_prob = matrix_power(P,k)[x0][N]
    return estimate_prob

def time_to_fixation(N, f, g, x0):
    '''
    Returns mean time to
    fixation at either state 0 or N,
    starting at x0.
    '''
    P = transition_matrix(N, 0, 0, f, g)
    Q = P[1:N,1:N]
    J = np.ones(N-1)
    I = np.identity(N-1)
    A = np.dot(np.linalg.inv(I-Q),J)
    mean_time = A[x0]
    return mean_time

def fixation_plots(pop_vec, pows, fit_vec, x0):
    fig, ax = plt.subplots(len(pop_vec), len(fit_vec), figsize=(12,8))
    col = ['cornflowerblue','orange','orchid']
    for i, (N,k) in enumerate(zip(pop_vec, pows)):
        for j, (f,g) in enumerate(fit_vec):
            mean_time = time_to_fixation(N, f, g, x0)
            trans_prob = []
            for iteration in range(k):
                p = fixation_prob(N, f, g, x0, iteration)
                trans_prob.append(p)
            ax[i,j].plot(np.linspace(1,k,k), trans_prob, color=col[i])
            ax[0,j].set_title('(f,g)={}'.format((f,g)), fontsize=12)
            ax[-1,j].set_xlabel('steps(k)', fontsize=12)
            ax[i,0].set_ylabel('N={}'.format(N), fontsize=14)
            ax[i,j].legend(['$Prob[X_k={}|x_0=1]$'.format(N)], fontsize=10)
            if i==0:
                ax[i,j].text(200,0.1,'Mean time to fixtaion\n= %1.1f' %mean_time, fontsize=9)
            elif i==1:
                ax[i,j].text(400,0.07,'Mean time to fixtaion\n= %1.1f' %mean_time, fontsize=9)
            else:
                ax[i,j].text(1000,0.03,'Mean time to fixtaion\n=  %1.1f' %mean_time, fontsize=9)
            ax[i,j].set_ylim([0,1/N + 0.01])
            ax[i,j].set_yticks(np.linspace(0,1/N,5))
            plt.suptitle('First Plot: $Prob[X_{k}=N | x_{0}=1]$ versus'\
                         ' $k\,(steps)$ for different values of $N$',y=1.05)
            plt.tight_layout()

def stationary_distribution(N, mu_a, mu_A,
                            f, g, k):
    P = transition_matrix(N, mu_a, mu_A, f, g)
    steady_state_approx = matrix_power(P, k)[0,:]
    return steady_state_approx

def stationary_plots(N, mut_vec, pows, fit_vec):
    fig, ax = plt.subplots(len(mut_vec), len(fit_vec), figsize=(12,8))
    samples = 1000
    mutations = zip(mut_vec, pows)
    for i,((mu_a, mu_A), k) in enumerate(mutations):
        for j, (f,g) in enumerate(fit_vec):
            P = transition_matrix(N, mu_a, mu_A, f, g)
            steady_state_approx = stationary_distribution(N, mu_a, mu_A,
                                                          f, g, k)
            state_frequency = steady_state_approx * samples
            mean_state = np.dot(state_frequency, np.arange(N+1))/samples
            ax[i,j].bar(np.arange(N+1), state_frequency)
            ax[i,j].axvline(x=mean_state, ymin=0, ymax=0.95, linestyle='dashed', color ='green', label='Mean')
            ax[0,j].set_title('(f,g)={}'.format((f,g)), fontsize=12)
            ax[-1,j].set_xlabel('states', fontsize=12)
            ax[i,0].set_ylabel('$\mu_a$=$\mu_A$={}'.format(mu_a), fontsize=12)
            ax[i,j].legend()
            ax[i,j].set_ylim([0,max(state_frequency)+30])
            plt.tight_layout()
            plt.suptitle('Second Plot: Frequncy of states at stationary for N=20'\
                         ' in 1000 samples' ,y=1.05)

start = timeit.default_timer()

# Plot the fixation probabilities
pop_vec = [5, 10, 20]
pows = [500, 1000, 2000]
fit_vec = [(1,1), (1,1.1), (1,1.2)]
x0 = 1
fixation_plots(pop_vec, pows, fit_vec, x0)

# Plot the stationary distributions
N = 20
mut_vec = [(0.005,0.005), (0.05,0.05), (0.5,0.5)]
pows = [50000, 20000, 5000]
fit_vec = [(1,1), (1,1.1), (1,1.2)]
stationary_plots(N, mut_vec, pows, fit_vec)

# timing
end = timeit.default_timer()
print('run time = %1.2f sec.'%(end-start))


import numpy as np

class Regularizer(object):
    def __call__(self, w):
        return self.forward(w)

    def forward(self, w):
        pass

    def backward(self, w):
        """
        Computes the gradient of the loss with respect to w
        """
        pass

class L1(Regularizer):
    """
    L1 regularization, also known as Lasso regularization

    Computed as l1 * sum(abs(w))
    """

    def __init__(self, l1=0.01):
        self.l1 = l1

    def forward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l1 to depend on m
        """

        J = self.l1 * np.sum(np.abs(w))
        return J

    def backward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l1 to depend on m
        """

        # dw = (w > 0).astype(np.int) * 2 - 1
        # dw[w == 0] = 0
        dJ_dw = self.l1 * np.sign(w)
        return dJ_dw

class L2(Regularizer):
    """
    L2 regularization, also known as Ridge regularization

    Computed as l2 * sum(square(w))
    """

    def __init__(self, l2=0.01):
        self.l2 = l2

    def forward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l2 to depend on m
        """

        J = self.l2 * np.sum(np.square(w))
        return J

    def backward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l2 to depend on m
        """

        dJ_dw = self.l2 * 2 * w
        return dJ_dw

class L1L2(Regularizer):
    """
    Applies both L1 and L2 regularization

    Computed as l1 * sum(abs(w)) + l2 * sum(square(w))
    """

    def __init__(self, l1=0.0, l2=0.0):
        self.l1 = l1
        self.l2 = l2

        self.l1_regularizer = L1(l1)
        self.l2_regularizer = L2(l2)

    def forward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l1 and l2 to depend on m
        """

        self.l1_regularizer.l1, self.l2_regularizer.l2 = self.l1, self.l2

        J = self.l1_regularizer(w) + self.l2_regularizer(w)
        return J

    def backward(self, w):
        """
        Note: you have to divide the result by m, the batch size, if you don't want l1 and l2 to depend on m
        """

        self.l1_regularizer.l1, self.l2_regularizer.l2 = self.l1, self.l2

        dJ_dw = self.l1_regularizer.backward(w) + self.l2_regularizer.backward(w)
        return dJ_dw


import cv2 as cv
import numpy as np
import os
import time

def gaussian(r2, std=1):
    """
    Sample one instance from gaussian distribution regarding
    given squared-distance:r2, standard-deviation:std and general-constant:k

    :param r: squared distance from center of gaussian distribution
    :param std: standard deviation

    :return: A sampled number obtained from gaussian
    """
    return np.exp(-r2/(2.*std**2)) / (2.*np.pi*std**2)

# implement a function that returns a gaussian filter
def make_gaussian(std=1, size=3):
	"""
    Creates a gaussian kernel regarding given size and std.
    Note that to define interval with respect to the size,
    I used linear space sampling which may has
    lower accuracy from renowned libraries.

    :param std: standard deviation value
    :param size: size of the output kernel
    :return: A gaussian kernel with size of (size*size)
    """

    size = int(size) // 2
    x, y = np.mgrid[-size:size+1, -size:size+1]
    distance = x**2+ y**2
    kernel = gaussian(r2=distance, std=std)
    return kernel

# implement a 2D convolution
def convolve2d(image, kernel):
    """
    Applies 2D convolution via provided kernel over given image

    :param image: input image in grayscale mode
    :param kernel: kernel of convolution
    :return: A convolved image with 'same' size using zero-padding
    """
    # you do not need to modify these, but feel free to implement however you know from scratch
    kernel       = np.flipud(np.fliplr(kernel))  # Flip the kernel, if it's symmetric it does not matter
    k = kernel.shape[0]
    padding      = (k - 1)
    offset       = padding // 2
    output       = np.zeros_like(image)
    # Add zero padding to the input image
    image_padded = np.zeros((image.shape[0] + padding, image.shape[1] + padding))
    image_padded[offset:-offset, offset:-offset] = image

    # implement the convolution inside the inner for loop
    for y in range(image.shape[0]):
        for x in range(image.shape[1]):
            output[y,x] = np.sum(kernel * image_padded[y:y+k, x:x+k])
    return output

# use opencv Gaussian blur to complete this function
def opencv_gaussian_blur(image, std, size):
    """
    Applies gaussian filter to smooth image

    :param image: Open cv or numpy ndarray image
    :param size: The size of gaussian filter
    :param std: if 0, it will be calculated automatically, otherwise for x and y sould be indicated.
    :return: An open cv image
    """
    return cv2.GaussianBlur(src=image, ksize=(size, size), sigmaX=std, sigmaY=std)

# use opnecv median blur to complete this function
def opencv_median_blur(image, size):
    """
    Applies median filter regarding given window `size`

    :param image: open cv or numpy ndarray image
    :param size: size of median kernel
    :return: An open cv image
    """
    median_blured = cv2.medianBlur(image, size)
    return median_blured

src_path = 'images_noisy/'
dst_path = 'images_filtered/'
names = os.listdir(src_path)

# Do not modify these for loops
for name in names:
    # load image
    src_name = src_path + name
    image = cv.imread(src_name, cv.IMREAD_GRAYSCALE)

    # gaussian blur using your implementation
    start = time.time()
    kernel1 = make_gaussian(std=1, size=11)
    blur1 = convolve2d(image, kernel1)
    dt1 = time.time() - start
    dst_name1 = dst_path + name + '.bmp'
    cv.imwrite(dst_name1, blur1)

    # gaussian blur using opencv implementation
    start = time.time()
    blur2 = opencv_gaussian_blur(image, std=1, size=11)
    dt2 = time.time() - start
    dst_name2 = dst_path + name + '_CV.bmp'
    cv.imwrite(dst_name2, blur2)

    # meidan blur
    start = time.time()
    blur3 = opencv_median_blur(image)
    dt3 = time.time() - start
    dst_name3 = dst_path + name + '_median.bmp'
    cv.imwrite(dst_name3, blur3)


import numpy as np
def gini_index(array, alpha = 0.0000001 ):
    """
    Gini Index

    0 : maximal diversity
    1 : extreme inequality in clonal frequency of a single sequence

    Fast numpy implementation of Gini Index, modified from solution proposed by Olivia Guest,
    with efficiency gain by placing values in ascending order.

    Parameters
    ----------

    array: np.array
        array of values,
        negative values, cause a shift of values, such that the most negative value is zero
        zero values are smoothed by addition of alpha to all values

    alpha : float
        represents the smoothing parameter added to all values to avoid zero

    Calculate the Gini coefficient of a numpy array, adapted directed
    by efficient solution by oliviaguest (https://github.com/oliviaguest/gini).

    See More at:
    @misc{guest2017gini,
      author = "Olivia Guest",
      title = "Using the Gini Coefficient to Evaluate Deep Neural Network Layer Representations",
      year = "2017",
      howpublished = "Blog post",
      url = "http://neuroplausible.com/gini"
    }

    """
    # based on bottom eq: http://www.statsdirect.com/help/content/image/stat0206_wmf.gif
    # from: http://www.statsdirect.com/help/default.htm#nonparametric_methods/gini.htm
    array = array.flatten() #all values are treated equally, arrays must be 1d
    if np.amin(array) < 0:
        array -= np.amin(array) #values cannot be negative
    if np.any(array == 0):
        array += alpha  #values cannot be 0
    array = np.sort(array) #values must be sorted
    index = np.arange(1,array.shape[0]+1) #index per array element
    n = array.shape[0]#number of array elements
    return ((np.sum((2 * index - n  - 1) * array)) / (n * np.sum(array))) #Gini coefficient


'''
This module contains different methods to calculate the optimal weights of combination of assets
'''
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from typing import Union
from .porfolio import portfolio_vol, portfolio_return
from.decorators import accepts

@accepts((pd.DataFrame, np.array), (pd.DataFrame, np.array), float)
def minimize_vol(covariance_matrix: Union[pd.DataFrame, np.array],
                 expected_returns: Union[pd.DataFrame, np.array] = None,
                 target_return: float = None) -> np.array:
    """
    Returns the optimal weights that achieve the target return
    given a set of expected returns and a covariance matrix. If a target is specified,
    user need to include the expected_returns. Else if no target_return is specified,
    the function finds the global minimum volatility

    :param covariance_matrix: covariance matrix for the assets
    :type covariance_matrix: pd.DataFrame or np.array
    :param expected_returns: array of expected_returns in decimal form (e.g. 40% is 0.4)
    :type expected_returns: pd.DataFrame , np.array or None, defaults to None
    :return: An array of weights specifying the fractional weight of each asset (same order as covariance matrix column)
    :rtype: np.array
    """
    if target_return and expected_returns is None:
        raise ValueError(
            "In order to calculate the minimum volatility at a specified return, expected returns cannot be None")

    n = covariance_matrix.shape[0]  # number of assets
    init_guess = equal_weight(n)  # initial guess
    bounds = ((0.0, 1.0),) * n  # an N-tuple of 2-tuples!
    weights_sum_to_1 = {'type': 'eq',
                        'fun': lambda weights: np.sum(weights) - 1
                        }

    # find minimum volatlity at a specified target return level
    if target_return:
        return_is_target = {'type': 'eq',
                            'args': (expected_returns,),
                            'fun': lambda weights, expected_returns: target_return - portfolio_return(weights, expected_returns)
                            }
        weights = minimize(portfolio_vol, init_guess,
                           args=(covariance_matrix,), method='SLSQP',
                           options={'disp': False},
                           constraints=(weights_sum_to_1, return_is_target),
                           bounds=bounds)

    # return the minimum volatility
    else:
        weights = minimize(portfolio_vol, init_guess,
                           args=(covariance_matrix,), method='SLSQP',
                           options={'disp': False},
                           constraints=(weights_sum_to_1),
                           bounds=bounds)

    return weights.x

@accepts((int, float))
def equal_weight(n_assets: Union[int, float]) -> np.array:
    '''
    Generate an array of weights for a portfolio split between n-assets equally

    :param n_assets: Number of assets in portfolio
    :type n_assets: Either int or float
    :return: An numpy array of weights [1/n,1/n, ... , 1/n]
    :rtype: np.array
    '''
    return np.repeat(1 / n_assets, n_assets)

@accepts(float, (pd.DataFrame, np.array), (pd.DataFrame, np.array))
def max_sharpe(riskfree_rate: float,
               expected_returns: Union[pd.DataFrame, np.array],
               covariance_matrix: Union[pd.DataFrame, np.array]) -> np.array:
    """
    Returns the weights of the portfolio that gives you the maximum sharpe ratio
    given the riskfree rate and expected returns and a covariance matrix
    """
    n = expected_returns.shape[0]
    init_guess = equal_weight(n)  # split inital allocation equally
    bounds = ((0.0, 1.0),) * n  # an N-tuple of 2-tuples!
    # construct the constraints
    weights_sum_to_1 = {'type': 'eq',
                        'fun': lambda weights: np.sum(weights) - 1
                        }

    def neg_sharpe(weights, riskfree_rate, expected_returns,
                   covariance_matrix):
        """
        Returns the negative of the sharpe ratio
        of the given portfolio
        """
        r = portfolio_return(weights, expected_returns)
        vol = portfolio_vol(weights, covariance_matrix)
        return -(r - riskfree_rate) / vol

    weights = minimize(neg_sharpe, init_guess,
                       args=(
                           riskfree_rate, expected_returns, covariance_matrix), method='SLSQP',
                       options={'disp': False},
                       constraints=(weights_sum_to_1,),
                       bounds=bounds)
    return weights.x

@accepts(float, float, float)
def cppi_single_step(account_value: Union[float, int],
                     floor_value: Union[float, int],
                     m: Union[float, int] = 3) -> np.array:
    '''
    Run CPPI (Constant Proportional Portfolio Insurance) on a single time step,
    returning the weights allocation of risky and riskless asset as a np.array, and the cushion pct.

    e.g. return ([0.6,0.4] , 0.2)
    where 0.6 is the risky weight value and 0.2 is the cushion

    :param account_value: Current account value of portfolio at this time step
    :type account_value: float, int
    :param floor_value: Floor value of portfolio that you want to insure
    :param floor_value: float,int
    :param m: Multipler of risky asset to allocate. If cushion percentage is 20% and m =3,
    will allocatte 60% (20%*3) in risky asset.Typical m ranges from 3 to 5, default to 3.
    :type m: float, int
    '''
    cushion_pct = (account_value - floor_value) / account_value
    risky_w = m * cushion_pct
    risky_w = np.clip(risky_w, 0, 1)
    safe_w = 1 - risky_w
    return np.array([risky_w, safe_w]), cushion_pct


import numpy as np
import sympy as smp
from tqdm import tqdm
from scipy.integrate import odeint

def _iter(the_i1, the_i2, tv, gv, m1v, m2v, l1v, l2v):
    # Define Constants
    t, g = smp.symbols('t g')
    m1, m2 = smp.symbols('m1 m2')
    L1, L2 = smp.symbols('L1, L2')

    # Define variables w.r.t. time
    the1, the2 = smp.symbols(r'\theta_1, \theta_2', cls=smp.Function)
    the1 = the1(t)
    the2 = the2(t)

    # Define differentials for the1(t) & the2(t)
    the1_d = smp.diff(the1, t)
    the2_d = smp.diff(the2, t)
    the1_dd = smp.diff(the1_d, t)
    the2_dd = smp.diff(the2_d, t)

    # Calculations for x&y cordinates of m1 and m2
    x1 = L1*smp.sin(the1)
    y1 = -L1*smp.cos(the1)
    x2 = L1*smp.sin(the1)+L2*smp.sin(the2)
    y2 = -L1*smp.cos(the1)-L2*smp.cos(the2)

    # Calculations for kinetic and potential energy
    # Kinetic
    T1 = 1/2 * m1 * (smp.diff(x1, t)**2 + smp.diff(y1, t)**2)
    T2 = 1/2 * m2 * (smp.diff(x2, t)**2 + smp.diff(y2, t)**2)
    T = T1+T2
    # Potential
    V1 = m1*g*y1
    V2 = m2*g*y2
    V = V1 + V2
    # Lagrangian
    L = T-V

    # Generation of Lagrangian differential equations
    LE1 = (smp.diff(L, the1) - smp.diff(smp.diff(L, the1_d), t)).simplify()
    LE2 = (smp.diff(L, the2) - smp.diff(smp.diff(L, the2_d), t)).simplify()

    # Problem Definition
    sols = smp.solve([LE1, LE2], (the1_dd, the2_dd),
                     simplify=False, rational=False)

    # Linearization of second order differential equations
    dz1dt_f = smp.lambdify(
        (t, g, m1, m2, L1, L2, the1, the2, the1_d, the2_d), sols[the1_dd])
    dz2dt_f = smp.lambdify(
        (t, g, m1, m2, L1, L2, the1, the2, the1_d, the2_d), sols[the2_dd])
    dthe1dt_f = smp.lambdify(the1_d, the1_d)
    dthe2dt_f = smp.lambdify(the2_d, the2_d)

    # Matrix function representation of system of linear equations obtained
    def dSdt(S, t, g, m1, m2, L1, L2):
        the1, z1, the2, z2 = S
        return [
            dthe1dt_f(z1),
            dz1dt_f(t, g, m1, m2, L1, L2, the1, the2, z1, z2),
            dthe2dt_f(z2),
            dz2dt_f(t, g, m1, m2, L1, L2, the1, the2, z1, z2),
        ]
    return odeint(dSdt, y0=[the_i1, 0, the_i2, 0], t=tv, args=(gv, m1v, m2v, l1v, l2v))

def datagen(config):
    """Genrates data for double pendulum domain.

    Uses odeint in a loop to generate data for various values of initial theta_1 and theta_2.

    Args:
        config: Python dictionary representation of the config yaml file.

    Returns:
        Saves a file containing the data generated in a csv format.
        Column headers - [time, initial_theta1, initial_theta2, theta1_at_t, theta2_at_t]
    """
    ts = np.arange(0, config['TIME_STEP'] *
                   config['NUM_SNAPSHOTS'], config['TIME_STEP'])
    thetas = np.linspace(config['THETA_START'],
                         config['THETA_END'], config['THETA_NUM'])

    num_cols = config['NUM_INPUTS'] + config['NUM_OUTPUTS']
    data = np.array([]).reshape(0, num_cols)
    for theta1 in tqdm(thetas, desc='Double pendulum data generation progress'):
        for theta2 in tqdm(thetas, desc=f'Simulations for theta_1 = {theta1}', leave=False):
            # solved_data column headers - [theta1, omega1, theta2, omega2]
            solved_data = _iter(
                theta1, theta2, ts, config['g'], config['m1'], config['m2'], config['l1'], config['l2'])
            iteration_data = np.hstack([
                np.reshape(ts, (-1, 1)),
                theta1*np.ones(shape=(config['NUM_SNAPSHOTS'], 1)),
                theta2*np.ones(shape=(config['NUM_SNAPSHOTS'], 1)),
                solved_data[:, [0]],
                solved_data[:, [2]],
            ])
            data = np.vstack([data, iteration_data])

    if config['save_collected']:
        np.savetxt(config['datadir'] + config['datafile'], data, delimiter=",")

if __name__ == "__main__":
    print("Not meant to be executed as main!")
    from sys import exit
    exit(1)


import numpy as np
import pandas as pd
import plotly.express as px

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

# Arithmetic Brownian Motion

class Arithmetic_Brownian_Motion:
    """dS = mu*dt + sigma*dW
    Requires numpy, pandas and plotly.express to run"""
    def __init__(self, mu, sigma, n_paths, n_steps, t, T, S_0):
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T = T
        self.S_0 = S_0

    def get_paths(self):
        """Returns the paths, S, for the Arithmetic Brownian Motion using the Euler-Maruyama method"""
        dt = self.T/self.n_steps
        dW = np.sqrt(dt)*np.random.randn(self.n_paths, self.n_steps)
        dS = self.mu*dt + self.sigma*dW

        dS = np.insert(dS, 0, self.S_0, axis=1)
        S = np.cumsum(dS, axis=1)

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Arithmetci Brownian Motion"""
        ES = self.mu*self.t+self.S_0
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Arithmetic Brownian Motion"""
        VarS = self.t*self.sigma**2
        return VarS

    def get_auto_cov(self, t1, t2):
        """Returns the auto-covariance for the Arithmetic Brownian Motion"""
        AC = (self.sigma**2)*min(self.t[t1], self.t[t2])
        return AC

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Arithmatic Brownian Motion"""
        plotting_df = pd.DataFrame(self.get_paths().transpose())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Geometric Brownian Motion

class Geometric_Brownian_Motion:
    """dS = mu*S*dt + sigma*S*dW
    Model describing the evolution of stock prices
    Requires numpy, pandas and plotly.express to run"""
    def __init__(self, mu, sigma, n_paths, n_steps, t, T, S_0):
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0

    def get_paths(self):
        """Returns the paths, S, for the Geometric Brownian Motion using Euler-Maruyama method"""
        dt = self.T/self.n_steps
        dW = np.sqrt(dt)*np.random.randn(self.n_paths, self.n_steps)
        dS = self.mu*dt + self.sigma*dW

        dS = np.insert(dS, 0, 0, axis=1)
        S = np.cumsum(dS, axis=1)

        S = self.S_0*np.exp(S)
        return S

    def get_expection(self):
        """Returns the expectation, E[S], of the Geometric Brownian Motion"""
        ES = self.S_0*np.exp(self.mu*self.t)
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], of the Geometric Brownian Motion"""
        VarS = (self.S_0**2)*np.exp(2*self.mu*self.t)*(np.exp(self.t*self.sigma**2)-1)
        return VarS

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Geometric Brownian Motion"""
        plotting_df = pd.DataFrame(self.get_paths().transpose())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expection()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Ornstein-Uhlenbeck Process

class Ornstein_Uhlenbeck_Process:
    """dS = alpha*(mu-S)*dt + sigma*dW
    Model describes the evolution of interest rates
    Requires numpy, pandas and plotly.express"""
    def __init__(self, alpha, mu, sigma, n_paths, n_steps, t, T, S_0):
        self.alpha =alpha
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0

    def get_paths(self, analytic_EM=False):
        """Returns the paths, S, for the Ornsteain_uhlenbeck Process using Euler-Maruyama method.
        Intakes an argument analytic_EM with bool values. If True, then returns the simulation with the analytic
        moments for Euler-Maruyama; if False, then returns plain Euler-Maruyama simulation"""
        dt = self.T/self.n_steps
        N = np.random.randn(self.n_steps, self.n_paths)
        S = np.concatenate((self.S_0*np.ones((1, self.n_paths)), np.zeros((self.n_steps, self.n_paths))), axis=0)

        if analytic_EM==True:
            sdev = self.sigma*np.sqrt((1-np.exp(-2*self.alpha*dt))/(2*self.alpha))
            for i in range(0, self.n_steps):
                S[i+1,:] = self.mu + (S[i,:]-self.mu)*np.exp(-self.alpha*dt) + sdev*N[i,:]
        else:
            sdev = self.sigma*np.sqrt(dt)
            for i in range(0, self.n_steps):
                S[i+1,:] = S[i,:] + self.alpha*(self.mu-S[i,:])*dt + sdev*N[i,:]

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Ornstein-Uhlenbeck Process"""
        ES = self.mu + (self.S_0-self.mu)*np.exp(-self.alpha*t)
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Ornstein-Uhlenbeck Process"""
        VarS = (1-np.exp(-2*self.alpha*t))*(self.sigma**2)/(2*self.alpha)
        return VarS

    def simulate(self, analytic_EM=False, plot_expected=False):
        """Returns the plot of the random paths taken by the Ornstain_uhlenbeck Process"""
        plotting_df = pd.DataFrame(self.get_paths(analytic_EM))
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Brownian Bridge

class Brownian_Bridge:
    """dS = ((b-X)/(T-t))*dt + sigma*dW
    Model can support useful variance reduction techniques for pricing derivative contracts using Monte-Carlo simulation,
    such as sampling. Also used in scenario generation.
    Requires numpy, pandas and plotly.express"""
    def __init__(self, alpha, beta, sigma, n_paths, n_steps, t, T):
        self.alpha =alpha
        self.beta = beta
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T

    def get_paths(self):
        """Returns the paths, S, for the Brownian Bridge using the Euler-Maruyama method"""
        dt = self.T/self.n_steps
        dW = np.sqrt(dt)*np.random.randn(self.n_steps, self.n_paths)
        S = np.concatenate((self.alpha*np.ones((1, self.n_paths)),
                             np.zeros((self.n_steps-1, self.n_paths)), self.beta*np.ones((1, self.n_paths))), axis=0)

        for i in range(0, self.n_steps-1):
            S[i+1,:] = S[i,:] + (self.beta-S[i,:])/(self.n_steps-i+1) +self.sigma*dW[i,:]

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Brownian Bridge"""
        ES = self.alpha + (self.beta-self.alpha)/T*t
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Brownian Bridge"""
        VarS = self.t*(self.T-self.t)/self.T
        return VarS

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Brownian Bridge"""
        plotting_df = pd.DataFrame(self.get_paths())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Feller Square-Root Process

class Feller_Square_Root_Process:
    """dS = alpha*(mu-S)*dt + sigma*sqrt(S)*dW
    Model describes the evolution of interest rates
    Requires numpy, pandas and plotly.express"""
    def __init__(self, alpha, mu, sigma, n_paths, n_steps, t, T, S_0):
        self.alpha =alpha
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0

    def get_paths(self, sim_type="EM", analytic_EM=False):
        """Returns the paths, S, for the Feller Square-Root Process using either Euler-Maruyama method or the exact method.
        For Euler-Maruyama, set sim_type to "EM"; for exact, set it to "E". Intakes an argument analytic_EM with bool values.
        If True, then returns the simulation with the analytic moments for Euler-Maruyama; if False, then returns plain
        Euler-Maruyama simulation"""
        dt = self.T/self.n_steps
        N = np.random.randn(self.n_steps, self.n_paths)
        S = np.concatenate((self.S_0*np.ones((1, self.n_paths)), np.zeros((self.n_steps, self.n_paths))), axis=0)

        if sim_type=="EM":
            if analytic_EM==True:
                a = (self.sigma**2)/self.alpha*(np.exp(-self.alpha*dt)-np.exp(-2*self.alpha*dt))
                b = self.mu*(self.sigma**2)/(2*self.alpha)*(1-np.exp(-self.alpha*dt))**2
                for i in range(0, self.n_steps):
                    S[i+1,:] = self.mu + (S[i,:]-self.mu)*np.exp(-self.alpha*dt) + np.sqrt(a*S[i,:]+b)*N[i,:]
                    S[i+1,:] = np.maximum(S[i+1,:], np.zeros((1, self.n_paths)))
            else:
                for i in range(0, self.n_steps):
                    S[i+1,:] = S[i,:] + self.alpha*(self.mu-S[i,:])*dt + self.sigma*np.sqrt(S[i,:]*dt)*N[i,:]
                    S[i+1,:] = np.maximum(S[i+1,:], np.zeros((1, self.n_paths)))
        elif sim_type=="E":
            d = 4*self.alpha*self.mu/(self.sigma**2)
            k = (self.sigma**2)*(1-np.exp(-self.alpha*dt))/(4*self.alpha)
            for i in range(0, self.n_steps):
                delta = 4*self.alpha*S[i,:]/((self.sigma**2)*(np.exp(self.alpha*dt)-1))
                S[i+1,:] = np.random.noncentral_chisquare(d, delta, (1, self.n_paths))*k
        else:
            raise TypeError("sim_type can only take values in [EM, E]")

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Feller Square-Root Process"""
        ES = self.mu + (self.S_0-self.mu)*np.exp(-self.alpha*t)
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Feller Square-Root Process"""
        VarS = ((self.sigma**2)*(np.exp(-self.alpha*self.t)-np.exp(-self.alpha*2*self.t))*self.S_0/self.alpha +
               (self.sigma**2)*np.exp(-self.alpha*2*self.t)*(np.exp(self.alpha*self.t)-1)**2*self.mu/(2*self.alpha))
        return VarS

    def simulate(self, sim_type="EM", analytic_EM=False, plot_expected=False):
        """Returns the plot of the random paths taken by the Feller Square-Root Process"""
        plotting_df = pd.DataFrame(self.get_paths(sim_type, analytic_EM))
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Constant Elasticity of Variance

class Constant_Elasticity_Of_Variance:
    """dS = mu*S*dt + sigma*S^(beta+1)*dW
    Model used to reproduce the volatility smile effect
    Requires numpy, pandas and plotly.express"""
    def __init__(self, mu, sigma, n_paths, n_steps, t, T, S_0, beta):
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0
        self.beta = beta

    def get_paths(self):
        """Returns the paths, S, for the Constant Elasticity of Variance Process"""
        dt = self.T/self.n_steps
        dW = np.sqrt(dt)*np.random.randn(self.n_steps, self.n_paths)
        S = np.concatenate((self.S_0*np.ones((1, self.n_paths)), np.zeros((self.n_steps, self.n_paths))), axis=0)

        for i in range(0, self.n_steps):
            S[i+1,:] = S[i,:] + self.mu*S[i,:]*dt + self.sigma*(S[i,:]**(self.beta+1))*dW[i,:]
            S[i+1,:] = np.maximum(S[i+1,:], np.zeros((1, self.n_paths)))

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Constant Elasticity of Variance Process"""
        ES = self.S_0*np.exp(self.mu*self.t)
        return ES

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Constant Elasticity of Variance Process"""
        plotting_df = pd.DataFrame(self.get_paths())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Heston Stochastic Volatility Process

class Heston_Stochastic_Volatility:
    """"dS = mu*S*dt + sqrt(v)*S*dW^{S}
    dv = k*(theta-v)*dt + epsilon*sqrt(v)dW^{v}
    Model describes the evolution of stock price and its volatility
    Requires numpy, pandas and plotly.express"""
    def __init__(self, mu, k, theta, epsilon, n_paths, n_steps, t, T, S_0, v_0, rho):
        self.mu = mu
        self.k = k
        self.theta = theta
        self.epsilon = epsilon
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0
        self.v_0 = v_0
        self.rho = rho

    def get_paths(self):
        """Returns the paths, S, for the Heston Stochastic Volatility Process"""
        dt = self.T/self.n_steps
        Nv = np.random.randn(self.n_steps, self.n_paths)
        N = np.random.randn(self.n_steps, self.n_paths)
        NS = self.rho*Nv + np.sqrt(1-self.rho**2)*N

        v = np.concatenate((self.v_0*np.ones((1, self.n_paths)), np.zeros((self.n_steps, self.n_paths))), axis=0)
        S = np.concatenate((self.S_0*np.ones((1, self.n_paths)), np.zeros((self.n_steps, self.n_paths))), axis=0)

        a = (self.epsilon**2)/self.k*(np.exp(-self.k*dt)-np.exp(-2*self.k*dt))
        b = self.theta*(self.epsilon**2)/(2*self.k)*(1-np.exp(-self.k*dt))**2
        for i in range(0, self.n_steps):
            v[i+1,:] = self.theta + (v[i,:]-self.theta)*np.exp(-self.k*dt) + np.sqrt(a*v[i,:]+b)*Nv[i,:]
            v[i+1,:] = np.maximum(v[i+1,:], np.zeros((1, self.n_paths)))

        for j in range(0, self.n_steps):
            S[j+1,:] = S[j,:] + (self.mu-0.5*v[j,:])*dt + self.epsilon*np.sqrt(v[j,:]*dt)*NS[j,:]
            S[j+1,:] = np.maximum(S[j+1,:], np.zeros((1, self.n_paths)))

        return S, v

    def get_expectation(self):
        """Returns the expectation, E[S], for the Heston Stochastic Volatility Process"""
        ES = (self.S_0 + (self.mu-0.5*self.theta)*self.t
                         + (self.theta-self.v_0)*(1-np.exp(-self.k*self.t))/(2*self.k))
        return ES

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Heston Stochastic Volatility Process"""
        plotting_df = pd.DataFrame(self.get_paths()[0])
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Variance Gamma Process

class Variance_Gamma_Process:
    """dS = mu*dG(t) + sigma*dW(dG(t))
    Model used in option pricing
    Requires numpy, pandas and plotly.express"""
    def __init__(self, mu, sigma, n_paths, n_steps, t, T, S_0, rate):
        self.mu = mu
        self.sigma = sigma
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0
        self.rate = rate

    def get_paths(self):
        """Returns the paths, S, for the Variance Gamma Process"""
        dt = self.T/self.n_steps
        kappa = 1/self.rate
        dG = np.random.gamma(dt/kappa, kappa, (self.n_steps, self.n_paths))

        dS = self.mu*dG+self.sigma*np.random.randn(self.n_steps, self.n_paths)*np.sqrt(dG)

        dS = np.insert(dS, 0, self.S_0, axis=0)
        S = np.cumsum(dS, axis=0)

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Variance Gamma Process"""
        ES = self.mu*self.t+self.S_0
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Variance Gamma Process"""
        VarS = (self.sigma**2 + (self.mu**2)/self.rate)*t
        return VarS

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Variance Gamma Process"""
        plotting_df = pd.DataFrame(self.get_paths())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Merton Jump-Diffusion Process

class Merton_Jump_Diffusion_Process:
    """S = (mu-0.5*sigma^2)*t + sigma*W(t) + sum_{i=1}^{N(t)} Z_i
    Model describes stock price with continuous movement that have rare large jumps
    Requires numpy, pandas and plotly.express"""
    def __init__(self, muS, sigmaS, muJ, sigmaJ, lambdaJ, n_paths, n_steps, t, T, S_0):
        self.muS = muS
        self.sigmaS = sigmaS
        self.muJ = muJ
        self.sigmaJ = sigmaJ
        self.lambdaJ = lambdaJ
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0

    def get_paths(self):
        """Returns the paths, S, for the Merton Jump-Diffusion Process"""
        dt = self.T/self.n_steps
        dX = (self.muS-0.5*self.sigmaS**2)*dt + self.sigmaS*np.sqrt(dt)*np.random.randn(self.n_steps, self.n_paths)
        dP = np.random.poisson(self.lambdaJ*dt, (self.n_steps, self.n_paths))
        dJ = self.muJ*dP + self.sigmaJ*np.sqrt(dP)*np.random.randn(self.n_steps, self.n_paths)

        dS = dX + dJ

        dS = np.insert(dS, 0, self.S_0, axis=0)
        S = np.cumsum(dS, axis=0)

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Merton Jump-Diffusion Process"""
        ES = (self.muS+self.lambdaJ*self.muJ)*t+self.S_0
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Merton Jump-Diffusion Process"""
        VarS = (self.muS**2+self.lambdaJ*(self.muJ**2+self.sigmaJ**2))*t
        return VarS

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Merton Jump-Diffusion Process"""
        plotting_df = pd.DataFrame(self.get_paths())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

#Kou Jump-Diffusion Model

class Kou_Jump_Diffusion_Process:
    """S = mu*t +sigma*W(t) + sum_{i=1}^{N(t)} Z_i
    Model describes stock price with continuous movement that have rare large jumps, with the jump sizes following a double
    exponential distribution
    Requires numpy, pandas and plotly.express"""
    def __init__(self, mu, sigma, lambdaN, eta1, eta2, p, n_paths, n_steps, t, T, S_0):
        self.mu = mu
        self.sigma = sigma
        self.lambdaN = lambdaN
        self.eta1 = eta1
        self.eta2 = eta2
        self.p = p
        self.n_paths = n_paths
        self.n_steps = n_steps
        self.t = t
        self.T =T
        self.S_0 = S_0

    def get_paths(self):
        """Returns the paths, S, for the Kou Jump-Diffusion Process"""
        dt = self.T/self.n_steps
        dX = (self.mu-0.5*self.sigma**2)*dt + self.sigma*np.sqrt(dt)*np.random.randn(self.n_steps, self.n_paths)
        dP = np.random.poisson(self.lambdaN*dt, (self.n_steps, self.n_paths))

        #Bilateral Exponential R.V.
        U = np.random.uniform(0,1, (self.n_steps, self.n_paths))
        Z = np.zeros((self.n_steps, self.n_paths))
        for i in range(0, len(U[0])):
            for j in range(0, len(U)):
                if U[j,i]>=self.p:
                    Z[j,i]=(-1/self.eta1)*np.log((1-U[j,i])/self.p)
                elif U[j,i]<self.p:
                    Z[j,i]=(1/self.eta2)*np.log(U[j,i]/(1-self.p))

        dJ = (np.exp(Z)-1)*dP
        dS = dX + dJ

        dS = np.insert(dS, 0, self.S_0, axis=0)
        S = np.cumsum(dS, axis=0)

        return S

    def get_expectation(self):
        """Returns the expectation, E[S], for the Kou Jump-Diffusion Process"""
        ES = (self.mu+self.lambdaN*(self.p/self.eta1-(1-self.p)/self.eta2))*t+self.S_0
        return ES

    def get_variance(self):
        """Returns the variance, Var[S], for the Kou Jump-Diffusion Process"""
        VarS = (self.sigma**2+2*self.lambdaN*(self.p/(self.eta1**2)+(1-self.p)/(self.eta2**2)))*t
        return VarS

    def simulate(self, plot_expected=False):
        """Returns the plot of the random paths taken by the Kou Jump-Diffusion Process"""
        plotting_df = pd.DataFrame(self.get_paths())
        if plot_expected==True:
            plotting_df["Expected Path"]=self.get_expectation()
        fig = px.line(plotting_df, labels={"value":"Value of S", "variable":"Paths"})
        return fig.show()

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

if __name__ == '__main__':

    #Useful Variables for Tests
    npaths = 50
    nsteps = 200
    T = 1
    dt = T/nsteps
    t = np.arange(0, T+dt, dt)

    Arithmetic_Brownian_Motion(0.05, 0.4, npaths, nsteps, t, T, 200).simulate(plot_expected=True)
    #Geometric_Brownian_Motion(0.2, 0.4, npaths, nsteps, t, T, 500)
    #Ornstein_Uhlenbeck_Process(10, 0.07, 0.1, npaths, nsteps, t, T, 0.05)
    #Brownian_Bridge(1, 2, 0.5, npaths, nsteps, t, T)
    #Feller_Square_Root_Process(5, 0.07, 0.265, npaths, nsteps, t, T, 0.03)
    #Constant_Elasticity_Of_Variance(0.2, 0.4, npaths, nsteps, t, T, 500, 0)
    #Heston_Stochastic_Volatility(0.1, 5, 0.07, 0.2, npaths, nsteps, t, T, 100, 0.03, 0)
    #Variance_Gamma_Process(0.2, 0.3, npaths, nsteps, t, T, 0, 1/0.05)
    #Merton_Jump_Diffusion_Process(0.2, 0.3, -0.1, 0.15, 0.5, npaths, nsteps, t, T, 0)
    #Kou_Jump_Diffusion_Process(0.2, 0.3, 0.5, 9, 5, 0.5, npaths, nsteps, t, T, 0)


import math
import numpy as np
# log = ln
from sympy import Symbol, Abs, exp, sqrt, log, cos, sin, nsolve
# sys.tracebacklimit = 0

"""
INSTRUÇÕES

1) Ir para https://live.sympy.org/
2) Substituir a função `f` apropriada
3) Determinar o número de casas decimais de precisão `N_REQ` desejado
4) Copiar o código abaixo, entre os sinais de # %%%%...
5) Clicar em "Evaluate"
6) O resultado irá aparecer como o exemplo:
    "
    Solução: o zero (raiz) da função está em:
    0.289
    "
"""

# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
x = Symbol('x', real=True)

# N_REQ = número de casas decimais de precisão
N_REQ = int(3)

f = x**3 - 7*x + 2

print("Solução: o zero (raiz) da função está em:")
print(nsolve(f, 0, prec=N_REQ))
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


#!/usr/bin/env python3
import numpy as np

# define x limimts, y limits and step size
xl,xh,yh,dx,dy=0,4,6,0.002,0.002

# define lower y limit as function of x
def yl(x):
    return (x-2)**2

# integrand
def func(x,y):
    return (542*y**2-12*x)

#double nested loop to go over each x,y pair and add f(x,y) to sum
sum = 0
for x in np.arange(xl,xh,dx):
    for y in np.arange(yl(x),yh,dy):
        sum = sum + func(x,y)

#multiple sum by dx*dy to get value of integration
sum = sum*dx*dy

print("value of double integral is ", "%.3f" % sum)


import numpy as np
import sympy as smp
from scipy.integrate import odeint
from tqdm import tqdm

def iter(the_i1, the_i2, tv, gv, m1v, m2v, l1v, l2v):
    #Define Constants
    t, g = smp.symbols('t g')
    m1, m2 = smp.symbols('m1 m2')
    L1, L2 = smp.symbols('L1, L2')

    #Define variables w.r.t. time
    the1, the2 = smp.symbols(r'\theta_1, \theta_2', cls=smp.Function)
    the1 = the1(t)
    the2 = the2(t)

    #Define differentials for the1(t) & the2(t)
    the1_d = smp.diff(the1, t)
    the2_d = smp.diff(the2, t)
    the1_dd = smp.diff(the1_d, t)
    the2_dd = smp.diff(the2_d, t)

    #Calculations for x&y cordinates of m1 and m2
    x1 = L1*smp.sin(the1)
    y1 = -L1*smp.cos(the1)
    x2 = L1*smp.sin(the1)+L2*smp.sin(the2)
    y2 = -L1*smp.cos(the1)-L2*smp.cos(the2)

    #Calculations for kinetic and potential energy
    # Kinetic
    T1 = 1/2 * m1 * (smp.diff(x1, t)**2 + smp.diff(y1, t)**2)
    T2 = 1/2 * m2 * (smp.diff(x2, t)**2 + smp.diff(y2, t)**2)
    T = T1+T2
    # Potential
    V1 = m1*g*y1
    V2 = m2*g*y2
    V = V1 + V2
    # Lagrangian
    L = T-V

    #Generation of Lagrangian differential equations
    LE1 = (smp.diff(L, the1) - smp.diff(smp.diff(L, the1_d), t)).simplify()
    LE2 = (smp.diff(L, the2) - smp.diff(smp.diff(L, the2_d), t)).simplify()

    #Problem Definition
    sols = smp.solve([LE1, LE2], (the1_dd, the2_dd),
                simplify=False, rational=False)

    #Linearization of second order differential equations
    dz1dt_f = smp.lambdify((t,g,m1,m2,L1,L2,the1,the2,the1_d,the2_d), sols[the1_dd])
    dz2dt_f = smp.lambdify((t,g,m1,m2,L1,L2,the1,the2,the1_d,the2_d), sols[the2_dd])
    dthe1dt_f = smp.lambdify(the1_d, the1_d)
    dthe2dt_f = smp.lambdify(the2_d, the2_d)

    #Matrix function representation of system of linear equations obtained
    def dSdt(S, t, g, m1, m2, L1, L2):
        the1, z1, the2, z2 = S
        return [
            dthe1dt_f(z1),
            dz1dt_f(t, g, m1, m2, L1, L2, the1, the2, z1, z2),
            dthe2dt_f(z2),
            dz2dt_f(t, g, m1, m2, L1, L2, the1, the2, z1, z2),
        ]
    return odeint(dSdt, y0=[the_i1, 0, the_i2, 0], t=tv, args=(gv,m1v,m2v,l1v,l2v))

def double_pendulum_data(config):
    t = config['t_range']
    data = np.array([]).reshape(0,5)
    for theta1 in tqdm(config['theta_range'], desc = 'Dynamic pendulum data generation progress'):
        for theta2 in config['theta_range']:
            # Format - each row [theta1, omega1, theta2, omega2]
            solved_data = iter(theta1, theta2, t, config['g'], config['m1'], config['m2'], config['l1'], config['l2'])
            iteration_data = np.hstack([
                np.reshape(t,(-1,1)),
                theta1*np.ones(shape=(len(t),1)),
                theta2*np.ones(shape=(len(t),1)),
                solved_data[:,[0]],
                solved_data[:,[2]]
            ])
            data = np.vstack((data, iteration_data))

    if config['save_collected']:
        np.savetxt(config['datadir'] + config['datafile'], data, delimiter=",")


import time
import numpy as np

def rng1(c, lower_limit, upper_limit, iteration):     # first Random number Generator model
    #a = 7**5; m = (2**31)-1;       #parameters based on the theory of the minimal standard
    a = 871212; m = (2**31)-1;
    seed = (time.time() * 1000) - 1490342000000  #seed based on time/clock
    x = ( (a * seed) + c) % m    #creates the very first random number
    numbr_list = []    #this stores a list of different random numbers
    while len(numbr_list) < iteration:   #loop generator
        x = ( (a * x) + c) % m
        numbr_list.append(x)
    for i in range(len(numbr_list)): #transform random numbers into a range of values
        numbr_list[i] /= m
        numbr_list[i] = ((upper_limit-lower_limit)*numbr_list[i]) + lower_limit
    return numbr_list

def rng2(lower_limit, upper_limit, iteration):     # second Random number Generator model
    m = (2**31)-1;
    seed = (time.time() * 1000) - 1490342000000
    #a1 = 107374182; a2 = 0; a3 = 0; a4 = 0; a5 = 104480;           #parameters based on the theory of the minimal standard
    a1 = 261924; a2 = 444265; a3 = 441900; a4 = 948839; a5 = 363843;#alternative model with arbitrary parameters
    seed1 = ((a1*seed)+(a5*seed)) % m
    seed2 = ((a1*seed1)+(a5*seed1)) % m
    seed3 = ((a1*seed2)+(a5*seed2)) % m
    seed4 = ((a1*seed3)+(a5*seed3)) % m
    seed5 = ((a1*seed4)+(a5*seed4)) % m
    numbr_list = [seed1, seed2, seed3, seed4, seed5]    #this stores a list of different random numbers
    while len(numbr_list) < iteration:
        x = ( (a1*numbr_list[-1])+(a2*numbr_list[-2])+(a3*numbr_list[-3])+(a4*numbr_list[-4])+(a5*numbr_list[-5])) % m
        numbr_list.append(x)
    for i in range(len(numbr_list)):
        numbr_list[i] /= (m+1)
        numbr_list[i] = ((upper_limit-lower_limit)*numbr_list[i]) + lower_limit
    return numbr_list

print("Hello there, you are welcome to try my random number generator")
print("You need to know that the range between 0 and 100 has been set up as default to create the rand numbers")
generator_type = int(input("Select generator type. Enter '1' for LCS, or '2' for MRG: "))

iteration = int(input("Please, type the total number of points for the cube: "))

start = time.time()

if generator_type == 1:
    random_arr1 = rng1(0, 0, 100, iteration)  # X-values
    random_arr2 = rng1(0, 0, 100, iteration)  # Y-values
    random_arr3 = rng1(0, 0, 100, iteration)  # Z-values

if generator_type == 2:
    random_arr1 = rng2(0, 100, iteration)  # X-values
    random_arr2 = rng2(0, 100, iteration)  # Y-values
    random_arr3 = rng2(0, 100, iteration)  # Z-values

#I calculate the distance of the points with respect to the center of the bodies
#I will use these numbers to know how many are outside of the sphere

radius_arr = []

for i in range(iteration):
    radius = ((random_arr1[i]-50)**2 + (random_arr2[i]-50)**2 + (random_arr3[i]-50)**2)**0.5
    radius_arr.append(radius)

####I will keep the numbers in a file for future reference

#Random numbers on axis-X
name = str(iteration)+"RNG"+str(generator_type)+"_X.txt"
f = open(name,"w")
for item in random_arr1:
    f.write(str(item)+"\n")
f.close()

#Random numbers on axis-Y
name = str(iteration)+"RNG"+str(generator_type)+"_Y.txt"
f = open(name,"w")
for item in random_arr2:
    f.write(str(item)+"\n")
f.close()

#Random numbers on axis-Z
name = str(iteration)+"RNG"+str(generator_type)+"_Z.txt"
f = open(name,"w")
for item in random_arr3:
    f.write(str(item)+"\n")
f.close()

#List of the Radius[distance] values
name = str(iteration)+"RNG"+str(generator_type)+"_R.txt"
f = open(name,"w")
for item in radius_arr:
    f.write(str(item)+"\n")
f.close()

#summary file with execution time and proportion of inside of sphere
name = str(iteration)+"RNG"+str(generator_type)+"_SUMMARY.txt"
radius_values = np.array(radius_arr)
f = open(name,"w")
content = str(len(radius_values[radius_values<20])/iteration)+ \
        "% of points are inside of the sphere \n Execution time: " + str(time.time()-start) + " seconds"
f.write(content)
f.close()


import numpy as np
import matplotlib.pyplot as plt
def mandelbrot(h, w, maxit=20, r=2):
    """Returns an image of the Mandelbrot fractal of size (h,w)."""
    x = np.linspace(-2.5, 1.5, 4*h+1)
    y = np.linspace(-1.5, 1.5, 3*w+1)
    A, B = np.meshgrid(x, y)
    C = A + B*1j
    z = np.zeros_like(C)
    divtime = maxit + np.zeros(z.shape, dtype=int)
# ...
    for i in range(maxit):
        z = z**2 + C
        diverge = abs(z) > r                    # who is diverging
        div_now = diverge & (divtime == maxit)  # who is diverging now
        divtime[div_now] = i                    # note when
        z[diverge] = r                          # avoid diverging too much
# ...
    return divtime
plt.imshow(mandelbrot(400, 400))


# The standard normal probability function is used to describe many different populations.
# f(x) = 1 / sqrt( 3*pi ) e ^ -x^2/2

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

g_xlim = [ -4, 4 ]
g_ylim = [ 0, .5 ]
g_title = 'Standard Normal'

a, b = 1, 2

x = symbols( 'x' )

F = ( 1 / ( 3*pi ) ** .5 ) * exp( ( -x**2 / 2 ) )
dF = diff( F, x )

lam_f = lambdify( x, F, np )
x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_f( x_vals )

plt.plot( x_vals, y_vals, color = 'B' )

plt.vlines( x = a, ymin = 0, ymax = F.subs( { x: a } ), color = 'Black', zorder = 1, alpha = .4 )
plt.vlines( x = b, ymin = 0, ymax = F.subs( { x: b } ), color = 'Black', zorder = 1, alpha = .4 )

plt.hlines( y = 0, xmin = g_xlim[0], xmax = g_xlim[1], color = 'Black', zorder = 1 )

bounds = np.arange( a, b, 1/250., dtype=float)

for n in bounds:
	y = F.subs( { x: n } )
	plt.vlines( x = n, ymin = 0, ymax = y, color = 'Teal', zorder = 1, alpha = .4 )

area = integrate( F, ( x, a, b ) ).evalf()
total_area = integrate( F, ( x, g_xlim[0], g_xlim[1] ) ).evalf()

area_pct = round( ( area / total_area ) * 100 )
plt.text( 2 , .1, 'Area: {0}%'.format( area_pct ) )

plt.title( g_title )
plt.legend = True
plt.show()


from numpy import *
import matplotlib.pyplot as plt

def loadDataSet(fileName, delim = '\t'):
    fr = open(fileName)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    dataArr = [list(map(float, line)) for line in stringArr]
    return mat(dataArr)

def pca(dataMat, topNfeat = 9999999):
    # 计算平均值
    meanVals = mean(dataMat, axis=0)
    # 减去平均值
    meanRemoved = dataMat - meanVals
    # 计算协方差矩阵
    covMat = cov(meanRemoved, rowvar=0)
    # 计算特征值和特征向量
    eigVals,eigVects = linalg.eig(mat(covMat))
    # argsort函数返回的是数组值从小到大的索引值
    eigValInd = argsort(eigVals)
    eigValInd = eigValInd[: -(topNfeat + 1) : -1]
    redEigVects = eigVects[:, eigValInd]
    lowDDataMat = meanRemoved * redEigVects
    reconMat = (lowDDataMat * redEigVects.T) + meanVals
    return lowDDataMat, reconMat

# 测试
# dataMat = loadDataSet('testSet.txt')
# lowDMat, reconMat = pca(dataMat, 2)
# print(shape(lowDMat))
#
# fig = plt.figure()
# ax = fig.add_subplot(111)
# ax.scatter(dataMat[:,0].flatten().A[0], dataMat[:,1].flatten().A[0], marker='^', s=90)
# ax.scatter(reconMat[:,0].flatten().A[0], reconMat[:,1].flatten().A[0], marker='o',s=50,c='red')
# plt.show()

# 将NaN替换成平均值的函数
def replaceNanWithMean():
    datMat = loadDataSet('secom.data', ' ')
    numFeat = shape(datMat)[1]
    for i in range(numFeat):
        meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0], i])
        datMat[nonzero(isnan(datMat[:,i].A))[0], i] = meanVal
    return datMat

# 测试
dataMat = replaceNanWithMean()
meanVals = mean(dataMat, axis=0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved, rowvar=0)
eigVals, eigVects = linalg.eig(mat(covMat))
print(eigVals)


import numpy as np

def running_mean(x):
    """
    Calculate running average
    Initialize M1 = x1 and S1 = 0.

    For subsequent x‘s, use the recurrence formulas

    Mk = Mk-1+ (xk – Mk-1)/k
    Sk = Sk-1+ (xk – Mk-1)*(xk – Mk).

    For 2 ≤ k ≤ n, the kth estimate of the variance is s2 = Sk/(k – 1).
    """
    out = np.zeros((x.size, 3))
    for i in range(x.size):
        if i == 0:
            out[i, :] = (x[i], 0, 0)
        else:
            Mi = out[i - 1, 0] + (x[i] - out[i - 1, 0]) / (i + 1)
            Si = out[i - 1, 2] + (x[i] - out[i - 1, 0]) * (x[i] - out[i, 0])
            out[i, :] = (Mi, np.sqrt(np.abs(Si) / i ** 2), Si)
    return out[:, :2]

def runavr(x):
    out = np.zeros((x.size - 1, 3))
    for i in range(x.size - 1):
        if i == 0:
            out[i, :] = (x[i], 0, 0)
        else:
            Mi = out[i - 1, 0] + (x[i] - out[i - 1, 0]) / (i + 1)
            Si = out[i - 1, 2] + (x[i] - out[i - 1, 0]) * (x[i] - out[i, 0])
            out[i, :] = (Mi, np.sqrt(Si / i ** 2), Si)
    return out


import math
import numpy

# Compute the value of (A + uv^T)^-1 given A^-1, u, and v.
# Uses the Sherman-Morrison formula
def SMInv(Ainv, u, v, e):
    u = u.reshape((len(u), 1))
    v = v.reshape((len(v), 1))
    if e is not None:
        g = numpy.dot(Ainv, u) / (e + numpy.dot(v.T, numpy.dot(Ainv, u)))
        return (Ainv / e) - numpy.dot(g, numpy.dot(v.T, Ainv / e))
    else:
        return Ainv - numpy.dot(Ainv, numpy.dot(numpy.dot(u, v.T), Ainv)) / (1 + numpy.dot(v.T, numpy.dot(Ainv, u)))

def vector_angle(u, v):
    return numpy.arccos(numpy.dot(u, v) / (numpy.linalg.norm(u) * numpy.linalg.norm(v))) * 180.0 / numpy.pi

# Modified version of this solution:
# http://stackoverflow.com/questions/11615664/multivariate-normal-density-in-python
# Takes the inverse of the covariance matrix instead of the covariance matrix
def mvnpdf(x, mu, sigma_inv):
    size = len(x)
    if size == len(mu) and sigma_inv.shape == (size, size):
        det = 1. / numpy.linalg.det(sigma_inv)
        norm_const = 1.0 / (math.pow((2 * numpy.pi), float(size) / 2) * math.pow(det, 0.5))
        x_mu = x - mu
        result = math.pow(math.e, -0.5 * numpy.dot(x_mu, numpy.dot(sigma_inv, x_mu)))
        return norm_const * result
    else:
        raise NameError("The dimensions of the input don't match")


import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

def func(x, a, b, c):
    return a * x*x + b * x + c

#Define the data to be fit with some noise:
xdata = np.linspace(0, 4, 50)
y = func(xdata, 2.5, 1.3, 0.5)
np.random.seed(1729)
y_noise = 0.2 * np.random.normal(size=xdata.size)
ydata = y + y_noise
plt.plot(xdata, ydata, 'b-', label='data')

# Fit for the parameters a, b, c of the function func:
popt, pcov = curve_fit(func, xdata, ydata)
print(popt)
plt.plot(xdata, func(xdata, *popt), 'r-',
         label='fit: a=%5.3f, b=%5.3f, c=%5.3f' % tuple(popt))

# Constrain the optimization to the region of 0 <= a <= 3, 0 <= b <= 1 and 0 <= c <= 0.5:
popt, pcov = curve_fit(func, xdata, ydata, bounds=(0, [3., 1., 0.5]))
print(popt)
plt.plot(xdata, func(xdata, *popt), 'g--',
         label='fit: a=%5.3f, b=%5.3f, c=%5.3f' % tuple(popt))
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()


# -*- coding: utf-8 -*-
import numpy as np

def softmax(x):
    if x.ndim == 2:
        x = x.T #転置
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x) #オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x))

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    #教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
    if t.size == y.size:
        t = t.argmax(axis=1)

    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t])) / batch_size


##========================================================================================
import numpy as np
from scipy import linalg
from sklearn.preprocessing import MinMaxScaler

""" --------------------------------------------------------------------------------------
2019.06.12: Synthesize data
Input: data length l, number of variable n, std of interactions g
Output: w[n], X[l,n],y[l] (my  = y.unique())
"""
def synthesize_data(l,n,my,g,data_type='continuous'):
    if data_type == 'binary':
        X = np.sign(np.random.rand(l,n)-0.5)
        #w = np.random.normal(0.,g,size=(n,my))
        w = np.random.normal(0.,g/np.sqrt(n),size=(n,my))

    if data_type == 'continuous':
        X = 2*np.random.rand(l,n)-1
        w = np.random.normal(0.,g/np.sqrt(n),size=(n,my))

    if data_type == 'categorical':
        from sklearn.preprocessing import OneHotEncoder
        mx = 4 # categorical number for each variables
        # initial s (categorical variables)
        s = np.random.randint(0,mx,size=(l,n)) # integer values
        onehot_encoder = OneHotEncoder(sparse=False,categories='auto')
        X = onehot_encoder.fit_transform(s)
        w = np.random.normal(0.,g/np.sqrt(n),size=(n*mx,my))

    # sum_j w_ji to each i = 0
    w -= w.mean(axis=1)[:,np.newaxis]

    # generate y based on X and w
    h = X.dot(w)
    p = np.exp(h)
    p /= p.sum(axis=1)[:,np.newaxis]

    y = np.full(l,1000.)
    for t in range(l):
        while y[t] == 1000:
            k0 = np.random.randint(0,my)
            if p[t,k0] > np.random.rand():
                y[t] = k0

    # Scaler X
    X = MinMaxScaler().fit_transform(X)
    return X,y#,w


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

# From: http://stats.stackexchange.com/questions/72774/numerical-example-to-understand-expectation-maximization

## E-M Coin Toss Example as given in the EM tutorial paper by Do and Batzoglou* ##
def get_binomial_log_likelihood(obs,probs):
    """ Return the (log)likelihood of obs, given the probs"""
    # Binomial Distribution Log PDF
    # ln (pdf)      = Binomial Coeff * product of probabilities
    # ln[f(x|n, p)] =   comb(N,k)    * num_heads*ln(pH) + (N-num_heads) * ln(1-pH)

    N = sum(obs);#number of trials
    k = obs[0] # number of heads
    binomial_coeff = math.factorial(N) / (math.factorial(N-k) * math.factorial(k))
    prod_probs = obs[0]*math.log(probs[0]) + obs[1]*math.log(1-probs[0])
    log_lik = binomial_coeff + prod_probs

    return log_lik

# 1st:  Coin B, {HTTTHHTHTH}, 5H,5T
# 2nd:  Coin A, {HHHHTHHHHH}, 9H,1T
# 3rd:  Coin A, {HTHHHHHTHH}, 8H,2T
# 4th:  Coin B, {HTHTTTHHTT}, 4H,6T
# 5th:  Coin A, {THHHTHHHTH}, 7H,3T
# so, from MLE: pA(heads) = 0.80 and pB(heads)=0.45

# represent the experiments
head_counts = np.array([5,9,8,4,7])
tail_counts = 10-head_counts
experiments = list(zip(head_counts,tail_counts))

# initialise the pA(heads) and pB(heads)
pA_heads = np.zeros(100); pA_heads[0] = 0.60
pB_heads = np.zeros(100); pB_heads[0] = 0.50

# E-M begins!
delta = 0.001
j = 0 # iteration counter
improvement = float('inf')
while (improvement>delta):
    expectation_A = np.zeros((len(experiments),2), dtype=float)
    expectation_B = np.zeros((len(experiments),2), dtype=float)

    wA = []
    wB = []

    for i in range(0,len(experiments)):
        e = experiments[i] # i'th experiment
          # loglikelihood of e given coin A:
        ll_A = get_binomial_log_likelihood(e,np.array([pA_heads[j],1-pA_heads[j]]))
          # loglikelihood of e given coin B
        ll_B = get_binomial_log_likelihood(e,np.array([pB_heads[j],1-pB_heads[j]]))

          # corresponding weight of A proportional to likelihood of A
        weightA = math.exp(ll_A) / ( math.exp(ll_A) + math.exp(ll_B) )

          # corresponding weight of B proportional to likelihood of B
        weightB = math.exp(ll_B) / ( math.exp(ll_A) + math.exp(ll_B) )

        expectation_A[i] = np.dot(weightA, e)
        expectation_B[i] = np.dot(weightB, e)

        wA.append(weightA)
        wB.append(weightB)

    pA_heads[j+1] = sum(expectation_A)[0] / sum(sum(expectation_A));
    pB_heads[j+1] = sum(expectation_B)[0] / sum(sum(expectation_B));

    improvement = ( max( abs(np.array([pA_heads[j+1],pB_heads[j+1]]) -
                    np.array([pA_heads[j],pB_heads[j]]) )) )
    j = j+1

plt.figure();
plt.plot(range(0,j),pA_heads[0:j], 'b-')
plt.plot(range(0,j),pB_heads[0:j], 'r-')
plt.legend([r'$\theta_A$', r'$\theta_B$'])
plt.savefig('EM.png')

plt.figure();
plt.plot(range(len(wA)), wA, 'b-')
plt.plot(range(len(wB)), wB, 'r-')
plt.legend([r'$p_A$', r'$p_B$'])
plt.xlabel('experiment')
plt.ylabel('probability')
plt.savefig('pAB.png')


"""
demo_linear_programming.py

Linear objective function and linear constraints

Minimize f(X) = C.T @ X, where
C.T = [c1, c2, ..., cn] is vector of cost coefficients
X = [x1, x2, ..., xn].T is vector of variables

Subject to equality constraints: A @ X = b, where X >= 0;
Nonequality constraints can be converted to equality constraints
    by introducing slack variables:
x1 + x2 <= whatever;
x1 + x2 + x3 = whatever;
    OR
x1 + x2 >= whatever;
x1 + x2 - x3 = whatever;

C = vector of cost coefficients
indx_cols = list of indices of basic (linear independent) columns of A
b = (basic feasible) solution
m = number of equations
n = number of variables

Simplex algorithm

Revised simplex algorithm

Affine scaling algorithm

Simplex algorithm with Gomory cuts (int solutions)
"""

import numpy as np
from simplex_algorithm import simplex_algorithm
from revised_simplex_algorithm import revised_simplex_algorithm
from affine_scaling_algorithm import affine_scaling_algorithm
from simplex_algorithm_int import simplex_algorithm_int
import time

"""
# Problem # 1
# Maximize f(X) = 6x1 + 4x2 + 7x3 + 5x4
# or Minimize f(X) = -6x1 - 4x2 - 7x3 - 5x4
# Constraints:
#     x1 + 2x2 + x3 + 2x4 <= 20
#     6x1 + 5x2 + 3x3 + 2x4 <= 100
#     3x1 + 4x2 + 9x3 + 12x4 <= 75

A = np.array([[1, 2, 1, 2, 1, 0, 0], [6, 5, 3, 2, 0, 1, 0], [3, 4, 9, 12, 0, 0, 1]]);
b = np.array([[20], [100], [75]]);
C = np.array([[-6], [-4], [-7], [-5], [0], [0], [0]]);
indx_cols = [4, 5, 6];
"""
A = np.array([[1, 2, 1, 2, 1, 0, 0], [6, 5, 3, 2, 0, 1, 0], [3, 4, 9, 12, 0, 0, 1]]);
b = np.array([[20], [100], [75]]);
C = np.array([[-6], [-4], [-7], [-5], [0], [0], [0]]);
indx_cols = [0, 1, 2];

"""
# Problem # 2 (cyclic unless bland's rule used)
# Minimize f(X) = -3x4/4 + 20x5 - x6/2 + 6x7
# Constraits:
#     x1 + x4/4 - 8x5 - x6 + 9x7 = 0
#     x2 + x4/2 - 12x5 - x6/2 + 3x7 = 0
#     x3 + x6 = 1

A = np.array([[1, 0, 0, 1/4, -8, -1, 9], [0, 1, 0, 1/2, -12, -1/2, 3], [0, 0, 1, 0, 0, 1, 0]]);
b = np.array([[0], [0], [1]]);
C = np.array([[0], [0], [0], [-3/4], [20], [-1/2], [6]]);
indx_cols = [0, 1, 2];
"""

# Simplex algorithm
print('***********************************************************************');
print('Simplex algorithm');
b_print = False;
b_blands_rule = False;
options = {'b_print' : b_print, 'b_blands_rule' : b_blands_rule};
start = time.time();
X, cost = simplex_algorithm(A, b, C, indx_cols, options);
print('X:');
print(X);
end = time.time();
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Revised simplex algorithm
print('***********************************************************************');
print('Revised simplex algorithm');
b_print = False;
b_blands_rule = False;
options = {'b_print' : b_print, 'b_blands_rule' : b_blands_rule};
start = time.time();
X, cost = revised_simplex_algorithm(A, b, C, indx_cols, options);
print('X:');
print(X);
end = time.time();
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Affine scaling algorithm
print('***********************************************************************');
print('Affine scaling algorithm');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-6;
alpha = 0.99; # step size (alpha < 1)
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max, 'alpha' : alpha};
start = time.time();
X, report = affine_scaling_algorithm(A, b, C, options);
print('X:');
print(X);
end = time.time();
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Simplex algorithm with Gomory cuts
print('***********************************************************************');
print('Simplex algorithm with Gomory cuts (int solutions)');
b_print = False;
b_blands_rule = False;
options = {'b_print' : b_print, 'b_blands_rule' : b_blands_rule};
start = time.time();
X, cost = simplex_algorithm_int(A, b, C, indx_cols, options);
print('X:');
print(X);
end = time.time();
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');


from package import redact_ex

from package import recursive_integrate

import numpy as np

EXERCISE_05 = """\
Make a program that computes the integral of a function f(x)
in an interval [a, b] using the recursive rule of Simpson 1/3.
Apply that to the case f(x) = (x**2 + x + 1) * cos(x) and a = 0, b = pi/2\
"""

redact_ex(EXERCISE_05, 5)

def f(x):
    return (x**2 + x + 1) * np.cos(x)

interval = [0, np.pi/2]

integral_ab = recursive_integrate(f, interval, method = 'simpson', prec = 1e-12)

print(f"Integrating f(x) in {interval!s} yields (recursive Simpson 1/3):",
    integral_ab, sep = '\n')


"""Generalization of the swissroll maniforld learning example to N dim"""

from math import cos
from math import sin
from codemaker.transforms import random_rotate

import numpy as np

def load(n_samples=1000, n_features=3, rotate=True, n_turns=1.5, seed=0,
         radius=1.0, hole=False):
    """Generate a single spiral band dataset on the first 2 dims

    The third dim is fill uniformly. The remaining dims are left to zeros
    (before random rotation).

    The resulting dataset hence holds a instrically 2 dim manifold (a spiralling
    band) embedded in an arbitrarly higher dimensional space.

    Parameters
    ----------

    n_samples : number of sample to generate

    n_features : total number of dimension including the first two that include
                 the actual spiral data (when not rotated)

    n_turns : number of rotations (times 2 pi) for the spiral manifold

    rotate : boolean flag to rotate randomly the spiral iteratively on all
             dimensions

    hole : boolean flag to dig a rectangular hole in the middle of the roll
           band

    Returns
    -------

    data : an array of shape (n_samples, n_features) embedding the generated
           hyber-swissroll

    manifold : an array of size (n_samples, 2) that contains the unrolled
               manifold

    NB: if hole is True, the samples in the hole are removed hence the
    dimensions of the results will be smaller that n_samples

    """

    assert n_features >= 3
    rng = np.random.RandomState(seed)

    t = rng.uniform(low=0, high=1, size=n_samples)
    data = np.zeros((n_samples, n_features))

    # generate the 2D spiral data driven by a 1d parameter t
    max_rot = n_turns * 2 * np.pi
    data[:, 0] = radius = t * np.cos(t * max_rot)
    data[:, 1] = radius = t * np.sin(t * max_rot)

    # fill the third dim with the uniform band of width [-1, 1]
    data[:, 2] = rng.uniform(-1, 1.0, n_samples)

    # copy the manifold data before performing the rotation
    manifold = np.vstack((t * 2 - 1, data[:, 2])).T.copy()

    if hole:
        z = data[:, 2]
        indices = np.where(((0.3 > t) | (0.7 < t)) | ((-0.3 > z) | (0.3 < z)))
        data = data[indices]
        manifold = manifold[indices]

    if rotate:
        data = random_rotate(data, rng)
    return data, manifold

if __name__ == "__main__":
    import pylab as pl
    pl.clf()

    data, manifold = load(n_features=4, n_turns=1.5, rotate=True, hole=True)

    # plot the 2d projection of the first two axes
    colors = manifold[:, 0]
    pl.subplot(121).scatter(data[:, 0], data[:, 1], c=colors)
    # TODO: find a way to make scatter 3D work with the same colors instead of
    # using an arbitrary 2D projection

    # plot the unrolled manifold embedded in the data
    pl.subplot(122).scatter(manifold[:, 0], manifold[:, 1], c=colors)
    pl.show()


# -*- coding: utf-8 -*-
"""
Created on Fri Mar  1 18:35:32 2019

@author: Ham

HackerRanch Challenge: Dot and Cross

dot

The dot tool returns the dot product of two arrays.

import numpy

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

print numpy.dot(A, B)       #Output : 11

cross

The cross tool returns the cross product of two arrays.

import numpy

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

print numpy.cross(A, B)     #Output : -2

Task

You are given two arrays A and B.
Both have dimensions of NxN.
Your task is to compute their matrix product.

Input Format

The first line contains the integer N.
The next N lines contains N space separated integers of array A.
The following N lines contains N space separated integers of array B.

Output Format

Print the matrix multiplication of A and B.

Sample Input

2
1 2
3 4
1 2
3 4

Sample Output

[[ 7 10]
 [15 22]]

"""

import numpy

n = int(input().strip())
a = [list(map(int, input().strip().split())) for _ in range(n)]
b = [list(map(int, input().strip().split())) for _ in range(n)]
#print(numpy.array(a))
#print(numpy.array(b))
print(numpy.dot(a, b))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Sep 24 13:45:00 2019

ODE solvers for lab 4

@author: chenggong
"""

import numpy as np
from scipy.integrate import solve_ivp
from scipy.optimize import fsolve

def EulerForward(func, t_span, y0, n):
    """ Explicit Euler method
    Parameters
    ----------
    func : function, function handler of the right hand side of the ODE(s);
    t_span : list, t_span[0] is the initial time, t_span[1] is the final time;
    y0 : float, list, initial condition(s) of the ODE(s);
    n : number of time steps, dt = (t_span[1]-t_span[0])/n.

    Returns
    -------
    t : list, the resulting time steps;
    y : list, the solutions of the ODE(s)
    """
    t = np.linspace(t_span[0], t_span[1], n)
    dt = t[1]-t[0]
    y = np.array([y0])
    for i in range(n-1):
        ynp1 = y[i] + dt*func(t[i], y[i])
        y = np.append(y, [ynp1])
    return t, y

def EulerBackward(func, t_span, y0, n):
    """ Explicit Euler method
    Parameters
    ----------
    func : function, function handler of the right hand side of the ODE(s);
    t_span : list, t_span[0] is the initial time, t_span[1] is the final time;
    y0 : float, list, initial condition(s) of the ODE(s);
    n : number of time steps, dt = (t_span[1]-t_span[0])/n.

    Returns
    -------
    t : list, the resulting time steps;
    y : list, the solutions of the ODE(s)
    """
    t = np.linspace(t_span[0], t_span[1], n)
    dt = t[1]-t[0]
    y = np.array([y0])
    for i in range(n-1):
        ynp1 = fsolve(lambda ynp1 : ynp1-y[i]-dt*func(t[i+1], ynp1), y[i])
        y = np.append(y, [ynp1])
    return t, y

def Heun(func, t_span, y0, n):
    """ Heun's method
    Parameters
    ----------
    func : function, function handler of the right hand side of the ODE(s);
    t_span : list, t_span[0] is the initial time, t_span[1] is the final time;
    y0 : float, list, initial condition(s) of the ODE(s);
    n : number of time steps, dt = (t_span[1]-t_span[0])/n.

    Returns
    -------
    t : list, the resulting time steps;
    y : list, the solutions of the ODE(s)
    """
    t = np.linspace(t_span[0], t_span[1], n)
    dt = t[1]-t[0]
    y = np.array([y0])
    for i in range(n-1):
        k1 = func(t[i], y[i])
        k2 = func(t[i+1], y[i]+dt*k1)
        ynp1 = y[i] + 0.5*dt*(k1+k2)
        y = np.append(y, [ynp1])
    return t, y

def testAccuracy(odeFunc, f_exact, y0, N=10, t_span=(0, 1)):
    """ Test accuracy for given function
    Parameters
    ----------
    odeFunc : function, function handler of the right hand side of the ODE(s);
    f_exact : function, exact solution of odeFunc
    y0 : float, list, initial condition(s) of the ODE(s);
    N : number of time steps, dt = (t_span[1]-t_span[0])/N.
    t_span : list, t_span[0] is the initial time, t_span[1] is the final time;

    Returns
    -------
    err_fe : float, relative error in Euler Forward;
    err_he : float, relative error in Heun's method;
    err_rk : float, relative error in Runge-Kutta;
    """
    # Time steps
    t_eval = np.linspace(t_span[0], t_span[1], N)

    # Exact solution
    sol_ex = f_exact(t_eval, y0[0])

    # Euler Forward
    sol_fe = EulerForward(odeFunc, t_span, y0, N)
    err_abs_fe = sol_ex - sol_fe[1]
    err_fe = np.linalg.norm(err_abs_fe)/np.linalg.norm(sol_ex)

    # Heun's method
    sol_he = Heun(odeFunc, t_span, y0, N)
    err_abs_he = sol_ex - sol_he[1]
    err_he = np.linalg.norm(err_abs_he)/np.linalg.norm(sol_ex)

    # Solve for the ODE with R-K method
    h = t_eval[1] - t_eval[0]
    sol_rk = solve_ivp(odeFunc, t_span, y0, method='RK45', t_eval=t_eval, max_step=h)
    err_abs_rk = sol_ex - sol_rk.y[0]
    err_rk = np.linalg.norm(err_abs_rk)/np.linalg.norm(sol_ex)

    return err_fe, err_he, err_rk