import math
import numpy as np

class variable:

    def __init__(self,val,der=1):
        """
            Initialize a variable object
            with a specified value and derivative.
            Expect scalar for val. Expect scalar
            or a numpy array for der.
            If no derivative is specified,
            a seed of 1 is assumed.
        """

        self._val = val
        assert isinstance(der, (np.ndarray, float, int)), "der must be a scalar or a numpy array"
        self._der = der

    @property
    def val(self):
        return self._val

    @property
    def der(self):
        return self._der

    def __repr__(self):
        """
            Define representation of variable object:
        """
        return "awesomediff.variable(val={},der={})".format(self.val,self.der)

    def __neg__(self):
        """
            Overload negation.
            Returns a variable object with:
            val : negated value.
            der : negated derivative.
        """
        return variable(val=-self.val,der=-self.der)

    def __add__(self,other):
        """
            Overload addition.
            Returns a variable object with:
            val : sum of values.
            der : sum of derivatives.
        """
        self_val = self.val
        self_der = self.der
        try:
            # Assume other object is a variable:
            other_val = other.val
            other_der = other.der
        except:
            # If not, use this value and derivative 0.
            try:
                float(other)
            except:
                raise ValueError("{} is not a number.".format(other))
            other_val = other
            other_der = 0  # Derivative of a constant is zero.
        # Calculate new values (simple summation):
        new_val = self_val + other_val
        new_der = self_der + other_der
        # Return variable with new value and derivative:
        return variable(val=new_val,der=new_der)

    def __radd__(self,other):
        """
            Overload reverse addition by calling __add__().
        """
        return self.__add__(other)

    def __mul__(self,other):
        """
            Overload multiplication.
            Returns a variable object with:
            val : product of values.
            der : result of product rule
        """
        self_val = self.val
        self_der = self.der
        try:
            # Assume other object is a variable:
            other_val = other.val
            other_der = other.der
        except:
            # If not, uses this value and derivative 0.
            try:
                float(other)
            except:
                raise ValueError("{} is not a number.".format(other))
            other_val = other
            other_der = 0  # Derivative of a constant is zero.
        # Calculate new values (applying product rule):
        new_val = self_val * other_val
        new_der = (self_der * other_val) + (self_val * other_der)
        # Return variable with new value and derivative:
        return variable(val=new_val,der=new_der)

    def __rmul__(self,other):
        """
            Overload reverse multiplication
            Calls __mul__.
        """
        return self.__mul__(other)

    def __sub__(self,other):
        """
            Overload subtraction.
            Implemented using negation and addition.
        """
        return self.__add__(-other)

    def __rsub__(self,other):
        """
            Overload reverse subtraction.
            Implemented using negation and addition.
        """
        return self.__neg__().__add__(other)

    def __truediv__(self, other):
        try:
            # assume other is an instance variable
            new_val = self.val / other.val
            new_der = (self.der * other.val - self.val * other.der) / other.val ** 2
        except:
            # assume in the form of variable/scaler
            try:
                float(other)
            except:
                raise ValueError("{} is not a number or instance variable.".format(other))

            if float(other) == 0:
                raise ZeroDivisionError('Cannot perform division by zero')
            new_val = self.val / float(other)
            new_der = self.der / float(other)

        return variable(val=new_val, der=new_der)

    def __rtruediv__(self, other):
        new_val = other / self.val
        new_der = -other * self.val ** (-2)  # other*self.pow(-1)
        return variable(val=new_val, der=new_der)

    def __eq__(self, other):
        """
            Overload equal
            Check if the thing compared to is a variable and has the same vale
        """
        return isinstance(other, variable) and self.val == other.val

    def __ne__(self, other):
        """
            Overload not equal
            Check if the thing compared to is a variable and does not have the same vale
        """
        return not isinstance(other, variable) or not (self.val == other.val)

    def __lt__(self, other):
        """
            Overload less than
        """
        try:
            return self.val<other.val
        except:
            raise AttributeError("Can only compare varibles.")

    def __gt__(self, other):
        """
            Overload greater than
        """
        try:
            return self.val>other.val
        except:
            raise AttributeError("Can only compare varibles.")

    def __le__(self, other):
        """
            Overload less than or equal to
        """
        try:
            return self.val<=other.val
        except:
            raise AttributeError("Can only compare varibles.")

    def __ge__(self,other):
        """
            Overload greater than or equal to
        """
        try:
            return self.val>=other.val
        except:
            raise AttributeError("Can only compare varibles.")

    def __pow__(self, other):
        try:
            # check whether other is a number
            new_val = self.val ** other
        except:
            raise ValueError("{} must be a number.".format(other))
        new_der = other * self.val ** (other - 1) * self.der
        return variable(val=new_val, der=new_der)

    def __rpow__(self, other):
        try:
            new_val = other** self.val
        except:
            raise ValueError("{} must be a number.".format(other))
        new_der = other**self.val * np.log(other)
        return variable(val=new_val, der=new_der)

def _build_jacobian(outputs):
    """
    INPUT:
        outputs: a list of awesomediff.variable objects
    OUTPUT:
        returns n x m jacobian matrix where n is the number of functions
        and m is the total number of variables

    EXAMPLE:
    outputs = [awesomediff.variable(val=5, der=[4]), awesomediff.variable
    (val=0.5, der=[0.5])]

    ## _build_jacobian(outputs)
    [[4]
     [0.25]]

    """
    n = len(outputs) # get number of functions
    m = len(outputs[0].der) # get number of variables
    jacobian = np.zeros((n,m))
    for i, output in enumerate(outputs):
         jacobian[i,:] = output.der

    return jacobian

def evaluate(func,vals,seed=None):
    """
        A wapper function that evaluates a user-defined function
        using awesomediff.variable objects.
        INPUTS:
            func: A user-defined function of n variables and m functions
                that will be evaluated to find the value and derivative
                at a specified point.
                The user-defined function may take one or more variables
                as arguments and may return one or more function outputs (as a list).
                Functions may include basic operations (+,*,etc),
                as well as any special functions supported by awesomediff.

            vals: A scalar (for univariate function) or a list of scalars
                (for multivariate function) at which to evaluate the function
                and its derivative.

            seed: A scalar (for univariate function) or a list of lists (for multivariate
                function) containing seed values for each variable. The lengths of the
                outer list and inner list must equal the number of arguments taken by `func`
                (i.e. the number of variables). Defaults to a seed of 1 for each
                variable if no seed value is provided.

                EXAMPLE:
                To set seed of variables x, y, z at 1, 2, and 3, respectively, user
                passes the following list of lists as seed:
                [[1,0,0],[0,2,0],[0,0,3]]
                The length of outer list and the lengths of each of the inner lists
                must be equal to the total number of variables (in this example, 3)
        ...
        RETURNS:
            output_value: value of function at a specified point. A scalar for
            a function, a vector of scalars for a vector function

            jacobian: the jacobian of function. A vector of scalar or scalars
            for univariate and multivariate functions, respectively. A matrix of
            scalars for univariate and multivariate vector functions
        ...
        EXAMPLE:
        ## def parametric_ellipse(a,b,t):
                x = a * ad.cos(t)
                y = b * ad.sin(t)
                return [x, y]
        ## output_value, jacobian = ad.evaluate(func=parametric_ellipse,vals=[4,2,0])

        ## output_value
        np.array([4, 0])    # [a*cos(t), b*sin(t)]

        ## jacobian
        np.array([[1,0,0],[0,0,4]]) #[[cos(t), 0, -a*sin(t)],[0 , sin(t), a*cos(t)]]
    """

    ## check user input for vals and convert it to numpy array
    # user input for vals will be a scalar for univariate function and
    # a list of scalars for multivariate function
    try:
        float(vals) # if vals is a scalar
        vals = np.array([vals])
    except:
        if isinstance(vals, list): # if vals is a list
            vals = np.array(vals)

    ## get number of values that were passed as inputs
    num_vars = len(vals)

    ## check user input for seed
    # user input for seed will be a scalar for univariate function and
    # a list of scalars for multivariate function

    # if user doesn't pass in value for seed argument, create seed with value of 1
    if seed == None:
        if num_vars == 1: #if univariate function
            seed = np.array([1])
        else: #if multivariate function
            seed_matrix = np.identity(num_vars)

    #if user passes in value for seed argument
    else:
        try:
            float(seed) # if seed is a scalar
            seed = np.array([seed])
        except:
            if isinstance(seed, list): # if seed is a list
                # check length of outer list
                assert len(seed) == num_vars, "length of outer list must equal number of variables"

                # check length of inner list
                for s in seed:
                    try:
                        len(s)
                    except: # seed for univariate function given as a list
                        raise ValueError("seed for univariate function should be given as a scalar")
                    assert len(s) == num_vars, "length of inner list must equal number of variables"

                seed_matrix = np.array(seed)

            else: # if seed is not provided as a scalar or a list
                raise ValueError("seed must be provided as a scalar or a list")

    ## evaluate the user-defined function passed in

    # instantiate awesomediff.variable objects
    if num_vars == 1: # if univariate function
        var = variable(val=vals[0], der=seed)
        inputs = [var]
    else: # if multivariate function
        inputs = []
        for i,v in enumerate(vals):
            var = variable(val=v,der=seed_matrix[i,:])
            inputs.append(var)

    # pass awesomediff.variable objects into user-defined function
    # outputs will be an awesomediff.variable object or a list of
    # awesomediff.variable objects storing values and derivatives of functions
    try:
        outputs = func(*inputs)
    except Exception as e:
        raise RuntimeError("function evaluation failed with the following error: {}".format(e))

    # create output_value and jacobian
    try: # if a vector-function
        len(outputs)
        output_value = np.zeros(len(outputs))
        for i,output in enumerate(outputs):
            output_value[i] = output.val
        jacobian = _build_jacobian(outputs)

    except: # if not a vector function
        output_value = outputs.val
        jacobian = outputs.der

    return output_value, jacobian


import numpy as np
import math

def trapezoidal(n,f):
    sum = .5*(f(0.) + f(1.))
    for k in range (1,n):
        sum += f(1.*k/n)
    return sum/n

def romberg(n0,steps,order,quadrature,f):
    res = np.zeros((steps,steps))
    for i in range(0,steps):
        n = n0 * 1<<i
        res[i,0] = quadrature(n,f)
        for j in range(1,i+1):
            res[i,j] = res[i,j-1] + \
            (res[i,j-1]-res[i-1,j-1])/(2**(order*j)-1.)
    return res

def func(x):
    return np.sin(np.pi*x)

initial = 2
steps = 5
exact = 2./np.pi

result = romberg(initial,steps,2,trapezoidal,func)
print(result)

logerror = np.zeros((steps,steps))

print("Fehler")
for k in range (0,steps):
    print ("Spalte ", k)
    for i in range (k,steps):
        e = abs(exact-result[i,k])
        print ("Zeile ", i, " Fehler ", e, "\tLog ", math.log2(e))
        logerror[i,k] = math.log2(e)

print("Experimentelle Ordnungen")
for k in range (0,steps):
    print ("Spalte ", k)
    for i in range (k+1,steps):
        p = logerror[i-1,k]-logerror[i,k]
        print (p)

diff = np.zeros((steps,steps))

print("Intrinsische Ordnungen")
for k in range (0,steps):
    print ("Spalte ", k)
    for i in range (k+1,steps):
        diff[i,k] = result[i,k] - result[i-1,k]


import numpy as np

def saturate(src, low, up):
    '''return the mask of unsaturated colors'''
    return np.all(np.logical_and(src>=low,src<=up), axis = -1)

def gamma_correction(rgb, gamma):
    '''
    gamma correction;
    see ColorSpace.pdf for details;
    '''

    arr = rgb.copy()
    mask = rgb>=0
    arr[mask] = np.power(arr[mask], gamma)
    arr[~mask] = -np.power(-arr[~mask], gamma)
    return arr

def rgb2gray(rgb):
    '''
    it is an approximation grayscale function for relative RGB color space;
    see Miscellaneous.pdf for details;
    rgb->gray
    '''
    return rgb@np.array([0.2126, 0.7152, 0.0722])


import numpy as np

def generate_target():
    # Generate the target function f
    div_points = np.random.uniform(low=-1, high=1, size=(2, 2))
    w1 = (div_points[1, 1] - div_points[0, 1])/(div_points[1, 0] - div_points[0, 0])
    w0 = div_points[0, 1] - div_points[0, 0]*w1
    w = np.array([w0, w1, -1])
    return w

def generate_sample(num_points, w):
    # Generate the test points and classify
    test_points = np.ones([num_points, 4])
    test_points[:, 1:3] = np.random.uniform(low=-1, high=1, size=(num_points, 2))
    test_points[:, 3] = np.sign(np.dot(test_points[:, 0:3], w))
    return test_points

def log_regression(training, g_init=np.zeros(3), tol=1e-6, max_iter=10**4, eta=0.1):
    d_E = lambda W, x_n, y_n: (-y_n * x_n)/(1+np.exp(y_n * W.dot(x_n)))

    g = np.copy(g_init)
    w = np.zeros(3)
    old_w = np.copy(w)
    iters = 0
    n_points, n_cols = training.shape
    y = training[:, -1]
    X = training[:, 0:n_cols-1]
    error = tol+1

    while True:
        old = np.copy(g)
        for i in np.random.permutation(n_points):
            xn = X[i]
            yn = y[i]
            g += eta*(yn*xn)/(1 + np.exp(yn*np.dot(xn, g)))
        iters += 1
        error = np.linalg.norm(g - old)
        error1 = np.linalg.norm(old_w - w)
        if error < tol:
            break
        old_w = np.copy(w)

    return g, iters

