import numpy as np
from numpy.linalg import eig, inv
from playLA.Matrix import Matrix
from playLA.LinearSystem import rank

def diagonalize(A):
    assert A.ndim == 2
    assert A.shape[0] == A.shape[1]

    eigenvalues, eigenvectors = eig(A)

    P = eigenvectors
    if rank(Matrix(P.tolist())) != A.shape[0]:
        print("Matrix cannot be diagonalized!")
        return None, None, None

    D = np.diag(eigenvalues)
    Pinv = inv(P)

    return P, D, Pinv

if __name__ == "__main__":

    A1 = np.array([[4, -2],
                   [1, 1]])
    P1, D1, Pinv1 = diagonalize(A1)
    print(P1)
    print(D1)
    print(Pinv1)
    print(P1.dot(D1).dot(Pinv1))
    print()

    A2 = np.array([[3, 1],
                   [0, 3]])
    P2, D2, Pinv2 = diagonalize(A2)
    print(P2)
    print(D2)
    print(Pinv2)


# integrate the equations of motion of a pendulum, w/o the small angle
# approximation

import numpy
import pylab
import math

# global parameters
g = 9.81     # gravitational acceleration [m/s]
L = 9.81     # length of pendulum [m]

class pendulumHistory:
    """ simple container to store the pendulum history """

    def __init__(self):
        self.t = None
        self.theta = None
        self.omega = None

    def energy(self):
        """ return the energy (per unit mass) """
        return 0.5*L**2*self.omega**2 - g*L*numpy.cos(self.theta)

def rhs(theta, omega):
    """ equations of motion for a pendulum
        dtheta/dt = omega
        domega/dt = - (g/L) sin theta """

    return omega, -(g/L)*numpy.sin(theta)

def intEuler(theta0, dt, tmax, rhs):
    """ integrate the equations of motion using Euler's method """

    # initial conditions
    t = 0.0
    theta = theta0
    omega = 0.0    # at the maximum angle, the angular velocity is 0

    # store the history for plotting
    tPoints = [t]
    thetaPoints = [theta]
    omegaPoints = [omega]

    while (t < tmax):

        # get the RHS
        thetadot, omegadot = rhs(theta, omega)

        # advance
        thetanew = theta + dt*thetadot
        omeganew = omega + dt*omegadot

        t += dt

        # store
        tPoints.append(t)
        thetaPoints.append(thetanew)
        omegaPoints.append(omeganew)

        # set for the next step
        theta = thetanew; omega = omeganew

    # return a pendulumHistory object with the trajectory
    H = pendulumHistory()
    H.t = numpy.array(tPoints)
    H.theta = numpy.array(thetaPoints)
    H.omega = numpy.array(omegaPoints)

    return H

def intEC(theta0, dt, tmax, rhs):
    """ integrate the equations of motion using Euler-Cromer """

    # initial conditions
    t = 0.0
    theta = theta0
    omega = 0.0    # at the maximum angle, the angular velocity is 0

    # store the history for plotting
    tPoints = [t]
    thetaPoints = [theta]
    omegaPoints = [omega]

    while (t < tmax):

        # get the RHS
        thetadot, omegadot = rhs(theta, omega)

        # advance
        omeganew = omega + dt*omegadot
        thetanew = theta + dt*omeganew

        t += dt

        # store
        tPoints.append(t)
        thetaPoints.append(thetanew)
        omegaPoints.append(omeganew)

        # set for the next step
        theta = thetanew; omega = omeganew

    # return a pendulumHistory object with the trajectory
    H = pendulumHistory()
    H.t = numpy.array(tPoints)
    H.theta = numpy.array(thetaPoints)
    H.omega = numpy.array(omegaPoints)

    return H

def intVVerlet(theta0, dt, tmax, rhs):
    """ integrate the equations of motion using Euler-Cromer """

    # initial conditions
    t = 0.0
    theta = theta0
    omega = 0.0    # at the maximum angle, the angular velocity is 0

    # store the history for plotting
    tPoints = [t]
    thetaPoints = [theta]
    omegaPoints = [omega]

    while (t < tmax):

        # get the RHS at time-level n
        thetadot, omegadot = rhs(theta, omega)

        thetanew = theta + dt*thetadot + 0.5*dt**2*omegadot

        # get the RHS with the updated theta -- omega doesn't matter
        # here, since we only need thetadot and omega doesn't affect
        # that.
        thetadot_np1, omegadot_np1 = rhs(thetanew, omega)

        omeganew = omega + 0.5*dt*(omegadot + omegadot_np1)

        t += dt

        # store
        tPoints.append(t)
        thetaPoints.append(thetanew)
        omegaPoints.append(omeganew)

        # set for the next step
        theta = thetanew; omega = omeganew

    # return a pendulumHistory object with the trajectory
    H = pendulumHistory()
    H.t = numpy.array(tPoints)
    H.theta = numpy.array(thetaPoints)
    H.omega = numpy.array(omegaPoints)

    return H

# 10 degree pendulum
theta0 = 10.0*math.pi/180.0
dt = 0.1
tmax = 30.0

HEuler = intEuler(theta0, dt, tmax, rhs)
HEC = intEC(theta0, dt, tmax, rhs)
HVVerlet = intVVerlet(theta0, dt, tmax, rhs)

pylab.plot(HEuler.t, HEuler.theta, label="Euler")
pylab.plot(HEC.t, HEC.theta, label="Euler-Cromer")
pylab.plot(HVVerlet.t, HVVerlet.theta, label="velocity Verlet")

