import math
import scipy as sp
import scipy.stats

####Detecting the overlapping and hierarchical community structure in complex networks
####Normalized Mutual Information to evaluate overlapping community finding algorithms
logBase = 2

def partialEntropyAProba(proba):
    if proba==0:
        return 0
    return -proba * math.log(proba,logBase)

def entropyFracSet(fracSet):
    toReturn = 0
    for frac in fracSet:
        toReturn+=partialEntropyAProba(frac)
    return toReturn

def comPairConditionalEntropy(cl,clKnown,allNodes): #cl1,cl2, snapshot_communities (set of nodes)
    #H(Xi|Yj ) =H(Xi, Yj ) − H(Yj )
    # h(a,n) + h(b,n) + h(c,n) + h(d,n)
    # −h(b + d, n)−h(a + c, n)
    #a: count agreeing on not belonging
    #b: count disagreeing : not in 1 but in 2
    #c: count disagreeing : not in 2 but in 1
    #d: count agreeing on belonging
    nbNodes = len(allNodes)

    a =len((allNodes - cl) - clKnown)/nbNodes
    b = len(clKnown-cl)/nbNodes
    c = len(cl-clKnown)/nbNodes
    d = len(cl & clKnown)/nbNodes

    if partialEntropyAProba(a)+partialEntropyAProba(d)>partialEntropyAProba(b)+partialEntropyAProba(c):
        entropyKnown=sp.stats.entropy([len(clKnown)/nbNodes,1-len(clKnown)/nbNodes],base=logBase)
        conditionalEntropy = sp.stats.entropy([a,b,c,d],base=logBase) - entropyKnown
        #print("normal",entropyKnown,sp.stats.entropy([a,b,c,d],base=logBase))
    else:
        conditionalEntropy = sp.stats.entropy([len(cl)/nbNodes,1-len(cl)/nbNodes],base=logBase)
    #print("abcd",a,b,c,d,conditionalEntropy,cl,clKnown)

    return conditionalEntropy #*nbNodes

def coverEntropy(cover,allNodes): #cover is a list of set, no com ID
    allEntr = []
    for com in cover:
        fractionIn = len(com)/len(allNodes)
        allEntr.append(sp.stats.entropy([fractionIn,1-fractionIn],base=logBase))

    return sum(allEntr)

def coverConditionalEntropy(cover,coverRef,allNodes): #cover and coverRef and list of set
    X=cover
    Y=coverRef

    allMatches = []
    #print(cover)
    #print(coverRef)
    for com in cover:
        matches = [(com2,comPairConditionalEntropy(com,com2,allNodes)) for com2 in coverRef]
        bestMatch = min(matches,key=lambda c: c[1])
        allMatches.append(bestMatch[1])
    #print(allMatches)
    return sum(allMatches)

def NMI(cover,coverRef,allNodes=-1,variant="LFR",adjustForChance=False): #cover and coverRef should be list of set, no community ID
    """
    :param cover: set of set of nodes
    :param coverRef:set of set of nodes
    :param allNodes:
    :param variant:
    :param adjustForChance:
    :return:
    """
    if (len(cover)==0 and len(coverRef)!=0) or (len(cover)!=0 and len(coverRef)==0):
        return 0
    if cover==coverRef:
        return 1

    if allNodes==-1:
        allNodes={n for c in coverRef for n in c}
        allNodes|={n for c in cover for n in c}

    #print("compute HXY")
    HXY = coverConditionalEntropy(cover,coverRef,allNodes)
    #print("compute HYX")

    HYX = coverConditionalEntropy(coverRef,cover,allNodes)
    HX = coverEntropy(cover,allNodes)
    #print("HY")
    HY = coverEntropy(coverRef,allNodes)

    #print("HXY...",HXY,HYX,HX,HY)
    NMI = -10
    if variant=="LFR":
        NMI = 1- 0.5*(HXY/HX+HYX/HY)
    elif variant=="MGH":
        IXY = 0.5*(HX-HXY+HY-HYX)
        NMI =  IXY/(max(HX,HY))
    if NMI<0 or NMI>1 or math.isnan(NMI):
        print("NMI: %s  from %s %s %s %s "%(NMI,HXY,HYX,HX,HY))
        raise Exception("incorrect NMI")
    return NMI

def NMIdynamic(covers,coversRef,allNodes=-1,variant="LFR", symmetric=True): #covers,coversRef : ordered list of cover type:(dict/bidic com:set(nodes))
    combinedCover = {}
    combinedCoverRef = {}

    for t in range(len(coversRef)): #for each slice
        #if len(coversRef[t])==0:

        for c in covers[t]: #for each com:
            combinedCover.setdefault(c,set())
            combinedCover[c]|= {(t,n) for n in covers[t][c]}
        for c in coversRef[t]:  # for each com:
            combinedCoverRef.setdefault(c, set())
            combinedCoverRef[c] |= {(t, n) for n in coversRef[t][c]}

    if not symmetric:
        acceptableNodes = set()
        for c in combinedCoverRef:
            acceptableNodes |= {n for n in combinedCoverRef[c]}
        for c in combinedCover:
            combinedCover[c] = combinedCover[c] & acceptableNodes
        combinedCover = {c:v for c,v in combinedCover.items() if len(v)>0}

    if len(combinedCoverRef)>0: # if there is no com in the reference, we cannot compute NMI
        if len(combinedCover)==0: # if the set of tested coms is empty, its NMI value is 0
            return 0
        return NMI(combinedCover.values(),combinedCoverRef.values(),allNodes,variant=variant)
    else:
        return None


import plotly.graph_objects as go
import numpy as np

# Changing K plot
# Create figure
fig_k = go.Figure()

# Add traces, one for each slider step
k= np.linspace(-5, 5, 21)
phi_0 = 0
sig = 1
x=np.arange(-5, 5.05, 0.05)

for k_i in np.arange(-5, 5, 0.5):
    psi = np.multiply(np.exp(1j*k_i*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -np.ones(psi.shape),
            mode="lines"))

x2 = np.arange(-5, 5.05, 0.05)
y2 =-np.ones(x2.shape)
z2 = np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))
#fig2 = px.line_3d(df2, x="x", y="y", z="z",color="z")
fig_k.add_trace(go.Scatter3d(visible=True, x=x2, y=y2, z=z2, name="prob amplitude", mode="lines", line=dict(color='black', width=2)))
# Make 10th trace visible
fig_k.data[30].visible = True
fig_k.data[31].visible = True
fig_k.data[32].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_k.data)-1, 3):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_k.data)},
              {"title": "k= " + str(k[i//3])}],  # layout attribute
        label=str(k[i//3])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][-1] = True
    steps.append(step)

sliders = [dict(
    active=10,
    currentvalue={"prefix": "Wave number: "},
    pad={"t": 50},
    steps=steps
)]

fig_k.update_layout(
    sliders=sliders
)

fig_k.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-1,1],),
        zaxis = dict(nticks=4, range=[-1,1],),
        xaxis_title='Phi',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_k.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_k.show()
fig_k.write_html("wavefunction_changing_k.html")

# Changing sig plot
# Create figure
fig_sig = go.Figure()

# Add traces, one for each slider step
k = 3
sig_arr = np.logspace(-0.71, 0.5, 4)
phi_0 = 0
sig = 1
x=np.arange(-5, 5.05, 0.05)

for sig_i in sig_arr:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi))))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-2*np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -2*np.ones(psi.shape),
            mode="lines"))
    z2 = np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi)))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -2*np.ones(x2.shape),
            z=z2,
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)))

# Make 10th trace visible
fig_sig.data[4].visible = True
fig_sig.data[5].visible = True
fig_sig.data[6].visible = True
fig_sig.data[7].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_sig.data), 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_sig.data)},
              {"title": "Sigma: " + str(round(sig_arr[i//4],2))}],  # layout attribute
        label=str(round(sig_arr[i//4],2))
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    steps.append(step)

sliders = [dict(
    active=1,
    currentvalue={"prefix": "Standard diviation: "},
    pad={"t": 40},
    steps=steps
)]

fig_sig.update_layout(
    sliders=sliders
)

fig_sig.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-2,2],),
        zaxis = dict(nticks=4, range=[-2,2],),
        xaxis_title='Phi',
        yaxis_title='Real',
        zaxis_title='Imaginary'))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_sig.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_sig.show()
fig_sig.write_html("wavefunction_changing_sig.html")

# Changing sig plot
# Create figure
fig_mean = go.Figure()

# Add traces, one for each slider step
k = 3
sig = 1
phi_0 = np.arange(-3, 3.5, 0.5)
sig = 1
x=np.arange(-5, 5.05, 0.05)

for phi_0_i in phi_0:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -np.ones(psi.shape),
            mode="lines"))
    z2 = np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -np.ones(x2.shape),
            z=z2,
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)))