NTRIALS = 10
NPOINTS = 100
MCPOINTS = 10**6
mcdata = np.ones([MCPOINTS, 3])
mcdata[:, 1:3] = np.random.random([MCPOINTS, 2])
e_out = np.zeros(NTRIALS)
iters = np.zeros(NTRIALS)
for i in range(0, NTRIALS):
    w = generate_target()
    training = generate_sample(NPOINTS, w)
    g, iters[i] = log_regression(training, tol=0.01)
    g = -g/g[2]
    e_out[i] = np.mean(np.log(1+np.exp(-np.sign(np.dot(mcdata,w))*np.dot(mcdata, g))))

print(np.mean(e_out))
print(np.mean(iters))

# perfect_error = np.mean(np.log(1+np.exp(-np.sign(np.dot(mcdata,np.array([0.5,0.5,-1])))*np.dot(mcdata,np.array([0.5,0.5,-1])))))
# print(perfect_error)


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

from sklearn import decomposition
from sklearn import datasets

# load dataset into Pandas DataFrame
df = pd.read_csv("D:\Python_programs\ML\Complete_Data_.csv")

from sklearn.preprocessing import StandardScaler
n=[]
ind=[]
for i in range(200):
    n.append(i+1)
for i in range(200):
    ind.append('px'+str(n[i]))

features = ind
x = df.loc[:, features].values
# Separating out the target
y = df.loc[:,['target']].values
# Standardizing the features
from sklearn.preprocessing import MinMaxScaler
scaler_model = MinMaxScaler()
scaler_model.fit(x.astype(float))
x=scaler_model.transform(x)

from sklearn.decomposition import PCA

## Finding the principle components
pca = PCA(n_components=10)
principalComponents = pca.fit_transform(x)
ev=pca.explained_variance_ratio_

# *Since the initial 2 principal components have high variance.
#   so, we select pc-1 and pc-2.
#---------------------------------------------------
pca = PCA(n_components=2)
principalComponents = pca.fit_transform(x)
principalDf = pd.DataFrame(data = principalComponents
             , columns = ['PC-1','PC-2'])
# Adding lables
finalDf = pd.concat([principalDf, df[['target']]], axis = 1)

#--------- Bar Graph for Explained Variance Ratio ------------
plt.bar([1,2,3,4,5,6,7,8,9,10],list(ev*100),label='Principal Components',color='b')
plt.legend()
plt.xlabel('Principal Components')
pc=[]
for i in range(10):
    pc.append('PC'+str(i+1))
#plt.xticks([1,2,3,4,5,6,7,8,9,10],pc, fontsize=8, rotation=30)
plt.xticks([1,2,3,4,5,6,7,8,9,10],pc, fontsize=8, rotation=30)
plt.ylabel('Variance Ratio')
plt.title('Variance Ratio of INDIAN PINES Dataset')
plt.show()

#---------------------------------------------------
# Plotting pc1 & pc2
fig = plt.figure(figsize = (8,8))
ax = fig.add_subplot(1,1,1)
ax.set_xlabel('PC-1', fontsize = 15)
ax.set_ylabel('PC-2', fontsize = 15)
ax.set_title('PCA on INDIAN PINES Dataset', fontsize = 20)
targets = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
colors = ['r','g','b','y','m','c','k','r','g','b','y','m','c','k','b','r']
for target, color in zip(targets,colors):
    indicesToKeep = finalDf['target'] == target
    ax.scatter(finalDf.loc[indicesToKeep, 'PC-1']
               , finalDf.loc[indicesToKeep, 'PC-2']
               , c = color
               , s = 9)
ax.legend(targets)
ax.grid()
plt.show() # FOR SHOWING THE PLOT

#-------------------SENDING REDUCED DATA INTO CSV FILE------------

finalDf.to_csv('indian_pines_after_pca.dat')


## =========================================================================
## @author Leonardo Florez-Valencia (florez-l@javeriana.edu.co)
## =========================================================================

import numpy

## -------------------------------------------------------------------------
class LogisticSigmoid:

  Threshold = 0.5

  def __call__( self, X ):
    return 1.0 / ( 1.0 + numpy.exp( -X ) )
  # end def

  def derivative( self, X ):
    s = self( X )
    return numpy.multiply( s, ( 1.0 - s ) )
  # end def
# end class

## -------------------------------------------------------------------------
class TanhSigmoid:

  Threshold = 0.0

  def __call__( self, X ):
    return numpy.tanh( X )
  # end def

  def derivative( self, X ):
    s = self( X )
    return 1.0 - numpy.multiply( s, s )
  # end def
# end class

## eof - $RCSfile$


import numpy as np

def waverage(x,sigx):
    # Weighted average
    # Bryan Miller
    # 2014-06-10
    # References: http://en.wikipedia.org/wiki/Weighted_arithmetic_mean
    # http://stats.stackexchange.com/questions/47325/bias-correction-in-weighted-variance

    ax = np.array(x)
    asigx = np.array(sigx)

    w = 1./asigx**2
    xw = ax*w
    sumw = np.sum(w)
    sumwsq = np.sum((w**2))
    sumxw = np.sum(xw)

    # Weighted average
    mean = sumxw/sumw
    # Standard deviation of the mean (weighted)
    sigmean = 1./np.sqrt(sumw)
    # Unbiased estimator of the weighted population variance
    sigpop = np.sqrt(sumw*(np.sum(w*(x-mean)**2))/(sumw**2 - sumwsq))
    # Variance in the weighted mean (account for error underestimate)
    # sigwm = np.sqrt(sigmean*(np.sum(w*(x-mean)**2))/(len(ax) - 1.))

    return mean, sigmean, sigpop


# Perform the necessary imports
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

# Assign the 0th column of grains: width
width = grains[:,0]

# Assign the 1st column of grains: length
length = grains[:,1]

# Scatter plot width vs length
plt.scatter(width, length)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation
correlation, pvalue = pearsonr(width, length)

# Display the correlation
print(correlation)

# Import PCA
from sklearn.decomposition import PCA

# Create PCA instance: model
model = PCA()

# Apply the fit_transform method of model to grains: pca_features
pca_features = model.fit_transform(grains)

# Assign 0th column of pca_features: xs
xs = pca_features[:,0]

# Assign 1st column of pca_features: ys
ys = pca_features[:,1]

# Scatter plot xs vs ys
plt.scatter(xs, ys)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation of xs and ys
correlation, pvalue = pearsonr(xs, ys)

# Display the correlation
print(correlation)

# Make a scatter plot of the untransformed points
plt.scatter(grains[:,0], grains[:,1])

# Create a PCA instance: model
model = PCA()

# Fit model to points
model.fit(grains)

# Get the mean of the grain samples: mean
mean = model.mean_

# Get the first principal component: first_pc
first_pc = model.components_[0,:]

# Plot first_pc as an arrow, starting at mean
plt.arrow(mean[0], mean[1], first_pc[0], first_pc[1], color='red', width=0.01)

# Keep axes on same scale
plt.axis('equal')
plt.show()

# Perform the necessary imports
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
import matplotlib.pyplot as plt

# Create scaler: scaler
scaler = StandardScaler()

# Create a PCA instance: pca
pca = PCA()

# Create pipeline: pipeline
pipeline = make_pipeline(scaler, pca)

# Fit the pipeline to 'samples'
pipeline.fit(samples)

# Plot the explained variances
features = range(pca.n_components_)
plt.bar(features, pca.explained_variance_)
plt.xlabel('PCA feature')
plt.ylabel('variance')
plt.xticks(features)
plt.show()

# Import PCA
from sklearn.decomposition import PCA

# Create a PCA model with 2 components: pca
pca = PCA(n_components=2)

# Fit the PCA instance to the scaled samples
pca.fit(scaled_samples)

# Transform the scaled samples: pca_features
pca_features = pca.transform(scaled_samples)

# Print the shape of pca_features
print(pca_features.shape)

# Import TfidfVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer

# Create a TfidfVectorizer: tfidf
tfidf = TfidfVectorizer()

# Apply fit_transform to document: csr_mat
csr_mat = tfidf.fit_transform(documents)

# Print result of toarray() method
print(csr_mat.toarray())

# Get the words: words
words = tfidf.get_feature_names()

# Print words
print(words)

# Perform the necessary imports
from sklearn.decomposition import TruncatedSVD
from sklearn.cluster import KMeans
from sklearn.pipeline import make_pipeline

# Create a TruncatedSVD instance: svd
svd = TruncatedSVD(n_components=50)

# Create a KMeans instance: kmeans
kmeans = KMeans(n_clusters=6)

# Create a pipeline: pipeline
pipeline = make_pipeline(svd,kmeans)

# Import pandas
import pandas as pd

# Fit the pipeline to articles
pipeline.fit(articles)

# Calculate the cluster labels: labels
labels = pipeline.predict(articles)

# Create a DataFrame aligning labels and titles: df
df = pd.DataFrame({'label': labels, 'article': titles})

# Display df sorted by cluster label
print(df.sort_values(by="label"))


#
# 《孫子算經》(https://zh.wikipedia.org/wiki/孫子算經)
# 有物不知其數，三三數之剩二，五五數之剩三，七七數之剩二。問物幾何？
#
# Chinese remainder theorem(https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
# There are certain things whose number is unknown.
# If we count them by threes, we have two left over;
# by fives, we have three left over;
# and by sevens, two are left over.
# How many things are there?
#
# Here is the equations:
# x % 3 = 2
# x % 5 = 3
# x % 7 = 2

import sympy as sp

import deco
from deco.solver import Problem, Variable, Constraint
from deco.solver import deSolve

x = sp.symbols("x")

variables = [Variable(x, 0, 105)]
constraints = [
    Constraint(sp.Eq(x%3, 2, evaluate=False)),
    Constraint(sp.Eq(x%5, 3, evaluate=False)),
    Constraint(sp.Eq(x%7, 2, evaluate=False)),
    Constraint(sp.Eq(x%1, 0, evaluate=False)),
    Constraint(x>=0),
    Constraint(x<105),
    ]
objective = sp.numbers.Zero()
problem = Problem(variables, constraints, objective)
solution = deSolve(problem, seed=64, verbose=True)

print()
print("The number is {0}.".format(int(solution[0]+0.5)))
print()


import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
#Define plot axes.
fig,ax = plt.subplots(1,1,figsize=(10,8))
#Not declaring this way raises an error for streamplot but not quiver.
x,y = np.meshgrid( np.linspace(0,5,20),np.linspace(0,5,20) )

#Define function: Using lambda seems quite efficient.
# For dx/dt = x(1-x); p 35 Strogatz.
#Note that the x = x[0], y = dx/dt is in terms of x[1]
f = lambda x : [x[0], x[1]*(1- x[1])]
#Apply the function to the axes.
U,V = f([x,y])
#Both ways work/
# U = x
# V = y*(1-y)
#Plot the curve.
ax.streamplot(x,y,U,V, color='r',linewidth=.5)
ax.set_title('Phase and Trajectory plot')
ax.set_xlabel('x')
ax.set_ylabel('xdot')
#Make a new function also to integrate
def odef(vect,t):
    x =vect
    return x*(1-x)

#Calculate a trajectory with a starting point.
number_of_points = 200
t = np.linspace(0,5,number_of_points)
y0 = [.2,1,5]#[(0,2.5),(2,1),(3,1.2)]
N = len(y0)
tt = odeint(odef,y0,t)

#Plot a trajectory
ax.plot(t, tt[:,0],'g', lw=1)
ax.plot(t, tt[:,1],'b',lw=1)
ax.plot(t, tt[:,2],'k',lw=1)
#print(yint)
plt.show()


import numpy as np

a = np.arange(12).reshape([3, 4])

b = np.arange(8).reshape([4, 2])
# martix multiplication
c = np.matmul(a, b)
# element-wise multiplication
d = a*a
# bool index
d[a > 6] = 0
# sin
np.sin(a)
# shape
a.shape
# concatenate
np.hstack((a, b))
np.vstack((a, b))


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

datos = pd.read_csv('USArrests.csv')
columnas = ['Murder', 'Assault', 'UrbanPop','Rape']
datos2 = datos[columnas]
datos2 = (datos2 - datos2.mean())/datos2.std()

cov_matrix = np.cov(datos2.T)

values, vectors = np.linalg.eig(cov_matrix)

vector_A = vectors[:,0]
vector_B = vectors[:,1]

plt.figure(figsize=(12,12))
datos = np.array(datos)

for i in range(len(datos)):
    ciudades = datos[:,0][i]
    v = np.array(datos2.iloc[i])
    x = np.dot(vector_A, v)
    y = np.dot(vector_B, v)
    plt.text(x,y, ciudades, fontsize=10, color='blue')
    plt.scatter(x, -y, s=0.001)

for j in range(len(columnas)):
    plt.arrow(0.0, 0.0, 2.5*vector_A[j], 2.5*vector_B[j], color='red', head_width=0.1)
    plt.text(2.75*vector_A[j], 2.75*vector_B[j], columnas[j], color='red')
plt.ylim(-3,3)
plt.xlabel('Primera Componente Principal')
plt.ylabel('Segunda Componente Principal')
plt.savefig('arrestos.png', bbox_inches='tight')

values2 = values*25
y = [values2[0]]
for i in values2[1:]:
    y.append(y[-1]+i)