pylab.xlabel("t")
pylab.ylabel(r"$\theta$(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.savefig("pendulum-theta-10.png")

pylab.clf()

pylab.subplot(211)

pylab.plot(HEuler.t, HEuler.energy(), label="Euler")

pylab.xlabel("t")
pylab.ylabel("E(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.subplot(212)

pylab.plot(HEC.t, HEC.energy(), label="Euler-Cromer")
pylab.plot(HVVerlet.t, HVVerlet.energy(), label="velocity Verlet")

pylab.xlabel("t")
pylab.ylabel("E(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.tight_layout()

pylab.savefig("pendulum-energy-10.png")

# 100 degree pendulum
pylab.clf()

theta0 = 100.0*math.pi/180.0
dt = 0.1
tmax = 30.0

HEuler = intEuler(theta0, dt, tmax, rhs)
HEC = intEC(theta0, dt, tmax, rhs)
HVVerlet = intVVerlet(theta0, dt, tmax, rhs)

pylab.plot(HEuler.t, HEuler.theta, label="Euler")
pylab.plot(HEC.t, HEC.theta, label="Euler-Cromer")
pylab.plot(HVVerlet.t, HVVerlet.theta, label="velocity Verlet")

pylab.xlabel("t")
pylab.ylabel(r"$\theta$(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.savefig("pendulum-theta-100.png")

pylab.clf()

pylab.subplot(211)
pylab.plot(HEuler.t, HEuler.energy(), label="Euler")

pylab.xlabel("t")
pylab.ylabel("E(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.subplot(212)
pylab.plot(HEC.t, HEC.energy(), label="Euler-Cromer")
pylab.plot(HVVerlet.t, HVVerlet.energy(), label="velocity Verlet")

pylab.xlabel("t")
pylab.ylabel("E(t)")

leg = pylab.legend()
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(0)

pylab.tight_layout()

pylab.savefig("pendulum-energy-100.png")


## This is my implementation of biphivals.py which
## Generate biorthogonal scaling functions and their associated
## using Python libraries numpy, scipy, matlibplot, PyWavelets
## the given filter coefficients.
##
## The main reference that I'll use is
## Gilbert Strang, and Kevin Amaratunga. 18.327 Wavelets, Filter Banks and Applications, Spring 2003. (Massachusetts Institute of Technology: MIT OpenCourseWare), http://ocw.mit.edu (Accessed 19 Jun, 2015). License: Creative Commons BY-NC-SA
## Note that even though biphivals.m was needed in the MIT OCW 18.327,
## it was NOT included in the MIT OCW; I found it here:
## http://web.mit.edu/1.130/WebDocs/1.130/Software/Examples/biphivals.m
##
#####################################################################################
## Copyleft 2015, Ernest Yeung <ernestyalumni@gmail.com>
##
## 20150702
##
## This program, along with all its code, is free software;
## you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You can have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software Foundation, Inc.,
## S1 Franklin Street, Fifth Floor, Boston, MA
## 02110-1301, USA
##
## Governing the ethics of using this program, I default to the Caltech Honor Code:
## ``No member of the Caltech community shall take unfair advantage of
## any other member of the Caltech community.''
##
## If you like what I'm doing and would like to help and contribute support,
## please take a look at my crowdfunding campaign at ernestyalumni.tilt.com
## and subscription-based Patreon
## read my mission statement and give your financial support,
## no matter how small or large,
## if you can
## and to keep checking my ernestyalumni.wordpress.com blog and
## various social media channels
## for updates as I try to keep putting out great stuff.
##
## Fund Science! Help my physics education outreach and research efforts at
## Open/Tilt or subscription Patreon - Ernest Yeung
##
## ernestyalumni.tilt.com
##
## Facebook     : ernestyalumni
## gmail        : ernestyalumni
## google       : ernestyalumni
## linkedin     : ernestyalumni
## Patreon      : ernestyalumni
## Tilt/Open    : ernestyalumni
## tumblr       : ernestyalumni
## twitter      : ernestyalumni
## youtube      : ernestyalumni
## wordpress    : ernestyalumni
##
##
################################################################################
##

import numpy as np
import scipy
from scipy.linalg import toeplitz

def biphivals(h0,h1,f0,f1,i):
    """
    Kevin Amaratunga (4 August, 1993) wrote the Matlab code but I
    (Ernest Yeung ernestyalumni) implemented biphivals
    using Python with numpy (20150702)

    Here's Dr. Amaratunga's original comments:

    function [x,phi,phitilde,psi,psitilde] = biphivals(h0,h1,f0,f1,i)
    Generate biorthogonal scaling functions and their associated
    wavelets using the given filter coefficients
    Kevin Amaratunga
    4 August, 1993

    h0, h1, f0, f1 = wavelet filters (from BIORFILT).
    i = discretization parameter.  The number of points per integer
    step is 2^i.  Thus, setting i = 0 gives the scaling function
    and wavelet values at integer points.
    """
    dum = len(f0)
    f0 = np.vstack(np.array(f0))
    f1 = np.vstack(np.array(f1))
    h0 = np.vstack(np.array(h0))
    h1 = np.vstack(np.array(h1))
    N = dum

    tmp,dum = h0.shape

    assert i>=0, "biphivals: i must be non-negative"

    m,n = f0.shape
    tmp,dum = h0.shape

    assert m == tmp, "biphivals: filters f0 and h0 must be the same length"

    #
    # Make sure the lowpass filters sum up to 2
    #
    fac = 2./np.sum( h0 )
    h0 = np.multiply( h0[::-1],fac)
    h1 = np.multiply( h1[::-1],fac)
    f0 = np.multiply( f0, fac)
    f1 = np.multiply( f1, fac)

    cf0 = np.vstack( (f0 , np.vstack( np.zeros(m)  ) ) )
    rf0 = np.hstack( ( f0[0], np.zeros(m-1) ) )
    tmp = toeplitz(cf0,rf0)
    M = np.zeros((m,m))

    M = tmp.flatten('F')[0:-1:2].reshape((m,m)).T - np.identity(m)

    M[-1,:] = np.ones(m)
    tmp = np.vstack( np.append(np.zeros(m-1),np.identity(1)) )
    phi = np.linalg.solve( M,tmp)  # Integer values of phi

    ch0 = np.vstack( (h0 , np.vstack( np.zeros(m)  ) ) )
    rh0 = np.hstack( ( h0[0], np.zeros(m-1) ) )
    tmp = toeplitz(ch0,rh0)
    M = np.zeros((m,m))
    M = tmp.flatten('F')[0:-1:2].reshape((m,m)).T - np.identity(m)
    M[-1,:] = np.ones(m)
    tmp = np.vstack( np.append(np.zeros(m-1),np.identity(1)) )
    phitilde = np.linalg.solve( M,tmp)  # Integer values of phi

    if i > 0:
        for k in range(0,i):
            p = 2**(k+1)*(m-1)+1   # No of rows in toeplitz matrix
            q = 2**k *(m-1)+1      # No of columns toeplitz matrix
            if k==0:
                cf00 = np.vstack( np.append(f0, np.zeros(p-1-m)) )
                cf0  = np.vstack(( cf00, np.zeros(1) ))
                ch10 = np.vstack(np.append(h1, np.zeros(p-1-m)))
                ch00 = np.vstack(np.append(h0, np.zeros(p-1-m)))
                ch0  = np.vstack(( cf00, np.zeros(1) ))
                cf10 = np.vstack(np.append(f1, np.zeros(p-1-m)))
            else:
                cf0 = np.vstack( np.append( np.identity(1), np.zeros(2**k-1))).dot(cf00.T)
                cf0 = np.vstack( np.append( cf0.flatten('F'), np.zeros(1) ) )
                ch0 = np.vstack( np.append( np.identity(1), np.zeros(2**k-1))).dot(ch00.T)
                ch0 = np.vstack( np.append( ch0.flatten('F'), np.zeros(1) ) )
            rf0 = np.append( cf0[0], np.zeros(q-1) )
            Tf0 = toeplitz(cf0,rf0)
            rh0 = np.append( ch0[0], np.zeros(q-1) )
            Th0 = toeplitz(ch0,rh0)
            if k == i-1:
                ch1 = (np.vstack(np.append(np.identity(1),np.zeros(2**k-1)))).dot( ch10.T)
                ch1 = ch1.flatten('F') # flatten
                ch1 = np.vstack(np.append(ch1,np.zeros(1)))
                rh1 = np.append( ch1[0], np.zeros(q-1) )
                Th1 = toeplitz(ch1,rh1)
                cf1 = (np.vstack(np.append(np.identity(1),np.zeros(2**k-1)))).dot( cf10.T)
                cf1 = cf1.flatten('F') # flatten
                cf1 = np.vstack(np.append(cf1,np.zeros(1)))
                rf1 = np.append( cf1[0], np.zeros(q-1) )
                Tf1 = toeplitz(cf1,rf1)
                psi = Tf1.dot(phi)
                psitilde = Th1.dot(phitilde)
            phi = Tf0.dot(phi)
            phitilde = Th0.dot(phitilde)

    elif i==0:
        ch10 = np.vstack( np.append( h1, np.zeros(m-1) ) )
        ch1 = np.vstack((ch10, np.zeros(1) ) )
        rh1 = np.append(ch1[0],np.zeros(m-1))
        Th1 = toeplitz(ch1,rh1)
        cf10 = np.vstack( np.append( f1, np.zeros(m-1) ) )
        cf1 = np.vstack((cf10, np.zeros(1) ) )
        rf1 = np.append(cf1[0],np.zeros(m-1))
        Tf1 = toeplitz(cf1,rf1)
        psi = Tf1.dot(phi)
        psi = psi[::2]
        psitilde=Th1.dot(phitilde)
        psitilde = psitilde[::2]

    a,b = phi.shape
    x = np.vstack( np.arange(0,a)/2.**i )

    return x, phi, phitilde, psi, psitilde


from scipy.optimize import fsolve
from numpy import cosh, log
import matplotlib.pyplot as plt
import numpy as np

# The function whose zeros we want to find
def numeric(V):
    return V**2/32.2*log(cosh(32.2*116/V))-29300.0

# Determine the terminal solution from the analytic expansion
def analytic(t, s, g=32.2):
    """
    g is given in ft/s^2, t in s and s in ft.
    """
    delta = np.sqrt(t**2 - 4*log(2)*s/g)
    return (t-delta)/(2*log(2)/g), (t+delta)/(2*log(2)/g)

if __name__ == "__main__":
    # Take as initial guess the average velocity
    initial_guess = 253.
    print(f"Terminal velocity (from numerical solution)  : {fsolve(numeric, initial_guess)[0]} ft/s")
    print(f"Terminal velocity (from analytic expansion) 1: {analytic(116., 29300)[0]} ft/s")
    print(f"Terminal velocity (from analytic expansion) 2: {analytic(116., 29300)[1]} ft/s")
    # Determine a value for k
    k = 32.2*261.2/265.69**2
    print(f"k={k} pounds / ft")

    # Check the terminal velocity also graphically
    values = np.linspace(240., 270., 1000)
    evals = [numeric(val) for val in values]
    plt.plot(values, np.zeros(len(evals)), color="black")
    plt.plot(values, evals)
    plt.title("Determination of the Terminal Velocity")
    plt.xlabel("V [ft/s]")
    plt.ylabel("f(V)")
    plt.show()


from numpy import array, matmul, sqrt, cos, sin, reshape, newaxis, finfo, log10, floor

EPS = int(floor(-log10(finfo(float).eps)))
SQRT3 = sqrt(3)

def ab2uvw(Z_ab):
    """
    2 phase equivalent to 3 phase coordinate transformation, i.e. Clarke transformation

    Parameters
    ----------
    Z_ab : numpy array
        matrix (N x 2) of 2 phase equivalent values

    Outputs
    -------
    Z_uwv : numpy array
        transformed matrix (N x 3) of 3 phase values

    """
    # Transformation matrix
    ab_2_uvw = 1 / 2 * array([[2, -1, -1], [0, SQRT3, -SQRT3]])

    Z_uvw = matmul(Z_ab, ab_2_uvw)

    return Z_uvw

def uvw2ab(Z_uvw):
    """3 phase to 2 phase equivalent coordinate transformation, i.e. Clarke transformation

    Parameters
    ----------
    Z_uvw : numpy array
        matrix (N x 3) of 3 phase values

    Outputs
    -------
    Z_ab : numpy array
        transformed matrix (N x 2) of 2 phase equivalent values

    """
    # Transformation matrix
    uvw_2_ab = 2 / 3 * array([[1, 0], [-1 / 2, SQRT3 / 2], [-1 / 2, -SQRT3 / 2]])

    Z_ab = matmul(Z_uvw, uvw_2_ab)

    return Z_ab

def ab2dq(Z_ab, theta):
    """
    alpha-beta to dq coordinate transformation
    NOTE: sin/cos values are rounded to avoid numerical errors

    Parameters
    ----------
    Z_ab : numpy array
        matrix (N x 2) of alpha-beta - reference frame values

    theta : numpy array
        angle of the rotor coordinate system

    Outputs
    -------
    Z_dq : numpy array
        transformed (dq) values

    """
    if len(Z_ab.shape) == 1:
        Z_ab = Z_ab[newaxis, :]

    sin_theta = sin(theta).round(decimals=EPS)
    cos_theta = cos(theta).round(decimals=EPS)

    Z_d = Z_ab[:, 0] * cos_theta + Z_ab[:, 1] * sin_theta
    Z_q = -Z_ab[:, 0] * sin_theta + Z_ab[:, 1] * cos_theta

    return reshape([Z_d, Z_q], (2, -1)).transpose()

def dq2ab(Z_dq, theta):
    """
    dq to alpha-beta coordinate transformation
    NOTE: sin/cos values are rounded to avoid numerical errors

    Parameters
    ----------
    Z_dq : numpy array
        matrix (N x 2) of dq - reference frame values

    theta : numpy array
        angle of the rotor coordinate system

    Outputs
    -------
    Z_ab : numpy array
        transformed array

    """
    if len(Z_dq.shape) == 1:
        Z_dq = Z_dq[newaxis, :]

    sin_theta = sin(theta).round(decimals=EPS)
    cos_theta = cos(theta).round(decimals=EPS)

    Z_a = Z_dq[:, 0] * cos_theta - Z_dq[:, 1] * sin_theta
    Z_b = Z_dq[:, 0] * sin_theta + Z_dq[:, 1] * cos_theta

    return reshape([Z_a, Z_b], (2, -1)).transpose()


from sympy import *
from sympy.plotting import plot
from sympy.codegen.cfunctions import log10
import matplotlib.pyplot as plt
import numpy as np
# https://docs.sympy.org/latest/install.html
# conda install sympy
# conda install mpmath

# Sobre matrices: https://docs.sympy.org/latest/tutorial/matrices.html

def get_passive_mai(Y_a, Y_b, Y_c, Y_d, Y_e):
  return Matrix([[Y_a, -Y_a, 0, 0, 0],\
                 [-Y_a, Y_a + Y_b + Y_c + Y_d, -Y_c, -Y_d, -Y_b],\
                 [0, -Y_c, Y_c+Y_e, Y_e, 0],\
                 [0, -Y_d, -Y_e, Y_d+Y_e, 0],\
                 [0, -Y_b, 0, 0, Y_b]])

def get_active_mai(A0, Y_i, Y_o):
  return Matrix([[0, 0, 0, 0, 0],\
                 [0, 0, 0, 0, 0],\
                 [0, 0, Y_i, 0, -Y_i],\
                 [0, 0, A0 * Y_o, Y_o, -A0 * Y_o - Y_o],\
                 [0, 0, -Y_i - A0 * Y_o, -Y_o, Y_i + Y_o + A0 * Y_o]])

def get_mfb_mai(Y_a, Y_b, Y_c, Y_d, Y_e, A0, Y_i, Y_o):
  return get_passive_mai(Y_a, Y_b, Y_c, Y_d, Y_e) + get_active_mai(A0, Y_i, Y_o)

# Para mejorar la impresion
init_printing()

# Defino simbolos
Ga = Symbol('Ga')
Cb = Symbol('Cb')
Gc = Symbol('Gc')
Gd = Symbol('Gd')
Ce = Symbol('Ce')
s = Symbol('s')

A0 = Symbol('A0')
Y_i = Symbol('Y_i')
Y_o = Symbol('Y_o')

Y_45_15 = get_mfb_mai(Ga, s*Cb, Gc, Gd, s*Ce, A0, Y_i, Y_o)
Y_45_15.row_del(4)
Y_45_15.row_del(0)
Y_45_15.col_del(4)
Y_45_15.col_del(3)
print('Y_45_15')
print(Y_45_15)

Y_15_15 = get_mfb_mai(Ga, s*Cb, Gc, Gd, s*Ce, A0, Y_i, Y_o)
Y_15_15.row_del(4)
Y_15_15.row_del(0)
Y_15_15.col_del(4)
Y_15_15.col_del(0)
print('Y_15_15')
print(Y_15_15)

V_15_45 = (-1.0)**(-1.0-5.0-4.0-5.0) * Y_45_15.det() / Y_15_15.det()
V_15_45 = cancel(V_15_45)
print('La transferencia de tension es:')
print(V_15_45)


"""
Finds the asymptotic expansions of the three roots of
  the polynomial e*x^3 - 3*x + 1 = 0 where e << 1
"""

from sympy import *

# Define symbols, expansion of x, and the function
e, x0, x1, x2 = symbols('e x0 x1 x2')
x = x0 + e*x1 + e**2*x2 + O(e**3)
f = e*x**3 - 3*x + 1

# Write f as a power series in e
f = collect(expand(f),e)

# Set the O(1) terms equal to 0, and output result
print("\nFirst root\n")
eq0 = f.coeff(e,0)
print("Zeroth order:", solveset(eq0))
sol0 = Rational(1, 3) # Solution for x0

# Substitute the solution into x0
x = x.subs(x0, sol0) # In the expansion of x
f = f.subs(x0, sol0) # and in the expansion of f

# Set the O(e) terms equal to 0, and output result
eq1 = f.coeff(e,1)
print("First order:", solveset(eq1))
sol1 = Rational(1, 81) # Solution for x1

# Substitute the solution into x1
x = x.subs(x1, sol1)
f = f.subs(x1, sol1)

# Set the O(e^2) terms equal to 0, and output result
eq2 = f.coeff(e, 2)
print("Second order:", solveset(eq2))
sol2 = Rational(1, 729) # Solution for x2

#Substitute the solution into x2 and output x
x = x.subs(x2, sol2)
print("x = ", x)

# Change of variables to reveal the solutions near infinity
# The change of variables is x = y/sqrt(e)
# Define new variable y, and the rescaled equation f
# NOTE: e is redefined below this point:
#       e_below := sqrt(e_above)
y0, y1, y2 = symbols('y0 y1 y2')
y = y0 + e*y1 + e**2*y2 + O(e**3)
f = y**3 - 3*y + e

# Write f as a power series in e
f = collect(expand(f), e)

# Set the O(1) terms equal to 0, and output result
print("\nSecond Root\n")
eq0 = f.coeff(e,0)

# There are three solutions to the leading order problem:
# 0 is a trivial solution, and the other two are relevant
print("Zeroth order:", solveset(eq0))
sol0 = sqrt(3) # Pick one of the relevant roots

# Substitute the solution into y0
y = y.subs(y0, sol0)
f = f.subs(y0, sol0)

# Set the O(e) terms equal to 0, and output result
eq1 = f.coeff(e, 1)
print("First order:", solveset(eq1))
sol1 = Rational(-1, 6) # Solution for y1

# Substitute the solution into y1
y = y.subs(y1, sol1)
f = f.subs(y1, sol1)

# Set the O(e^2) terms equal to 0, and output result
eq2 = f.coeff(e, 2)
print("Second order:", solveset(eq2))
sol2 = -sqrt(3)/Integer(72) # Solution for y2

# Substitute the solution into y2, and change variables back to original
y = y.subs(y2, sol2)
x = collect(expand(y/e), e)
print("x =", x)

# Using the same change of variables as for the second root
# Redefine expansion of y, and rescaled equation f
print("\nThird Root\n")
y = y0 + e*y1 + e**2*y2 + O(e**3)
f = y**3 - 3*y + e

# Collect terms in powers of e
f = collect(expand(f), e)

# Set the O(1) terms equal to 0, and output result
eq0 = f.coeff(e,0)
print("Zeroth order:", solveset(eq0))
sol0 = -sqrt(3) # Now use the other relevant root for y0

# Substitute the solution into y0
y = y.subs(y0, sol0)
f = f.subs(y0, sol0)

# Set the O(e) terms equal to 0, and output result
eq1 = f.coeff(e, 1)
print("First order:", solveset(eq1))
sol1 = Rational(-1, 6) # Solution for y1

# Substitute the solution into y1
y = y.subs(y1, sol1)
f = f.subs(y1, sol1)

# Set the O(e^2) terms equal to 0, and output result
eq2 = f.coeff(e, 2)
print("Second order:", solveset(eq2))
sol2 = sqrt(3)/Integer(72) # Solution for y2

# Substitute the solution into y2, and change variables back to original
y = y.subs(y2, sol2)
x = collect(expand(y/e), e)
print("x =", x)
print("\n")


import numpy as np

# REFERENCES
#https://towardsdatascience.com/fast-fourier-transform-937926e591cb

#https://pythonnumericalmethods.berkeley.edu/notebooks/chapter24.03-Fast-Fourier-Transform.html
#https://jakevdp.github.io/blog/2013/08/28/understanding-the-fft/

def show_M(N):
    """
    N: int
    """

    n = np.arange(N)
    k = n.reshape((N,1))

    M  = k*n
    print("M:", M)

def get_data(len):
    """
    len: int
        lenght of data
    """
    data = np.random.random(len)
    return data

def get_circular_terms(N):
    """
    N: int
    """

    terms =  np.exp(-1j *2*np.pi * np.arange(N)/N)

    return terms

def discrete_fourier_transform(data):
    """
    data: np.array
        1 dimensional array
    """
    #len of data
    N =data.shape[0]

    n = np.arange(N)
    k = n.reshape((N,1))
    M = np.exp(-1j * 2*np.pi * k * n/N)

    return np.dot(M,data)

def fast_fourier_transform(data):
    """
    data: np.array
        data as 1D array
    return discrete fourier transform of data
    """

    # len of data
    N = data.shape[0]

    # Must be a power of 2
    assert   N % 2 == 0, 'len of data: {} must be a power of 2'.format(N)

    if N<= 2:
        return discrete_fourier_transform(data)

    else:
        data_even = fast_fourier_transform(data[::2])
        data_odd = fast_fourier_transform(data[1::2])
        terms = get_circular_terms(N)

        return np.concatenate(
            [
            data_even + terms[:N//2] * data_odd,
            data_even + terms[N//2:] * data_odd
            ])

N = 4

X = get_data(N)
print("Data: ",X)

dt =  discrete_fourier_transform(X)
fdft = fast_fourier_transform(X)
dtnp = np.fft.fft(X)

print('DFT:',fdft)

print(np.allclose(dt,dtnp),
    np.allclose(fdft,dtnp))

print("")
show_M(N)


import numpy as np
import scipy

'''Gaussian Elimination with Complete Pivoting. it will result in a decomposition of form: PAQ = LU.'''

def gaussian_elimination_complete_pivoting(matrix):
    matrix = np.array(matrix)
    m, n = matrix.shape
    multipliers = np.zeros((n, n))
    p_matrix = np.identity(n)
    q_matrix = np.identity(n)
    assert m == n
    absolute_matrix = np.absolute(matrix)
    for k in range(n - 1):
        '''finding the maximum absolute value indices (row & column) of the given matrix.'''
        r, s = np.unravel_index(absolute_matrix.argmax(), absolute_matrix.shape)
        if matrix[r, s] == 0:
            break
        for j in range(k, n):
            matrix[[k, r]] = matrix[[r, k]]  # swapping rows
        p_matrix[[k, r]] = p_matrix[[r, k]]  # generating this step P matrix
        for i in range(n):
            matrix[:, [s, k]] = matrix[:, [k, s]]  # swapping columns
        q_matrix[:, [s, k]] = q_matrix[:, [k, s]]  # generating this step Q matrix
        for t in range(k + 1, n):
            multipliers[t, k] = - (matrix[t, k] / matrix[k, k])  # generating m_i_j multipliers
        for q in range(k, n):
            for e in range(k, n):
                matrix[q, e] += (multipliers[q, k] * matrix[k, e])  # updating matrix entries to form U matrix
        u_matrix = matrix
        l_matrix = np.identity(n)  # creating an identity matrix to form L matrix.
        for row in range(n):
            for column in range(row):
                l_matrix[row, column] = - multipliers[row, column]  # updating identity matrix entries to form L matrix
        return l_matrix, u_matrix, p_matrix, q_matrix

'''a linear system of equation is solved by Gaussian Elimination with Complete Pivoting method by following these steps:
        1) given system: AX = b
        2) decompose A matrix to PAQ = LU
        3) solve system: LZ = Pb
        4) solve system: UY = z
        5) X = QY'''

def solver(coefficient_matrix, right_hand_side_vector):
    l, u, p, q = gaussian_elimination_complete_pivoting(coefficient_matrix)  # decomposing matrix A
    z = scipy.linalg.solve_triangular(l, np.matmul(p, right_hand_side_vector))  # third step
    y = scipy.linalg.solve_triangular(u, z)  # forth step
    solution = np.matmul(q, y)  # fifth step
    return solution

if __name__ == '__main__':
    given_matrix = eval(input("enter A matrix like: [[1, 1, 1], [2, 2, 2], [3, 3, 3]\n"))
    vector = eval(input("enter b vector like: [1, 1, 1]\n"))
    print("solution is: ", solver(given_matrix, vector))


import math
import random
import time

import numpy
import torch

def numpy_network():

    # Create random input and output data
    x = numpy.linspace(-math.pi, math.pi, 2000)
    y = numpy.sin(x)

    # Randomly initialize weights
    a = numpy.random.randn()
    b = numpy.random.randn()
    c = numpy.random.randn()
    d = numpy.random.randn()

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y
        # y = a + b x + c x^2 + d x^3
        y_pred = a + b * x + c * x**2 + d * x**3

        # Compute and print loss
        loss = numpy.square(y_pred - y).sum()
        if t % 100 == 99:
            print(t, loss)

        # Backprop to compute gradients of a, b, c, d with respect to loss
        grad_y_pred = 2.0 * (y_pred - y)
        grad_a = grad_y_pred.sum()
        grad_b = (grad_y_pred * x).sum()
        grad_c = (grad_y_pred * x ** 2).sum()
        grad_d = (grad_y_pred * x ** 3).sum()

        # Update weights
        a -= learning_rate * grad_a
        b -= learning_rate * grad_a
        c -= learning_rate * grad_c
        d -= learning_rate * grad_d

    print(f'Result: y = {a} + {b} x + {c} x^2 + {d} x^3')

def pytorch_network():

    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0") # Uncomment this to run on GPU

    # Create random input and output data
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Randomly initialize weights
    a = torch.randn((), device=device, dtype=dtype)
    b = torch.randn((), device=device, dtype=dtype)
    c = torch.randn((), device=device, dtype=dtype)
    d = torch.randn((), device=device, dtype=dtype)

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y
        y_pred = a + b * x + c * x**2 + d * x**3

        # Compute and print loss
        loss = (y_pred - y).pow(2).sum().item()
        if t % 100 == 99:
            print(t, loss)

        # Backprop to compute gradients of a, b, c, d with respect to loss
        grad_y_pred = 2.0 * (y_pred - y)
        grad_a = grad_y_pred.sum()
        grad_b = (grad_y_pred * x).sum()
        grad_c = (grad_y_pred * x ** 2).sum()
        grad_d = (grad_y_pred * x ** 3).sum()

        # Update weights using gradient descent
        a -= learning_rate * grad_a
        b -= learning_rate * grad_b
        c -= learning_rate * grad_c
        d -= learning_rate * grad_d

    print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')

def autograd_network():
    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0")  # Uncomment this to run on GPU

    # Create Tensors to hold input and outputs.
    # By default, requires_grad=False, which indicates that we do not need to
    # compute gradients with respect to these Tensors during the backward pass.
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Create random Tensors for weights. For a third order polynomial, we need
    # 4 weights: y = a + b x + c x^2 + d x^3
    # Setting requires_grad=True indicates that we want to compute gradients with
    # respect to these Tensors during the backward pass.
    a = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    b = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    c = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    d = torch.randn((), device=device, dtype=dtype, requires_grad=True)

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y using operations on Tensors.
        y_pred = a + b * x + c * x ** 2 + d * x ** 3

        # Compute and print loss using operations on Tensors.
        # Now loss is a Tensor of shape (1,)
        # loss.item() gets the scalar value held in the loss.
        loss = (y_pred - y).pow(2).sum()
        if t % 100 == 99:
            print(t, loss.item())

        # Use autograd to compute the backward pass. This call will compute the
        # gradient of loss with respect to all Tensors with requires_grad=True.
        # After this call a.grad, b.grad. c.grad and d.grad will be Tensors holding
        # the gradient of the loss with respect to a, b, c, d respectively.
        loss.backward()

        # Manually update weights using gradient descent. Wrap in torch.no_grad()
        # because weights have requires_grad=True, but we don't need to track this
        # in autograd.
        with torch.no_grad():
            a -= learning_rate * a.grad
            b -= learning_rate * b.grad
            c -= learning_rate * c.grad
            d -= learning_rate * d.grad

            # Manually zero the gradients after updating weights
            a.grad = None
            b.grad = None
            c.grad = None
            d.grad = None

    print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')

class LegendrePolynomial3(torch.autograd.Function):
    """
    We can implement our own custom autograd Functions by subclassing
    torch.autograd.Function and implementing the forward and backward passes
    which operate on Tensors.
    """

    @staticmethod
    def forward(ctx, input):
        """
        In the forward pass we receive a Tensor containing the input and return
        a Tensor containing the output. ctx is a context object that can be used
        to stash information for backward computation. You can cache arbitrary
        objects for use in the backward pass using the ctx.save_for_backward method.
        """
        ctx.save_for_backward(input)
        return 0.5 * (5 * input ** 3 - 3 * input)

    @staticmethod
    def backward(ctx, grad_output):
        """
        In the backward pass we receive a Tensor containing the gradient of the loss
        with respect to the output, and we need to compute the gradient of the loss
        with respect to the input.
        """
        input, = ctx.saved_tensors
        return grad_output * 1.5 * (5 * input ** 2 - 1)

def legendre_network():
    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0")  # Uncomment this to run on GPU

    # Create Tensors to hold input and outputs.
    # By default, requires_grad=False, which indicates that we do not need to
    # compute gradients with respect to these Tensors during the backward pass.
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Create random Tensors for weights. For this example, we need
    # 4 weights: y = a + b * P3(c + d * x), these weights need to be initialized
    # not too far from the correct result to ensure convergence.
    # Setting requires_grad=True indicates that we want to compute gradients with
    # respect to these Tensors during the backward pass.
    a = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
    b = torch.full((), -1.0, device=device, dtype=dtype, requires_grad=True)
    c = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
    d = torch.full((), 0.3, device=device, dtype=dtype, requires_grad=True)

    learning_rate = 5e-6
    for t in range(2000):
        # To apply our Function, we use Function.apply method. We alias this as 'P3'.
        P3 = LegendrePolynomial3.apply

        # Forward pass: compute predicted y using operations; we compute
        # P3 using our custom autograd operation.
        y_pred = a + b * P3(c + d * x)

        # Compute and print loss
        loss = (y_pred - y).pow(2).sum()
        if t % 100 == 99:
            print(t, loss.item())

            # Use autograd to compute the backward pass.
        loss.backward()

        # Update weights using gradient descent
        with torch.no_grad():
            a -= learning_rate * a.grad
            b -= learning_rate * b.grad
            c -= learning_rate * c.grad
            d -= learning_rate * d.grad

            # Manually zero the gradients after updating weights
            a.grad = None
            b.grad = None
            c.grad = None
            d.grad = None

    print(f'Result: y = {a.item()} + {b.item()} * P3({c.item()} + {d.item()} x)')

def nn_network():
    # Create Tensors to hold input and outputs.
    x = torch.linspace(-math.pi, math.pi, 2000)
    y = torch.sin(x)

    # For this example, the output y is a linear function of (x, x^2, x^3), so
    # we can consider it as a linear layer neural network. Let's prepare the
    # tensor (x, x^2, x^3).
    p = torch.tensor([1, 2, 3])
    xx = x.unsqueeze(-1).pow(p)

    # In the above code, x.unsqueeze(-1) has shape (2000, 1), and p has shape
    # (3,), for this case, broadcasting semantics will apply to obtain a tensor
    # of shape (2000, 3)

    # Use the nn package to define our model as a sequence of layers. nn.Sequential
    # is a Module which contains other Modules, and applies them in sequence to
    # produce its output. The Linear Module computes output from input using a
    # linear function, and holds internal Tensors for its weight and bias.
    # The Flatten layer flatens the output of the linear layer to a 1D tensor,
    # to match the shape of `y`.
    model = torch.nn.Sequential(
        torch.nn.Linear(3, 1),
        torch.nn.Flatten(0, 1)
    )

    # The nn package also contains definitions of popular loss functions; in this
    # case we will use Mean Squared Error (MSE) as our loss function.
    loss_fn = torch.nn.MSELoss(reduction='sum')

    learning_rate = 1e-6
    for t in range(2000):

        # Forward pass: compute predicted y by passing x to the model. Module objects
        # override the __call__ operator so you can call them like functions. When
        # doing so you pass a Tensor of input data to the Module and it produces
        # a Tensor of output data.
        y_pred = model(xx)

        # Compute and print loss. We pass Tensors containing the predicted and true
        # values of y, and the loss function returns a Tensor containing the
        # loss.
        loss = loss_fn(y_pred, y)
        if t % 100 == 99:
            print(t, loss.item())

        # Zero the gradients before running the backward pass.
        model.zero_grad()

        # Backward pass: compute gradient of the loss with respect to all the learnable
        # parameters of the model. Internally, the parameters of each Module are stored
        # in Tensors with requires_grad=True, so this call will compute gradients for
        # all learnable parameters in the model.
        loss.backward()

        # Update the weights using gradient descent. Each parameter is a Tensor, so
        # we can access its gradients like we did before.
        with torch.no_grad():
            for param in model.parameters():
                param -= learning_rate * param.grad

    # You can access the first layer of `model` like accessing the first item of a list
    linear_layer = model[0]

    # For linear layer, its parameters are stored as `weight` and `bias`.
    print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + '
          f'{linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')

def optim_network():
    # Create Tensors to hold input and outputs.
    x = torch.linspace(-math.pi, math.pi, 2000)
    y = torch.sin(x)

    # Prepare the input tensor (x, x^2, x^3).
    p = torch.tensor([1, 2, 3])
    xx = x.unsqueeze(-1).pow(p)

    # Use the nn package to define our model and loss function.
    model = torch.nn.Sequential(
        torch.nn.Linear(3, 1),
        torch.nn.Flatten(0, 1)
    )
    loss_fn = torch.nn.MSELoss(reduction='sum')

    # Use the optim package to define an Optimizer that will update the weights of
    # the model for us. Here we will use RMSprop; the optim package contains many other
    # optimization algorithms. The first argument to the RMSprop constructor tells the
    # optimizer which Tensors it should update.
    learning_rate = 1e-3
    optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate)

    for t in range(2000):
        # Forward pass: compute predicted y by passing x to the model.
        y_pred = model(xx)

        # Compute and print loss.
        loss = loss_fn(y_pred, y)
        if t % 100 == 99:
            print(t, loss.item())

        # Before the backward pass, use the optimizer object to zero all of the
        # gradients for the variables it will update (which are the learnable
        # weights of the model). This is because by default, gradients are
        # accumulated in buffers( i.e, not overwritten) whenever .backward()
        # is called. Checkout docs of torch.autograd.backward for more details.
        optimizer.zero_grad()

        # Backward pass: compute gradient of the loss with respect to model
        # parameters
        loss.backward()

        # Calling the step function on an Optimizer makes an update to its
        # parameters
        optimizer.step()

    linear_layer = model[0]
    print(
        f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + '
        f'{linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')

class Polynomial3(torch.nn.Module):

    def __init__(self):
        """
        In the constructor we instantiate four parameters and assign them as
        member parameters.
        """
        super().__init__()
        self.a = torch.nn.Parameter(torch.randn(()))
        self.b = torch.nn.Parameter(torch.randn(()))
        self.c = torch.nn.Parameter(torch.randn(()))
        self.d = torch.nn.Parameter(torch.randn(()))

    def forward(self, x):
        """
        In the forward function we accept a Tensor of input data and we must return
        a Tensor of output data. We can use Modules defined in the constructor as
        well as arbitrary operators on Tensors.
        """
        return self.a + self.b * x + self.c * x ** 2 + self.c * x ** 2 + self.d * x ** 3

    def string(self):
        """
        Just like any class in Python, you can also define custom method on PyTorch modules
        """

        return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3'

def custom_nn_network():

    # Create Tensors to hold input and outputs.
    x = torch.linspace(-math.pi, math.pi, 2000)
    y = torch.sin(x)

    # Construct our model by instantiating the class defined above
    model = Polynomial3()

    # Construct our loss function and an Optimizer. The call to model.parameters()
    # in the SGD constructor will contain the learnable parameters of the nn.Linear
    # module which is members of the model.
    criterion = torch.nn.MSELoss(reduction='sum')
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-6)

    for t in range(2000):
        # Forward pass: Compute predicted y by passing x to the model
        y_pred = model(x)

        # Compute and print loss
        loss = criterion(y_pred, y)
        if t % 100 == 99:
            print(t, loss.item())

        # Zero gradients, perform a backward pass, and update the weights.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print(f'Result: {model.string()}')

class DynamicNet(torch.nn.Module):

    def __init__(self):
        """
        In the constructor we instantiate five parameters and assign them as members.
        """
        super().__init__()
        self.a = torch.nn.Parameter(torch.randn(()))
        self.b = torch.nn.Parameter(torch.randn(()))
        self.c = torch.nn.Parameter(torch.randn(()))
        self.d = torch.nn.Parameter(torch.randn(()))
        self.e = torch.nn.Parameter(torch.randn(()))

    def forward(self, x):
        """
        For the forward pass of the model, we randomly choose either 4, 5
        and reuse the e parameter to compute the contribution of these orders.

        Since each forward pass builds a dynamic computation graph, we can use normal
        Python control-flow operators like loops or conditional statements when
        defining the forward pass of the model.

        Here we also see that it is perfectly safe to reuse the same parameter many
        times when defining a computational graph.
        """
        y = self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3
        for exp in range(4, random.randint(4, 6)):
            y = y + self.e * x ** exp
        return y

    def string(self):
        """
        Just like any class in Python, you can also define custom method on PyTorch modules
        """
        return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + ' \
               f'{self.d.item()} x^3 + {self.e.item()} x^4 ? + {self.e.item()} x^5 ?'

def weight_sharing_network():

    # Create Tensors to hold input and outputs.
    x = torch.linspace(-math.pi, math.pi, 2000)
    y = torch.sin(x)

    # Construct our model by instantiating the class defined above
    model = DynamicNet()

    # Construct our loss function and an Optimizer. Training this strange model with
    # vanilla stochastic gradient descent is tough, so we use momentum
    criterion = torch.nn.MSELoss(reduction='sum')
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-8, momentum=0.9)
    for t in range(30000):
        # Forward pass: Compute predicted y by passing x to the model
        y_pred = model(x)

        # Compute and print loss
        loss = criterion(y_pred, y)
        if t % 2000 == 1999:
            print(t, loss.item())

        # Zero gradients, perform a backward pass, and update the weights.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print(f'Result: {model.string()}')

def main():
    numpy_network()
    pytorch_network()
    autograd_network()
    legendre_network()
    nn_network()
    optim_network()
    custom_nn_network()
    weight_sharing_network()

start = time.time()
main()
end = time.time()
total_time = end - start
print("%s: Total time = %f seconds" % (time.strftime("%Y/%m/%d-%H:%M:%S"), total_time))


# -*- coding: utf-8 -*-
"""
Created on Sat Jun 29 18:59:52 2019

@author: Marcus

Script da Tarefa 4 de Bases Computacionais

"""

"""
1) Calculo do volume de uma esfera com raio R
"""

import numpy as np #importar biblioteca de equações
print('1) Calculo do volume de uma esfera com raio R')
print()
R1 = 0.32 #raio da esfera
VR1 = (4* np.pi * (R1**3))/3 #fórmula de cálculo de volume
print('R1 =',R1,'m') #exibir raio no console
print('VR1 =',round (VR1, 3),'m³') #exibir volume no console
print()

R2 = 1
VR2 = (4* np.pi * (R2**3))/3
print('R2 =',R2,'m')
print('VR2 =',round (VR2, 3),'m³')
print()

R3 = 1.9
VR3 = (4* np.pi * (R3**3))/3
print('R3 =',R3,'m')
print('VR3 =',round (VR3, 3),'m³')
print()
print()
print()

"""
2) Temperatura em Fahrenheit dada a temperatura em Celcius na variável T
"""
print('2) Temperatura em Fahrenheit dada a temperatura em Celcius')
print()
T1 = -10 #Temperatura em Celcius
F1 = T1 * (9/5) + 32 #Fórmula de convesão para F
print('T1 =',T1,'°C') #Exibir T em celcius no console
print('F1 =', F1, '°F') #Exibir temp. convertida
print()

T2 = 30
F2 = T2 * (9/5) + 32
print('T2 =',T2,'°C')
print('F2 =', F2, '°F')
print()

T3 = 5
F3 = T3 * (9/5) + 32
print('T3 =',T3,'°C')
print('F3 =', F3, '°F')
print()
print()
print()

"""
3) Lado c de um triângulo com a b e θ conhecidos
"""

print('3)Definição do lado c de um triângulo com os lados a e b e o ângulo θ conhecidos')
print()
print('Triangulo 1')
a1 = 1 #Lado a do triangulo
b1 = 2 #Lado b do triangulo
θ1 = 30 #angulo dos lados a e b triangulo
rad1 = np.deg2rad(θ1) #conversão do angulo para radianos
c1 = np.sqrt(a1**2 + b1**2 - (a1 * b1 * 2 * np.cos(rad1))) #aplicação da lei dos cossenos
print('a =',a1)
print('b =',b1)
print('θ=',θ1,'°')
print('c =',round (c1, 3))
print()

print('Triangulo 2')
a2 = 3
b2 = 1
θ2 = 45
rad2 = np.deg2rad(θ2)
c2 = np.sqrt(a2**2 + b2**2 - (a2 * b2 * 2 * np.cos(rad2)))
print('a =',a2)
print('b =',b2)
print('θ=',θ2,'°')
print('c =',round (c2, 3))
print()

print('Triangulo 3')
a3 = 10
b3 = 11
θ3 = 15
rad3 = np.deg2rad(θ3)
c3 = np.sqrt(a3**2 + b3**2 - (a3 * b3 * 2 * np.cos(rad3)))
print('a =',a3)
print('b =',b3)
print('θ=',θ3,'°')
print('c =',round (c3, 3))
print()
print()
print()

"""3) Sequencia de Fibonacci"""

print('3) Identificação do n-ésimo número na sequência de Fibonacci')
print()
n1 = 30 # número na sequencia
f1 = np.floor(((((1 + np.sqrt(5)) / 2)**n1) - (((1 - np.sqrt(5)) / 2)**n1)) / np.sqrt(5)) #cálculo para valor da sequencia
print('n =',n1)
print('F =',f1)
print()

n2 = 31
f2 = np.floor(((((1 + np.sqrt(5)) / 2)**n2) - (((1 - np.sqrt(5)) / 2)**n2)) / np.sqrt(5))
print('n =',n2)
print('F =',f2)
print()

n3 = 32
f3 = np.floor(((((1 + np.sqrt(5)) / 2)**n3) - (((1 - np.sqrt(5)) / 2)**n3)) / np.sqrt(5))
print('n =',n3)
print('F =',f3)
print()

"""
Fim do Script
"""


import numpy as np

class BayesianModel():
    def __init__(self, alpha=1e-5, beta=1e-5):
        """
        Construction of Bayesian model.

        Args:
            alpha: Single value parameter.
            beta: Single value parameter.
            m: Mean of the posterior distribution.
            S: Covariance matrix of the posterior distribution.
        """
        self.alpha = alpha
        self.beta = beta
        self.m = None
        self.S = None

    def posterior(self, phi, t):
        """
        Computes mean and covariance matrix of the posterior distribution.

        Args:
            phi: Design matrix (N x M).
            t: Target vector (N).

        Returns:
            m: Mean of the posterior distribution (M).
            S: Covariance matrix of the posterior distribution (M x M).
            S_inv: Inverse of S (M x M).
        """
        S_inv = self.alpha * np.eye(phi.shape[1]) + self.beta * phi.T.dot(phi) # 1.72
        S = np.linalg.inv(S_inv)
        m = self.beta * S.dot(phi.T).dot(t) # 1.70
        return m, S, S_inv

    def fit(self, phi, t, max_iter=200, rtol=1e-12, verbose=False):
        """
        Jointly infers the posterior sufficient statistics and optimal values
        for alpha and beta by maximizing the log marginal likelihood.

        Args:
            phi: Design matrix (N x M).
            t: Target value array (N x 1).
            max_iter: Maximum number of iterations.
            rtol: Convergence criterion.

        Returns:
            posterior mean, posterior covariance.
        """
        N, M = phi.shape
        eigen_0 = np.linalg.eigvalsh(phi.T.dot(phi))
        for iter in range(max_iter):
            pre_beta = self.beta
            pre_alpha = self.alpha
            eigen = eigen_0 * self.beta
            m, S, S_inv = self.posterior(phi, t)
            gamma = np.sum(eigen / (eigen + self.alpha))
            self.alpha = gamma / np.sum(m ** 2)
            beta_inv = 1 / (N - gamma) * np.sum((t - phi.dot(m)) ** 2)
            self.beta = 1 / beta_inv
            if np.isclose(pre_alpha, self.alpha, rtol=rtol) and np.isclose(pre_beta, self.beta, rtol=rtol):
                if verbose:
                    print(f'alpha:{self.alpha} beta:{self.beta}')
                    print(f'Convergence after {iter + 1} iterations.')
                self.m, self.S = m, S
                return
            if verbose:
                    print(f'alpha:{self.alpha} beta:{self.beta}')
        if verbose:
            print(f'Stopped after {max_iter} iterations.')
        self.m, self.S = m, S

    def predict(self, phi_):
        """
        Computes mean and variances of the posterior predictive distribution.

        Args:
            phi_: Design matrix of test input x (M).

        Returns:
            y: Mean of the posterior predictive distribution.
            y_var: Variances of the posterior predictive distribution (M x M).
        """
        y = phi_.dot(self.m) # pick the mean of the distribution to predict the target
        y_var = 1 / self.beta + np.sum(phi_.dot(self.S) * phi_, axis=1)   # 1.71
        return y, y_var


import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
import random
import torch
import torch.optim as optim
import utils
import math

print('定义 rmsprop')
eta = 0.4
gamma = 0.9

def rmsprop_2d(x1, x2, s1, s2):
    g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
    s1 = gamma * s1 + (1 - gamma) * g1 ** 2
    s2 = gamma * s2 + (1 - gamma) * g2 ** 2
    x1 -= eta / math.sqrt(s1 + eps) * g1
    x2 -= eta / math.sqrt(s2 + eps) * g2
    return x1, x2, s1, s2

def f_2d(x1, x2):
    return 0.1 * x1 ** 2 + 2 * x2 ** 2

print('lr= 0.4 的轨迹')
utils.show_trace_2d(f_2d, utils.train_2d(rmsprop_2d))

print('自行实现 RMSprop')

features, labels = utils.get_nasa_data()

def init_rmsprop_states():
    s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
    s_b = torch.zeros(1, dtype=torch.float32)
    return (s_w, s_b)

def rmsprop(params, states, hyperparams):
    eps = 1e-6
    gamma = hyperparams['gamma']
    for p, s in zip(params, states):
        s.data = gamma * s.data + (1-gamma) * (p.grad.data)**2
        p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)

print('RMSProp 进行优化')
utils.train_opt(rmsprop, init_rmsprop_states(), {'lr': 0.1, 'gamma': 0.9}, features, labels)

print('简洁实现')
utils.train_opt_pytorch(optim.RMSprop, {'lr': 0.1, 'alpha': 0.9}, features, labels)


import numpy as np
import scipy.sparse
import scipy.optimize

def softmax_cost(theta, num_classes, input_size, lambda_, data, labels):
    """

    :param theta:
    :param num_classes: the number of classes
    :param input_size: the size N of input vector
    :param lambda_: weight decay parameter
    :param data: the N x M input matrix, where each column corresponds
                 a single test set
    :param labels: an M x 1 matrix containing the labels for the input data
    """
    m = data.shape[1]
    theta = theta.reshape(num_classes, input_size)
    theta_data = theta.dot(data)
    theta_data = theta_data - np.max(theta_data)
    prob_data = np.exp(theta_data) / np.sum(np.exp(theta_data), axis=0)
    indicator = scipy.sparse.csr_matrix((np.ones(m), (labels, np.array(range(m)))))
    indicator = np.array(indicator.todense())
    cost = (-1 / m) * np.sum(indicator * np.log(prob_data)) + (lambda_ / 2) * np.sum(theta * theta)

    grad = (-1 / m) * (indicator - prob_data).dot(data.transpose()) + lambda_ * theta

    return cost, grad.flatten()

def softmax_predict(model, data):
    # model - model trained using softmaxTrain
    # data - the N x M input matrix, where each column data(:, i) corresponds to
    #        a single test set
    #
    # Your code should produce the prediction matrix
    # pred, where pred(i) is argmax_c P(y(c) | x(i)).

    opt_theta, input_size, num_classes = model
    opt_theta = opt_theta.reshape(num_classes, input_size)

    prod = opt_theta.dot(data)
    pred = np.exp(prod) / np.sum(np.exp(prod), axis=0)
    pred = pred.argmax(axis=0)

    return pred

def softmax_train(input_size, num_classes, lambda_, data, labels, options={'maxiter': 400, 'disp': True}):
    #softmaxTrain Train a softmax model with the given parameters on the given
    # data. Returns softmaxOptTheta, a vector containing the trained parameters
    # for the model.
    #
    # input_size: the size of an input vector x^(i)
    # num_classes: the number of classes
    # lambda_: weight decay parameter
    # input_data: an N by M matrix containing the input data, such that
    #            inputData(:, c) is the cth input
    # labels: M by 1 matrix containing the class labels for the
    #            corresponding inputs. labels(c) is the class label for
    #            the cth input
    # options (optional): options
    #   options.maxIter: number of iterations to train for

    # Initialize theta randomly
    theta = 0.005 * np.random.randn(num_classes * input_size)

    J = lambda x: softmax_cost(x, num_classes, input_size, lambda_, data, labels)

    result = scipy.optimize.minimize(J, theta, method='L-BFGS-B', jac=True, options=options)

    print(result)
    # Return optimum theta, input size & num classes
    opt_theta = result.x

    return opt_theta, input_size, num_classes


# ----------
#
# As with the previous perceptron exercises, you will complete some of the core
# methods of a sigmoid unit class.
#
# There are two functions for you to finish:
# First, in activate(), write the sigmoid activation function.
# Second, in update(), write the gradient descent update rule.
#
# ----------

import numpy as np

class Sigmoid:
    """
    This class models an artificial neuron with sigmoid activation function.
    """

    def __init__(self, weights = np.array([1])):
        """
        Initialize weights based on input arguments. Note that no type-checking
        is being performed here for simplicity of code.
        """
        self.weights = weights

        # NOTE: You do not need to worry about these two attribues for this
        # programming quiz, but these will be useful for if you want to create
        # a network out of these sigmoid units!
        self.last_input = 0 # strength of last input
        self.delta      = 0 # error signal

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

    def activate(self, values):
        """
        Takes in @param values, a list of numbers equal to length of weights.
        @return the output of a sigmoid unit with given inputs based on unit
        weights.
        """

        # YOUR CODE HERE

        # First calculate the strength of the input signal.
        strength = np.dot(values, self.weights)
        self.last_input = strength

        # TODO: Modify strength using the sigmoid activation function and
        # return as output signal.
        # HINT: You may want to create a helper function to compute the
        #   logistic function since you will need it for the update function.
        result = self.logistic(strength)

        return result

    def update(self, values, train, eta=.1):
        """
        Takes in a 2D array @param values consisting of a LIST of inputs and a
        1D array @param train, consisting of a corresponding list of expected
        outputs. Updates internal weights according to gradient descent using
        these values and an optional learning rate, @param eta.
        """

        # TODO: for each data point...
        for X, y_true in zip(values, train):
            # obtain the output signal for that point
            y_pred = self.activate(X)

            # YOUR CODE HERE

            # TODO: compute derivative of logistic function at input strength
            # Recall: d/dx logistic(x) = logistic(x)*(1-logistic(x))
            slope = y_pred * (1 - y_pred)
            # TODO: update self.weights based on learning rate, signal accuracy,
            # function slope (derivative) and input value
            self.weights += eta*(y_true - y_pred) * slope *X

def test():
    """
    A few tests to make sure that the perceptron class performs as expected.
    Nothing should show up in the output if all the assertions pass.
    """
    def sum_almost_equal(array1, array2, tol = 1e-5):
        return sum(abs(array1 - array2)) < tol

    u1 = Sigmoid(weights=[3,-2,1])
    assert abs(u1.activate(np.array([1,2,3])) - 0.880797) < 1e-5

    u1.update(np.array([[1,2,3]]),np.array([0]))
    assert sum_almost_equal(u1.weights, np.array([2.990752, -2.018496, 0.972257]))

    u2 = Sigmoid(weights=[0,3,-1])
    u2.update(np.array([[-3,-1,2],[2,1,2]]),np.array([1,0]))
    assert sum_almost_equal(u2.weights, np.array([-0.030739, 2.984961, -1.027437]))

if __name__ == "__main__":
    test()


# Corresponde a la implementacion en python de algunos algoritmos
# en el primer capitulo de Algorithms and Computations de Werner Krauth.

import numpy as np

def direct_pi(N):
    N_hits = 0
    for i in range(N):
        x = 2.0*(np.random.random()-0.5)
        y = 2.0*(np.random.random()-0.5)
        if(x**2 + y**2 < 1):
            N_hits += 1
    return N_hits

print('direct_pi(1000)',direct_pi(1000))

def markov_pi(N):
    N_hits = 0
    x = 1.0
    y = 1.0
    delta = 0.1
    for i in range(N):
        delta_x = delta * 2.0 * (np.random.random()-0.5)
        delta_y = delta * 2.0 * (np.random.random()-0.5)
        if(abs(x+delta_x)<1 and abs(y+delta_y)<1):
            x += delta_x
            y += delta_y
        if (x**2 + y**2 < 1):
            N_hits += 1
    return N_hits

print('markov_pi(1000):', markov_pi(1000))

def markov_two_site(k, p_0=0.1, p_1=0.9):
    def proba(m):
        if(m==0): a = 0.5
        if(m==1): a = 0.5
        return a

    if(k==0): l=1
    if(k==1): l=0

    gamma = proba(l)/proba(k)
    r = np.random.random()
    if r < gamma:
        k = l
    return k

print('markov_two_site:')
k = 0
for i in range(10):
    k = markov_two_site(k)
    print(k)

def reject_continous():
    def proba_dens(x):
        """Corresponde a exp(-x) entre 0 y 1.
        """
        norm = 1.0 - np.exp(-1)
        return norm * np.exp(-x)
    p_max = proba_dens(0)

    x_rand = np.random.random()
    gamma = np.random.random() * p_max
    while gamma > proba_dens(x_rand) :
            x_rand = np.random.random()
            gamma = np.random.random() * p_max
    return x_rand

print('reject_continous')
for i in range(10):
    print(reject_continous())

def gauss(sigma):
    phi = np.random.random() * 2.0 * np.pi
    gamma = -np.log(np.random.random())
    r = sigma * np.sqrt(2.0 * gamma)
    x = r * np.cos(phi)
    y = r * np.sin(phi)
    return x, y

print('gauss')
for i in range(10):
    print(gauss(1.0))


import numpy as np
import numpy.linalg as la
import time
from ReadParticleData import read

def generate_low_rank(epsilon, max_t):
    x, y = read()
    ns = len(x)
    nt = len(y)
    A = np.zeros((nt, ns))
    print("Data Read.")

    start = time.time()
    for j in range(nt):
        for i in range(ns):
            A[j, i] = 1/la.norm(x[i]-y[j])**2
    end = time.time()
    print("A completed,   elapsed time is " + str(int(end-start)) + " seconds")

    start = time.time()
    SVD = la.svd(A, full_matrices=False)
    u, s, v = SVD
    for k in (np.arange(len(s))+1)*10:
        Ak = np.zeros((nt, ns))
        for i in range(k):
            Ak += s[i] * np.outer(u.T[i], v[i])
        if la.norm(A-Ak) <= epsilon:
            break
        end = time.time()
        if (end-start >= max_t):
            print("Failed to converge within " + str(max_t) + " seconds")
            break

    print("SVD completed, elapsed time is " + str(int(end-start)) + " seconds.")
    return x, A, Ak

def test(x, A, Ak):
    w = np.zeros(len(x))
    r = np.zeros(3)
    for j in range(len(x)):
        w[j] = 1/la.norm(x[j]-r)**2

    bk = Ak@w
    b = A@w

    pred_accu = epsilon*la.norm(w)
    real_accu = la.norm(bk-b)

    pred_info = "Predicted Error: {pa:8.4f}"
    real_info = "Actual Error:    {ra:8.4f}"
    pred_info = pred_info.format(pa = pred_accu)
    real_info = real_info.format(ra = real_accu)
    print(pred_info)
    print(real_info)

def total_illum(r, x, A):
    b = 0
    w = np.zeros(len(x))
    for i in range(len(r)):
        for j in range(len(x)):
            w[j] = 1/la.norm(x[j]-r[i])**2
        b += A@w

epsilon = 0.1
max_t = 300
num_test = 1000
x, A, Ak = generate_low_rank(epsilon, max_t)
test(x, A, Ak)
r = np.random.rand(num_test, 3)*10
start = time.time()
total_illum(r, x, A)
end = time.time()
print("Raw calculation takes          " + str(int(end-start)) + " seconds.")
start = time.time()
total_illum(r, x, Ak)
end = time.time()
print("Reduced rank calculation takes " + str(int(end-start)) + " seconds.")

Scientific Computing Projects (Code Parts)/3/Problem 3/LowRankApprox.py


import numpy as np

"""
A2-Part-3: Implement the discrete Fourier transform (DFT)

Write a function that implements the discrete Fourier transform (DFT). Given a sequence x of length
N, the function should return its DFT, its spectrum of length N with the frequency indexes ranging from 0
to N-1.

The input argument to the function is a numpy array x and the function should return a numpy array X which
is of the DFT of x.

EXAMPLE: If you run your function using x = np.array([1, 2, 3, 4]), the function shoulds return the following numpy array:
array([10.0 + 0.0j,  -2. +2.0j,  -2.0 - 9.79717439e-16j, -2.0 - 2.0j])

Note that you might not get an exact 0 in the output because of the small numerical errors due to the
limited precision of the data in your computer. Usually these errors are of the order 1e-15 depending
on your machine.
"""
def DFT(x):
    """
    Input:
        x (numpy array) = input sequence of length N
    Output:
        The function should return a numpy array of length N
        X (numpy array) = The N point DFT of the input sequence x
    """
    ## Your code here
    N = x.shape[-1]
    X = np.zeros(N, dtype=np.complex)

    for k in range(N):
        s = np.exp(-1j * 2 * np.pi * k / N * np.arange(N))
        X[k] = x.dot(s)

    return X


# The branch and bound algorithm (backtracking on steroids) for the knapsack 0-1 problem.
# Branch and bound algorithm enhances the search bymalso pruning the recursion tree
# as soon as the algorithm detects that it will not be able to improve the existing
# optimal solution by expanding the partial one.
# It uses an extra parameter (max_val) to store the maximum sum of values that could be
# acquired by expanding a partial solution. If this value is smaller than the
# existing optimal value, it will not continue expanding.
# The item values and weights are provided through input lists.
# The algorithm has exponential time complexity in the number of items: O(2^n)

# import numpy as np

def knapsack(i, sol, val, opt_sol, opt_val, max_val, w_left, v, w, C):
	# Base case
	if i == len(sol):
		# Check if better than current best
		if val > opt_val:
			# Update optimal value and solution
			opt_val = val
			for k in range(0, len(sol)):
				opt_sol[k] = sol[k]
	else:
		# Generate candidates
		for k in range(0, 2):
			# Check capacity constraint (pruning)
			if k * w[i] <= w_left:
				# Update maximum possible value
				new_max_val = max_val - (1 - k) * v[i]
				# Check if better than existing optimal value (pruning)
				if new_max_val > opt_val:
					# Expand partial solution
					sol[i] = k
					# Update remaining capacity
					w_left = w_left - k * w[i]
					# Update partial value
					val = val + k * v[i]
					# Expand partial solution
					opt_val = knapsack(i + 1, sol, val, opt_sol, opt_val, new_max_val, w_left, v, w, C)
	return opt_val

def print_solution(opt_sol, opt_val, v, w, C):
	n = len(opt_sol)
	k = 0
	while k < n and opt_sol[k] == 0:
		k = k + 1

	total_weight = 0
	if k < n:
		print ('(', w[k], ',', v[k], ')', sep='', end='')
		total_weight = total_weight + w[k]
		for i in range(k + 1, n):
			if opt_sol[i] == 1:
				total_weight = total_weight + w[i]
				print (' + ', sep='', end='')
				print ('(', w[i], ',', v[i], ')', sep='', end='')

	print(' => ', '(', total_weight, ',', opt_val, ')', sep='')

def knapsack_0_1_bnb(v, w, C):
	sol = [None] * len(v)
	opt_sol = [None] * len(v)
	opt_val = knapsack(0, sol, 0, opt_sol, 0, sum(v), C, v, w, C)
	print_solution(opt_sol, opt_val, v, w, C)
#	print(opt_val)

# List of item values
v = [7, 2, 10, 4]
# List of item weights
w = [3, 6, 9, 5]
# Knapsack capacity
C = 15
# v, w = np.loadtxt("knapsack_dataset.txt", unpack=True)
knapsack_0_1_bnb(v, w, C)


import numpy as np
import scipy as sc
from scipy.sparse import csc_matrix

def Tridiag(d,N):
# ~ Matriz con diagonal d=[dsub,dmid,dsub] y dimensión N
    diagSub = np.diag(np.repeat(d[0],N-1),-1)
    diagMid = np.diag(np.repeat(d[1],N),0)
    diagSup = np.diag(np.repeat(d[2],N-1),1)
    return diagSub + diagMid + diagSup

def Mesh(Xlim, N, opt):
	# ~ Genera una malla uniforme de Xlim[0] a Xlim[1], con N puntos en el interior.
	# ~ la variable 'opt' indica si queremos el intervalo abierto o el cerrado.
    x = np.linspace(Xlim[0],Xlim[1],N+2)
    return x[1:-1] if ( opt == 'open' ) else x

def Solve(A,b):
    if not(isinstance(A, sc.sparse.csc.csc_matrix)):
        print("Advertencia: la matriz es mayor a 1000 y no es rala, se recomienda convertirla mediante csc_matrix")
        A = csc_matrix(A)
        return sc.sparse.linalg.spsolve(A,b)
    else:
        return sc.sparse.linalg.spsolve(A,b)

# Agustin Arias
def MatrizAInvertir_Implicito(tamaño, paso):
    # tamaño es el tamaño de la matriz, paso es h
    A = Tridiag([1, -2, 1], tamaño)
    Identidad = sc.sparse.identity(tamaño)  # necesario para poder aplicar kron

    matAInv = sc.sparse.kron(A, Identidad) + sc.sparse.kron(Identidad, A)
    matAInv = matAInv/(paso**2)

    return matAInv.toarray()

# Tilman Goebel
def MatrizAInvertir_Implicito_Rectangulo(tamañoX, tamañoY, pasoX, pasoY, dt):
    # tamañoX es la cantidad de filas, tamañoY la cantidad de columnas y pasoX y pasoY son hx y hy
    AX = Tridiag([1, -2, 1], tamañoX)
    AY = Tridiag([1, -2, 1], tamañoY)
    IdentidadX = sc.sparse.identity(tamañoX)
    IdentidadY = sc.sparse.identity(tamañoY)

    matAInv = sc.sparse.kron(AX, IdentidadY)/(pasoX**2) + sc.sparse.kron(IdentidadX, AY)/(pasoY**2)
    matAInv = dt * matAInv

    return matAInv

def MatricesCBPer2D( Nx,Ny ):
    Dx2 = Tridiag([1,-2,1],Nx)
    Dx2[0,-1] = 1 #condiciones periódicas
    Dx2[-1,0] = 1
    Dy2 = Tridiag([1,-2,1],Ny)
    Dy2[0,-1] = 1 #condiciones periódicas
    Dy2[-1,0] = 1
    return Dx2,Dy2


# coding: utf-8

# ## Elementwise Operations and Statistics.
# In this section I will cover on Elementwise operations, Basic reductions, Broadcasting, and Sorting data. Arrays are important because they enable you to express batch operations on data without writing any for loops. This is usually called vectorization. Any arithmetic operations between equal-size arrays applies the operation elementwise:
# ### Here are the main steps we will go through
# * Element-wise operations
#     * Elementwise operations
#     * Basic reductions
#     * Broadcasting
#     * Sorting data
# * Do some statistics on numpy
#
# This is just little illustration.
# <img src="https://s3.amazonaws.com/dq-content/6/numpy_ndimensional.svg">

# #### Basic operations

# In[1]:

import numpy as np
arr1 = np.array([[1,2,3],[4,5,6]])
arr2 = np.array([[7,8,9]])

# In[2]:

#elementwise add arr2 to arr1
np.add(arr1, arr2)

# In[3]:

#elementwise subtract arr2 from arr1
np.subtract(arr1, arr2)

# In[4]:

#elementwise multiply arr1 by arr2
np.multiply(arr1,arr2)

# In[5]:

#elementwise divide arr1 by arr2
np.divide(arr1, arr2)

# In[6]:

#elementwise raise arr1 raised to the power of arr2
np.power(arr1,arr2)

# In[7]:

#returns True if the arrays have the same elements and shape
np.array_equal(arr1,arr2)

# In[9]:

#square root of each element in the array
np.sqrt(arr1)

# In[26]:

#Transcendental functions:
#sine of each element in the array
np.sin(arr1)

# In[11]:

#natural log of each element in the array
np.log(arr1)

# In[12]:

#absolute value of each element in the array
np.abs(arr1)

# In[24]:

arr = np.random.random(9)
print("original array:\n ", arr)
#rounds up to the nearest int
np.ceil(arr)

# In[22]:

#rounds down to the nearest int
np.floor(arr)

# In[23]:

#rounds to the nearest int
np.round(arr)

# In[25]:

#Logical operations:
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 0], dtype=bool)
np.logical_or(a, b)