# Make 10th trace visible
fig_mean.data[24].visible = True
fig_mean.data[25].visible = True
fig_mean.data[26].visible = True
fig_mean.data[27].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_mean.data), 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_mean.data)},
              {"title": "Phi_0= " + str(phi_0[i//4])}],  # layout attribute
        label=str(phi_0[i//4])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    steps.append(step)

sliders = [dict(
    active=6,
    currentvalue={"prefix": "Mean: "},
    pad={"t": 40},
    steps=steps
)]

fig_mean.update_layout(
    sliders=sliders
)

fig_mean.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-1,1],),
        zaxis = dict(nticks=4, range=[-1,1],),
        xaxis_title='Phi',
        yaxis_title='Real',
        zaxis_title='Imaginary'))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_mean.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_mean.show()
fig_mean.write_html("wavefunction_changing_mu.html")


import numpy as np
from numpy.random import randn
import pandas as pd
import statsmodels.tsa.stattools as ts

class TimeSeriesBehavior:
    """
    This class provides functions to determine the behavior of a time series,
    specifically whether it is
        - A random walk. It has no memory. Examples: Brownian motion
        - A mean reverting series. Examples: Ornstein-Uhlenbeck process
        - A trending series
    """

    def __init__(self, time_series: pd.Series):
        self.time_series = time_series

    def augmented_dickey_fuller_test(self, max_lag: int = 1, pvalue_thresh: float = 0.05):
        """

        Null Hypothesis: The time series is not mean reverting

        :param max_lag:
        :param p_value_thresh:
        :return: True if time series is mean reverting, False if not
        """
        adfstat, pvalue, usedlag, nobs, critvalues = ts.adfuller(self.time_series, maxlag=max_lag)
        return pvalue < pvalue_thresh \
               and (adfstat < critvalues[0] and adfstat < critvalues[1] and adfstat < critvalues[2])

    def johansen_test(self, p_value_thresh: float = 0.95):
        pass

    def hurst_exponent(self, lag_range: tuple = (2, 100)):
        """
        Calculates the stationarity of the series. A time series (or stochastic process) is defined to be strongly
        stationary if its joint probability distribution is invariant under translations in time or space.
        In particular, and of key importance for traders, the mean and variance of the process do not change over time
        or space and they each do not follow a trend.

        A critical feature of stationary price series is that the prices within the series diffuse from their initial
        value at a rate slower than that of a Geometric Brownian Motion. By measuring the rate of this diffusive
        behaviour we can identify the nature of the time series.

        The Hurst Exponent tells you whether a series is
            * Geometric random walk (H=0.5)
            * Mean-reverting series (H<0.5)
            * Trending Series (H>0.5)

        Category: Measure of Autocorrelation

        :return:
        """

        lags = range(lag_range[0], lag_range[1])  # Create the range of lag values
        # Calculate the array of the variances of the lagged differences
        tau = [np.sqrt(np.std(np.subtract(self.time_series[lag:], self.time_series[:-lag]))) for lag in lags]
        poly = np.polyfit(np.log(lags), np.log(tau), 1)  # Use a linear fit to estimate the Hurst Exponent
        return poly[0] * 2.0  # Return the Hurst exponent from the polyfit output

if __name__ == '__main__':
    geometric_brownian_motion = np.log(np.cumsum(randn(100000)) + 1000)
    mean_reverting_series = np.log(randn(100000) + 1000)
    trending_series = np.log(np.cumsum(randn(100000) + 1) + 1000)

import numpy,from numpy,import statsmodels


# Source: http://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
# http://people.wku.edu/qi.li/teaching/446/cg14_curve_surface.pdf
import numpy as np
from utils import distance

def CatmullRomSpline(P0, P1, P2, P3, nPoints=100):
    """
    P0, P1, P2, and P3 should be (x,y) point pairs that define the Catmull-Rom spline.
    nPoints is the number of points to include in this curve segment.
    """
    # Convert the points to numpy so that we can do array multiplication
    P0, P1, P2, P3 = map(np.array, [P0, P1, P2, P3])

    # Calculate t0 to t4
    alpha = 0.5
    def tj(ti, Pi, Pj):
        xi, yi = Pi
        xj, yj = Pj
        return ( ( (xj-xi)**2 + (yj-yi)**2 )**0.5 )**alpha + ti

    t0 = P0[0]
    t1 = tj(t0, P0, P1)
    t2 = tj(t1, P1, P2)
    t3 = P3[0]

    # Only calculate points between P1 and P2
    t = np.linspace(t1,t2,nPoints)

    # Reshape so that we can multiply by the points P0 to P3
    # and get a point for each value of t.
    t = t.reshape(len(t),1)

    A1 = (t1-t)/(t1-t0)*P0 + (t-t0)/(t1-t0)*P1
    A2 = (t2-t)/(t2-t1)*P1 + (t-t1)/(t2-t1)*P2
    A3 = (t3-t)/(t3-t2)*P2 + (t-t2)/(t3-t2)*P3

    B1 = (t2-t)/(t2-t0)*A1 + (t-t0)/(t2-t0)*A2
    B2 = (t3-t)/(t3-t1)*A2 + (t-t1)/(t3-t1)*A3

    C  = (t2-t)/(t2-t1)*B1 + (t-t1)/(t2-t1)*B2
    return C

def CatmullRomLoop(loop, pointsPerUnitDist=1.):
    """
    Calculate Catmull Rom for a list of points, named loop, with loop[0] == loop[-1].
    """
    if len(loop) < 4:
        raise ValueError("Loop must have at least 4 points in it")
    ret = []
    # Add extra control points to ends
    loop = [loop[-2],] + loop + [loop[1],]
    # Produce coords for loop
    for i in xrange(len(loop)-3):
        numPoints = int(distance(loop[i+1], loop[i+2]) * pointsPerUnitDist)
        ret.append(CatmullRomSpline(loop[i], loop[i+1], loop[i+2], loop[i+3], nPoints=numPoints))
    ret = [tuple(coords) for seg in ret for coords in seg]
    return ret


import matplotlib.pyplot as plt
import numpy

x = numpy.arange(-1.0, 1.0, 0.001)

y = numpy.zeros( (len(x),) )
for i in range(len(x)):
    pos = x[i]
    if pos < 0.0:
        y[i] = -2*pos - 1
    else:
        y[i] = -pow(1+5*numpy.square(pos),-0.5)-numpy.square(pos)*pow(1 + 5*numpy.square(pos),-1.5)-pow(pos,4)/16.0

fig_potential = plt.figure(1,figsize=(6,6))
plt.plot(x,-0.05*0.001*numpy.cumsum(y))
plt.ylabel('Potential $G(x)$ / a.u.')
plt.xlabel('Position $x$ / a.u.')

fig_potential.subplots_adjust(left=0.15, bottom=0.1, right=0.95, top=0.95)

fig_potential.savefig('fig_potential.pdf')

fig_force = plt.figure(2,figsize=(6,6))
plt.plot(x,0.05*y)
plt.ylabel('Force $F_g(x)$ / a.u.')
plt.xlabel('Position $x$ / a.u.')

fig_force.subplots_adjust(left=0.15, bottom=0.1, right=0.95, top=0.95)

fig_force.savefig('fig_force.pdf')

plt.show()

#ifelse(T.lt(pos,0.0),-2*pos - 1,-T.pow(1+5*T.numpy.square(pos),-0.5)-T.numpy.square(pos)*T.pow(1 + 5*T.numpy.square(pos),-1.5)-T.pow(pos,4)/16.0) - 0.25*vt


import math
import matplotlib.pyplot as plt
from .GeneralDistribution import Distribution
import numpy as np

class Exponential(Distribution):
    """
    Exponential distribution class for calculating and visualizing a Exponential distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) a list of floats to be extracted from the data file
        rate (float) rate at which events occur
    """

    def __init__(self, rate = 1):

        self.rate = rate
        Distribution.__init__(self, self.calculate_mean(), self.calculate_stdev())

        self.data = np.random.exponential(1.0/self.rate, 500)

    def calculate_mean(self):

        """
        Function to calculate the mean from rate

        Args:
            None

        Returns:
            float: mean of the data set

        """

        self.mean = 1.0/self.rate

        return self.mean

    def calculate_stdev(self):

        """
        Function to calculate the standard deviation from rate

        Args:
            None

        Returns:
            float: standard deviation of the data set

        """

        self.stdev = 1.0/self.rate

        return self.stdev

    def read_data_file(self, file_name):

        """
		Function to read in data from a txt file. The txt file should have
		one number (float) per line. The numbers are stored in the data attribute.
		After reading in the file, the mean and standard deviation are calculated

		Args:
			file_name (string): name of a file to read from

		Returns:
			float: rate parameter

        """
        Distribution.read_data_file(self, file_name)

        self.rate = 1.0 * len(self.data)/sum(self.data)

        self.mean = self.calculate_mean()
        self.stdev = self.calculate_stdev()

        return self.rate

    def pdf(self, k):

        """
        Probability density function calculator for the poisson distribution.

        Args:
            k (float): point for calculating the probability density function

        Returns:
            float: probability density function output
        """

        return self.rate * math.exp(-self.rate * k) if k >= 0 else 0

    def cdf(self, k):

        """
        Cumulative density function calculator for the poisson distribution.

        Args:
            k (float): point for calculating the probability density function

        Returns:
            float: cumulative density function output
        """

        return 1 - math.exp(-self.rate * k) if k >= 0 else 0

    def plot(self):

        """
        Function to plot the data, pdf, cdf of the exponential distribution

        Args:
            None

        Returns:
            list: x values for the both plot
            list: y1 values for the pdf plot
            listL y2 values for the cdf plot

        """

        mu = self.mean
        sigma = self.stdev
        data = self.data.sort()

        x = list(set(self.data))
        x.sort()
        y1 = []
        y2 = []

		# calculate the y values to visualize
        for i in x:
            y1.append(self.pdf(i))
            y2.append(self.cdf(i))

		# make the plot
        fig, axes = plt.subplots(3,sharex=True)
        fig.subplots_adjust(hspace=.5)

        axes[0].hist(self.data, len(x), density=True)
        axes[0].set_title('Normed Histogram of Data')
        axes[0].set_ylabel('Density')

        axes[1].plot(x, y1)
        axes[1].set_title('Probability Distribution for Data')
        axes[1].set_ylabel('Probability Density')

        axes[2].plot(x, y2)
        axes[2].set_title('Cumulative Distribution for Data')
        axes[2].set_ylabel('Probability')

        plt.show()

        return x, y1, y2

    def __add__(self, other):
        pass

    def __repr__(self):

        """
        Function to output the characteristics of the Exponential instance

        Args:
            None

        Returns:
            string: characteristics of the Exponential

        """

        return "mean {}, standard deviation {}, rate parameter {}".\
        format(self.mean, self.stdev, self.rate)


import numpy as np

def get_mu(a, b):
    """Compute the mode of the latitude distribution."""
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    term = (
        4 * alpha ** 2
        - 8 * alpha
        - 6 * beta
        + 4 * alpha * beta
        + beta ** 2
        + 5
    )
    return 2 * np.arctan(np.sqrt(2 * alpha + beta - 2 - np.sqrt(term)))

def get_sigma(a, b):
    """Compute the local standard deviation of the latitude distribution."""
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    mu = get_mu(a, b)
    term = (
        1
        - alpha
        + beta
        + (beta - 1) * np.cos(mu)
        + (alpha - 1) / np.cos(mu) ** 2
    )
    return np.sin(mu) / np.sqrt(term)

def get_J(a, b):
    """
    Compute the Jacobian for the transformation (a, b) <--> (mu, sigma).

    """
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    mu = get_mu(a, b)
    sigma = get_sigma(a, b)
    C = 5 * (5 - np.log(0.5))
    return (
        C
        * (alpha * beta * (1 + np.cos(mu)) ** 3 * np.sin(2 * mu) ** 3)
        / (
            sigma
            * (-3 + 2 * alpha + beta + (-1 + 2 * alpha + beta) * np.cos(mu))
            * (
                2 * (-1 + alpha + beta)
                + 3 * (-1 + beta) * np.cos(mu)
                - 2 * (-1 + alpha - beta) * np.cos(2 * mu)
                + (-1 + beta) * np.cos(3 * mu)
            )
            ** 2
        )
    )

def test_jacobian(ntests=100, eps=1e-6):
    """
    Numerically show that our expression for the Jacobian agrees
    with its definition in terms of derivatives.

    """
    np.random.seed(0)

    for n in range(ntests):
        a = np.random.random()
        b = np.random.random()

        # Analytic expression
        J = get_J(a, b)

        # Numerical expression
        dmuda = (get_mu(a + eps, b) - get_mu(a - eps, b)) / (2 * eps)
        dmudb = (get_mu(a, b + eps) - get_mu(a, b - eps)) / (2 * eps)
        dsigmada = (get_sigma(a + eps, b) - get_sigma(a - eps, b)) / (2 * eps)
        dsigmadb = (get_sigma(a, b + eps) - get_sigma(a, b - eps)) / (2 * eps)
        J_num = dmuda * dsigmadb - dmudb * dsigmada

        assert np.allclose(J, J_num)


from numpy import array, dot, diag, zeros, ones

# Here are the important functions
def Setup_PDE_Alpha_ConstantBC(u0, x, dt, nr_times, t0):
	""" This function solves the heat equation :
			u_t = u_xx
	by using the Euler method. The function takes
	an initial condition u0, a domain  x,
	a time step dt, the number of times to run the loop,
	and the initial time, t0, as input.
	"""
	# Important Constants, etc.
	time = t0
	dx = x[1] - x[0]
	numUnknowns = len(u0) - 2
	r = dt/(dx**2) # Exponents are done with a double asterisk in python
	mainDiagonal = -2*ones(numUnknowns)
	offDiagonal = ones(numUnknowns - 1)
	T = MakeTridiagonalMatrix(mainDiagonal, offDiagonal) # We could consider a better place to make T to make the code faster.
	u = u0

	# Make the boundary condition vector
	BoundaryConditions = zeros(numUnknowns)
	BoundaryConditions[0] = u0[0]
	BoundaryConditions[-1] = u0[-1]

	# Loop to perform the calculations
	for step in range(nr_times):
		u[1:-1] = u[1:-1] + r*dot(T, u[1:-1]) + r*BoundaryConditions
		time = time + dt # We could reconsider how to do this to minimze rounding errors.

	# Return what we want
	return u, time

def MakeTridiagonalMatrix(main, offset_one):
	"""This function will make a tridiagonal 2D array (NOT a matrix)
	which has the main array on its main diagonal and the offset_one
	array on its super and sub diagonals.
	"""
	return diag(main) + diag(offset_one, k = -1) + diag(offset_one, k = 1)


from __future__ import division, print_function
import numpy as np

# Generates the Gauss-Legendre knots and weights for an arbitrary integration interval (a..b)
def leggauss_ab(n=96, a=-1.0, b=1.0):
  assert(n>0)
  from numpy.polynomial.legendre import leggauss
  x,w = leggauss(n)
  x = (b-a) * 0.5 * x+(b+a) * 0.5
  w = w * (b-a) * 0.5
  return x,w

# Generates the Gauss-Legendre knots and weights
#   Fortran version is written by James Talman
def gauleg_ab(n=96, a=-1.0, b=1.0, eps=1.0e-15, cmx=15):
  assert(n>0)
  x,w = np.zeros((n)),np.zeros((n))

  m=(n+1)//2
  for i in range(m):
    z=np.cos(np.pi*(i+1-0.25)/(n+0.5))
    for c in range(cmx):
      p1,p2=1.0,0.0
      for j in range(1,n+1):
        p3=p2
        p2=p1
        p1=((2.0*j-1)*z*p2-(j-1)*p3)/j
      pp=n*(z*p1-p2)/(z*z-1.0)
      z1=z
      z=z1-p1/pp
      if abs(z-z1)<eps : exit

    x[ i],x[-i-1] = -z,z
    w[ i]=w[-i-1] = 2.0/((1.0-z*z)*pp*pp)

  x = (b-a) * 0.5 * x+(b+a) * 0.5
  w = w * (b-a) * 0.5
  return x,w

if __name__ == '__main__':
  from pyscf.nao.m_gauleg import gauleg_ab
  from numpy.polynomial.legendre import leggauss
  for n in range(1,320):
    xx1,ww1=leggauss(n)
    xx2,ww2=gauleg_ab(n)
    if np.allclose(xx1,xx2) and np.allclose(ww1,ww2):
      print(n, 'allclose')
    else:
      print(n, (xx1-xx2).sum(), (ww1-ww2).sum())


import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Normalize
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import

def plot_function_surface(x1, x2, Z, C, t, cmap='gist_earth'):
    fig = plt.figure(figsize=(18, 12))
    ax = fig.add_subplot(projection='3d')
    vmax = np.max(np.abs(np.ravel(C)))
    vmin = -vmax
    norm = Normalize(vmin, vmax)
    scamap = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
    ax.plot_surface(x1, x2, Z, facecolors=scamap.to_rgba(C), cmap=cmap)
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.set_zlabel('z')
    ax.set_title(t)
    fig.colorbar(scamap, shrink=0.5, aspect=10)
    plt.tight_layout()

def f(x1, x2):
    # return -10 * x1 + 0 * x2 + 100
    return x2 * np.sin(x1 ** 2)
    # return (x1**2) * x2

def df_x1(x1, x2):
    # return np.ones_like(x1) * -10
    return 2 * x1 * x2 * np.cos(x1 ** 2)
    # return 2 * x1 * x2

def df_x2(x1, x2):
    # return np.zeros_like(x2)
    return np.sin(x1 ** 2)
    # return x1 ** 2

def main():
    x1 = np.linspace(-2, 2, 50)
    x2 = np.linspace(-5, 5, 50)
    x1, x2 = np.meshgrid(x1, x2)

    # Function plot
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=f(x1, x2), t='Z=f(x1, x2), C=f(x1, x2)')

    # Function plots with x1 and x2 gradients
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x1(x1, x2), t='Z=f(x1, x2), C=df_x1(x1, x2)')
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x2(x1, x2), t='Z=f(x1, x2), C=df_x2(x1, x2)')

    # Function plots with x1 gradient * input and x2 gradient * input
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x1(x1, x2) * x1, t='Z=f(x1, x2), C=df_x1(x1, x2) * x1')
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x2(x1, x2) * x2, t='Z=f(x1, x2), C=df_x2(x1, x2) * x2')

    # Gradient plots
    plot_function_surface(x1=x1, x2=x2, Z=df_x1(x1, x2), C=df_x1(x1, x2), t='Z=df_x1(x1, x2), C=df_x1(x1, x2)')
    # plot_function_surface(x1=x1, x2=x2, Z=df_x2(x1, x2), C=df_x2(x1, x2), t='Z=df_x2(x1, x2), C=df_x2(x1, x2)')

    # Gradient * input plots
    # plot_function_surface(x1=x1, x2=x2, Z=df_x1(x1, x2) * x1, C=df_x1(x1, x2) * x1, t='Z=df_x1(x1, x2) * x1, C=df_x1(x1, x2) * x1')
    # plot_function_surface(x1=x1, x2=x2, Z=df_x2(x1, x2) * x2, C=df_x2(x1, x2) * x2, t='Z=df_x2(x1, x2) * x2, C=df_x2(x1, x2) * x2')

    plt.show()