x = range(1,5)
plt.figure()
plt.plot(x,y)
plt.scatter(x,y)
plt.grid()
plt.ylim(0,100)
plt.xlabel('Número de autovalores')
plt.ylabel('Porcentaje de varianza explicada')
plt.savefig('varianza_arrestos.png')

datos = pd.read_csv('Cars93.csv')
columnas = ['MPG.city', 'MPG.highway', 'EngineSize', 'Horsepower', 'RPM', 'Rev.per.mile',
          'Fuel.tank.capacity', 'Length', 'Width', 'Turn.circle', 'Weight']
datos2 = datos[columnas]
datos2 = (datos2 - datos2.mean())/datos2.std()

cov_matrix = np.cov(datos2.T)
values, vectors = np.linalg.eig(cov_matrix)

vector_A = vectors[:,0]
vector_B = vectors[:,1]

plt.figure(figsize=(12,12))
datos = np.array(datos)

for i in range(len(datos)):
    model = datos[:,2][i]
    v = np.array(datos2.iloc[i])
    x = np.dot(vector_A, v)
    y = np.dot(vector_B, v)
    plt.text(x,-y, model, fontsize=10, color='blue')
    plt.scatter(x, y, s=0.001)

for j in range(len(columnas)):
    plt.arrow(0.0, 0.0, 2.5*vector_A[j], -2.5*vector_B[j], color='red', head_width=0.1)
    plt.text(2.75*vector_A[j], -2.75*vector_B[j], columnas[j], color='red')
plt.ylim(-3,3)
plt.xlabel('Primera Componente Principal')
plt.ylabel('Segunda Componente Principal')
plt.savefig('cars.png', bbox_inches='tight')

values2 = values*9.09
y = [values2[0]]
for i in values2[1:]:
    y.append(y[-1]+i)
x = range(1,12)
plt.figure()
plt.plot(x,y)
plt.scatter(x,y)
plt.grid()
plt.ylim(0,100)
plt.xlabel('Número de autovalores')
plt.ylabel('Porcentaje de varianza explicada')
plt.savefig('varianza_cars.png')


import math

import numpy as np
from numba import jit

from ..util.derivatives import numerical_gradients as ng

@jit
def sigmoid_prob(x, A, B):
    return 1./(1+math.exp(A*x+B))

@jit
def label(f, threshold=0.):
    if f == threshold:
        return 0
    else:
        return 1 if f > threshold else -1

@jit
def entropy_error_fcn(scores, A, B):
    fcnval = 0.
    for score in scores:
        y = label(score)
        t = (y+1) / 2
        p = sigmoid_prob(score, A, B)
        if p > 0 and p < 1:
            fcnval += - t*math.log(p) - (1-t)*math.log(1-p)
        elif p == 0.:
            fcnval += - (1-t)*math.log(1-p)
        elif p == 1.:
            fcnval += - t*math.log(p)
    return fcnval

@jit
def gradient_search_min_entropy_error_fcn(scores, tol=1e-1, sigma=0.1):
    max_score = max(scores)
    min_score = min(scores)
    initA = -30. / (max_score - min_score)
    initB = -0.5*(max_score+min_score) * initA

    A = initA
    B = initB
    curr_fcn_val = entropy_error_fcn(scores, A, B)
    converged = False
    while not converged:
        dfA, dfB = ng.tensor_gradient(lambda X: entropy_error_fcn(scores, X[0], X[1]),
                                      np.array([A, B]),
                                      tol=tol)
        new_fcn_val = entropy_error_fcn(scores, A-sigma*dfA, B-sigma*dfB)
        if abs(new_fcn_val-curr_fcn_val) < tol:
            converged=True
        curr_fcn_val = new_fcn_val
        A = A-sigma*dfA
        B = B-sigma*dfB

    return A, B


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

# # Álgebra Linear Computacional básica

# ## Objetivos
#
# - Associar conceitos abstratos de Álgebra Linear a estruturas computacionais;
# - Realizar operações básicas com vetores e matrizes;
# - Saber como resolver sistemas lineares de pequeno porte;
# - Calcular autovalores e autovetores em matrizes reais;

# ## Matrizes e vetores
#
# Uma matriz ${\bf A}$ de ordem $m \times n$ pode ser escrita como:
#
# $${\bf A} =
# \begin{bmatrix}
# a_{11} & a_{12} & \ldots & a_{1n} \\
# a_{21} & a_{22} & \ldots & a_{2n} \\
# \vdots & \vdots & \ddots & \vdots \\
# a_{m1} & a_{m2} & \ldots & a_{mn}
# \end{bmatrix}$$
#

# As colunas de uma matriz com $m$ linhas correspondem a $n$ vetores $\vec{v}_1, \vec{v}_2, \ldots,\vec{v}_n$, de maneira que
#
# $${\bf A} =
# \begin{bmatrix}
# \vec{v}_1 & \vec{v}_2 & \ldots & \vec{v}_n \\
# \end{bmatrix}$$
#
# é uma representação equivalente para a matriz anterior.

# Em Python, usamos o módulo _numpy_ para trabalhar com matrizes e vetores. Vetores são _arrays_ 1D, ao passo que matrizes são _arrays_ 2D, ou seja, um "_array_ de _arrays_".

# **Exemplo.** Represente computacionalmente os vetores do $\mathbb{R}^3$ a seguir:
#
# - $\vec{u} = 3\vec{i} - 2\vec{j} + 9\vec{k}$
# - $\vec{v} = -2\vec{i} + 4\vec{j}$
# - $\vec{w} = \vec{i}$

# In[1]:

import numpy as np

u = np.array([3,-2,9])
v = np.array([-2,4,0])
w = np.array([1,0,0])
print(u), print(v), print(w);

# **Exemplo.** Represente computacionalmente a matriz 3 x 3 dada por
#
# $${\bf A} =
# \begin{bmatrix}
# \vec{u} & \vec{v} & \vec{w} \\
# \end{bmatrix}$$

# Observe que os vetores devem ser escritos como "coluna".

# In[2]:

A = np.array([[3,-2,1],[-2,4,0],[9,0,0]])
print(A)

# **Exemplo.** Represente computacionalmente a matriz
#
# $$
# \begin{bmatrix}
# 2 & -2 \\
# 4 & 1 \\
# 2 & 1
# \end{bmatrix}$$
#

# Vamos escrever linha por linha.

# In[3]:

L1 = np.array([2,-2]) # linha 1
L2 = np.array([4,1]) # linha 2
L3 = np.array([2,1]) # linha 3

A2 = np.array([L1,L2,L3]) # lista de listas
print(A2)

# Diretamente, poderíamos também definir:

# In[4]:

A3 = np.array([[2,-2],[4,1],[2,1]])
print(A3)

# Note que cada lista representa uma linha.

# ### Transposição
#
# Matrizes e vetores podem ser transpostos com `.T`:

# In[5]:

A2T = A2.T
print(A2T)

# Assim, com as variáveis antes definidas, poderíamos, equivalentemente, fazer para ${\bf A}$:

# In[6]:

# modo 2: matriz transposta
At = np.array([u,v,w]).T
print(At)

# ### Teste de igualdade
#
# Podemos verificar a igualdade entre matrizes como

# In[7]:

A == At

# No caso de vetores:

# In[8]:

# vetor "linha" não difere
# do vetor "coluna"
u == u.T

# ## Operações fundamentais

# ### Adição e subtração
#
# A adição (subtração) de matrizes e vetores pode ser realizada de modo usual com computação vetorizada.

# **Exemplo:** $\vec{u} \pm \vec{v}$

# In[9]:

# adição
ad = u + v
print(ad)

# subtração
sub = u - v
print(sub)

# **Exemplo:** $\bf{A} \pm \bf{B}$, com
#
# $${\bf B} =
# \begin{bmatrix}
# \vec{u} & 2\vec{u} & 3\vec{v} \\
# \end{bmatrix}$$

# In[10]:

# adição

B = np.array([u,2*u,3*v]).T

print(B)

ad2 = A + B
print(ad2)

sub2 = A - B
print(sub2)

# ### Produto interno
#
# O produto interno $\langle \vec{u}, \vec{v}\rangle$ é computado com `.dot`:

# In[11]:

pi = np.dot(u,v)
print(pi)

pi2 = np.dot(np.array([3,1]),np.array([-1,-1]))
print(pi2)

# ### Norma de vetor
#
# A norma $||\vec{u}||$ de um vetor $\vec{u}$ é calculada como:

# In[12]:

np.sqrt(np.dot(u,u))

# ### Produto de matrizes
#
# O produto $\bf{A}\bf{B}$ entre matrizes bidimensionais pode ser calculado com `np.dot`, mas recomenda-se usar `np.matmul`.

# In[13]:

# não tem o mesmo efeito para
# matrizes A e B de tamanhos arbitrários
np.dot(A,B)

# In[14]:

# uso recomendado para a operação tradicional
np.matmul(A,B)

# ### Produto entre matriz e vetor
#
# Neste caso, sendo ${\vec{\vec A}}$ (dois símbolos indicam que a matriz é uma grandeza de ordem 2, ao passo que o vetor é de ordem 1 e aqui usamos para consistência de notação) e ${\vec{b}}$ uma matriz $m \times n$ e um vetor $n \times 1$, respectivamente, o produto $\vec{\vec{A}}\vec{b}$ é dado por:

# In[15]:

b = np.array([3,4,1])

np.dot(A,b)

# ## Demais operações com `numpy.linalg`
#
# Para outras operações, devemos utilizar o submódulo `numpy.linalg`. Para importá-lo com o _alias_ `lin`, fazemos:

# In[16]:

import numpy.linalg as lin

# ### Determinante
#
# O determinante de ${\bf A}$ é dado por $\det({\bf A})$ e pode ser computado pela função `det`.

# In[17]:

# calculando o determinante da matriz
det = lin.det(A)
print(det)

# ### Inversa de uma matriz
#
# A inversa de uma matriz é dada por ${\bf A}^{-1}$, onde ${\bf A}{\bf A}^{-1}={\bf I}$, e ${\bf I}$ é a matriz identidade.
# Para usar esta função, devemos fazer:

# In[18]:

B2 = np.array([[1,2,3],
              [2,3,4],
              [1,2,0]])

B3 = lin.inv(B2)
print(np.matmul(B3,B2))

# ### Soluções de sistemas lineares
#
# Para resolver sistemas lineares, devemos escrever as equações em forma de matriz, ou seja, ${\vec{\vec A}}\vec{x} = \vec{b}$ e utilizar `solve`.

# **Exemplo:** Resolva o sistema linear abaixo:
#
# $$\begin{cases}
# ax + by = c \\
# dx + ey = f
# \end{cases}$$
#
# para $a = -4$, $b = 1$, $c = 1/2$, $d = 3$, $e = 5$ e $f = 10$,

# In[19]:

A = np.array([[-4,1],[1/2,3]])

b = np.array([5,10]).T

# solução
x = lin.solve(A, b)
print(x)

# ### Inversa de matriz
#
# A inversa de uma matriz (faça esta operação apenas para matrizes quadradas de pequena dimensão) pode ser encontrada como:

# In[20]:

Ainv = lin.inv(A)
print(Ainv)

# Para realizar uma "prova real" da solução do sistema anterior, poderíamos fazer:

# In[21]:

x2 = np.dot(lin.inv(A), b)
print(x2)

# Note, entretanto que:

# In[22]:

x == x2

# Isto ocorre devido a erros numéricos. Um teste mais adequado deve computar a norma do vetor "erro", dado por ${\bf e} = \bf{b} - \bf{A}\bf{x}$. A norma pode ser calculada diretamente com:

# In[23]:

e = b - np.dot(A,x)
lin.norm(e)

# Isto é, esperamos que $||{\bf e}|| \approx 0$ quando a solução do sistema for exata, a menos de erros numéricos.

# ## Algumas matrizes especiais

# ### Nula
#
# Para criar uma matriz nula de ordem _m x n_, usamos `zeros`.

# In[24]:

m,n = 3,4
np.zeros((m,n))

# ### Identidade
#
# Uma matriz identidade (quadrada) de ordem _p_ é criada com `eye`.

# In[25]:

p = 4
np.eye(p)

# ### Matriz de "uns"
#
# Uma matriz composta apenas de valores 1 de ordem _m x n_ pode ser criada com `ones`:

# In[26]:

np.ones((3,5))

# ### Triangular inferior
#
# A matriz triangular inferior de uma dada matriz pode ser criada com `tril`. Note que podemos também defini-la explicitamente, linha a linha.

# In[27]:

# os valores correspondentes
# são zerados
np.tril(B)

# ### Triangular superior
#
# A matriz triangular superior de uma dada matriz pode ser criada com `triu`. Note que podemos também defini-la explicitamente, linha a linha.

# In[28]:

np.triu(B)

# **Exercício.** Por que há dois valores `False` no teste a seguir?

# In[29]:

B == np.tril(B) + np.triu(B)

# ## Autovalores e autovetores
#
# Um vetor ${\bf v} \in V$, ${\bf v} \neq {\bf 0}$ é vetor próprio de ${\bf A}$ se existir $\lambda \in \mathbb{R}$ tal que
#
# $${\bf Av}=\lambda {\bf v}.$$
#
# O número real $\lambda$ é denominado valor próprio (autovalor) de ${\bf A}$ associado ao vetor próprio (autovetor) ${\bf v}$.

# In[30]:

A = np.array([[2,1],
             [1,-5]])

w, v = lin.eig(A)
a,b = w

# autovalores
print(a,b)

# autovetor 1
print(v[:,0])

# autovetor 2
print(v[:,1])