# #### Basic reductions

# In[27]:

x = np.array([1, 2, 3, 4])
np.sum(x)

# In[28]:

#Sum by rows and by columns:
x = np.array([[1, 1], [2, 2]])
x

# In[29]:

x.sum(axis=0)   # columns (first dimension)

# In[30]:

x[:, 0].sum(), x[:, 1].sum()

# In[31]:

x.sum(axis=1)   # rows (second dimension)

# #### Basic reductions
#  * Basic operations on numpy arrays (addition, etc.) are elementwise
#
#  * This works on arrays of the same size.
#   Nevertheless, It’s also possible to do operations on arrays of different
#     sizes if NumPy can transform these arrays so that they all have
#     the same size: this conversion is called broadcasting.
#     The image below gives an example of broadcasting:

# In[32]:

a = np.tile(np.arange(0, 40, 10), (3, 1)).T
a

# In[33]:

b = np.array([0, 1, 2])
b

# In[34]:

a + b

# In[36]:

#We have already used broadcasting without knowing it!:
a = np.ones((4, 5))
a[0] = 2
a

# #### Sorting data

# In[37]:

a = np.array([[4, 3, 5], [1, 2, 1]])
b = np.sort(a, axis=1)
b

# In[38]:

#Sorts each row separately!
a.sort(axis=1)
a