main()


###########################################################################
# This file just takes a derivative of the data
############################################################################

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm

def checkStep2( x, y, z, plot=False, plotTitle = 'u_y over u_x' ):
    # Step 2 says that if u_y(x,y,0) = \beta u_x(x,y,0) for some \beta, then the
    # data is one-dimensional. We calculate u_y and u_x then divide pointwise to
    # see if there is a pattern.
    u_x, u_y = np.gradient( z );
    u_y_over_u_x = np.divide( u_y, u_x );

    if plot==True:
        fig = plt.figure()
        ax1 = plt.gca( projection = '3d' )
        surf = ax1.plot_surface(x,y,u_y_over_u_x, cmap = cm.coolwarm);
        ax1.set_xlabel('x'); ax1.set_ylabel('y');
        fig.suptitle(plotTitle)

        #fig = plt.figure()
        #ax2 = plt.gca( projection = '3d' )
        #surf = ax2.plot_surface(x2,y2,u_y_over_u_x2, cmap = cm.coolwarm);
        #ax2.set_xlabel('x'); ax1.set_ylabel('y');
        #fig.suptitle(plotTitle + ' edges removed')

    # Find average, throwing out outliers which are very large numbers
    # First Remove edge cases
    u_y_over_u_x2 = u_y_over_u_x[1:-1, 1:-1]
    u_y_over_u_x2 = u_y_over_u_x2[ np.isfinite(u_y_over_u_x2) ]
    print('The mean of u_y/u_x = ' + str(u_y_over_u_x2.mean()) +
            ' with standard deviation = ' + str(u_y_over_u_x2.std()) )

    return u_y_over_u_x


import numpy as np

class LSE(object):
    """
    Least Squares Error Linear Regression.
    # Attributes
    ----------
    coef_ : array, shape (1, )
        Estimated coefficient for the linear regression problem.
    intercept_ : float
        Independent term in the linear model.

    # Examples
    --------
    >>> import numpy as np
    >>> from mlitera.linreg import LSE
    >>> X = np.array([[1], [2], [3], [4], [5]])
    >>> # y = 2x + 3
    >>> y = (2 * X + 3).flatten()
    >>> m = LSE().fit(X, y)
    >>> m.score(y, m.predict(X))
    1.0
    >>> m.intercept_
    3.0
    >>> m.coef_
    2.0
    """

    def fit(self, X, y):
        """
        Fit linear model.
        Parameters
        ----------
        X : array-like, shape (n_samples, 1)
            Training data
        y : array_like, shape (n_samples, )
            Target values.
        Returns
        -------
        self : returns an instance of self.
        """
        X = X.flatten() # unrolled X
        N = len(X)
        theta1 = ( sum(X * y) - (N * X.mean() * y.mean()) ) / sum((X - X.mean()) ** 2)
        theta0 = y.mean() - theta1 * X.mean()
        self.thetas = np.array([theta0, theta1])
        return self

    @property
    def intercept_(self):
        """ Independent term in the linear model."""
        return self.thetas[0]

    @property
    def coef_(self):
        """ Estimated coefficient for the linear regression problem."""
        return self.thetas[-1]

    def score(self, y_true, y_pred):
        """Returns the coefficient of determination R^2 of the prediction.
        The coefficient R^2 is defined as (1 - SSR/SST), where SSR is the residual
        sum of squares sum((y_true - y_pred) ** 2) and SST is the total
        sum of squares sum((y_true - y_true.mean()) ** 2).
        The best possible score is 1.0 and it can be negative (because the
        model can be arbitrarily worse). A constant model that always
        predicts the expected value of y, disregarding the input features,
        would get a R^2 score of 0.0.
        Parameters
        ----------
        y_true : array-like, shape = (n_samples)
            True values for X.
        y_pred : array-like, shape = (n_samples)
            Prediction values for X.
        Returns
        -------
        score : float
            R^2
        """
        SST = sum((y_true - y_true.mean()) ** 2)
        SSR = sum((y_true - y_pred) ** 2)
        return 1 - SSR/ SST

    def predict(self, X):
        """Predict using the linear model
        Parameters
        ----------
        X : array_like , shape (n_samples, 1)
            Samples.
        Returns
        -------
        y : array, shape (n_samples,)
            Returns predicted values.
        """
        X = X.flatten() # unrolled X
        y = self.intercept_ + self.coef_ * X
        return y

if __name__ == '__main__':
    import sys
    import doctest
    sys.path.append("/home/jose/machine-learning-itera")
    doctest.testmod()


# Utitlity file with functions for handling rotations
#
# Author: Trevor Ablett
# University of Toronto Institute for Aerospace Studies

import numpy as np

def skew_symmetric(v):
    """ Skew symmetric operator for a 3x1 vector. """
    return np.array(
        [[0, -v[2], v[1]],
         [v[2], 0, -v[0]],
         [-v[1], v[0], 0]]
    )