# ## Somas e valores extremos
#
# Podemos calcular somas de elementos de matrizes e vetores de maneiras diferentes. Para matrizes, em particular, há soma total, por linha, ou por coluna.

# In[31]:

a = np.array([1,-2,-3,10])

# soma de todos os elementos
np.sum(a)

# In[32]:

# modo alternativo
a.sum()

# In[33]:

# soma total de matriz
O = np.ones((5,3))

np.sum(O)

# In[34]:

# modo alternativo
O.sum()

# In[35]:

# soma por linha
np.sum(O,axis=0)

# In[36]:

# soma por coluna
np.sum(O,axis=1)

# Valores máximos e mínimos, absolutos ou não, também podem ser computados com funções simples.

# In[37]:

# min
np.min(a)

# In[38]:

# max
np.max(a)

# In[39]:

# modo alternativo
a.min()

# In[40]:

a.max()

# In[41]:

# mínimo absoluto
np.abs(a).min()

# In[42]:

# máximo absoluto
np.abs(a).max()

# In[43]:

O2 = np.array([[-4,5],[2,7]])

# min
np.min(O2)

# In[44]:

# max
np.max(O2)

# In[45]:

O2.min()

# In[46]:

O2.max()

# In[47]:

np.abs(O2).min()

# In[48]:

np.abs(O2).max()


import numpy as np

def Jacobian(func,y,fev=None, args=()):
    '''
    Forward differentiation formula to estimate the jacobian.
    func: R^n --> R^n
    '''
    fe_tot = 0
    if(fev is None):
        fev=func(y,args)
        fe_tot +=1

    n=len(y)
    J= np.zeros((n,n))
    # The step is the machine precision * 10^2
    # It is problem dependent... (add as ~tolerance)
    # Even better, copy how h is chosen by scipy/integrate/odepack/prja.f (lines 80-100)
    # Explained in section 3.4.5 of Description and Use of LSODE, the Livermore Solver for ODEs
    h = np.finfo(np.dtype(type(y[0]))).eps*1e2
    for i in range(n):
        yi = y[i]
        y[i] += h
        J[:,i] = 1/h*(func(y,args)-fev)
        y[i]=yi
        fe_tot +=1

    return (J,fe_tot)


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

def costFunctionReg(theta, x, y, _lambda):
    import numpy as np
    from ex2_logistic_regression.sigmoid import sigmoid
    h = sigmoid(np.dot(x, theta.T))

    m, _ = np.shape(x)
    cost = (np.dot(-y, np.log(h)) - np.dot(1 - y, (np.log(1 - h))))
    reg_param = sum(np.power(theta[1:], 2)) * _lambda / 2 / m
    return (cost + reg_param) / m

def compute_grad_reg(theta, x, y, _lambda):
    import numpy as np
    m, n = np.shape(x)
    from ex2_logistic_regression.sigmoid import sigmoid

    h = sigmoid(np.dot(x, theta.T))
    t = h - y
    grad = [sum(np.dot(t, x)) / m] * n

    for c in range(1, n):
        grad[c] = grad[c] + _lambda / m * theta[c]
    return grad


import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets

class PCA:
    def __init__(self, n_components):
        self.n_components = n_components
        self.components, self.mean = None, None

    def fit(self, X):
        # Mean centering
        self.mean = np.mean(X, axis=0)
        X = X - self.mean

        # Get the covariance
        cov = np.cov(X.T)

        # eigenvalues, eigenvectors
        eigenvalues, eigenvectors = np.linalg.eig(cov)

        # Eigen vector sort
        eigenvectors = eigenvectors.T
        idxs = np.argsort(eigenvalues)[::-1]

        eigenvectors = eigenvectors[idxs]

        # store first n eigenvectors
        self.components = eigenvectors[0: self.n_components]

    def transform(self, X):
        X = X - self.mean
        return np.dot(X, self.components.T)

# Testing
if __name__ == "__main__":
    data = datasets.load_iris()
    X = data.data
    y = data.target

    # Project the data onto the 2 primary principal components
    pca = PCA(2)
    pca.fit(X)
    X_projected = pca.transform(X)

    # Display the shapes
    print("Shape of X:", X.shape)
    print("Shape of transformed X:", X_projected.shape)

    # Get the projections
    x1, x2 = X_projected[:, 0], X_projected[:, 1]

    # Create the graph
    plt.scatter(x1, x2, c=y, edgecolor="none", alpha=0.8, cmap=plt.cm.get_cmap("viridis", 3))

    plt.xlabel("Principal Component 1")
    plt.ylabel("Principal Component 2")
    plt.colorbar()
    plt.show()


import math
import torch
import numpy as np
from sklearn.decomposition import PCA

import logging
logger = logging.getLogger('nmtpytorch')

def center_emb(x):
    logger.info(f'Apply centering')
    zero_rows = np.all(x == 0, axis=1)
    # transform embedding to make the average to zero vector
    # excluding zero vectors
    x -= x[~zero_rows].mean(axis=0)
    # leave zero vector as it was
    x[zero_rows] = 0
    return x

def norm_emb(x):
    x_norm = np.linalg.norm(x, axis=1, keepdims=True)
    x_norm[x_norm==0] = 1.
    return x / x_norm

def apply_abtt(embs, centering=True, n_pca=3, applys_norm=True):
    pad_emb = embs[1]
    word_embs = embs[1:]

    # Centering
    word_embs = center_emb(word_embs) if centering else word_embs

    # Substract PCA components
    if n_pca > 0:
        logger.info(f'Substracting PCA components (n={n_pca})')
        pca = PCA(n_components=n_pca)
        pca.fit(word_embs)
        # use tensor to calculate vectors for each PCA components
        embs_pca = pca.transform(word_embs).reshape(-1, n_pca, 1) * \
            pca.components_.reshape(1, n_pca, -1)
        # and accumulate them to get final PCA values to substract
        embs_pca = embs_pca.sum(axis=-2)

        word_embs -= embs_pca

    word_embs = norm_emb(word_embs) if applys_norm else word_embs

    return np.append([pad_emb], word_embs, axis=0)

def apply_lc(embs, k=10, applys_norm=True):
    assert k > 0, f'`k_nn` not found or should be a positive value'
    logger.info(f'Apply localized centering with {k}-NN')

    special_embs = torch.torch.from_numpy(embs[:1])

    # use GPU to calculate matrix production at scale
    word_embs = torch.torch.from_numpy(embs[1:])
    n_vocab, dim = word_embs.shape

    # use cosine similarity to select k-NN
    word_emb_norms = word_embs.norm(dim=-1, keepdim=True)
    word_sims = word_embs @ word_embs.t() / word_emb_norms / word_emb_norms.t()

    # set self similarity to 0 to avoid argsort from selecting itself
    word_sims[range(n_vocab), range(n_vocab)] = 0
    top_k = word_sims.argsort(descending=True)[:, :k]

    # calculate localized centroid
    word_embs = word_embs - word_embs[top_k].mean(dim=-2)

    embs = torch.cat([special_embs, word_embs]).numpy()
    embs = norm_emb(embs) if applys_norm else embs

    return embs


import pandas as pd
import numpy as np
#
from pyta.helper import er

def kaufmans_adaptive_moving_average(c: pd.Series, n: int = 10, n_fast: int = 2, n_slow: int = 30) -> pd.Series:
    er_ = er(c, n)
    # SC = [ER x (2/(2+1) - 2/(30+1)) + 2/(30+1)]2
    fast_ema: float = 2 / (n_fast + 1)
    slow_ema: float = 2 / (n_slow + 1)
    sc = (er_ * (fast_ema - slow_ema) + slow_ema) ** 2
    kama = pd.Series([np.nan] * (len(c) - 1))
    # First kama entry is close price.
    kama[n - 1] = c[n - 1]
    # KAMA = Prior KAMA + SC x (Price - Prior KAMA)
    for i in range(n, len(c)):
        kama[i] = (kama[i - 1] + sc[i] * (c[i] - kama[i - 1]))
    return kama

kaufmans_adaptive_moving_average.__doc__ = """Kaufman's Adaptive Moving Average (KAMA)

A moving average that accounts for volatility and market noise.

Source:
    https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average

Formula:
    ER = Change / Volatility
    SC = [ER * (2 / (n_fast + 1) - 2 / (n_slow + 1)) + 2 / (n_slow + 1)] ** 2
    KAMA = Prior KAMA + SC x (Price - Prior KAMA)

Arguments:
    c (pdSeries) : Close series.
    n (int)      : Periods for ER [Default: 10]
    n_fast (int) : Periods for fast SC [Default: 2]
    n_slow (int) : Periods for slow SC [Default: 30]

Returns:
    pdSeries
"""


import scipy.special as sc
from scipy import integrate
from numpy import exp

yp = 40
yc = 50
alpha = 0.2
beta = 1
t = 7
delta_y = 1.4

shape = alpha*t
gamma_shape = sc.gamma(shape)
gamma_shape_1 = sc.gamma(shape-1)
gammainc_yp = sc.gammaincc(shape-1, yp)*gamma_shape_1  # because funct calculates normalized value; divided by gamma(a)
k = (beta**shape)/gamma_shape

# Creating list of values to approximate f(y-yp) and estimate the integral using trapezoidal rule
n = 50
step = (yc-yp)/n
Y = [yp+(i*step) for i in range(n+1)]

def u(y0):  # primitive found at https://www.integral-calculator.com/
    return k*((sc.gammaincc(shape-1, yp-y0)*gamma_shape_1)-gammainc_yp)

def g(x):
    return k*((x**(shape-1))*exp(-beta*x))

def G(x):
    return sc.gammaincc(shape, beta*x)  # the division by gamma_shape is included

def fy(y):
    f = lambda y0: u(y0)*g(y-y0)/(1+G(yp-y0))
    fy_yp = integrate.quad(f, yp-delta_y, yp)
    print("Error estimated for integral in ", y, " is ", fy_yp[1])   # quick check
    return fy_yp[0]

if __name__ == "__main__":
    fy_array = list(map(fy, Y))
    ps = integrate.simps(fy_array, dx=step)
    print("ps = ", ps)


import numpy as np

def ra_to_deg(hour: float, minute: float, second: float) -> float:
    """ Convert RA from (hr, min, sec) -> degree """
    return  (hour + minute / 60. + second / 3600.) * 15.

def dec_to_deg(degree: float, minute: float, second: float) -> float:
    """ Convert Dec from (deg, arcmin, arcsec) -> degree """
    deg_sign = 1. if (degree + np.absolute(degree)) > 0 else - 1.
    return  deg_sign * (np.absolute(degree) + minute / 60. + second / 3600.)

def dist_modulus_to_distance(dm: float) -> float:
    """ Convert distance modulus -> distance (pc)

    : dm : distance modulus
    """
    return  10.**(dm / 5. + 1.)

def distance_to_dist_modulus(distance: float) -> float:
    """ Convert distance (pc) -> distance modulus """
    return  5. * np.log10(distance) - 5.


# This performs a centered (or unbiased) root mean squared error (cRMSE)
# calculation, essentially calculate rmse using de-meaned data:
# rmse = root(mean( ( (x - mean(x)) - (y - mean(y)) )^2 ))
#
# Joseph Lee <joseph.lee at pnnl.gov>

import numpy as np

class crmse:

    def compute(self, x, y):

        x_demean = x - np.mean(x)
        y_demean = y - np.mean(y)

        return float(np.sqrt(np.mean((x_demean - y_demean)**2)))


import math
import numpy as np
import scipy

def cosine_sim(vecA, vecB):
  """
  Return the cosine simularity between two vectors vecA and vecB.
  @return integer between zero and one inclusive. One indicates identicial
  """
  csim = np.dot(vecA, vecB) / (np.linalg.norm(vecA) * np.linalg.norm(vecB))
  if np.isnan(np.sum(csim)):
    return 0
  return csim

def angular_similarity(vecA, vecB):
  """
  Return the angular simularity between two vectors vecA and vecB.
  @return integer between zero and one inclusive. One indicates identicial
  """
  #Ensure cosine is between zero and one
  cosdist=max(0,min(scipy.spatial.distance.cosine(vecA,vecB),1))
  return 1-math.acos(1-cosdist)/math.pi


import numpy as np
import random
random.seed(0)

class Perceptron:
    def __init__(self, eps=0.01, max_iters=2000):
        self.eps = eps
        # self.best_params = None
        self.losses = None
        self.n_iters = None
        self.activation_func=self._step_func
        self.max_iters = max_iters
        self.weights = None  #size=(n_iter,max_iters)
        self.bias= None #size=(1,max_iters)

    def _step_func(self, X):
        return np.where(X >= 0, 1, -1)

    def compute_loss(self, X, y, w, b): # Ls = |xi ; hs(xi) != yi| / |s|
        n = len(X)
        S = 0

        for i in range(n):

            linear_output = np.dot(X[i], w) + b
            y_predicted =self.activation_func(linear_output)
            if y_predicted != y[i]:
                S += 1

        Ls = S / n

        return Ls

    def fit(self, X, y):

        n_samples,n_features = X.shape

        # Init weights and bias
        # self.weights=np.zeros(n_features)
        # self.weights=[random.random()*1 for i in range(n_features)]
        # self.weights=np.ones(n_features)
        self._initialise_weights(X)
        self.bias= 0

        # Compute the loss function
        loss = self.compute_loss(X, y, self.weights,self.bias)
        n_iters = 0
        Weights = [(self.weights,self.bias)]
        self.losses = [loss]

        while loss >= self.eps: # Stop condition

            for i in range(n_samples):
                # Compute the linear output
                linear_output = np.dot(X[i], self.weights) + self.bias

                # Adjusting weights and bias
                if self.activation_func(linear_output) * y[i] < 0: # If the point is misclassified
                    self.weights = self.weights + y[i] * X[i]   # w = w + yi.xi
                    self.bias = self.bias + y[i]                # b = b + yi

            Weights.append((self.weights,self.bias))

            loss = self.compute_loss(X, y, self.weights, self.bias )

            self.losses.append(loss)

            n_iters = n_iters + 1

            if n_iters >= self.max_iters:
                break

        self.n_iters = n_iters
        print("le nombre d'iterations est: ", self.n_iters)

        return [Weights,self.losses,self.n_iters]

    def predict(self, X):
        linear_output= np.dot(X, self.weights)+self.bias
        y_predicted= self.activation_func(linear_output)
        return y_predicted

    def _initialise_weights(self, X):
        """ Initialise weigths - normal distribution sample with standard dev 0.01 """
        random_gen = np.random.RandomState(1)
        self.weights = random_gen.normal(loc = 0.0, scale = 0.01, size = X.shape[1])
        return self


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