# In[45]:

s = np.array([[2,4,5,8,2,0,3,7]])
t = np.sort(s, axis=1)
t

# #### Do some statistics on numpy

# In[49]:

stat = np.arange(25).reshape(5,5)
stat

# In[50]:

#returns mean along specific axis
np.mean(stat, axis=0)

# In[51]:

#returns sum of arr
stat.sum()

# In[52]:

#returns minimum value of arr
stat.min()

# In[53]:

#returns maximum value of specific axis
stat.max(axis=0)

# In[54]:

#returns the variance of array
np.var(stat)

# In[55]:

#returns the standard deviation of specific axis
np.std(stat, axis=1)

# In[57]:

#returns correlation coefficient of array
np.corrcoef(stat)

All Python Codes/2017-23-11-so-stats-vector-math-numpy (1).py


"""
This script demonstrates the implementation of the Softmax function.

Its a function that takes as input a vector of K real numbers, and normalizes
it into a probability distribution consisting of K probabilities proportional
to the exponentials of the input numbers. After softmax, the elements of the
vector always sum up to 1.

Script inspired from its corresponding Wikipedia article
https://en.wikipedia.org/wiki/Softmax_function
"""

import numpy as np

def softmax(vector):
    """
        Implements the softmax function

        Parameters:
            vector (np.array,list,tuple): A  numpy array of shape (1,n)
            consisting of real values or a similar list,tuple

        Returns:
            softmax_vec (np.array): The input numpy array  after applying
            softmax.

        The softmax vector adds up to one. We need to ceil to mitigate for
        precision
        >>> np.ceil(np.sum(softmax([1,2,3,4])))
        1.0

        >>> vec = np.array([5,5])
        >>> softmax(vec)
        array([0.5, 0.5])

        >>> softmax([0])
        array([1.])
    """

    # Calculate e^x for each x in your vector where e is Euler's
    # number (approximately 2.718)
    exponentVector = np.exp(vector)

    # Add up the all the exponentials
    sumOfExponents = np.sum(exponentVector)

    # Divide every exponent by the sum of all exponents
    softmax_vector = exponentVector / sumOfExponents

    return softmax_vector