class Quaternion():
    def __init__(self, w=1., x=0., y=0., z=0., axis_angle=None, euler=None):
        """
        Allow initialization either with explicit wxyz, axis angle, or euler XYZ (RPY) angles.

        :param w: w (real) of a quaternion.
        :param x: x (i) of a quaternion.
        :param y: y (j) of a quaternion.
        :param z: z (k) of a quaternion.
        :param axis_angle: Set of three values from axis-angle representation, as list or [3,] or [3,1] np.ndarray.
                           See C2M5L2 Slide 7 for details.
        :param euler: Set of three angles from euler XYZ rotating frame angles.
        """
        if axis_angle is None and euler is None:
            self.w = w
            self.x = x
            self.y = y
            self.z = z
        elif euler is not None and axis_angle is not None:
            raise AttributeError("Only one of axis_angle and euler can be specified.")
        elif axis_angle is not None:
            if not (type(axis_angle) == list or type(axis_angle) == np.ndarray) or len(axis_angle) != 3:
                raise ValueError("axis_angle must be list or np.ndarray with length 3.")
            axis_angle = np.array(axis_angle)
            norm = np.linalg.norm(axis_angle)
            self.w = np.cos(norm / 2)
            if norm < 1e-50:  # to avoid instabilities and nans
                self.x = 0
                self.y = 0
                self.z = 0
            else:
                imag = axis_angle / norm * np.sin(norm / 2)
                self.x = imag[0].item()
                self.y = imag[1].item()
                self.z = imag[2].item()
        else:
            roll = euler[0]
            pitch = euler[1]
            yaw = euler[2]

            cy = np.cos(yaw * 0.5)
            sy = np.sin(yaw * 0.5)
            cr = np.cos(roll * 0.5)
            sr = np.sin(roll * 0.5)
            cp = np.cos(pitch * 0.5)
            sp = np.sin(pitch * 0.5)

            # static frame
            self.w = cr * cp * cy + sr * sp * sy
            self.x = sr * cp * cy - cr * sp * sy
            self.y = cr * sp * cy + sr * cp * sy
            self.z = cr * cp * sy - sr * sp * cy

            # rotating frame
            # self.w = cr * cp * cy - sr * sp * sy
            # self.x = cr * sp * sy + sr * cp * cy
            # self.y = cr * sp * cy - sr * cp * sy
            # self.z = cr * cp * sy + sr * sp * cy

    def __repr__(self):
        return "Quaternion (wxyz): [%2.5f, %2.5f, %2.5f, %2.5f]" % (self.w, self.x, self.y, self.z)

    def to_mat(self):
        v = np.array([self.x, self.y, self.z]).reshape(3,1)
        return (self.w ** 2 - np.dot(v.T, v)) * np.eye(3) + \
               2 * np.dot(v, v.T) + 2 * self.w * skew_symmetric(v)

    def to_euler(self):
        """ Return as xyz (roll pitch yaw) Euler angles, with a static frame """
        roll = np.arctan2(2 * (self.w * self.x + self.y * self.z), 1 - 2 * (self.x**2 + self.y**2))
        pitch = np.arcsin(2 * (self.w * self.y - self.z * self.x))
        yaw = np.arctan2(2 * (self.w * self.z + self.x * self.y), 1 - 2 * (self.y**2 + self.z**2))
        return np.array([roll, pitch, yaw])

    def to_numpy(self):
        """ Return numpy wxyz representation. """
        return np.array([self.w, self.x, self.y, self.z])

    def normalize(self):
        """ Return a normalized version of this quaternion to ensure that it is valid. """
        norm = np.linalg.norm([self.w, self.x, self.y, self.z])
        return Quaternion(self.w / norm, self.x / norm, self.y / norm, self.z / norm)

    def quat_mult(self, q, out='np'):
        """
        Give output of multiplying this quaternion by another quaternion.
        See equation from C2M5L2 Slide 7 for details.

        :param q: The quaternion to multiply by, either a Quaternion or 4x1 ndarray.
        :param out: Output type, either np or Quaternion.
        :return: Returns quaternion of desired type
        """

        v = np.array([self.x, self.y, self.z]).reshape(3, 1)
        sum_term = np.zeros([4,4])
        sum_term[0,1:] = -v[:,0]
        sum_term[1:, 0] = v[:,0]
        sum_term[1:, 1:] = -skew_symmetric(v)
        sigma = self.w * np.eye(4) + sum_term

        if type(q).__name__ == "Quaternion":
            quat_np = np.dot(sigma, q.to_numpy())
        else:
            quat_np = np.dot(sigma, q)

        if out == 'np':
            return quat_np
        elif out == 'Quaternion':
            quat_obj = Quaternion(quat_np[0], quat_np[1], quat_np[2], quat_np[3])
            return quat_obj

Course 2 - State Estimation and Localization/Course2FinalProject/rotations.py


import numpy as np
import numpy.linalg as la
import numpy.random as random
import math

class GMM:

    def fit(self, X, num_clusters=2, num_epochs=50):

        # describes the training criteria for the GMM using EM formulation
        num_samples = X.shape[0];
        num_dims = X.shape[1];

        """Step1 : Initialize the gaussian parameters and the discrete distribution"""
        pi_distribution = random.rand(num_clusters,);
        pi_distribution = pi_distribution / la.norm(pi_distribution, ord=1);

        print pi_distribution;

        mean = random.randn(num_clusters, num_dims);
        covariance = random.randn(num_clusters, num_dims, num_dims);

        phi = np.zeros(shape=(num_samples, num_clusters));

        prev_error = 0.0;
        new_error = 0.0;

        epsilon = 0.0001;

        for epoch_idx in range(num_epochs):

            """Step2: Assign the data to the clusters"""
            for sample_idx in range(num_samples):

                for cluster_idx in range(num_clusters):
                    phi[sample_idx, cluster_idx] = pi_distribution[cluster_idx] * \
                                                    self.compute_gaussian(np.reshape(X[sample_idx],(1,2)),
                                                                          np.reshape(mean[cluster_idx],(1,2)),
                                                                          covariance[cluster_idx]);

                # normalize the distribution over the weights
                phi[sample_idx] = phi[sample_idx] / la.norm(phi[sample_idx], ord=1);

            """Step3: Update the parameters of the model """
            # update the pi distribution
            nk = np.sum(phi, axis=0);
            pi_distribution = nk / num_samples;

            # update the mean of the clusters
            mean = np.matmul(np.transpose(phi), X);
            for cluster_idx in range(num_clusters):
                mean[cluster_idx] = mean[cluster_idx] / nk[cluster_idx];

            # update the covariance of the clusters
            for cluster_idx in range(num_clusters):

                sample = np.zeros(shape=(num_dims, num_dims));
                for sample_idx in range(num_samples):

                    tmp = X[sample_idx] - mean[cluster_idx];
                    tmp = np.reshape(tmp, (1,2));

                    sample = sample + phi[sample_idx,cluster_idx] * np.matmul(np.transpose(tmp), tmp);

                covariance[cluster_idx] = sample / nk[cluster_idx];

            # compute the error at the end of each iteration
            error = 0;
            for sample_idx in range(num_samples):

                sample_error = 0.0;
                for cluster_idx in range(num_clusters):
                    sample_error += (pi_distribution[cluster_idx] *
                              self.compute_gaussian(np.reshape(X[sample_idx],(1,2)),
                                                    np.reshape(mean[cluster_idx],(1,2)),
                                                    covariance[cluster_idx]));

                error += math.log(sample_error);

            prev_error = new_error;
            new_error = error;

            print ('Log likelihood:%0.5f at Epoch:%d' % (error, epoch_idx));
            print pi_distribution;
            if abs(new_error - prev_error) < epsilon:
                break;

        # return the theta,mean and the covariance
        return (phi, pi_distribution, mean, covariance);

    def compute_gaussian(self, x, mean, covariance):

        # computes the rbf distance of x using the mean and the covariance
        cov_inv = la.inv(covariance);
        tmp = np.matmul(x - mean , cov_inv);
        dist = np.exp(-np.matmul(tmp, np.transpose(x - mean)))/(math.sqrt(2 * np.pi) * np.abs(la.det(covariance)));

        return dist;


'''
module docstring for carrier equilibrium density
'''

from numpy import exp,sqrt,pi
from semic.constants.constants import value
from semic.math.fdint import fdint_approx

def find_n0(n_c=0,conduction_band_energy=0,fermi_energy=0,temp=0):
    '''
    Function to find nondegenerate Equilibrium electron density
    in the conduction band.

    n_c: Thermal effective density of states in the
        conduction band.

    conduction_band_energy: Conduction band edge energy in a semiconductor.
        This is the potential energy of electrons, including
        electrostatic potential.

    fermi_energy: Fermi energy, or the chemical potential for electrons.

    k_b: Boltzmann's constant in eV/K

    temp: Temperature in Kelvin

    n0 = n_c*exp(-(conduction_band_energy-fermi_energy)/(k_b*temp))
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    n_0 = n_c*exp(-(conduction_band_energy-fermi_energy)/kb_t)

    return n_0

def find_nc(mass_c=0,temp=0):
    '''
    Function to find thermal effective density of states in
    conduction band.

    mass_c: The effective mass of a carrier in the conduction band

    k_b: Boltzmann constant in eV/K

    temp: Temperature in Kelvin

    hbar: reduced Planck constant in eV s

    n_c = 2*[mass_c*k_b*temp/(2pi*hbar^2)]^(3/2)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    h_bar = value('reduced Planck constant in eV s')
    n_c = 2*((mass_c*kb_t/(2*pi*(h_bar**2)))**(3/2))

    return n_c

def find_p0(n_v=0,valence_band_energy=0,fermi_energy=0,temp=0):
    '''
    Function to find the nondegenerate Equilibrium hole density
    in the valence band.

    n_v: Thermal effective density of states in valence band

    valence_band_energy: Valence band edge energy in a semiconductor.
        This is the potential energy of electrons, including
        electrostatic potential.

    k_b: Boltzmann constant in eV/K

    temp: Temperature in Kelvin

    p0 = n_v*exp(valence_band_energy-fermi_energy/k_b*temp)
    '''

    kb_t = value('Boltzmann constant in eV/K') * temp
    p_0 = n_v*exp((valence_band_energy-fermi_energy)/kb_t)

    return p_0

def find_nv(mass_v=0,temp=0):
    '''
    Function to find the thermal effective density
    of states in valence band.

    mass_v: The effective mass of of carrier in valence band

    temp: Temperature in Kelvin

    k_b: Boltzmann constant in eV/K

    hbar: reduced Planck constant in eV s

    n_v = 2*[mass_v*k_b*temp/(2pi*hbar^2)]^(3/2)
    '''

    kb_t = value('Boltzmann constant in eV/K') * temp
    h_bar = value('reduced Planck constant in eV s')

    n_v = 2*(mass_v*kb_t/(2*pi*(h_bar**2)))**(3/2)

    return n_v

def find_ni(n_0 = 0, p_0 = 0):
    '''
    Function to find the intrinsic carrier density

    n_0 = Equilibrium carrier density of electrons in conduction band

    p_0 = Equilibrium carrier density of holes in valence band

    n_i = sqrt(n_0*p_0)
    '''

    n_i = sqrt(n_0 * p_0)

    return n_i

def n0_from_ni(n_i=0,fermi_energy=0,in_fermi_level=0,temp=1):
    '''
    Function to find the equilibrium electron density from the
    intrinsic carrier density of a semiconductor.

    n_i: Intrinsic carrier density of a semiconductor

    fermi_energy: Fermi energy, or the chemical potential of an electron

    in_fermi_level: Fermi level of an intrinsic semiconductor

    temp: Temperature in Kelvin

    n0 = ni*exp((Ef-Efi)/kbT)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    exponential = exp((fermi_energy-in_fermi_level)/kb_t)
    n_0 = n_i*exponential

    return n_0

def p0_from_ni(n_i=0,fermi_energy=0,in_fermi_level=0,temp=1):
    '''
    Function to find the equilibrium hole density from the
    intrinsic carrier density of a semiconductor.

    n_i: Intrinsic carrier density of a semiconductor

    fermi_energy: Fermi energy, or the chemical potential of an electron

    in_fermi_level: Fermi level of an intrinsic semiconductor

    temp: Temperature in Kelvin

    p0 = ni*exp((Efi-Ef)/kbT)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    exponential = exp((in_fermi_level-fermi_energy)/kb_t)
    p_0 = n_i*exponential

    return p_0

def p0_approx(n_v=0,eta=0):
    '''
    Function to find the equilibrium hole density
    using an approximation of the Fermi-Dirac integral
    of order 1/2.

    n_v: The thermal effective density of states in the valence band.

    eta: (Ev - Ef)/kT
    '''
    p_0 = n_v*fdint_approx(eta)

    return p_0

def n0_approx(n_c=0,eta=0):
    '''
    Function to find the equilibrium electron density
    using an approximation of the Fermi-Dirac integral
    of order 1/2.

    n_c: The thermal effective density of states in the conduction band.

    eta: (Ef - Ec)/kT
    '''
    n_0 = n_c*fdint_approx(eta)

    return n_0


import math
import unittest

import numpy as np