N = 50
xs = np.random.rand(N)
ys = 5 * xs + 4
noise = (np.random.rand(N) - 0.5) * 0.1
ys = ys + noise

df = pd.DataFrame(columns=['x', 'y'])
df['x'] = xs
df['y'] = ys
df.to_csv('./assets/hw3/pA1.csv', index=None, float_format='%.3f')

fig, ax = plt.subplots()
ax.plot(xs, ys, '.')
plt.show()

N = 50
xs = np.random.rand(N) - 0.5
ys = -2 * xs ** 2 + 1 * xs + 4
noise = (np.random.rand(N) - 0.5) * 0.1
ys = ys + noise

df = pd.DataFrame(columns=['x', 'y'])
df['x'] = xs
df['y'] = ys
df.to_csv('./assets/hw3/pA2.csv', index=None, float_format='%.3f')

fig, ax = plt.subplots()
ax.plot(xs, ys, '.')
plt.show()


## Speed Comparison ##

# This program is to show the advantage of using numpy arrays for matrix functions
# and how they compare to lists. Increase the x/y values to make the matrix larger.
# numpy is much faster.

# Results depend on the system, but here is some rough numbers based on mine
# Eg. 200x200 matrix = ~1115 times faster
# Eg. 2000x2000 matrix = ~

import numpy as np
# import cupy as np # Used for cuda support
from datetime import datetime

# matrix size
x = 2000
y = 2000

# create an x*y matrix
A = np.random.choice(10, size=(x,y))
# create an x*y matrix
B = np.random.choice(10, size=(x,y))
# create an x*y matrix for the results
result= np.zeros((x,y))

# take timestamp
t0 = datetime.now()
# Slow calculation
for i in range(len(A)):
  # iterating by coloum by B
  for j in range(len(B[0])):
      # iterating by rows of B
      for k in range(len(B)):
          result[i][j] += A[i][k] * B[k][j]
# read duration
dt1 = datetime.now() - t0

t0 = datetime.now()
#Fast calculation
result = A @ B # can also be done with np.dot(A,B) or matmul
dt2 = datetime.now() - t0

print("Time for slow method:", dt1.total_seconds())
print("Time for fast method:", dt2.total_seconds())
print("(slow / fast) Factor:", dt1.total_seconds() / dt2.total_seconds())


"""
Title: Finding the Total Charge of an Equal Charge Electric Dipole using the Two-dimensional Trapezoidal Method

Solution to Problem Set 2, Problem 3
~ Arsh R. Nadkarni

To run:
python charge_dipole_2_3.py

"""
# import libraries
import math
import numpy as np
import matplotlib.pyplot as plt

def totalcharge(n):
    # function to be integrated (integrand)
    def f(theta,phi):
        return 2*np.sin(theta)+0.1*np.cos(phi)*(np.sin(theta))**2
    # construct the trapezoidRule function based on Problem 2
    def trapezoidalRule(f,n):
        a = 0
        b = 2*np.pi
        c = 0
        d = np.pi
        hx = (b - a)/n
        hy = (d - a)/n
        I1 = f(a, c) + f(b, d)
        I2 = 0
        i = 0
        j = 0
        for i in range (n+1):
            for j in range (n+1):
                x = a + (i * hx)
                y = a + (j * hy)
                I2 += f(x, y)
                i += 1
                j += 1
        I = ((hx/2) * (hy/2) * (I1 + (2 * I2)))
        return I
    # function to return the total charge
    def answer(f,n):
        q=1.60217662*1e-19
        global ans
        ans=(q/(4*np.pi))*(trapezoidalRule(f,n))
        return ans
    return answer(f,n)