if __name__ == "__main__":
    print(softmax((0,)))


def midpoint_triple1(g, a, b, c, d, e, f, nx, ny, nz):
    hx = (b - a)/nx
    hy = (d - c)/ny
    hz = (f - e)/nz
    I = 0
    for i in range(nx):
        for j in range(ny):
            for k in range(nz):
                xi = a + hx/2 + i*hx
                yj = c + hy/2 + j*hy
                zk = e + hz/2 + k*hz
                I = I + hx*hy*hz*g(xi, yj, zk)
    return I

def midpoint(f, a, b, n):
    h = (b-a)/n
    f_sum = 0
    for i in range(0, n, 1):
        x = (a + h/2.0) + i*h
        f_sum = f_sum + f(x)
    return h*f_sum

def midpoint_triple2(g, a, b, c, d, e, f, nx, ny, nz):
    def p(x, y):
        return midpoint(lambda z: g(x, y, z), e, f, nz)

    def q(x):
        return midpoint(lambda y: p(x, y), c, d, ny)

    return midpoint(q, a, b, nx)

def test_midpoint_triple():
    """Test that a linear function is integrated exactly."""
    def g(x, y, z):
        return 2*x + y - 4*z

    a = 0;  b = 2;  c = 2;  d = 3;  e = -1;  f = 2
    import sympy
    x, y, z = sympy.symbols('x y z')
    I_expected = sympy.integrate(
        g(x, y, z), (x, a, b), (y, c, d), (z, e, f))
    for nx, ny, nz in (3, 5, 2), (4, 4, 4), (5, 3, 6):
        I_computed1 = midpoint_triple1(
            g, a, b, c, d, e, f, nx, ny, nz)
        I_computed2 = midpoint_triple2(
            g, a, b, c, d, e, f, nx, ny, nz)
        tol = 1E-14
        print(I_expected, I_computed1, I_computed2)
        assert abs(I_computed1 - I_expected) < tol
        assert abs(I_computed2 - I_expected) < tol