def det(mat):
    """ det(a) --> d """
    assert isinstance(mat, np.ndarray)
    assert len(mat.shape) == 2
    assert mat.shape[0] == mat.shape[1]
    val_a, val_b, val_c, val_d = mat.reshape((-1,))
    return val_a * val_d - val_b * val_c

def det_recursive(mat):
    """ det(a)  = b1 * det (b) + b2 * det(c) + b3* det(d)
    𝒪(n!)
    """
    rows, _ = mat.shape
    if rows == 2:
        return det(mat)
    row = mat[0, :]
    det_mat = 0.0
    sign = 1
    for i, val in enumerate(row):
        # print(i, val)
        mat_i = np.zeros((rows-1, rows-1))
        mat_i[:, 0:i] = mat[1:, 0:i]
        mat_i[:, i:] = mat[1:, i+1:]
        # print(mat_i)
        det_i = det_recursive(mat_i)
        det_mat += sign * val * det_i
        sign *= -1
    return det_mat

def num_ops_det(num):
    if num == 1:
        num_ops = 1
    elif num == 2:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    elif num == 3:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    elif num >= 4:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    else:
        num_ops = -1
    return num_ops

def demo():
    mat_a = np.array([[1, 2], [3, 4]])
    print(det(mat_a))

    mat_b = np.array([[789]])
    print(np.linalg.det(mat_b))

    det_num_ops = [num_ops_det(num) for num in range(2, 16)]
    print(det_num_ops)

    val_fact = [math.factorial(num) for num in range(2, 16)]
    print(val_fact)

    vec_det = np.array(det_num_ops, dtype=float)
    vec_fac = np.array(val_fact, dtype=float)

    print(vec_det/vec_fac)

class TestDeterminant(unittest.TestCase):

    def test_det_2x2(self):
        mat_a = np.random.randint(1, 10, size=(2, 2))
        nrows, ncols = mat_a.shape
        self.assertEqual(nrows, ncols)
        self.assertEqual(nrows, 2)

        val_a, val_b, val_c, val_d = mat_a.reshape((-1,))
        det_a = val_a * val_d - val_b * val_c
        self.assertEqual(det(mat_a), det_a)
        np.testing.assert_allclose(
            np.linalg.det(mat_a), det(mat_a))

    def test_det_lu_nxn(self):
        for num in range(3, 9+1):
            mat_a = np.random.randint(-5, 5, size=(num, num))
            mat_l, mat_u = np.tril(mat_a, k=-1) + np.eye(num, dtype=int), \
                np.triu(mat_a)
            mat_a = mat_l @ mat_u
            det_u = np.prod(np.diag(mat_u))
            np.testing.assert_allclose(np.linalg.det(mat_a), det_u, atol=1e-7)
            self.assertAlmostEqual(np.linalg.det(mat_a), det_u)

    def test_det_nxn(self):
        for num in range(3, 9+1):
            mat_a = np.random.randint(1, 10, size=(num, num))
            self.assertAlmostEqual(np.linalg.det(mat_a), det_recursive(mat_a))

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


import matplotlib.pyplot as plt
import numpy as np
# Link - https://towardsdatascience.com/linear-regression-using-gradient-descent-97a6c8700931
# refer the github link too

#dataset from geeksforgeek

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

t0=1
t1=1
alpha=0.01
m=float(len(x))
epochs=1000

for i in range(epochs):
    predicted_result=t0+(t1*x)
    diff0=sum(y-predicted_result)/(-2*m)
    diff1=sum((y-predicted_result)*x)/(-2*m)
    t0=t0-alpha*diff0
    t1=t1-alpha*diff1

print(t0,t1)

final_fitted_data=t0+(t1*x)
print(t0+(t1*9))
plt.scatter(x,y)
plt.plot([min(x),max(x)],[min(final_fitted_data),max(final_fitted_data)],color='red')
plt.show()


import numpy as np

def mean_squared_error(y, t):
    return (1 / 2) * np.sum((y - t) ** 2)

if __name__ == '__main__':
    t = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0])

    y1 = np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y2 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y3 = np.array([0.1, 0.9, 0.3, -0.3, 0, 0, 0.2, 0, 0, 0.6])

    print('t = {0}'.format(t))
    print('y1 = {0}, mean_squared_error = {1}'.format(
        y1, mean_squared_error(y1, t)))
    print('y2 = {0}, mean_squared_error = {1}'.format(
        y2, mean_squared_error(y2, t)))
    print('y3 = {0}, mean_squared_error = {1}'.format(
        y3, mean_squared_error(y3, t)))
    print('t = {0}, mean_squared_error = {1}'.format(
        t, mean_squared_error(t, t)))


import numpy as np

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

def sigmoid_grad(x):
    sigmoid_grad = sigmoid(x)
    sigmoid_grad = sigmoid_grad * (1. - sigmoid_grad)
    return sigmoid_grad

def tanh(x):
    return np.tanh(x)

def tanh_grad(x):
    return (1. - (tanh(x) ** 2))

def relu(x):
    return np.maximum(0., x)

def relu_grad(x):
    gradient = np.ones(x.shape)
    gradient[x < 0] = 0
    return gradient

def softmax(x):
    e_x = np.exp(x - np.max(x, axis=-1).reshape(x.shape[0], 1))
    return e_x / np.sum(e_x, axis=1).reshape(e_x.shape[0], 1)

def softmax_grad(x):
    # This is a special case. It is easier to precompute the gradient wrt the activation and send it below
    return 1.

def linear(x):
    return x

def linear_grad(x):
    return 1.

def leaky_relu(x, alpha=0.01):
    mask = np.ones(x.shape)
    mask[x < 0] = alpha
    return x * mask

def leaky_relu_grad(x, alpha=0.01):
    mask = np.ones(x.shape)
    mask[x < 0] = alpha
    return mask


# 5g + 17m = 30.5
# 8g + 22m = 41

# Can be solved in NumPy using
import numpy as np

coeffs  = np.array([[5, 17], [8, 22]])
depvars = np.array([30.5, 41])
solution = np.linalg.solve(coeffs, depvars)

# solution
print ( solution )
# array([1, 1.5])
# means g=1, m=1.5 or 1 gallon per hour for generator,
# 1.5 gallon per hour for main moter


import numpy as np
import matplotlib.pyplot as plt

def permutation_test(first_array, second_array, resampling=30000, print_result=True, seed=0):

    np.random.seed = seed

    H0_diff = abs(first_array.mean() - second_array.mean())

    total_array = np.hstack([first_array, second_array])

    mean_diffs = []

    for _ in range(resampling):

        np.random.shuffle(total_array)

        random_first_array = total_array[:first_array.shape[0]]
        random_second_array = total_array[first_array.shape[0]:]

        mean_diff = abs(random_first_array.mean() - random_second_array.mean())
        mean_diffs.append(mean_diff)

    nb_extreme_values = 0

    for min_diff in mean_diffs:
        if min_diff > abs(H0_diff):
            nb_extreme_values += 1

    p_value = nb_extreme_values / len(mean_diffs)

    if print_result : print(f"P-value is {p_value:.2f}")
    return p_value


# -*- coding: utf-8 -*-
"""
Rotation operations
"""

import numpy as np

def x(phi):
    """
    returns rotation array along x-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along x-axis
    """
    return np.array(((1, 0, 0), (0, np.cos(phi), -np.sin(phi)),
                     (0, np.sin(phi), np.cos(phi))), float)

def y(phi):
    """
    returns rotation array along y-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along y-axis
    """
    return np.array(((np.cos(phi), 0, np.sin(phi)), (0, 1, 0),
                     (-np.sin(phi), 0, np.cos(phi))), float)

def z(phi):
    """
    returns rotation array along z-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along z-axis
    """
    return np.array(((np.cos(phi), -np.sin(phi), 0),
                     (np.sin(phi), np.cos(phi), 0), (0, 0, 1)), float)

def z_2d(phi):
    """
    returns rotation array along z-axis for 2d case

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (2,2)-np.ndarray
        rotation array along z-axis
    """
    return np.array(((np.cos(phi), -np.sin(phi)), (np.sin(phi), np.cos(phi))),
                    float)

def zyz(alpha, beta, gamma):
    """
    returns rotation array along z-axis y-axis z-axis

    Parameters
    ----------
    alpha: float
        rotation angle
    beta: float
        rotation angle
    gamma: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    return np.dot(z(alpha), np.dot(y(beta), z(gamma)))

def zymz(theta, phi):
    """
    returns rotation array along z-axis y-axis -z-axis

    Parameters
    ----------
    theta: float
        rotation angle
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    return np.dot(z(phi), np.dot(y(theta), z(-phi)))