# function to evaluate the error and plot the relative error as a function of n on a log-log plot, comparing it with the expected order of convergence
def B():
    t = 4
    N = np.zeros(t)
    err = np.zeros(t)
    I = np.zeros(t)
    aval = -1.56*1e-19 # analytical value of the integral (Calculated by Hand)
    for i in range(t):
        n = 50 * (2**i) # n= 50,100,200,400
        N[i] = n
        I[i] = totalcharge(n)
        err[i] = abs(aval-I[i])
    figure,a = plt.subplots()
    a.loglog(N,err,'r--',label='Relative Error',lw=3)
    a.loglog(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    a.set_xlabel('log(n)',fontweight='bold')
    a.set_ylabel('log(Relative Error)',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n: log-log Plot', fontweight='bold')
    plt.show()
    figure,b = plt.subplots()
    b.plot(N,err,'r--',label='Relative Error',lw=3)
    b.plot(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    b.set_xlabel('n',fontweight='bold')
    b.set_ylabel('Relative Error',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n', fontweight='bold')
    plt.show()
B()

"""
The value obtained numerically converges to the expected result (O(1/n^2)) at n = 400 approximately.
This can be verified using the plots above.

"""

Problem Sets/Problem Set 2/charge_dipole_2_3.py


"""
    Автор: Орел Максим
    Группа: КБ-161
    Вариант: 11 (task 1: var 2; task 2: var 11)
    Дата создания: 6/05/2018
    Python Version: 3.6
"""
import matplotlib.pyplot as plt
import numpy as np

# http://mathprofi.ru/metod_prjamougolnikov.html
# http://mathprofi.ru/formula_simpsona_metod_trapecij.html

a = -1
b = 2
h = 0.1
h2 = [2, 4, 6, 8, 10, 10000]

def pauline(point):
    return 1 + 0.9 * point + 0.8 * point ** 2 + 0.7 * point ** 3 + 0.5 * point ** 4

def rectangle_left(left, right):
    return (right - left) * pauline(left)

def rectangle_right(left, right):
    return (right - left) * pauline(right)

def trapeze(left, right):
    return (right - left) / 2 * (pauline(left) + pauline(right))

def simpson(left, right):
    return (right - left) / 6 * (pauline(left) + 4 * pauline((left + right) / 2) + pauline(right))

def f(point):
    return (2 * point) ** 3 * np.cos(point)

def trapeze2(left, right):
    return (right - left) / 2 * (f(left) + f(right))

def simpson2(left, right):
    return (right - left) / 6 * (f(left) + 4 * f((left + right) / 2) + f(right))

def sum(coef, left, right):
    return_sum = 0
    for i in coef:
        return_sum += f((right + left) / 2 + ((right - left) / 2) * i)
    return return_sum

if __name__ == "__main__":
    try:
        x = np.linspace(a, b)
        plt.plot(x, pauline(x))
        plt.grid()
        plt.show()

        # task 1
        print('Метод левого прямоугольника: {0}'.format(rectangle_left(a, b)))
        print('Метод правого прямоугольника: {0}'.format(rectangle_right(a, b)))
        print('Метод трапеции: {0}'.format(trapeze(a, b)))
        print('Метод Симпсона: {0}'.format(simpson(a, b)))

        points = np.arange(a, b + h, h)
        A = [0, 0, 0, 0]
        for i in range(0, len(points) - 1):
            A[0] += rectangle_left(points[i], points[i + 1])
            A[1] += rectangle_right(points[i], points[i + 1])
            A[2] += trapeze(points[i], points[i + 1])
            A[3] += simpson(points[i], points[i + 1])

        print('Метод левого прямоугольника (обобщённый): {0}'.format(A[0]))
        print('Метод правого прямоугольника (обобщённый): {0}'.format(A[1]))
        print('Метод трапеции (обобщённый): {0}'.format(A[2]))
        print('Метод Симпсона (обобщённый): {0}'.format(A[3]))

        # task 2
        x = np.linspace(a, b)
        plt.plot(x, f(x))
        plt.grid()
        plt.show()

        print()
        for k in h2:
            h = (b - a) / k
            points = np.arange(a, b + h, h)
            A = [0, 0]
            for i in range(0, len(points) - 1):
                A[0] += trapeze2(points[i], points[i + 1])
                A[1] += simpson2(points[i], points[i + 1])
            print('Метод трапеций (h = (b-a)/{0}): {1}'.format(k, A[0]))
            print('Метод Симпсона (h = (b-a)/{0}): {1}'.format(k, A[1]))
            print()

        coef = [0.707197, 0, -0.707197]
        answer = sum(coef, a, b) * (b - a) / 3
        print('Чебышев (n = 3):', answer)

        coef = [0.794654, 0.187592, -0.187592, -0.794654]
        answer = sum(coef, a, b) * (b - a) / 4
        print('Чебышев (n = 4):', answer)

        coef = [0.832498, 0.374541, 0, -0.374541, -0.832498]
        answer = sum(coef, a, b) * (b - a) / 5
        print('Чебышев (n = 5):', answer)

        # answer must be near -2.44

    except Exception as e:
        print(e)


#! /usr/bin/env python

import rospy
import math
from converter.msg import roll_yaw_pitch, quaternion
from math import atan2
import numpy as np

global euler
def rotation(msg):
	global x, y, z, w
	x = msg.x
	y = msg.y
	z = msg.z
	w = msg.w

	sinr_cosp = 2 * (w * x + y * z)
    cosr_cosp = 1 - 2 * (x**2 + y**2)
    roll = np.arctan2(sinr_cosp, cosr_cosp)

    sinp = 2 * (w * y - z * x)
    pitch = np.where(np.abs(sinp) >= 1,
                     np.sign(sinp) * np.pi / 2,
                     np.arcsin(sinp))

    siny_cosp = 2 * (w * z + x * y)
    cosy_cosp = 1 - 2 * (y**2 + z**2)
    yaw = np.arctan2(siny_cosp, cosy_cosp)

    euler = [roll, pitch, yaw]
    return euler

def main():
	rospy.init_node("my_converter")

	sub = rospy.Subscriber("topic1", quaternion, rotation)
	pub = rospy.Publisher("topic2", roll_yaw_pitch, queue_size = 2)

	r = rospy.Rate(1)
	msg_value = roll_yaw_pitch()
	while not rospy.is_shutdown():

		msg_value.roll = euler[0]
		msg_value.pitch = euler[1]
		msg_value.yaw = euler[2]

		pub.publish(msg)

		r.sleep()


import numpy as np

def classic_multi(A, B):
    if len(B) != len(A[0]):
        print("Different dimension of the matrics")
        return

    n = len(A)
    m = len(A[0])
    t = len(B[0])

    answer = [[0 for i in range(t)] for j in range(n)]
    for i in range(n):
        for j in range(m):
            for k in range(t):
                answer[i][k] += A[i][j] * B[j][k]
    return answer

if __name__ == "__main__":
    A = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
    B = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8]]
    print(classic_multi(A, B))


__author__ = 'HarperMain'

from scipy.stats import norm
import numpy as np

# class EuropeanLookbackGreeks():
#
#     def __init__(self, spot, strike, rate, dividend, sigma, expiry, t):
#
#         self.spot = spot
#         self.strike = strike
#         self.rate = rate
#         self.dividend = dividend
#         self.sigma = sigma
#         self.expiry = expiry
#         self.t = t
#
#         self.tau = tau = self.expiry-self.t
#         self.d1 = (np.log(spot/strike) + (rate - dividend + 1/2 * sigma**2)*tau) / (np.sqrt(tau))
#         self.d2 = self.d1 - sigma * np.sqrt(tau)
#
#         self.Delta = self.EuroDelta()
#         self.Gamma = self.EuroGamma()
#         self.Theta = self.EuroTheta(rate, strike, tau, sigma, spot)
#         self.Rho = self.EuroRho(tau, strike, rate)
#         self.Vega = self.EuroVega(tau, spot)

def EuroDelta(d1):
    # Delta is the price sensitivity
    Delta = norm.cdf(d1)
    return Delta

def EuroGamma(d1, spot, sigma, tau):
    # Gamma is a second order time-price sensitivity
    Gamma = norm.ppf(norm.cdf(d1)) / (spot*sigma*np.sqrt(tau))
    return Gamma

def EuroTheta(d1, d2, rate, strike, tau, sigma, spot):
    # Theta is the time sensitivity
    Theta = -rate*strike*np.exp(-rate*tau) * norm.cdf(d2) - \
            (sigma*spot*norm.ppf(norm.cdf(d1))/(2*tau))
    return Theta

def EuroRho(d2, tau, strike, rate):
    # Rho is the interest rate sensitivity
    Rho = tau*strike*np.exp(-rate*tau) * norm.cdf(d2)
    return Rho

def EuroVega(d1, tau, spot):
    # Vega is a volatility sensitivity
    Vega = np.sqrt(tau)*spot*norm.cdf(d1)
    return Vega

def EuroD1(spot, strike, rate, dividend, sigma, tau):
    d1 = ((np.log(spot/strike) + (rate - dividend + 1/2 *
                                 sigma**2)*tau))/ (np.sqrt(tau))
    return d1

def EuroD2(d1, sigma, tau):
    d2 = d1 - sigma * np.sqrt(tau)
    return d2

    # def ReturnGreeks(self):
    #     EuroGreeks = {}
    #     EuroGreeks['Delta'] = self.Delta
    #     EuroGreeks['Gamma'] = self.Gamma
    #     EuroGreeks['Theta'] = self.Theta
    #     EuroGreeks['Rho'] = self.Rho
    #     EuroGreeks['Vega'] = self.Vega
    #
    #     return EuroGreeks


from math import sqrt,cos,sin
import numpy as np
from numpy.linalg import inv
import matplotlib.pyplot as plt

#### Sub routine 1 ####

##### Calculating effective properties of unidirectional composite lamina ####
## Inputs - E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre
## Outputs - E1 ,E2, E3, nu12, nu13, nu23, G12, G13, G23 of effective lamina
def effective_lamina_properties(E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre,chamis_correction=False):
    E1 = Volume_fraction_fibre*E1_fibre + (1-Volume_fraction_fibre)*E_matrix
    E2 = (E2_fibre * E_matrix )/(E2_fibre*(1-Volume_fraction_fibre)+E_matrix*Volume_fraction_fibre)
    E3 = (E2_fibre * E_matrix )/(E2_fibre*(1-Volume_fraction_fibre)+E_matrix*Volume_fraction_fibre)
    nu12 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu12_fibre
    nu13 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu12_fibre
    nu23 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu23_fibre
    G12 = (G12_fibre * G_matrix)/(G12_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    G13 = (G12_fibre * G_matrix)/(G12_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    G23 = (G23_fibre * G_matrix)/(G23_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    if chamis_correction :
        E22 = (E2_fibre * E_matrix )/(E2_fibre*(1-sqrt(Volume_fraction_fibre))+E_matrix*sqrt(Volume_fraction_fibre))
        E22 = (E2_fibre * E_matrix )/(E2_fibre*(1-sqrt(Volume_fraction_fibre))+E_matrix*sqrt(Volume_fraction_fibre))
        G12 = (G12_fibre * G_matrix)/(G12_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
        G13 = (G12_fibre * G_matrix)/(G12_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
        G23 = (G23_fibre * G_matrix)/(G23_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
    E1,E2,E3,nu12,nu13,nu23,G12,G13,G23 = round(E1,3),round(E2,3),round(E3,3),round(nu12,3),round(nu13,3),round(nu23,3),round(G12,3),round(G13,3),round(G23,3)
    return E1,E2,E3,nu12,nu13,nu23,G12,G13,G23

#### Sub routine 2 ####

##### Calculating S and Q matrix for lamina ####
## Inputs -E1,E2,E3,nu12,nu13,nu23,G12,G13,G23
## Outputs - Q(3x3 Matrix), S(3x3 Matrix)

def stiffness_compliance_matrix(E1=0,E2=0,E3=0,nu12=0,nu13=0,nu23=0,G12=0,G13=0,G23=0):
    S_matrix = np.zeros((6,6),dtype=float)
    if(E1!=0):
        S_matrix[0][0] = 1/E1
        S_matrix[0][1] = (-1*nu12)/E1
        S_matrix[0][2] = (-1*nu13)/E1
    S_matrix[1][0] = S_matrix[0][1]
    if(E2!=0): S_matrix[1][1] = 1/E2
    if(E1!=0): S_matrix[1][2] = (-1*nu23)/E1
    S_matrix[2][0] = S_matrix[0][2]
    S_matrix[2][1] = S_matrix[1][2]
    if(E3!=0): S_matrix[2][2] = 1/E3
    if(G23!=0): S_matrix[3][3] = 1/G23
    if(G13!=0): S_matrix[4][4] = 1/G13
    if(G12!=0): S_matrix[5][5] = 1/G12

    nu21 = (nu12 * E2)/E1
    nu31 = (nu13 * E3)/E1
    nu32 = (nu23 * E3)/E2
    try:
        Q_matrix = inv(S_matrix)
    except:
        pass

    S_planar = np.zeros((3,3),dtype=float)
    S_planar[0][0] = S_matrix[0][0]
    S_planar[0][1] = S_matrix[0][1]
    S_planar[1][0] = S_matrix[1][0]
    S_planar[1][1] = S_matrix[1][1]
    S_planar[2][2] = S_matrix[5][5]
    Q_planar = inv(S_planar)
    Q_planar,S_planar = np.around(Q_planar,decimals = 3),np.around(S_planar,decimals = 3)
    return Q_planar,S_planar

#### Calculate the modulus and elastic propties at another angle ####
## Inputs - Q_matrix(3X3 Matrix)/S_matrix(3X3 Matrix)/Material_Properties([E1,nu12,E2,G12])
## Outputs - Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y
def effective_lamina_properties_angle(Q_matrix=False,S_matrix=False,Material_properties=False,data=1,angle=0):
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    if Q_matrix == True:
        data = inv(data)
        Ex = 1/(pow(m,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(n,4)*data[1][1])
        nuxy = Ex*(-pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-data[2][2])-data[0][1]*(pow(m,4)+pow(n,4)))
        Ey = 1/(pow(n,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(m,4)*data[1][1])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-2*data[0][1])+pow(pow(m,2)-pow(n,2),2)*data[2][2])
        etaxy_x = Ex*(pow(m,3)*n*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(n,3)*m*(2*data[1][1]-2*data[0][1]-data[2][2]))
        etaxy_y = Ey*(pow(n,3)*m*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(m,3)*n*(2*data[1][1]-2*data[0][1]-data[2][2]))

    if S_matrix == True:
        Ex = 1/(pow(m,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(n,4)*data[1][1])
        nuxy = Ex*(-pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-data[2][2])-data[0][1]*(pow(m,4)+pow(n,4)))
        Ey = 1/(pow(n,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(m,4)*data[1][1])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-2*data[0][1])+pow(pow(m,2)-pow(n,2),2)*data[2][2])
        etaxy_x = Ex*(pow(m,3)*n*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(n,3)*m*(2*data[1][1]-2*data[0][1]-data[2][2]))
        etaxy_y = Ey*(pow(n,3)*m*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(m,3)*n*(2*data[1][1]-2*data[0][1]-data[2][2]))

    if Material_properties == True:
        Ex = 1/(pow(m,4)/data[0]+pow(m,2)*pow(n,2)*(1/data[3]-(2*data[1])/data[0])+pow(n,4)/data[2])
        nuxy = Ex*(-1*pow(m,2)*pow(n,2)*(1/data[0]+1/data[2]-1/data[3])+(data[1]*(pow(m,4)+pow(n,4)))/data[0])
        Ey = 1/(pow(n,4)/data[0]+pow(m,2)*pow(n,2)*(1/data[3]-(2*data[1])/data[0])+pow(m,4)/data[2])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(1/data[0]+1/data[2]+(2*data[1])/data[0])+(pow(pow(m,2)-pow(n,2),2))/data[3])
        etaxy_x = abs(Ex*(pow(m,3)*n*(2/data[0]+(2*data[1])/data[0]-1/data[3])-pow(n,3)*m*(2/data[2]+(2*data[1])/data[0]-1/data[3])))
        etaxy_y = abs(Ey*(pow(n,3)*m*(2/data[0]+(2*data[1])/data[0]-1/data[3])-pow(m,3)*n*(2/data[2]+(2*data[1])/data[0]-1/data[3])))
    try:
        Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = round(Ex,3),round(nuxy,3),round(Ey,3),round(Gxy,3),round(etaxy_x,3),round(etaxy_y,3)
    except:
        Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = np.around(Ex,3),np.round(nuxy,3),np.around(Ey,3),np.around(Gxy,3),np.around(etaxy_x,3),np.around(etaxy_y,3)
    return Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y

## Helper function
def plot_properties_angle(data,angle=0):
    x = np.linspace(0,90,90)
    Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = effective_lamina_properties_angle(Material_properties=True,data=data,angle=x)
    fig, axs = plt.subplots(3, 2)
    fig.suptitle('Laminate properties with Angle(theta)',fontsize='xx-large',fontweight='bold')
    axs[0,0].plot(x,Ex,color='red',label='Ex')
    axs[0,0].set_title('Ex v/s angle')
    axs[0,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[0,0].legend()
    axs[0,0].set(ylabel='Stress(GPa)')
    axs[0,1].plot(x,Ey,color='blue',label='Ey')
    axs[0,1].set_title('Ey v/s angle')
    axs[0,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[0,1].legend()
    axs[0,1].set(ylabel='Stress(GPa)')
    axs[1,0].plot(x,Gxy,color='green',label='Gxy')
    axs[1,0].set_title('Gxy v/s angle')
    axs[1,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[1,0].legend()
    axs[1,0].set(ylabel='Stress(GPa)')
    axs[1,1].plot(x,nuxy,color='orange',label='nuxy')
    axs[1,1].set_title('nuxy v/s angle')
    axs[1,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[1,1].legend()
    axs[1,1].set(ylabel='Poisson ratio')
    axs[2,0].plot(x,etaxy_x,color='magenta',label='etaxy-x')
    axs[2,0].set_title('etaxy-x v/s angle')
    axs[2,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[2,0].legend()
    axs[2,0].set(xlabel='angle(degrees)',ylabel='shear coupling ratio')
    axs[2,1].plot(x,etaxy_y,color='cyan',label='etaxy-y')
    axs[2,1].set_title('etaxy-y v/s angle')
    axs[2,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[2,1].legend()
    axs[2,1].set(xlabel='angle(degrees)',ylabel='shear coupling ratio')
    '''
    for ax in fig.get_axes():
        ax.label_outer()
    '''
    Ex_ang,nuxy_ang,Ey_ang,Gxy_ang,etaxy_x_ang,etaxy_y_ang = effective_lamina_properties_angle(Material_properties=True,data=data,angle=angle)
    axs[0,0].plot(angle,Ex_ang,'bo')
    axs[0,1].plot(angle,Ey_ang,'bo')
    axs[1,0].plot(angle,Gxy_ang,'bo')
    axs[1,1].plot(angle,nuxy_ang,'bo')
    axs[2,0].plot(angle,etaxy_x_ang,'bo')
    axs[2,1].plot(angle,etaxy_y_ang,'bo')
    plt.show()
    fig.savefig('lamina_variation_angle.jpg')
    return fig

#### Calculate of Stiffness Matrix at angle ####
## Inputs - Material_Propties([E1,nu12,E2,G12]),Angle
## Outputs - Q_planar_angle
def Stiffness_matrix_angle(data,angle):
    Q_planar_angle = np.zeros((3,3),dtype=float)
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    Q_planar,S_planar = stiffness_compliance_matrix(E1=data[0],E2=data[2],nu12=data[1],G12=data[3])
    Q_planar_angle[0][0] = pow(m,4)*Q_planar[0][0]+2*pow(m,2)*pow(n,2)*(Q_planar[0][1]+2*Q_planar[2][2])+pow(n,4)*Q_planar[1][1]
    Q_planar_angle[0][1] = pow(m,2)*pow(n,2)*(Q_planar[0][0]+Q_planar[1][1]-4*Q_planar[2][2])+Q_planar[0][1]*(pow(m,4)+pow(n,4))
    Q_planar_angle[0][2] = pow(m,3)*n*(Q_planar[0][0]-Q_planar[0][1]-2*Q_planar[2][2])+pow(n,3)*m*(Q_planar[0][1]-Q_planar[1][1]+2*Q_planar[2][2])
    Q_planar_angle[1][0] = Q_planar_angle[0][1]
    Q_planar_angle[1][1] = pow(n,4)*Q_planar[0][0]+2*pow(m,2)*pow(n,2)*(Q_planar[0][1]+2*Q_planar[2][2])+pow(m,4)*Q_planar[1][1]
    Q_planar_angle[1][2] = pow(n,3)*m*(Q_planar[0][0]-Q_planar[0][1]+2*Q_planar[2][2])-pow(n,3)*m*(Q_planar[0][1]-Q_planar[1][1]-2*Q_planar[2][2])
    Q_planar_angle[2][0] = Q_planar_angle[0][2]
    Q_planar_angle[2][1] = Q_planar_angle[1][2]
    Q_planar_angle[2][2] = pow(m,2)*pow(n,2)*(Q_planar[0][0]+Q_planar[1][1]-2*Q_planar[0][1])+pow(pow(m,2)-pow(n,2),2)*Q_planar[2][2]
    Q_planar_angle = np.around(Q_planar_angle,decimals=3)
    return Q_planar_angle

#### Sub routine 3 ####

##### Calculating A B D matrix for laminate ####
## Inputs - material properties of 0 deg lamina, lamina_sequence,lamina_thickness
## Outputs - A,B,D

def Laminate_parameters(data,laminate_sequence,lamina_thickness=1000):
    z_sequence = []
    Q_matrices = []
    no_laminas = len(laminate_sequence)
    lamina_thickness = lamina_thickness/no_laminas
    for i in range(no_laminas):
        Q_matrices.append(Stiffness_matrix_angle(data,laminate_sequence[i]))
    z_sequence.append(-(no_laminas/2)*lamina_thickness)
    for i in range(no_laminas):
        z_sequence.append(z_sequence[-1]+lamina_thickness)
    A = 0
    B = 0
    D = 0
    for i in range(no_laminas):
        A += Q_matrices[i] * (z_sequence[i+1]-z_sequence[i])
        B += Q_matrices[i] * (pow(z_sequence[i+1],2)-pow(z_sequence[i],2))
        D += Q_matrices[i] * (pow(z_sequence[i+1],3)-pow(z_sequence[i],3))
    B = B/2
    D = D/3
    z_laminas = []
    for i in range(no_laminas):
        z_laminas.append((z_sequence[i+1]+z_sequence[i])/2)
    A,B,D = np.around(A,decimals=3),np.around(B,decimals=3),np.around(D,decimals=3)
    return A,B,D,z_laminas,Q_matrices,laminate_sequence

#### Calculation of stress vectors for each lamina
## Inputs - A,B,D matrix of laminate ,N(Normal traction),M(Bending moment)
## Oututs - Stress lamina (3*1 vector) for each lamina

def stress_lamina(A,B,D,z_laminas,Q_matrices,laminate_sequence,N,M):
    laminate_matrix = np.block([[A,B],[B,D]])
    traction_bending_vector = np.transpose(np.array([N[0],N[1],N[2],M[0],M[1],M[2]]))
    strain_curvature_vector = np.dot(inv(laminate_matrix),traction_bending_vector)
    print(strain_curvature_vector)
    strain_laminas = []
    stress_laminas = []
    for i in range(len(z_laminas)):
        strain_laminas.append(strain_curvature_vector[:3]+z_laminas[i]*strain_curvature_vector[3:])
    for i in range(len(z_laminas)):
        stress_laminas.append(angle_correction(np.dot(Q_matrices[i],strain_laminas[i]),laminate_sequence[i]))
    return stress_laminas

#Helper Function
def angle_correction(stress_xy,angle):
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    T = np.zeros((3,3),dtype=float)
    T[0][0] = pow(m,2)
    T[0][1] = pow(n,2)
    T[0][2] = 2*m*n
    T[1][0] = pow(n,2)
    T[1][1] = pow(m,2)
    T[1][2] = -2*m*n
    T[2][0] = -m*n
    T[2][1] = m*n
    T[2][2] = pow(m,2)-pow(n,2)
    stress_12 = np.dot(T,stress_xy)
    return stress_12

#### Sub routine 4 ####

### Implementation of Hashin Failure 2D
## Input - Stress Lamina, Strength of Lamina
## Output - True(if Failure occurs)/False(if Failure doesnt occurs)
def hashin_failure(stress_lamina,X,X_,Y,Y_,S):
    result = True
    safety_factor = 2
    ## Calculating for Fibre ##
    if stress_lamina[0]>0: #Tensile fibre failure
        if(pow(stress_lamina[0]/X,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = quadratic_solver_safety(pow(stress_lamina[0]/X,2)+pow(stress_lamina[2]/S,2),0,-1)
    else: #Compressive fibre failure
        if(pow(stress_lamina[0]/X_,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = quadratic_solver_safety(pow(stress_lamina[0]/X_,2),0,-1)
    ## Calculating for Matrix ##
    if stress_lamina[1]>0: #Matrix Tensile Failure
        if(pow(stress_lamina[1]/Y,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = min(safety_factor,quadratic_solver_safety(pow(stress_lamina[1]/Y,2)+pow(stress_lamina[2]/S,2),0,-1))
    else: #Matrix Compressive Failure
        if((pow(Y_/2*S,2)-1)*(stress_lamina[1]/Y_)+pow(stress_lamina[1]/4*S,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = min(safety_factor,quadratic_solver_safety(pow(stress_lamina[1]/4*S,2)+pow(stress_lamina[2]/S,2),(pow(Y_/2*S,2)-1)*(stress_lamina[1]/Y_),-1))
    safety_factor = round(safety_factor,3)
    return result,safety_factor

## Helper function ##
def quadratic_solver_safety(a,b,c):
    # calculate the discriminant
    d = (b**2) - (4*a*c)

    # find two solutions
    sol1 = (-b-sqrt(d))/(2*a)
    sol2 = (-b+sqrt(d))/(2*a)
    if(sol1>0 and sol2>0):
        return min(sol1,sol2)
    elif(sol1>0 or sol2>0):
        if(sol1>0): return sol1
        else: return sol2
    else:
        raise Exception("Quadratic coeffecients give illegal answers")

if __name__ == "__main__":
    ### My composite properties ###
    ## Fibre name - AS4 + Matrix name - BSL914c Epoxy ##
    ## (Fibre Details) E1_Fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,Volume_fraction_fibre,chamis_correction ###
    ## (Matrix Details )E_matrix,nu_matrix,G_matrix ##
    E1_fibre = 225
    E2_fibre = 15
    G12_fibre = 15
    nu12_fibre = 0.2
    nu23_fibre = 0.25
    G23_fibre = E2_fibre/(2*(1+nu23_fibre))
    E_matrix = 4
    nu_matrix = 0.35
    G_matrix = E_matrix/(2*(1+nu_matrix))
    Volume_fraction_fibre = 0.516
    E1,E2,E3,nu12,nu13,nu23,G12,G13,G23 = effective_lamina_properties(E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre)
    #print(E1,E2,E3,nu12,nu13,nu23,G12,G13,G23)
    #print(G23_fibre,G_matrix)
    Q_planar,S_planar = stiffness_compliance_matrix(E1,E2,E3,nu12,nu13,nu23,G12,G13,G23)
    #print(Q_planar,S_planar)
    #Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = effective_lamina_properties_angle(Material_properties=True,data=[E1,nu12,E2,G12],angle=50)
    #fig = plot_properties_angle([E1,nu12,E2,G12],20)
    #Q_planar_angle = Stiffness_matrix_angle(data=[E1,nu12,E2,G12],angle=90)
    #print(Q_planar_angle)
    A,B,D,z_laminas,Q_matrices,laminate_seq = Laminate_parameters(data=[E1,nu12,E2,G12],laminate_sequence=[0,29,-29,90,90,-29,29,0],lamina_thickness=1000)
    print(D)
    #print(A,B,D,z_laminas,Q_matrices)
    #print(D)
    stress_laminas = stress_lamina(A,B,D,z_laminas,Q_matrices,laminate_seq,N=[100000,0,0],M=[0,0,0])
    #print(stress_laminas[0])
    result,safety_factor = hashin_failure(stress_laminas[0],X=1550,X_=1550,Y=150,Y_=220,S=93)
    #print(result,safety_factor)


from math import exp, sqrt
import numpy as np

class NPeriodBOPM:

    def __init__(self, op_type,
                        underlying, strike,
                        volatility, risk_free,
                        nperiods, time_in_years,
                        factor_method = 'Jarrow',
                        trade_position = 'Long'
                        ):

        self.op_type = op_type
        self.underlying = underlying
        self.strike = strike
        self.volatility = volatility
        self.risk_free = risk_free
        self.nperiods = nperiods
        self.time_in_years = time_in_years
        self.factor_method = factor_method
        self.trade_position = trade_position

        # internal calculations
        self.deltatime = self.__deltatime_calc()

        # when using Jarrow-Rudd specification
        if self.factor_method == 'Jarrow':
            self.upfactor = self.__jarrow_calc()[0]
            self.downfactor = self.__jarrow_calc()[1]
            self.up_neutral = self.__jarrow_calc()[2]
            self.down_neutral = self.__jarrow_calc()[3]

        # when using Cox-Ross-Rubinstein specification:
        if self.factor_method == 'Cox':
            self.upfactor = self.__cox_calc()[0]
            self.downfactor = self.__cox_calc()[1]
            self.up_neutral = self.__cox_calc()[2]
            self.down_neutral = self.__cox_calc()[3]

        self.underlying_vector = self.__underlying_vector_calc()
        self.payoff_vector = self.__payoff_vector_calc()
        self.price_vector = self.__price_calc()
        self.price = self.price_vector[0]

    # internal calc

    def __deltatime_calc(self):
        return self.time_in_years / self.nperiods

    def __jarrow_calc(self):
        up_factor = exp(self.risk_free*self.deltatime
                        - ((self.volatility**2) / 2) * self.deltatime
                        + self.volatility*sqrt(self.deltatime)
                        )

        dn_factor = exp(self.risk_free*self.deltatime
                        - ((self.volatility**2) / 2) * self.deltatime
                        - self.volatility*sqrt(self.deltatime)
                        )

        # The risk-neutral probabilites
        up_neutral = (
            (exp(self.risk_free * self.deltatime) - dn_factor)
                / (up_factor - dn_factor)
                    )

        dn_neutral = 1-up_neutral
        return (up_factor, dn_factor, up_neutral, dn_neutral)

    def __cox_calc(self):
        up_factor = exp(self.volatility*sqrt(self.deltatime))
        dn_factor = 1/up_factor

        # The risk-neutral probabilites
        up_neutral = ((exp(self.risk_free * self.deltatime) - dn_factor)
                    / (up_factor - dn_factor) )
        dn_neutral = 1-up_neutral

        return (up_factor, dn_factor, up_neutral, dn_neutral)

    def __underlying_vector_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        underlying_values = np.array(
            [(self.underlying * self.upfactor**j *
            self.downfactor**(self.nperiods-j))
            for j in range(self.nperiods + 1)])
        return underlying_values

    def __payoff_vector_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        payoffs = np.zeros(self.nperiods + 1) # get array going
        if self.op_type == 'Call':
            payoffs[:] = np.maximum(self.underlying_vector - self.strike, 0.0)
        else:
            payoffs[:] = np.maximum(self.strike - self.underlying_vector, 0.0)
        return payoffs

    def __price_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        price_vector = self.payoff_vector
        # find prices
        for i in range(self.nperiods-1, -1, -1):
            price_vector[:-1] = (
                np.exp(-self.risk_free*self.deltatime)
                * (self.up_neutral * price_vector[1:]
                    + self.down_neutral * price_vector[:-1]))
        return price_vector

    def get_price(self):
        return self.price

#test:
#example = NPeriodBOPM('Call', 100, 100, 0.20, 0.10, 2000, 1)
#print(example.deltatime)
#print(example.upfactor)
#print(example.downfactor)
#print(example.underlying_vector)
#print(example.price_vector)
#print(example.price)


import numpy
import sympy

from sympy import init_printing
init_printing(use_latex=True)

x, nu, t = sympy.symbols('x nu t')
phi = (sympy.exp(-(x - 4 * t)**2 / (4 * nu * (t + 1))) +
       sympy.exp(-(x - 4 * t - 2 * numpy.pi)**2 / (4 * nu * (t + 1))))
phi

# It's maybe a little small, but that looks right.  Now to evaluate our partial derivative $\frac{\partial \phi}{\partial x}$ is a trivial task.

phiprime = phi.diff(x)
phiprime

# If you want to see the unrendered version, just use the Python print command.

# In[48]:

print(phiprime)

#
# Now that we have the Pythonic version of our derivative, we can finish writing out the full initial condition equation and then translate it into a usable Python expression.  For this, we'll use the *lambdify* function, which takes a SymPy symbolic equation and turns it into a callable function.

# In[49]:

from sympy.utilities.lambdify import lambdify

u = -2 * nu * (phiprime / phi) + 4
print(u)

# ### Lambdify
#
# To lambdify this expression into a useable function, we tell lambdify which variables to request and the function we want to plug them in to.

ufunc = lambdify((t, x, nu), u)
print(ufunc(1, 4, 3))

# ### Back to Burgers' Equation
#
# Now that we have the initial conditions set up, we can proceed and finish setting up the problem.  We can generate the plot of the initial condition using our lambdify-ed function.

# In[51]:

from matplotlib import pyplot

###variable declarations
nx = 101
nt = 100
dx = 2.0 * numpy.pi / (nx - 1)
nu = .07
dt = dx * nu

x = numpy.linspace(0, 2 * numpy.pi, nx)
un = numpy.empty(nx)
t = 0

u = numpy.asarray([ufunc(t, x0, nu) for x0 in x])
u

# In[52]:

pyplot.figure(figsize=(11, 7), dpi=100)
pyplot.plot(x, u, marker='o', lw=2)
pyplot.xlim([0, 2 * numpy.pi])
pyplot.ylim([0, 10]);

# This is definitely not the hat function we've been dealing with until now. We call it a "saw-tooth function".  Let's proceed forward and see what happens.

# ### Periodic Boundary Conditions
#
# One of the big differences between Step 4 and the previous lessons is the use of *periodic* boundary conditions.  If you experiment with Steps 1 and 2 and make the simulation run longer (by increasing `nt`) you will notice that the wave will keep moving to the right until it no longer even shows up in the plot.
#
# With periodic boundary conditions, when a point gets to the right-hand side of the frame, it *wraps around* back to the front of the frame.
#
# Recall the discretization that we worked out at the beginning of this notebook:
#
# $$u_i^{n+1} = u_i^n - u_i^n \frac{\Delta t}{\Delta x} (u_i^n - u_{i-1}^n) + \nu \frac{\Delta t}{\Delta x^2}(u_{i+1}^n - 2u_i^n + u_{i-1}^n)$$
#
# What does $u_{i+1}^n$ *mean* when $i$ is already at the end of the frame?
#
# Think about this for a minute before proceeding.
#
#

for n in range(nt): # looping over time
    un = u.copy()   # creating a copy to use for the iteration
    for i in range(1, nx-1): # looping over space
        u[i] = un[i] - un[i] * dt / dx *(un[i] - un[i-1]) + nu * dt / dx**2 *                (un[i+1] - 2 * un[i] + un[i-1]) #looping over all of the internal nodes
    u[0] = un[0] - un[0] * dt / dx * (un[0] - un[-2]) + nu * dt / dx**2 *                (un[1] - 2 * un[0] + un[-2]) #setting the value for the first node
    u[-1] = u[0] #setting the value for the last not

u_analytical = numpy.asarray([ufunc(nt * dt, xi, nu) for xi in x]) #calculating the analytical solution

# In[54]:

pyplot.figure(figsize=(11, 7), dpi=100)
pyplot.plot(x,u, marker='o', lw=2, label='Computational')
pyplot.plot(x, u_analytical, label='Analytical')
pyplot.xlim([0, 2 * numpy.pi])
pyplot.ylim([0, 10])
pyplot.legend();


# -*- coding: utf-8 -*-
"""
Get the cartesian indices of input 1-D arrays
Similar to the Julia CartesianIndices
https://stackoverflow.com/questions/1208118/using-numpy-to-build-an-array-of-all-combinations-of-two-arrays
"""

import numpy as np

def cartesian(*arrays, order='F'):
    """
    -i- arrays : list of array-like,
        1-D arrays to form the cartesian product of
    -i- order : string, {'C', 'F', 'A'}, see numpy.reshape
        'F' changes the first axis fastest ("FORTRAN style" or "column-major")
        'C' changes the last axis fastest ("C style" or "row-major")
    """
    N = len(arrays)
    return np.transpose(np.meshgrid(*arrays, indexing='ij'),
        np.roll(np.arange(N + 1), -1)).reshape((-1, N), order=order)

def main():
    print(cartesian([1,2,3], [4,5], [6,7], order='F'))
    """
    [[1 4 6]
     [2 4 6]
     [3 4 6]
     [1 5 6]
     [2 5 6]
     [3 5 6]
     [1 4 7]
     [2 4 7]
     [3 4 7]
     [1 5 7]
     [2 5 7]
     [3 5 7]]
    """

    print(cartesian([1,2,3], [4,5], [6,7], order='C'))
    """
    [[1 4 6]
     [1 4 7]
     [1 5 6]
     [1 5 7]
     [2 4 6]
     [2 4 7]
     [2 5 6]
     [2 5 7]
     [3 4 6]
     [3 4 7]
     [3 5 6]
     [3 5 7]]
    """

if __name__ == '__main__':
    main()


import numpy as np
from numpy.random import uniform

def monte_carlo_pi(n):
    p = 0
    for i in range (n):
        u, v = uniform(), uniform()
        if np.sqrt((u - 0.5)**2 + (v - 0.5)**2) <= 0.5:
            p += 1
    return (p/n)*4

monte_carlo_pi(100000)


"""
Basic funcionalitty shared all over the code
"""

import math
import numpy as np

def add_grid(f, delta, color=255):
  g = np.copy(f)
  g[::delta, :] = color
  g[:, ::delta] = color

  return g

def add_inner_border(f, size, color=255):
  if not size:
    return g

  g = np.copy(f)
  g[:size, :] = color
  g[-size:, :] = color
  g[:, :size] = color
  g[:, -size:] = color

  return g

def rotate_90(f):
  return f.transpose()

def crop_binary(f):
  r, c = f.nonzero()
  return f[r[0]:r[-1] + 1, c.min():c.max() + 1]

def gamma_correction(f, gamma):
  g = np.copy(f)
  t_gamma = normalize(np.arange(256) ** gamma)

  return t_gamma[g]

def naive_percentile(arr, p):
  """
  Gives the value below which a given percentage
  of sorted observations fall.
  """
  return np.ceil(len(arr) * p / 100.0)

def normalize(arr, range=(0, 255), percentile=1):
  """
  Based on minimum and maximimum values, performs
  a linear interpolation of the values.
  """
  arr = np.asarray(arr)
  faux = np.ravel(arr).astype(float)
  min_val = faux.min()
  max_val = faux.max()
  lower, upper = range

  if upper == lower:
    g = np.ones(faux.shape) * max_val
  if max_val == min_val:
    g = np.ones(faux.shape) * (upper + lower) / 2.0
  else:
    g = (faux - min_val) * (upper - lower) / (max_val - min_val) + lower

  g = g.reshape(arr.shape).astype(arr.dtype)

  return g

def normalize_with_clip(f, p):
  p1, p2 = np.percentile(f, [p, 100 - p])
  f = np.clip(f, p1, p2)

  return normalize(f)

def scale(val):
  return np.array([
    [val,0,0],
    [0,val,0],
    [0,0,1]]
  )

def rotate(theta):
  return np.array([
    [math.cos(theta),-math.sin(theta),0],
    [math.sin(theta),math.cos(theta),0],
    [0,0,1]
  ])

def translate(x,y):
  return np.array([
    [1,0,x],
    [0,1,y],
    [0,0,1]
  ])

def affine(f, T):
  """Applies the affine transformation on a given
  image.

  ' g(r, c) = f(T^{-1}(r,c)) '

  Direct mapping:
    From image (f) map the values to image (g)
    - g(T(r,c)) = f((r,c)) ,
                  (r,c) in [0,H-1]x[0, W-1]
    - con: T(r,c) might not fill every pixel of G

  Indirect Mapping
    From image (g) search the values in image (f)
    - g(r', c') = f(T^{-1}(r', c')) ,
                  (r', c') in [0, H'-1]x[0, W'-1]
    - pro: every pixel of (g) receives a value
  """
  h, w = f.shape
  y1, x1 = np.indices(f.shape)
  g = np.lib.pad(f, 1, 'constant', constant_values=0)

  yx1 = np.array([
    y1.ravel(), x1.ravel(), np.ones(np.product(f.shape))
  ])

  yx_float = np.dot(np.linalg.inv(T), yx1)
  yy = np.rint(yx_float[0]).astype(int)+1
  xx = np.rint(yx_float[1]).astype(int)+1

  y = np.clip(yy, 0, h+1)
  x = np.clip(xx, 0, w+1)

  return g[y, x].reshape(h, w)

def equalize_histogram(img):
  """
  T(r) = (L-1)/n * (\sum\limits_{i=0}^{r},h(i)),
  where h(i) = hist(i).
  """
  # np.bincount() returns an array that, contains
  # the number of occurences of each integer I,
  # indexed by the array returned, i.e, out
  # 'absolute' histogram (divide it by N and then
  # we have a relative)
  bins = np.bincount(img.ravel())
  n = img.size
  T = 255 / n * np.cumsum(bins)
  T = T.astype(uint8)

  return T[img]

def f(t):
  return np.exp(-t) * np.cos(2 * np.pi * t)

def main():
  import matplotlib.pyplot as plt
  from scipy import ndimage

  # img = ndimage.imread('../assets/cameraman.tif', flatten=True)
  # img = img.astype(np.uint8)

  # new_img = affine(img, translate(2,0))

  # plt.imshow(new_img, cmap="gray")
  # plt.show()

  img = np.arange(12).reshape((3,4))
  print img
  print affine(img, translate(1,0))

if __name__ == '__main__':
  main()


# 1) Using your RW script from week 7, create a downsampled, random and event-based sparse sampling of 10 RW
#    (RW_01 - RW_10) with length 100.  Please make sure you use a continuous, uniform distribution for your
#    RW this week. This means the values should be in between -1, +1 but not only these values.

# https://numpy.org/doc/stable/reference/random/generated/numpy.random.uniform.html

# "low" = -1
# "high" = 1

# 1a) Calculate a reference RW_ref as the ensemble average of the 10 RW. The RW_ref will be used in the following steps as your "ground truth" to compare all other data with.
# 1b) Downsample RW_01 by every 2nd value, and every 10th value.
# 1c) Randomly (uniform distribution) resample RW_01 with 10 and 50 samples.
# 1d) Perform event-based resampling of RW_01 with the thresholds of λ = 0.5 and 0.8. Don't forget to take the first derivative before thresholding!

import numpy as np
import matplotlib.pyplot as plt

# Parameters
RW_LENGTH = 100
NR_OF_WALKS = 10
HIGH = 1.0
LOW = -1.0

# Random walk array
RAND_WALKS = np.zeros((NR_OF_WALKS, RW_LENGTH))

# Ensemble average array
RW_ref = np.zeros(RW_LENGTH)

# Calculations
for i in range (NR_OF_WALKS):
    for t in range(RW_LENGTH - 1):
        # 1)
        RAND_WALKS[i][t + 1] = RAND_WALKS[i][t] + np.random.uniform(low=LOW, high=HIGH)

# 1a)
RW_ref = np.sum(RAND_WALKS, axis=0) / NR_OF_WALKS

# 1b)
every2nd_indices = np.arange(0, RAND_WALKS[0].size, 2)
downsampled_2nd_RW01 = np.delete(RAND_WALKS[0], every2nd_indices)

every10th_indices = np.arange(0, RAND_WALKS[0].size, 10)
downsampled_10th_RW01 = np.delete(RAND_WALKS[0], every10th_indices)

# 1c)
indices_10 = np.sort(np.random.uniform(low=0, high=RW_LENGTH, size=10).astype(int))
resampled_with10_RW01 = np.take(RAND_WALKS[0], indices_10)

indices_50 = np.sort(np.random.uniform(low=0, high=RW_LENGTH, size=50).astype(int))
resampled_with50_RW01 = np.take(RAND_WALKS[0], indices_50)

# 1d)
LAMBDA_1 = 0.5
evnt_bsd_05 = np.zeros(0)
t_indices_05 = np.zeros(0)

LAMBDA_2 = 0.8
evnt_bsd_08 = np.zeros(0)
t_indices_08 = np.zeros(0)

prev_t = 0
for t, val in enumerate(RAND_WALKS[0]):
    if (t == 0):
        dx_dt = 0
    else:
        dx_dt = (RAND_WALKS[0][t] - RAND_WALKS[0][t - 1]) / (t - prev_t)
        prev_t = t

    if (abs(dx_dt) >= LAMBDA_1):
        evnt_bsd_05 = np.append(evnt_bsd_05, dx_dt)
        t_indices_05 = np.append(t_indices_05, t)

    if (abs(dx_dt) >= LAMBDA_2):
        evnt_bsd_08 = np.append(evnt_bsd_08, dx_dt)
        t_indices_08 = np.append(t_indices_08, t)

t_indices_05 = np.asarray(t_indices_05, dtype=int)
t_indices_08 = np.asarray(t_indices_08, dtype=int)

# 2) Calculate the RMSE of your 1b, 1c and 1d against the RW_ref.
#    Which method worked the best, and why?

# RMSE 1b
kept_indices_2nd = np.arange(1, RW_LENGTH, 2)
kept_indices_10th = np.delete(np.arange(0, RAND_WALKS[0].size, 1), every10th_indices)

rmse1b_1 = 0
for t, val in enumerate(kept_indices_2nd):
    rmse1b_1 = rmse1b_1 + np.square((RW_ref[val] - downsampled_2nd_RW01[t]))
rmse1b_1 = np.sqrt(rmse1b_1 / len(kept_indices_2nd))
print ("RMSE 1b_1: {}".format(rmse1b_1))

rmse1b_2 = 0
for t, val in enumerate(kept_indices_10th):
    rmse1b_2 = rmse1b_2 + np.square((RW_ref[val] - downsampled_10th_RW01[t]))
rmse1b_2 = np.sqrt(rmse1b_2 / len(kept_indices_10th))
print ("RMSE 1b_2: {}".format(rmse1b_2))

# RMSE 1c
rmse1c_1 = 0
for t, val in enumerate(indices_10):
    rmse1c_1 = rmse1c_1 + np.square((RW_ref[val] - resampled_with10_RW01[t]))
rmse1c_1 = np.sqrt(rmse1c_1 / len(indices_10))
print ("RMSE 1c_1: {}".format(rmse1c_1))

rmse1c_2 = 0
for t, val in enumerate(indices_50):
    rmse1c_2 = rmse1c_2 + np.square((RW_ref[val] - resampled_with50_RW01[t]))
rmse1c_2 = np.sqrt(rmse1c_2 / len(indices_50))
print ("RMSE 1c_2: {}".format(rmse1c_2))

# RMSE 1d
rmse1d_1 = 0
for t, val in enumerate(t_indices_05):
    rmse1d_1 = rmse1d_1 + np.square((RW_ref[val] - evnt_bsd_05[t]))
rmse1d_1 = np.sqrt(rmse1d_1 / len(t_indices_05))
print ("RMSE 1d_1: {}".format(rmse1d_1))

rmse1d_2 = 0
for t, val in enumerate(t_indices_08):
    rmse1d_2 = rmse1d_2 + np.square((RW_ref[val] - evnt_bsd_08[t]))
rmse1d_2 = np.sqrt(rmse1d_2 / len(t_indices_08))
print ("RMSE 1d_2: {}".format(rmse1d_2))

# Plotting
#plt.plot(RAND_WALKS[0], color='b', label='RW_01')
plt.plot(RW_ref, color='r', label='Ensemble')

plt.plot(indices_10, resampled_with10_RW01, 'k.--', label='resampled_10')
plt.plot(indices_50, resampled_with50_RW01, 'c.--', label='resampled_50')

plt.plot(t_indices_05, evnt_bsd_05, 'g:.', label='evnt-bsd resample 0.5')
plt.plot(t_indices_08, evnt_bsd_08, 'y:.', label='evnt-bsd resample 0.8')

plt.ylabel("Y")
plt.xlabel("Number of steps")
plt.legend()
plt.show()