if __name__ == '__main__':
    test_midpoint_triple()


# determines whether a matrix is orthogonal. A square matrix is orthogonal,
# if its columns and rows are orthogonal unit vectors,
# which is equivalent to: MT M = I

import numpy as np

def check_orthogonal(M):
    # make sure the input is a matrix
    if len(np.shape(M)) !=2:
        print("error: input is not a matrix")
        return
    # make sure the input is not a square matrix
    dim = np.shape(M)[0]
    if dim != np.shape(M)[1]:
        print("error: input is not a square matrix")
        return
    A = np.dot(M, M.T)
    #  if np.array_equal(A, np.identity(dim)):
    [rows, cols] = A.shape
    I = np.identity(dim)
    for i in range(rows):
        for j in range(cols):
            if not (A[i, j] - I[i, j] <= 10e-3):
                print("matrix is not orthogonal")
                return
    print("matrix is orthogonal")

if __name__ == '__main__':
    # Verify check_orthogonal function
    D = 1. / 3. * np.array(
        [[2, 2, -1],
         [2, -1, 2],
         [-1, 2, 2]])
    check_orthogonal(D)

    #  Test 2
    R = np.array([[np.cos(np.pi / 4), -np.sin(np.pi / 4)],
    [np.sin(np.pi / 4), np.cos(np.pi / 4)]])
    check_orthogonal(R)