def a_on_b(a, b):
    """
    returns rotation array from vector a on vector b

    Parameters
    ----------
    a: np.ndarray
        vector to rotate
    b: np.ndarray
        vector to rotate on

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    a = a / np.linalg.norm(a)
    b = b / np.linalg.norm(b)
    if np.all(a == b):
        return np.identity(3)

    v = np.cross(a, b)
    s = np.linalg.norm(v)
    c = np.dot(a, b)
    vx = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
    R = np.identity(3, float) + vx + np.dot(vx, vx) * (1 - c) / s**2
    return R


# -*- coding: utf-8 -*-
#%% NumPyの読み込み
import numpy as np
#   SciPyのstatsモジュールの読み込み
import scipy.stats as st
#   PyMCの読み込み
import pymc3 as pm
#   MatplotlibのPyplotモジュールの読み込み
import matplotlib.pyplot as plt
#   日本語フォントの設定
from matplotlib.font_manager import FontProperties
import sys
if sys.platform.startswith('win'):
    FontPath = 'C:\\Windows\\Fonts\\meiryo.ttc'
elif sys.platform.startswith('darwin' ):
    FontPath = '/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc'
elif sys.platform.startswith('linux'):
    FontPath = '/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf'
else:
    sys.exit('このPythonコードが対応していないOSを使用しています．')
jpfont = FontProperties(fname=FontPath)
#%% ノイズを含むAR(1)過程からデータを生成
n = 500
np.random.seed(99)
x = np.empty(n)
x[0] = st.norm.rvs() # 定常分布の分散 = 0.19/(1 - 0.9**2) = 1.0
for t in range(1, n):
    x[t] = 0.9 * x[t-1] + st.norm.rvs(scale=np.sqrt(0.19))
y = x + st.norm.rvs(scale=0.5, size=n)
#%% 事後分布の設定
ar1_model = pm.Model()
with ar1_model:
    sigma = pm.HalfCauchy('sigma', beta=1.0)
    rho = pm.Uniform('rho', lower=-1.0, upper=1.0)
    omega = pm.HalfCauchy('omega', beta=1.0)
    ar1 = pm.AR('ar1', rho, sigma=omega, shape=n,
                init=pm.Normal.dist(sigma=omega/pm.math.sqrt(1 - rho**2)))
    observation = pm.Normal('y', mu=ar1, sigma=sigma, observed=y)
#%% 事後分布からのサンプリング
n_draws = 5000
n_chains = 4
n_tune = 1000
with ar1_model:
    trace = pm.sample(draws=n_draws, chains=n_chains, tune=n_tune,
                      random_seed=123)
    param_names = ['sigma', 'rho', 'omega']
    print(pm.summary(trace, var_names=param_names))
#%% 事後分布のグラフの作成
labels = ['$\\sigma$', '$\\rho$', '$\\omega$']
k = len(labels)
fig, ax = plt.subplots(k, 2, num=1, figsize=(8, 1.5*k), facecolor='w')
for index in range(k):
    mc_trace = trace[param_names[index]]
    x_min = mc_trace.min() - 0.2 * np.abs(mc_trace.min())
    x_max =  mc_trace.max() + 0.2 * np.abs(mc_trace.max())
    x = np.linspace(x_min, x_max, 250)
    posterior = st.gaussian_kde(mc_trace).evaluate(x)
    ax[index, 0].plot(mc_trace, 'k-', linewidth=0.1)
    ax[index, 0].set_xlim(1, n_draws*n_chains)
    ax[index, 0].set_ylabel(labels[index], fontproperties=jpfont)
    ax[index, 1].plot(x, posterior, 'k-')
    ax[index, 1].set_xlim(x_min, x_max)
    ax[index, 1].set_ylim(0, 1.1*posterior.max())
    ax[index, 1].set_ylabel('確率密度', fontproperties=jpfont)
ax[k-1, 0].set_xlabel('乱数系列', fontproperties=jpfont)
ax[k-1, 1].set_xlabel('周辺事後分布', fontproperties=jpfont)
plt.tight_layout()
plt.savefig('pybayes_fig_mcmc_ar1.png', dpi=300)
plt.show()


'''
Reference
[1] P. N. Belhumeur, J. P. Hespanha and D. J. Kriegman, "Eigenfaces vs. Fisherfaces: recognition using class specific linear projection," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 19, no. 7, pp. 711-720, July 1997, doi: 10.1109/34.598228.
> The variable names used in this source code are consistent with this paper.
[2] Bhattacharyya, Suman & Rahul, Kumar. (2013). Face recognition by linear discriminant analysis. International Journal of Communication Network Security. 2. 31-35.
'''
import numpy as np
import sys

from settings import FACE_COUNT
from settings import IMAGE_COUNT_PER_FACE
from settings import TRAIN_COUNT_PER_FACE
from image_parser import read_all_images
from math_utils import get_dist

def get_W(training_set, m):
    assert len(training_set[0]) == TRAIN_COUNT_PER_FACE*FACE_COUNT
    n = len(training_set)
    '''
    The between-class scatter matrix is defined as S_B.
    The total scatter matrix is defined as S_T.
    The within-class scatter matrix is defined as S_W.

    '''
    S_B, S_T, S_W = np.zeros([n, n]), np.zeros([n, n]), np.zeros([n, n])
    '''
    mu is the mean image of all images in the training set.
    mu_i is is the mean image of the images of the i-th face (X_i).
    '''
    mu = np.array([np.mean(training_set, axis=1)]).T
    for i in range(FACE_COUNT):
        X_i = training_set[:, TRAIN_COUNT_PER_FACE *
                           i:TRAIN_COUNT_PER_FACE*(i+1)]
        mu_i = np.array([np.mean(X_i, axis=1)]).T
        S_B += np.dot(mu_i-mu, (mu_i-mu).T)*TRAIN_COUNT_PER_FACE
        for j in range(TRAIN_COUNT_PER_FACE):
            x_i_j = np.array([X_i[:, j]]).T
            S_T += np.dot(x_i_j-mu, (x_i_j-mu).T)
            S_W += np.dot(x_i_j-mu_i, (x_i_j-mu_i).T)
    '''
    S_W is always singular, for the rank of S_W is <= (N - c).
    N = TRAIN_COUNT_PER_FACE*FACE_COUNT (= 200), c = FACE_COUNT (=40); m <= (c-1) (=39).
    Fisherfaces is proposed to avoid this problem by projecting the images to a lower dimensional space.
    1. Use PCA to reduce the dimension of the feature space to (N - c);
    2. Applying the standard FLD to reduce the dimension to (c - 1).
    W_opt = W_pca * W_fld. W_opt is the most optimized W, that is, the return value.
    W_pca = argmax(X) det(W.T * S_T * W),
    performed over n * (N - c) matrices with orthonormal columns.
    W_fld = argmax(X) det(W.T * W_pca.T * S_B * W_pca * W) / det(W.T * W_pca.T * S_W * W_pca * W),
    performed over (N - c) * m matrices with orthonormal columns.

    W_pca is the set of eigenvectors of S_T corresponding to the (N - c) largest eigenvalues.
    S_T is a symmetric matrix, the purpose of calling `eigh` is to output eigenvectors without `+0.j`.
    '''
    eigenvalues_S_T, eigenvectors_S_T = np.linalg.eigh(S_T)
    sorted_eigenvalues_S_T_rank = np.argsort(eigenvalues_S_T)
    W_pca = []
    max_rank = len(eigenvalues_S_T)-1
    for i in range(TRAIN_COUNT_PER_FACE*FACE_COUNT-FACE_COUNT):
        rank = sorted_eigenvalues_S_T_rank[max_rank-i]
        if eigenvalues_S_T[i] == 0.0:
            break
        W_pca.append(eigenvectors_S_T[:, rank])
    W_pca = np.array(W_pca).T
    '''
    W_fld is the set of eigenvectors of inv(W_pca.T * S_W * W_pca) * W_pca.T * S_B * W_pca
    corresponding to the m largest eigenvalues.
    '''
    mat_fld_numerator = np.dot(np.dot(W_pca.T, S_B), W_pca)
    mat_fld_denominator = np.dot(np.dot(W_pca.T, S_W), W_pca)
    mat_fld = np.dot(np.linalg.inv(mat_fld_denominator), mat_fld_numerator)
    eigenvalues_fld, eigenvectors_fld = np.linalg.eig(mat_fld)
    sorted_eigenvalues_fld_rank = np.argsort(eigenvalues_fld)
    W_fld = []
    max_rank = len(eigenvalues_fld)-1
    assert m <= max_rank
    for i in range(m):
        rank = sorted_eigenvalues_fld_rank[max_rank-i]
        if eigenvalues_fld[i] == 0.0:
            break
        W_fld.append(eigenvectors_fld[:, rank])
    W_fld = np.array(W_fld).T
    '''
    Confirm that all elements of W_fld do not contain the imaginary part.
    Output W_fld without `+0.j`.
    '''
    if np.all(np.imag(W_fld) == 0.0):
        W_fld = np.real(W_fld)
    return np.dot(W_pca, W_fld)

def get_training_image_projections(W, training_images):
    assert len(W) == len(training_images)
    return np.dot(W.T, training_images)

def recognize_face(W, training_image_projections, face):
    assert len(training_image_projections[0]) == TRAIN_COUNT_PER_FACE * \
        FACE_COUNT and len(W) == len(face) and len(face[0]) == 1
    face_projection = np.dot(W.T, face)
    min_dist = get_dist(
        np.array([training_image_projections[:, 0]]).T, face_projection)
    face_recognized = 0
    for i in range(FACE_COUNT):
        for j in range(TRAIN_COUNT_PER_FACE):
            if i == 0 and j == 0:
                continue
            dist = get_dist(np.array(
                [training_image_projections[:, TRAIN_COUNT_PER_FACE*i+j]]).T, face_projection)
            if dist < min_dist:
                min_dist = dist
                face_recognized = i
    return face_recognized

if __name__ == '__main__':
    if len(sys.argv) == 1:
        print('Please specify a series of `m` via command line parameters.')
        print(f'0 < `m` < {FACE_COUNT}')
        print('E.g. python fld_recognizer.py 39 (...)')
        sys.exit()

    print('###### Fisher’s Linear Discriminant (FLD) Face Recognition ######')
    print('The face recognition results of each person are listed in the following format:')
    print('Face Number | Image Number | Face Recognized | Right or Wrong (T/F)\n...\nError Rate')
    training_images, test_images = read_all_images()
    for i in range(1, len(sys.argv)):
        m = int(sys.argv[i])
        print('################################################################################')
        print(f'The dimension of projections is set to {m}.')
        W = get_W(training_images, m)
        print('The projection matrix is as follows:')
        print(W)
        print(f'(Its shape: {len(W)} * {len(W[0])})')
        training_image_projections = get_training_image_projections(
            W, training_images)
        error_count = 0
        for j in range(FACE_COUNT):
            error_count_j = 0
            for k in range(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE):
                face = np.array(
                    [test_images[:, (IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*j+k]]).T
                face_recognized = recognize_face(
                    W, training_image_projections, face)
                t_or_f = 'T'
                if face_recognized != j:
                    t_or_f = 'F'
                    error_count_j += 1
                print(f'{j+1}\t{k+1}\t{face_recognized+1}\t{t_or_f}')
            error_count += error_count_j
            print(f'{error_count_j} / {IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE} = {error_count_j/(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)}\n')
        print('Total Error Rate:')
        print(f'{error_count} / {(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*FACE_COUNT} = {error_count/((IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*FACE_COUNT)}')
        print('################################################################################')


from scipy.linalg import eigh
from utils import *
import numpy as np

import numpy as np

def get_weights(data, nbors_idx, reg_func=None):
    """
    Calculate weights

    Parameters
    ----------
    data: (d,n) array, Input data
        d is its dimensionality
        n is the number of points.
    nbors: (n,k) array. Indices of neghbours
        n is the number of points
        k is the number of neighbours
    reg: regularization function

    Returns
    -------
    weights: (n,n) array. Weight matrix in row-major order
        weights[i,:] is weights of x_i
    """

    n = data.shape[1]
    weights = np.zeros((n, n))

    eps = 1e-3
    for i in range(n):
        x = data[:, i].reshape(-1, 1)
        k = nbors_idx[i].shape[0]  # number of neighbors
        ones = np.ones((k, 1))

        # k-neareast neighbors
        eta = data[:, nbors_idx[i]]
        eta_t = eta.T
        C = eta_t.dot(eta)

        # regularization term
        if reg_func is None:
            trace = np.trace(C)
            if trace > 0:
                R = eps/k*trace
            else:
                R = eps
            C += np.eye(k)*R
        else:
            C += reg_func(C, k)

        # C_inv = np.linalg.inv(C)
        C_inv = np.linalg.pinv(C)

        # calculate lagranian multipler lamda
        tmp = eta_t.dot(x)
        lam_num = 1. - ones.T.dot(C_inv).dot(tmp)
        lam_denom = ones.T.dot(C_inv).dot(ones)
        lam = lam_num / (lam_denom + 1e-15)
        w = C_inv.dot(tmp + lam*ones)
        weights[i, nbors_idx[i]] = w.reshape(-1)

    return weights

def Y_(Weights, d):
    """
    Calculate embedded coordinates in target space

    Parameters
    ----------
    Weights: (n,n) array, weight matrix
    d: dimensionality of target space

    Returns
    -------
    Y: (n,d) array
        Embedded coordinates in target space
    """
    n, p = Weights.shape
    I = np.eye(n)
    m = (I-Weights)
    M = m.T.dot(m)

    eigvals, eigvecs = eigh(M)
    ind = np.argsort(np.abs(eigvals))

    return(eigvecs[:, ind[1:d+1]])

class LocalLinearEmbedding:
    def __init__(self, n_dim=2,
                 n_neighbors=None,
                 epsilon=None):
        assert(n_neighbors != None or epsilon != None)

        self.dist_func = nearest_neighbor_distance if n_neighbors != None else fixed_radius_distance
        self.n_neighbors = n_neighbors
        self.n_dim = n_dim
        self.epsilon = epsilon

    def fit(self, X):
        # Select neighbors
        if self.epsilon is not None:
            _, nbors = self.dist_func(X, self.epsilon)
        elif self.n_neighbors is not None:
            _, nbors = self.dist_func(X, self.n_neighbors)

        # Reconstruct with linear weights
        Weights = get_weights(X, nbors, None)

        # Map to embedded coordinates
        Y = Y_(Weights, self.n_dim)
        return Y.T


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

class KMeans:
    def __init__(
            self,
            k: int,
            metric: str = "euclidean",
            tol: float = 1e-6,
            max_iter: int = 100,
            ):
        """
        inputs:
            k: int
                the number of centroids to use in cluster fitting
            metric: str
                the name of the distance metric to use
            tol: float
                the minimum error tolerance from previous error during optimization to quit the model fit
            max_iter: int
                the maximum number of iterations before quitting model fit
        """
        self.k = k
        if self.k==0:
            raise ValueError('k value cannot be zero')
        self.metric = metric
        self.tol = tol
        self.max_iter = max_iter
        self._err = np.inf
        self._all_scores = []

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

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features
        """
        if self.k>len(mat):
            raise ValueError('more cluster centers than points')

        closest_centroid = np.array([np.random.randint(0,self.k) for e in range(len(mat))]) # randomly assign each point in mat to a k
        itr=0
        while itr <= self.max_iter: # max iterations to go for before exiting
            centroids = []
            for k_idx in range(self.k): # for each k center
                # find a new centroid at the middle of the assigned points
                centroids.append(mat[closest_centroid==k_idx].mean(axis=0))
            self._centroids = centroids
            dist_to_centroids = cdist(mat, centroids, self.metric) # find dist of each point to each of the new centroids
            closest_centroid = np.array([np.argmin(d) for d in dist_to_centroids]) # find the closest centroid to each point
            itr+=1

            new_err = self._calc_mse(mat, np.array(closest_centroid)) # calculate the error of this round
            if len(self._all_scores) > 0:
                self._all_scores.append(new_err)
            else:
                self._all_scores= [new_err]
            if abs(self._err - new_err) < self.tol: # if new error is the value of tol less than the previous error
                self._err = new_err
                break
            else:
                self._err = new_err

    def _calc_mse(self, mat, closest_centroid):
        """
        calculates the mean square error of a matrix and the centroids

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features
            closest_centroid:
                the closest centroid to each point in mat
        returns:
            float
                the mean square error score
        """
        sq_err = []
        for k_idx in range(self.k):

            n = len(mat[closest_centroid==k_idx]) # denominator of mse (# of points assigned to that k)
            # numerator of mse (square of distances of points to centroid)
            num = sum(np.square(cdist(mat[closest_centroid==k_idx],[self._centroids[k_idx]], self.metric)))
            if n>0:
                sq_err.append((num/n)[0])
        return sum(sq_err) # final mse is sum of this for all k's

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

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

        outputs:
            np.ndarray
                a 1D array with the cluster label for each of the observations in `mat`
        """
        dist_to_centroids = cdist(mat, self._centroids) # use the centroids determined in fit
        closest_centroid = np.array([np.argsort(d)[0] for d in dist_to_centroids]) # find closest centroid to each point
        return closest_centroid

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

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

    def get_all_errors(self) -> np.ndarray:
        """
        returns all the MSE error scores for each itteration

        outputs:
            np.ndarray
                an array of each score
        """
        return self._all_scores

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

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


from pymc3 import *
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
import pandas as pd
import theano
from scipy.stats.kde import gaussian_kde
NUM_SAMPLES = 30
SDEV = 0.3
np.random.seed(42)

def f(x):
    return 7 *(x**3 -1.3 *x**2+0.5*x - 0.056)

gridx = np.linspace(0,1,200)
x = np.random.random(NUM_SAMPLES)
truey = f(x)
y = truey + SDEV*np.random.randn(NUM_SAMPLES)

# poly = PolynomialFeatures(degree = 10)
# xpow = poly.fit_transform(x.reshape(NUM_SAMPLES,1))
# linpow = poly.fit_transform(gridx.reshape(200,1))
data = dict(x1=theano.shared(x), y=theano.shared(y))
print(data['x1'][:5])
fig = plt.figure()
ax = fig.add_subplot(111, title = 'Bayesian approach to degree 10 polynomial regression')
ax.scatter(x,y, label = 'Sampled data')
ax.plot(gridx, f(gridx), label = 'True curve', linewidth = 4 )

with Model() as model:
    # glm.GLM.from_formula('y ~ x1 + x2 + x3 ', data)
    coeff = Normal('coeff', mu = 0, sd = 10, shape = 11)
    sigma = HalfNormal('sigma', sd = 1)
    # mu = coeff[0] + coeff[1]*data['x1'] + coeff[2]*data['x2']+ coeff[3]*data['x3']
    mu = np.sum([coeff[i]*x**i for i in range(0,11)])
    y_obs = Normal('y_obs', mu = mu, sd = sigma, observed = data['y'])
    trace = sample(15000, njobs = 4, tune = 1000 )
    # print(trace['coeff'])
    print(stats.df_summary(trace).to_html())
    mcoeff = np.mean(trace['coeff'], axis = 0)
    def g(x):
        return np.sum([mcoeff[i]*x**i for i in range(11)], axis =0)
    ax.plot(gridx, g(gridx), label = 'Bayesian mean polynomial fit', color = 'Black', alpha = 0.5, lw = 2.5)
    ax.set_ylim(-1., 1.25)

    for i in range(50):
        def h(x):
            return np.sum([trace['coeff'][-i][j]*x**j for j in range(4)], axis =0)
        ax.plot(gridx, h(gridx), color = 'Black', alpha = 0.1)
    gridx = np.linspace(0,1,30)
    data['x1'].set_value(gridx)
    # data['x2'].set_value(linpow[:,2])
    # data['x3'].set_value(linpow[:,3])
    data['y'].set_value(np.zeros_like(gridx))
    post_pred = sample_ppc(trace, samples = 200)
    print(post_pred['y_obs'].shape)

    ax.plot(gridx, np.mean(post_pred['y_obs'], axis = 0), label = 'Bayesian mean posterior', alpha = 0.5, color = 'Red')
    ax.fill_between(gridx, np.mean(post_pred['y_obs'], axis = 0)-np.std(post_pred['y_obs'], axis = 0), np.mean(post_pred['y_obs'], axis = 0)+np.std(post_pred['y_obs'], axis = 0), label = 'Bayesian error band', alpha = 0.1, color = 'Red')
    plt.legend(loc = 2)
    plt.savefig("bayreg10.png")
    plt.show()
    plt.clf()
    plt.close()
    plt.figure(figsize = (5, 24))
    fig, axs = plt.subplots(12)
    for i in range(11):
        tdata = trace['coeff'][:,i]
        kde = gaussian_kde(tdata)
        tgridx = np.linspace(min(tdata), max(tdata), 200)
        axs[i].plot(tgridx, kde(tgridx))
        axs[i].set_ylim(axs[i])
        axs[i].set_title('Coeff{}'.format(i))

    tdata = trace['sigma']
    kde = gaussian_kde(tdata)
    tgridx = np.linspace(min(tdata), max(tdata), 200)
    axs[11].plot(tgridx, kde(tgridx))
    axs[11].set_ylim(axs[11])
    axs[11].set_title('Sigma')
    plt.savefig("trace10.png")

    # plot_posterior_predictive_glm(trace, samples = 200, label ='Posterior predictive regression')
    # traceplot(trace)
    # plt.tight_layout()

import numpy,from scipy,import theano,from pymc3


import numpy as np
import pandas as pd
def linear_model(df1_x,df1_y):

    x=df1_x.iloc[:,0]
    x=pd.to_numeric(x)

    y=df1_y.iloc[:,0]
    y = pd.to_numeric(y)

    df1_x_mean=np.mean(df1_x)
    df1_y_mean=np.mean(df1_y)

    deviation_x=[]
    for i in x:
        deviation_x.append(i-df1_x_mean)

    deviation_y=[]
    for i in y:
        deviation_y.append(i-df1_y_mean)

    product_deviation=np.array(deviation_x)*np.array(deviation_y)

    Sum_product_deviation=np.sum(product_deviation)

    Sq_x_deviation=np.array(deviation_x)**2

    Sum_Sq_x_deviation=np.sum(Sq_x_deviation)

    Regression_coefficient=Sum_product_deviation/Sum_Sq_x_deviation
    print(Regression_coefficient)
    intercept=float(df1_y_mean)-(Regression_coefficient*float(df1_x_mean))
    print(intercept)

    return [Regression_coefficient, intercept]


'''
# Defining the sigmoid function for activations
def sigmoid(x):
    return 1/(1+np.exp(-x))

# Derivative of the sigmoid function
def sigmoid_prime(x):
    return sigmoid(x) * (1 - sigmoid(x))

# Input data
x = np.array([0.1, 0.3])
# Target
y = 0.2
# Input to output weights
weights = np.array([-0.8, 0.5])

# The learning rate, eta in the weight step equation
learnrate = 0.5

# The neural network output (y-hat)
nn_output = sigmoid(x[0]*weights[0] + x[1]*weights[1])
# or nn_output = sigmoid(np.dot(x, weights))

# output error (y - y-hat)
error = y - nn_output

# error term (lowercase delta)
error_term = error * sigmoid_prime(np.dot(x,weights))

# Gradient descent step
del_w = [ learnrate * error_term * x[0],
                 learnrate * error_term * x[1]]
# or del_w = learnrate * error_term * x
'''
import numpy as np
from data_prep import features, targets, features_test, targets_test

def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1 / (1 + np.exp(-x))

def sigmoid_prime(x):
    '''
    Derivative of the sigmoid function
    '''
    return sigmoid(x) * (1 - sigmoid(x))

# Use to same seed to make debugging easier
np.random.seed(42)

n_records, n_features = features.shape
last_loss = None

# Initialize weights
weights = np.random.normal(scale=1 / n_features**.5, size=n_features)

# Neural Network hyperparameters
epochs = 1000
learnrate = 0.5

for e in range(epochs):
    del_w = np.zeros(weights.shape)
    for x, y in zip(features.values, targets):
        # Loop through all records, x is the input, y is the target

        # TODO: Calculate the output
        output = sigmoid(x.dot(weights))

        # TODO: Calculate the error
        error = y - output

        # TODO: Calculate change in weights
        del_w += learnrate * error * sigmoid_prime(x.dot(weights)) * x

        # TODO: Update weights
    weights += del_w

    # Printing out the mean square error on the training set
    if e % (epochs / 10) == 0:
        out = sigmoid(np.dot(features, weights))
        loss = np.mean((out - targets) ** 2)
        if last_loss and last_loss < loss:
            print("Train loss: ", loss, "  WARNING - Loss Increasing")
        else:
            print("Train loss: ", loss)
        last_loss = loss

# Calculate accuracy on test data
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))


# --------------
#Importing header files
import pandas as pd
import scipy.stats as stats
import math
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.stats.weightstats import ztest
from statsmodels.stats.weightstats import ztest
from scipy.stats import chi2_contingency

import warnings

warnings.filterwarnings('ignore')
#Sample_Size
sample_size=2000

#Z_Critical Score
z_critical = stats.norm.ppf(q = 0.95)

# Critical Value
critical_value = stats.chi2.ppf(q = 0.95, # Find the critical value for 95% confidence*
                      df = 6)   # Df = number of variable categories(in purpose) - 1

#Reading file
data=pd.read_csv(path)

#Code starts here

#Confidence Interval
data_sample = data.sample(n=sample_size,random_state=0)

confidence_interval = [data_sample['installment'].mean() - (z_critical*data_sample['installment'].std()/math.sqrt(sample_size)), data_sample['installment'].mean() + (z_critical*data_sample['installment'].std()/math.sqrt(sample_size))]
print("confidence interval: ",confidence_interval)

true_mean = data['installment'].mean()
print("true mean: ",true_mean)

#CLT
sample_size = np.array([20,50,100])

fig, axes = plt.subplots(3,1, figsize=(10,20))

for i in range(len(sample_size)):
    m = []

    for j in range(1000):

        mean = data['installment'].sample(sample_size[i]).mean()

        m.append(mean)

    mean_series = pd.Series(m)

    axes[i].hist(mean_series)

plt.show()

#Small Business Interests
data['int.rate.new'] = data['int.rate'].map(lambda x: str(x)[:-1])

data['int.rate.new'] = data['int.rate.new'].astype(float)/100

z_statistic_1, p_value_1 = ztest(x1 = data[data['purpose'] == 'small_business']['int.rate.new'], value = data['int.rate.new'].mean(), alternative='larger')

print("z-statistic : ",z_statistic_1)
print("p-value : ",p_value_1)

#Installment vs Loan Defaulting
z_statistic_2, p_value_2 = ztest(x1 = data[data['paid.back.loan'] == 'No']['installment'], x2 = data[data['paid.back.loan'] == 'Yes']['installment'])

print("z-statistic 2 : ",z_statistic_2)
print("p-value 2 : ",p_value_2)

#Purpose vs Loan Defaulting
yes = data[data['paid.back.loan'] == 'Yes']['purpose'].value_counts()

no = data[data['paid.back.loan'] == 'No']['purpose'].value_counts()

observed = pd.concat([yes.transpose(), no.transpose()], 1, keys=['Yes','No'])

chi2, p , dof, ex = chi2_contingency(observed)

print("chi statistic is : ", chi2)
print("critical value : ", critical_value)

import numpy,import scipy,from scipy,from statsmodels


from numba import jit

'''
the problem is to find the kth smallest number in the multiplication table (n rows, m columns)
for k = 9 the answer is 5
to solve it:
the idea is to guess a number between 1 and n * m, lets call this number mid and
count the numbers up to mid (including repetitions) with the following conditions:
// every number below mid / m_i will be bigger than mid, so:
    if mid / m_i( = 1,2,...,m) = maxNumberReachedPerColumn > n, then
        count += n
    else
        count += maxNumberReachedPerColumn
now that we have the count of numbers lesser than mid
we check:
    if (count < k)
        we discard left
        left = mid + 1
    else
        we discard right
        right = mid - 1
we do this until left > right
now the answer is left, since right will be always one below the correct answer
(Binary Search)
'''
@jit
def main():
    n, m, k = map(int, input().split())

    lf = 1
    r = n * m

    while lf <= r:
        mid = int((lf + r) / 2)
        count = 0
        limit = m + 1
        for i in range(1, limit):
            maxInColumn = int(mid / i)
            if maxInColumn > n:
                count += n
            else:
                count += maxInColumn
        if count < k:
            lf = mid + 1
        else:
            r = mid - 1

    print(int(lf))

if __name__ == "__main__":
    main()


# Regresion polinomial
import matplotlib.pyplot as plt
import numpy as np

def createMatrix(m,n, valor=0):
    C = []
    for i in range(m):
        C.append([])                            #could be c.append([valor]*n)
        for j in range(n):
            C[i].append(valor)
    return C

def getDimensions(A):
    return (len(A),len(A[0]))

def copyMatrix(B):
    m,n = getDimensions(B)
    A = createMatrix(m,n)
    for i in range(m):
        for j in range(n):
            A[i][j]=B[i][j]
    return A

def sumMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (Am != Bm or An != Bn):
        print("Error, matrix of diferent size")
        return []
    C = createMatrix(Am,An)
    for i in range(Am):
        for j in range(An):
            C[i][j] = A[i][j] + B[i][j]
    return C

def restaMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (Am != Bm or An != Bn):
        print("Error, matrix of diferent size")
        return []
    C = createMatrix(Am,An)
    for i in range(Am):
        for j in range(An):
            C[i][j] = A[i][j] - B[i][j]
    return C

def multMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (An != Bm):
        print("Error multiplicacion # columnas y # renglos no son iguales")
        return []
    C = createMatrix(Am,Bn)
    counter = 0
    for i in range(Am):
        for j in range(Bn):
            for k in range(An):
                C[i][j] += A[i][k] * B[k][j]
    return C

def getAdyacente(A,r,c):
    Am, An = getDimensions(A)
    C = createMatrix(Am-1, An-1, 0)
    for i in range(Am):
        if (i == r):
            continue
        for j in range(An):
            if (j == c):
                continue
            ci = 0
            cj = 0
            if (i < r):
                ci = i
            else:
                ci = i-1
            if (j < c):
                cj = j
            else:
                cj = j-1
            C[ci][cj] = A[i][j]
    return C

def detMatrix(A):
    m,n = getDimensions(A)
    if m!=n:
        print("Matriz no es cuadrada")
        return []
    if (n==1):
        return A[0][0]
    if (n==2):
        return (A[0][0]*A[1][1]) - (A[1][0]*A[0][1])
    det = 0
    for j in range(m):
        det += (-1)**j*A[0][j]*detMatrix(getAdyacente(A,0,j))
    return det

def getMatrizTranspuesta(A):
    m,n = getDimensions(A)
    C = createMatrix(n,m,0)
    for i in range(m):
        for j in range(n):
            C[j][i] = A[i][j]
    return C

def getMatrizAdjunta(A):
    m,n = getDimensions(A)
    if m != n:
        print("La matriz no es cuadrada")
        return []
    C = createMatrix(m,n,0)
    for i in range(m):
        for j in range(n):
            C[i][j] = ((-1)**(i+j))*detMatrix(getAdyacente(A,i,j))
    return C

def getMatrizInversa(A):
    m,n = getDimensions(A)
    if m != n:
        print("La matriz no es cuadrada")
        return []
    detA = detMatrix(A)
    if detA== 0:
        print("La matriz no tiene inversa")
        return []
    At = getMatrizTranspuesta(A)
    adjA = getMatrizAdjunta(At)
    invDetA = 1/detA
    C = createMatrix(m,n,0)
    for i in range(m):
        for j in range(n):
            C[i][j]=invDetA*adjA[i][j]
    return C

def regPolinomial(grado, x, y):
    grado += 1
    A = createMatrix(grado,grado)
    for i in range(grado):
        for j in range(grado):
            A[i][j] = sum( xi**(i+j) for xi in x)
    C = createMatrix(grado,1)
    for i in range(grado):
        C[i][0] = sum((xi**i)*yi for xi,yi in zip(x,y))
    invA = getMatrizInversa(A)
    return multMatrix(invA,C)

def evalPolinomio(coef,x):
    y = []
    coef = np.asarray(coef)
    for i in range(len(x)):
        y.append(0)
        for c in range(len(coef)):
            y[i] += (x[i]**c) * coef[c]
    return y

x = [8, 16, 24, 32] #gradoMAX X-1
y = [4.1, 4.5, 5.1, 6.1]
plt.plot(x,y,'rx')
#plt.show()

coef = regPolinomial(3, x, y) #aqui se cambia para el grado de la funcion a buscar
print(coef)

x2 = np.linspace(5,35,100)
y2 = evalPolinomio(coef,x2)
plt.plot(x2,y2)
plt.show()

yesp = evalPolinomio(coef,[7])
print("peso esperado 7 semanas --> ", yesp[0][0])

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

# Regresion polinomial
import matplotlib.pyplot as plt
import numpy as np

def createMatrix(m,n, valor=0):
    C = []
    for i in range(m):
        C.append([])                            #could be c.append([valor]*n)
        for j in range(n):
            C[i].append(valor)
    return C

def getDimensions(A):
    return (len(A),len(A[0]))

def copyMatrix(B):
    m,n = getDimensions(B)
    A = createMatrix(m,n)
    for i in range(m):
        for j in range(n):
            A[i][j]=B[i][j]
    return A

def sumMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (Am != Bm or An != Bn):
        print("Error, matrix of diferent size")
        return []
    C = createMatrix(Am,An)
    for i in range(Am):
        for j in range(An):
            C[i][j] = A[i][j] + B[i][j]
    return C

def restaMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (Am != Bm or An != Bn):
        print("Error, matrix of diferent size")
        return []
    C = createMatrix(Am,An)
    for i in range(Am):
        for j in range(An):
            C[i][j] = A[i][j] - B[i][j]
    return C

def multMatrix(A,B):
    Am, An = getDimensions(A)
    Bm, Bn = getDimensions(B)
    if (An != Bm):
        print("Error multiplicacion # columnas y # renglos no son iguales")
        return []
    C = createMatrix(Am,Bn)
    counter = 0
    for i in range(Am):
        for j in range(Bn):
            for k in range(An):
                C[i][j] += A[i][k] * B[k][j]
    return C

def getAdyacente(A,r,c):
    Am, An = getDimensions(A)
    C = createMatrix(Am-1, An-1, 0)
    for i in range(Am):
        if (i == r):
            continue
        for j in range(An):
            if (j == c):
                continue
            ci = 0
            cj = 0
            if (i < r):
                ci = i
            else:
                ci = i-1
            if (j < c):
                cj = j
            else:
                cj = j-1
            C[ci][cj] = A[i][j]
    return C

def detMatrix(A):
    m,n = getDimensions(A)
    if m!=n:
        print("Matriz no es cuadrada")
        return []
    if (n==1):
        return A[0][0]
    if (n==2):
        return (A[0][0]*A[1][1]) - (A[1][0]*A[0][1])
    det = 0
    for j in range(m):
        det += (-1)**j*A[0][j]*detMatrix(getAdyacente(A,0,j))
    return det

def getMatrizTranspuesta(A):
    m,n = getDimensions(A)
    C = createMatrix(n,m,0)
    for i in range(m):
        for j in range(n):
            C[j][i] = A[i][j]
    return C

def getMatrizAdjunta(A):
    m,n = getDimensions(A)
    if m != n:
        print("La matriz no es cuadrada")
        return []
    C = createMatrix(m,n,0)
    for i in range(m):
        for j in range(n):
            C[i][j] = ((-1)**(i+j))*detMatrix(getAdyacente(A,i,j))
    return C

def getMatrizInversa(A):
    m,n = getDimensions(A)
    if m != n:
        print("La matriz no es cuadrada")
        return []
    detA = detMatrix(A)
    if detA== 0:
        print("La matriz no tiene inversa")
        return []
    At = getMatrizTranspuesta(A)
    adjA = getMatrizAdjunta(At)
    invDetA = 1/detA
    C = createMatrix(m,n,0)
    for i in range(m):
        for j in range(n):
            C[i][j]=invDetA*adjA[i][j]
    return C

def regPolinomial(grado, x, y):
    grado += 1
    A = createMatrix(grado,grado)
    for i in range(grado):
        for j in range(grado):
            A[i][j] = sum( xi**(i+j) for xi in x)
    C = createMatrix(grado,1)
    for i in range(grado):
        C[i][0] = sum((xi**i)*yi for xi,yi in zip(x,y))
    invA = getMatrizInversa(A)
    return multMatrix(invA,C)

def evalPolinomio(coef,x):
    y = []
    coef = np.asarray(coef)
    for i in range(len(x)):
        y.append(0)
        for c in range(len(coef)):
            y[i] += (x[i]**c) * coef[c]
    return y

x = [1, 2, 3, 4, 5] #gradoMAX X-1
y = [88, 87, 84, 82, 79]
plt.plot(x,y,'rx')
#plt.show()

coef = regPolinomial(2, x, y) #aqui se cambia para el grado de la funcion a buscar
print(coef)

x2 = np.linspace(0,8,100)
y2 = evalPolinomio(coef,x2)
plt.plot(x2,y2)
plt.show()

yesp = evalPolinomio(coef,[7])
print("peso esperado 7 semanas --> ", yesp[0][0])