"""
Kindly install these libraries before executing this code:
  1. numpy
  2. scipy
"""

import math
import numpy as np
from scipy.stats import norm

M = [100, 1000, 10000, 100000]

def generate_random_numbers(idx):
  np.random.seed(42)
  random_nums = np.random.uniform(0, 1, M[idx])
  return random_nums

def simpleEstimator():
  for idx in range(0, 4):
    print("\n# Iteration - {}\t\tM = {}\n".format(idx + 1, M[idx]))

    Y = []
    random_nums = generate_random_numbers(idx)
    for i in range(0, M[idx]):
      Y.append(math.exp(math.sqrt(random_nums[i])))

    Z_delta = norm.ppf(0.05/2)
    s = np.var(Y)
    I_m = np.mean(Y)
    l = I_m + (Z_delta * math.sqrt(s))/math.sqrt(M[idx])
    r = I_m - (Z_delta * math.sqrt(s))/math.sqrt(M[idx])

    print("I_m \t\t\t= {}".format(I_m))
    print("Confidence Interval \t= [{}, {}]".format(l, r))
    print("variance \t\t= {}".format(s))

def antitheticVariateEstimator():
  for idx in range(0, 4):
    print("\n# Iteration - {}\t\tM = {}\n".format(idx + 1, M[idx]))

    Y = []
    Y_hat = []
    random_nums = generate_random_numbers(idx)

    for i in range(0, M[idx]):
      Y.append(math.exp(math.sqrt(random_nums[i])))
      Y_hat.append((math.exp(math.sqrt(random_nums[i])) + math.exp(math.sqrt(1 - random_nums[i])))/2)

    Z_delta = norm.ppf(0.05/2)
    s = np.var(Y_hat)
    I_m = np.mean(Y_hat)
    l = I_m + (Z_delta * math.sqrt(s))/math.sqrt(M[idx])
    r = I_m - (Z_delta * math.sqrt(s))/math.sqrt(M[idx])

    print("I_m \t\t\t= {}".format(I_m))
    print("Confidence Interval \t= [{}, {}]".format(l, r))
    print("variance \t\t= {}".format(s))

def main():
  print("************ Part 1 ************")
  simpleEstimator()

  print("\n\n\n\n************ Part 2 ************")
  antitheticVariateEstimator()

if __name__=="__main__":
    main()


# -------- Programming Assignment --------
#
# * Author: Laxman Desai ($190020066$)
# * Course: CL $202$ (Data Analysis)
# * Date: $20^{th}$ April $2021$

# Initialization
from scipy import stats
from sklearn.linear_model import LinearRegression
import pandas as pd
import numpy as np

df = pd.read_csv('dataset/data.csv')
df['Z1'] *= 100 # Scaling
df['Z2'] *= 1000 # Scaling
df['Y'] *= 1000 # Scaling

# A) Correlation Coefficient
corr_coeff_Y_Z1 = df['Y'].corr(df['Z1'])
print(f'corr_coeff_Y_Z1 = {round(corr_coeff_Y_Z1, 4)}')
corr_coeff_Y_Z2 = df['Y'].corr(df['Z2'])
print(f'corr_coeff_Y_Z2 = {round(corr_coeff_Y_Z2, 4)}')

# B) Linear Model
X = df.drop('Y', axis=1) # Independant Variables
y = df['Y'] # Dependant Variables
N = len(X)
p = len(X.columns) + 1  # '+1' because LinearRegression adds an intercept term

model = LinearRegression()
model.fit(X, y)
a, b = model.coef_
c = model.intercept_
print(f'a = {round(a, 5)}, b = {round(b, 5)}, c = {round(c, 5)}')

# C) $95\%$ Confidence Interval
y_hat = model.predict(X)
err = y - y_hat # residual

RSS = err.T @ err # dot product, equivalent to sum(err**2)
S = np.sqrt(RSS / (N - p))

# M = matrix (20 x 3) with columns as [Z1, Z2, 1]
M = np.ones(shape=(N, p), dtype=float)
M[:, 0] = X.iloc[:, 0]
M[:, 1] = X.iloc[:, 1]

var_beta_hat = 1/(M.T @ M) * S**2 # Varience of beta hat

std_err_a = var_beta_hat[0, 0] ** 0.5
std_err_b = var_beta_hat[1, 1] ** 0.5
std_err_c = var_beta_hat[2, 2] ** 0.5

t = abs(stats.t.ppf((1-0.95)/2, N-p)) # 95% CI

# 95% CI Lower Bounds
ci_l_a = a - t * std_err_a
ci_l_b = b - t * std_err_b
ci_l_c = c - t * std_err_c
# 95% CI Upper Bounds
ci_u_a = a + t * std_err_a
ci_u_b = b + t * std_err_b
ci_u_c = c + t * std_err_c

print(f'a ∈ [{round(ci_l_a, 6)}, {round(ci_u_a, 6)}]' )
print(f'b ∈ [{round(ci_l_b, 6)}, {round(ci_u_b, 6)}]' )
print(f'c ∈ [{round(ci_l_c, 6)}, {round(ci_u_c, 6)}]' )

# D) $95\%$ Prediction Interval
# - Apartment size = 12 \* 100 ft2 and assessed value of 60 * 1000$
X_test = [[1200, 60000]]
y_test_hat = model.predict(X_test)[0]

pi_l_Y = y_test_hat - t * S
pi_u_Y = y_test_hat + t * S
print(f'lower value bound = {pi_l_Y}, upper value bound = {pi_u_Y}')

# E) Mean & varience of residuals
mean_resid = err.mean()
print(f'mean of residual = {mean_resid}')
var_resid = err.var()
print(f'varience of residual = {var_resid}')

# F) $R^2$ of the fit
y_diff = y - y.mean()
TSS = y_diff.T @ y_diff
Rsq = 1 - RSS/TSS
print(f'R^2 of the fit (calculated) = {Rsq}')


from sympy import *
import numpy as np
import math

cotezCoefs = [[1/2,     1/2],
             [1/6,      4/6,        1/6],
             [1/8,      3/8,        3/8,        1/8],
             [7/90,     32/90,      12/90,      32/90,      7/90],
             [19/288,   75/288,     50/288,     50/288,     75/288,19/288]]

#h^n - error term
convergeSpeeds = [2, 4, 4, 6, 6]

multiplier = [12, 90, 80/3, 945/8]

def CotezCoef(n):
    return cotezCoefs[n-1]
# M is sup(|f^(k)|) where f^(k) is the k-derivative of f, n is the degree of polynomial used
# k is the converge speed
# n < 5 pls ...
def Integral(f, M, rangeX, epsilon, n = 2):
    cotezCoef = CotezCoef(n)
    speed = convergeSpeeds[n]
    mult = multiplier[n-1]

    a,b = rangeX
    length = b-a

    #M/multiplier * length * h^k < eps
    h = math.pow(epsilon*mult/M/length, 1/speed)

    steps = (int) (length/h) + 1
    dx = length/steps/n

    integral = 0
    xn = a
    x = symbols('x')
    for step in range(steps):
        for t in range(n+1):
            integral += f.subs(x, xn)*cotezCoef[t]
            xn += dx
        xn -= dx

    return integral*dx*n

x = symbols('x')

############ WRITE YOUR FRICKING INPUT HERE ######################

f = 1/(x**2 + 1)
epsilon = 10**-12
M = 1000        # sup f^(n)

# khoảng tích phân
a = 0
b = 1

n = 4

############ WRITE YOUR FRICKING INPUT HERE ######################

############ PRINT IT OUT ...               ######################

integral = Integral(f, M, (a,b), epsilon, n)  # YOUR RESULT HERE

pi = 4*integral.evalf(30)                       # 30 CHỮ SỐ

print("pi = ", pi)

print("tích phân = ", integral.evalf(30))      # PRINT YOUR RESULT

############ PRINT IT OUT ...               ######################

Topic 3 - Function Approximation/20.Integral/Cotez.py


"""
 Purpose: The main purpose is to demonstrate how to find the running median, mode and
          mean over a sequence (list) of integers or reals or a mix of integers and reals.
          The secondary purpose, is to inspire Python programmers to explore some of
          the powerful packages (e.g. collections) available to the Python community and
          to learn more about list comprehension and lambda functions.
    Note:
       1. Much of the code here has been taken from code posted to the web (e.g. stackoverflow)
          by other Python programmers (e.g. Peter Otten)

  Author: V. Stokes (vs@it.uu.se)
 Version: 2013.03.06

"""
import numpy as np

#*******************************************************

from collections import deque,Counter
from bisect import insort, bisect_left
from itertools import islice

def RunningMode(seq,N,M):
    """
    Purpose: Find the mode for the points in a sliding window as it
             is moved from left (beginning of seq) to right (end of seq)
             by one point at a time.
     Inputs:
          seq -- list containing items for which a running mode (in a sliding window) is
                 to be calculated
            N -- length of sequence
            M -- number of items in window (window size) -- must be an integer > 1
     Otputs:
        modes -- list of modes with size M - N + 1
       Note:
         1. The mode is the value that appears most often in a set of data.
         2. In the case of ties it the last of the ties that is taken as the mode (this
            is not by definition).
    """
    # Load deque with first window of seq
    d = deque(seq[0:M])

    modes = [Counter(d).most_common(1)[0][0]]  # contains mode of first window

    # Now slide the window by one point to the right for each new position (each pass through
    # the loop). Stop when the item in the right end of the deque contains the last item in seq
    for item in islice(seq,M,N):
        old = d.popleft()                      # pop oldest from left
        d.append(item)                         # push newest in from right
        modes.append(Counter(d).most_common(1)[0][0])
    return modes

def RunningMedian(seq, M):
    """
     Purpose: Find the median for the points in a sliding window (odd number in size)
              as it is moved from left to right by one point at a time.
      Inputs:
            seq -- list containing items for which a running median (in a sliding window)
                   is to be calculated
              M -- number of items in window (window size) -- must be an integer > 1
      Otputs:
         medians -- list of medians with size N - M + 1
       Note:
         1. The median of a finite list of numbers is the "center" value when this list
            is sorted in ascending order.
         2. If M is an even number the two elements in the window that
            are close to the center are averaged to give the median (this
            is not by definition)
    """
    seq = iter(seq)
    s = []
    m = M // 2

    # Set up list s (to be sorted) and load deque with first window of seq
    s = [item for item in islice(seq,M)]
    d = deque(s)

    # Simple lambda function to handle even/odd window sizes
    median = lambda : s[m] if bool(M&1) else (s[m-1]+s[m])*0.5

    # Sort it in increasing order and extract the median ("center" of the sorted window)
    s.sort()
    medians = [median()]

    # Now slide the window by one point to the right for each new position (each pass through
    # the loop). Stop when the item in the right end of the deque contains the last item in seq
    for item in seq:
        old = d.popleft()          # pop oldest from left
        d.append(item)             # push newest in from right
        del s[bisect_left(s, old)] # locate insertion point and then remove old
        insort(s, item)            # insert newest such that new sort is not required
        medians.append(median())
    return medians

def RunningMean(seq,N,M):
    """
     Purpose: Find the mean for the points in a sliding window (fixed size)
              as it is moved from left to right by one point at a time.
      Inputs:
          seq -- list containing items for which a mean (in a sliding window) is
                 to be calculated (N items)
            N -- length of sequence
            M -- number of items in sliding window
      Otputs:
        means -- list of means with size N - M + 1

    """
    # Load deque (d) with first window of seq
    d = deque(seq[0:M])
    means = [np.mean(d)]             # contains mean of first window
    # Now slide the window by one point to the right for each new position (each pass through
    # the loop). Stop when the item in the right end of the deque contains the last item in seq
    for item in islice(seq,M,N):
        old = d.popleft()            # pop oldest from left
        d.append(item)               # push newest in from right
        means.append(np.mean(d))     # mean for current window
    return means


import numpy as np
import matplotlib.pyplot as plt

n = 10 # data points

# The following simple Python instructions define our x and y values (with 100 data points)
x = np.random.rand(n,1)
y = 5*x*x+np.random.randn(n,1) # y = 5x^2 + random noise

X = np.c_[np.ones((n,1)), x, x*x] # column wise array concatenation
print(X)

beta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)
print("y = " + str(beta[2,0]) + "*x^2 + " + str(beta[1,0]) + "*x + " + str(beta[0,0]))

nfit = 100
xplot = np.linspace(0.0,1.0, num=nfit)

Xplot = np.c_[np.ones((nfit,1)), xplot, xplot**2] # concatenate columns (as above)

ypredict = Xplot.dot(beta)

ytrue = 5*xplot*xplot

plt.plot(x, y ,'ro')
plt.plot(xplot, ytrue, label="$y_{\mathrm{Quadratic}}$")
plt.plot(xplot, ypredict, label="$y_{\mathrm{predict}}$")
plt.xlabel(r'$x$')
plt.ylabel(r'$y$')
plt.title(r'Quadratic Regression')
plt.legend()
plt.show()

from sklearn.linear_model import LinearRegression

clf2 = LinearRegression()
clf2.fit(X, y)
ysklearn = clf2.predict(Xplot)

print("ypredict = " + str(clf2.coef_[0, 2]) + "*x^2 + " + str(clf2.coef_[0, 1]) + "*x + " + str(clf2.coef_[0, 0]))
print("ysklearn = " + str(beta[2,0]) + "*x^2 + " + str(beta[1,0]) + "*x + " + str(beta[0,0]))
# note that the indices are reversed in the scikit-learn approach compared to what we did before:
# the shape is (1, n) instead of (n, 1)

plt.plot(x, y ,'ro')
plt.plot(xplot, ytrue, label="$y_{\mathrm{true}}$")
plt.plot(xplot, ypredict, label="$y_{\mathrm{predict}}$")
plt.plot(xplot, ysklearn, label="$y_{\mathrm{sklearn}}$")
plt.xlabel(r'$x$')
plt.ylabel(r'$y$')
plt.title(r'Quadratic Regression')
plt.legend()
plt.show()

err_predict = abs(ypredict[:, 0] - ytrue)/abs(ytrue) # the predicted y's have shape (n, 1)
err_sklearn = abs(ysklearn[:, 0] - ytrue)/abs(ytrue)

plt.plot(xplot, err_predict, label="$\epsilon_{\mathrm{predict}}$")
plt.plot(xplot, err_sklearn, label="$\epsilon_{\mathrm{sklearn}}$")
plt.xlabel(r'$x$')
plt.ylabel(r'$\epsilon_{\mathrm{rel}}$')
plt.axis([0, 1, 0, 2])
plt.title(r'Absolute relative error')
plt.legend()
plt.show()

plt.plot(xplot, abs(err_predict), label="$\epsilon_{\mathrm{predict}}$")
plt.plot(xplot, abs(err_sklearn), label="$\epsilon_{\mathrm{sklearn}}$")
plt.xlabel(r'$x$')
plt.ylabel(r'$\epsilon_{\mathrm{rel}}$')
plt.axis([0, 1.0, 0, 0.02])
plt.title(r'Absolute relative error')
plt.legend()
plt.show()

from sklearn.metrics import mean_squared_error

ypredict2 = X.dot(beta)
ysklearn2 = clf2.predict(X)

print("Mean squared error (ypredict):", mean_squared_error(y, ypredict2))
print("Mean squared error (ysklearn):", mean_squared_error(y, ysklearn2))

from sklearn.metrics import r2_score

print("R^2 score (ypredict):", r2_score(y, ypredict2))
print("R^2 score (ysklearn):", r2_score(y, ysklearn2))


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

###******************/PART II : Conjugate gradient method

"""QUESTION 3"""
"""Resolution of the linear system Ax=b by the conjugate gradient method where A is a symetric positive definite matrix and b a vector (without preconditioning)"""

def conjgrad(A,b,x) :
    r=b-np.dot(A,x)
    p=r
    rsOld= float(np.dot(np.transpose(r),r))
    tab_x=[x]
    iter=[0]
    for i in range(1,100001):
        Ap=np.dot(A,p)
        n = float(np.dot(np.transpose(p),Ap))
        alpha=rsOld/n
        x=x+ alpha*p
        r=r- alpha*Ap
        rsNew=float(np.dot(np.transpose(r),r))
        tab_x+=[x]
        iter+=[i]
        if m.sqrt(rsNew) < 1e-10 :
            break
        p=r+rsNew/rsOld*p
        rsOld=rsNew
    return x,tab_x,iter             #x is the solution
                                    #tab_x is a list regrouping the values of x at each iteration
                                    #iter is a list regrouping the iterations before arriving to the final solution.

"""QUESTION 4"""
"""Resolution of the linear system with preconditioning. Two preconditionners were implemented: using the Jacobi method and the incomplete cholesky method"""

##******************* Preconditionning with the ---Incomplete Cholesky Method-

def somme(T, i, j):
    return sum(np.fromiter((T[i][k] * T[j][k] for k in range(j)),float))

def facto_dense_inc(A):                   #this function is the implementation of the incomplete cholesky method seen in the Part I
    (n, n1) = A.shape
    T = np.zeros((n,n))
    for i in range(n):
        for j in range(i + 1):
            if A[i][j] != 0:
                if i==j:
                    T[i][j] = m.sqrt((A[i][i] - somme(T, i, j)))
                else:
                    T[i][j] = (A[i][j] - somme(T, i, j)) / T[j][j]
    return T

def preconditioner(A):                    #this function returns a matrix preconditionned with the incomplete cholesky method
    T= facto_dense_inc(A)
    return np.dot(T,np.transpose(T))

##******************* Preconditionning with the ---Jacobi Method-

def generate_jacobi_matrix(A):                       #M is a diagonal matrix whose coefficients are the inverse of the diagonal
                                                     #coefficients of the matrix A
    M=np.zeros((len(A),len(A)))
    for i in range(len(A)):
        if A[i][i]!=0:
            M[i][i]=1/A[i][i]
    return M

def PreconditionedConjgrad(A,b,x):
    tab_x=[x]
    iter=[0]
    r=b-np.dot(A,x)
    M=generate_jacobi_matrix(A)          # we choose the jacobi method but we can use the incomplete cholesky method
                                         # by puting M=np.linalg.inv(preconditionner(A)
    z=np.dot(M,r)
    p=z
    for k in range(1,100001):
        alpha=(np.dot(np.transpose(r),z))/(np.dot(np.transpose(p),np.dot(A,p)))
        x=x+ alpha*p
        tab_x+=[x]
        iter+=[k]
        r2=r-alpha*(np.dot(A,p))
        rsNew=float(np.dot(np.transpose(r2),r2))
        if m.sqrt(rsNew) < 1e-10 :
            break
        z2=np.dot(M,r2)
        beta=(np.dot(np.transpose(z2),r2))/(np.dot(np.transpose(z),r))
        p=z2+beta*p
        z=z2
        r=r2
    return x,tab_x,iter             #x is the solution
                                    #tab_x is a list regrouping the values of x at each iteration
                                    #iter is a list regrouping the iterations before arriving to the final solution.

def MdpGenerator(size) :          #this auxiliary function generates randomly, for a given size, a matrix A
                                  #symetric definite positive
                                  #T is an upper triangular matrix with positive diagonal coefficients
                                  #The diagonal coefficients of A are generated randomly between 5 and 10
    T=np.zeros((size,size))
    for i in range(size):
        for j in range(size):
            if j>=i:
                T[i,j] = random.randint(5,10)
            else:
                T[i,j] = 0
    A = np.dot(T,T.transpose())
    return A

##****************TEST: Conjugate gradient and Preconditionned Conjugate gradient methods

##Test 1:

class Test_gradient(unittest.TestCase):
    def test_conjgrad(self):                            # we test conjgrad with a matrix whose size is 2*2
        A= np.array([[4,1],[1,3]])
        b= np.array([1,2])
        x= np.array([2,1])                              # initialisation of x
        expected= np.array([0.0909,0.6363])             # expected is the exact solution
        result= conjgrad(A,b,x)                         # result is the solution given by conjgrad
        for i in range(len(A)):                         # we compare result and expected
                self.assertAlmostEqual(result[0][i],expected[i],3)
    def test_conjgrad_precond(self):                    # we test preconditionedconjgrad with a matrix whose size is 2*2
        A= np.array([[4,1],[1,3]])
        b= np.array([1,2])
        x= np.array([2,1])
        expected= np.array([0.0909,0.6363])
        result= PreconditionedConjgrad(A,b,x)
        print()
        for i in range(len(A)):                       #comparison between the solution with preconditionning and the exact solution
            self.assertAlmostEqual(result[0][i],expected[i],3)

##Test 2: Conjugate gradient VS Preconditionned Conjugate gradient

##We generate a curve representing the variations of the absolute error according to the number of iterations
##absolute error = the difference in magnitudes between the expected solution and the solution found by the algorithm

def test_conjgrad(size):
    R1=[]
    R2=[]
    A=MdpGenerator(size)
    Xs = np.random.rand(size,1)
    x1=np.zeros((size,1))
    x2=np.zeros((size,1))
    b=np.dot(A,Xs)
    s1=conjgrad(A,b,x1)
    s2=PreconditionedConjgrad(A,b,x2)
    for i in range(len(s1[1])):
        R1+=[abs(np.linalg.norm(s1[1][i])-np.linalg.norm(Xs))]
        R2+=[abs(np.linalg.norm(s2[1][i])-np.linalg.norm(Xs))]
    return R1,R2,s1[2]

def show_tests(size):
    t=test_conjgrad(size)
    plt.plot(t[2],t[1], label="PreConjgrad_Method")
    plt.plot(t[2],t[0], label="Conjgrad_Method")
    plt.xlabel('Iterations')
    plt.ylabel('Absolute Error')
    plt.legend()
    plt.show()

#show_tests(20)                              #we run the test with a matrix A whose size is 20*20

##Test 3: Conjugate gradient VS Preconditionned Conjugate gradient VS linalg.solve

def test_conjgrad_linalg(size):
                                    #In this test we generate the solutions given by np.linalg, conjgrad and
                                    #preconditionnedconjgrad for different size of matrix
                                    #we generate a graph with 3 curves which show how the absolute error varies for each method
    R1=[]
    R2=[]
    R3=[]
    iter=[]
    for i in range(25,size):                                          #we iterate on the matrix size
        iter+=[i]
        A=MdpGenerator(i)
        Xs = np.random.rand(i,1)                                      #Xs is the exact solution
        b=np.dot(A,Xs)

        xConjgrad=np.zeros((i,1))
        xConjgrad=conjgrad(A,b,xConjgrad)                             #we save the solution, for the system, given by conjgrad

        xPreconjgrad=np.zeros((i,1))
        xPreconjgrad=PreconditionedConjgrad(A,b,xPreconjgrad)         #we save the solution given by Preconditionnalconjgrad

        xLinalg=np.linalg.solve(A,b)                                  #we save the solution given by linalg.solve

        R1+=[abs(np.linalg.norm(xConjgrad[0])-np.linalg.norm(Xs))]    #we stock the absolute error for each solution x
        R2+=[abs(np.linalg.norm(xPreconjgrad[0])-np.linalg.norm(Xs))]
        R3+=[abs(np.linalg.norm(xLinalg)-np.linalg.norm(Xs))]
    plt.plot(iter, R1, label="Conjgrad Method")
    plt.plot(iter, R3, label="Linalg from numpy")
    plt.plot(iter, R2, label="PreConjgrad Method")
    plt.xlabel('Size of matrix')
    plt.ylabel('Absolute error')
    plt.title('Absolute error in the resolution of Ax=b')
    plt.legend()
    plt.show()

test_conjgrad_linalg(50)                                #we run the test for matrix whose size varies from 25*25 to 49*49

# unittests
if __name__ == '__main__':
    unittest.main(Test_gradient(), verbosity = 2)