"""
    shoot(ϕ,xspan,g₁,g₂,init)

Shooting method to solve a two-point boundary value problem with
ODE u'' = `ϕ`(x,u,u') for x in `xspan`, left boundary condition
`g₁`(u,u')=0, and right boundary condition `g₂`(u,u')=0. The
value `init` is an initial estimate for vector [u,u'] at x=a.

Returns vectors for the nodes, the solution u, and derivative u'.
"""
function shoot(ϕ,xspan,g₁,g₂,init,tol=1e-5)
    # ODE posed as a first-order equation in 2 variables.
    shootivp = (v,p,x) -> [ v[2]; ϕ(x,v[1],v[2]) ]

    # Evaluate the difference between computed and target values at x=b.
    function objective(s)
        IVP = ODEProblem(shootivp,s,float.(xspan))
        sol = solve(IVP,Tsit5(),abstol=tol/10,reltol=tol/10)
        x = sol.t;  y = sol;
        return [g₁(s...),g₂(y[end]...)]
    end

    # Find the unknown quantity at x=a by rootfinding.
    x = [];  y = [];   # these values will be overwritten
    s = levenberg(objective,init,xtol=tol)[:,end]

    # Use the stored last solution of the IVP.
    u,du_dx = eachrow(y)
    return x,u,du_dx
end

"""
    diffmat2(n,xspan)

Compute 2nd-order-accurate differentiation matrices on `n`+1 points
in the interval `xspan`. Returns a vector of nodes and the matrices
for the first and second derivatives.
"""
function diffmat2(n,xspan)
    a,b = xspan
    h = (b-a)/n
    x = [ a + i*h for i in 0:n ]   # nodes

    # Define most of Dₓ by its diagonals.
    dp = fill(0.5/h,n)        # superdiagonal
    dm = fill(-0.5/h,n)       # subdiagonal
    Dₓ = diagm(-1=>dm,1=>dp)

    # Fix first and last rows.
    Dₓ[1,1:3] = [-1.5,2,-0.5]/h
    Dₓ[n+1,n-1:n+1] = [0.5,-2,1.5]/h

    # Define most of Dₓₓ by its diagonals.
    d0 =  fill(-2/h^2,n+1)    # main diagonal
    dp =  ones(n)/h^2         # super- and subdiagonal
    Dₓₓ = diagm(-1=>dp,0=>d0,1=>dp)

    # Fix first and last rows.
    Dₓₓ[1,1:4] = [2,-5,4,-1]/h^2
    Dₓₓ[n+1,n-2:n+1] = [-1,4,-5,2]/h^2

    return x,Dₓ,Dₓₓ
end

"""
    diffcheb(n,xspan)

Compute Chebyshev differentiation matrices on `n`+1 points in the
interval `xspan`. Returns a vector of nodes and the matrices for the
first and second derivatives.
"""
function diffcheb(n,xspan)
    x = [ -cos( k*π/n ) for k in 0:n ]    # nodes in [-1,1]

    # Off-diagonal entries.
    c = [2; ones(n-1); 2];    # endpoint factors
    dij = (i,j) -> (-1)^(i+j)*c[i+1]/(c[j+1]*(x[i+1]-x[j+1]))
    Dₓ = [ dij(i,j) for i in 0:n, j in 0:n ]

    # Diagonal entries.
    Dₓ[isinf.(Dₓ)] .= 0         # fix divisions by zero on diagonal
    s = sum(Dₓ,dims=2)
    Dₓ -= diagm(s[:,1])         # "negative sum trick"

    # Transplant to [a,b].
    a,b = xspan
    x = @. a + (b-a)*(x+1)/2
    Dₓ = 2*Dₓ/(b-a)             # chain rule

    # Second derivative.
    Dₓₓ = Dₓ^2
    return x,Dₓ,Dₓₓ
end

"""
    bvplin(p,q,r,xspan,lval,rval,n)

Use finite differences to solve a linear bopundary value problem.
The ODE is u''+`p`(x)u'+`q`(x)u = `r`(x) on the interval `xspan`,
with endpoint function values given as `lval` and `rval`. There will
be `n`+1 equally spaced nodes, including the endpoints.

Returns vectors of the nodes and the solution values.
"""
function bvplin(p,q,r,xspan,lval,rval,n)
    x,Dₓ,Dₓₓ = diffmat2(n,xspan)

    P = diagm(p.(x))
    Q = diagm(q.(x))
    L = Dₓₓ + P*Dₓ + Q     # ODE expressed at the nodes

    # Replace first and last rows using boundary conditions.
    z = zeros(1,n)
    A = [ [1 z]; L[2:n,:]; [z 1] ]
    b = [ lval; r.(x[2:n]); rval ]

    # Solve the system.
    u = A\b
    return x,u
end

"""
    bvp(ϕ,xspan,lval,lder,rval,rder,init)

Finite differences to solve a two-point boundary value problem with
ODE u'' = `ϕ`(x,u,u') for x in `xspan`, left boundary condition
`g₁`(u,u')=0, and right boundary condition `g₂`(u,u')=0. The value
`init` is an initial estimate for the values of the solution u at
equally spaced values of x, which also sets the number of nodes.

Returns vectors for the nodes and the values of u.
"""
function bvp(ϕ,xspan,g₁,g₂,init)
    n = length(init) - 1
    x,Dₓ,Dₓₓ = diffmat2(n,xspan)
    h = x[2]-x[1]

    function residual(u)
        # Residual of the ODE at the nodes.
        du_dx = Dₓ*u                   # discrete u'
        d2u_dx2 = Dₓₓ*u                # discrete u''
        f = d2u_dx2 - ϕ.(x,u,du_dx)

        # Replace first and last values by boundary conditions.
        f[1] = g₁(u[1],du_dx[1])/h
        f[n+1] = g₂(u[n+1],du_dx[n+1])/h
        return f
    end

    u = levenberg(residual,init)
    return x,u[end]
end

"""
    fem(c,s,f,a,b,n)

Use a piecewise linear finite element method to solve a two-point
boundary value problem. The ODE is (`c`(x)u')' + `s`(x)u = `f`(x) on
the interval [`a`,`b`], and the boundary values are zero. The
discretization uses `n` equal subintervals.

Return vectors for the nodes and the values of u.
"""
function fem(c,s,f,a,b,n)
    # Define the grid.
    h = (b-a)/n
    x = @. a + h*(0:n)

    # Templates for the subinterval matrix and vector contributions.
    Ke = [1 -1; -1 1]
    Me = (1/6)*[2 1; 1 2]
    fe = (1/2)*[1; 1]

    # Evaluate coefficent functions and find average values.
    cval = c.(x);   cbar = (cval[1:n]+cval[2:n+1]) / 2;
    sval = s.(x);   sbar = (sval[1:n]+sval[2:n+1]) / 2;
    fval = f.(x);   fbar = (fval[1:n]+fval[2:n+1]) / 2;

    # Assemble global system, one interval at a time.
    K = zeros(n-1,n-1);  M = zeros(n-1,n-1);  f = zeros(n-1);
    K[1,1] = cbar[1]/h;  M[1,1] = sbar[1]*h/3;  f[1] = fbar[1]*h/2;
    K[n-1,n-1] = cbar[n]/h;  M[n-1,n-1] = sbar[n]*h/3;  f[n-1] = fbar[n]*h/2;
    for k in 2:n-1
        K[k-1:k,k-1:k] += (cbar[k]/h) * Ke
        M[k-1:k,k-1:k] += (sbar[k]*h) * Me
        f[k-1:k] += (fbar[k]*h) * fe
    end

    # Solve system for the interior values.
    u = (K+M) \ f
    u = [0; u; 0]      # put the boundary values into the result
    return x,u
end


export riffle, riffle!, cut, cut!

"""
    riffle!(list)

Perform an in-place riffle shuffle permutation on
`list` using the Gilbert–Shannon–Reeds model.
"""
function riffle!(list::Vector)
    n = length(list)
    if n < 2
        return
    end
    t = _simple_binomial(n)
    A = list[1:t]
    B = list[t+1:end]

    na = length(A)
    nb = length(B)

    # pointers into A and B lists
    a = 1
    b = 1

    for j = 1:n
        top = na - a + 1
        bot = (na - a + 1) + (nb - b + 1)
        p = top / bot   # prob we choose from A

        if rand() <= p
            list[j] = A[a]
            a += 1
        else
            list[j] = B[b]
            b += 1
        end
    end
    list
end

"""
    riffle(list)

Perform a riffle shuffle permutation on a copy of `list` using the
Gilbert–Shannon–Reeds model. The original `list` is unchanged.
"""
function riffle(list::Vector)
    tmp = deepcopy(list)
    riffle!(tmp)
end

"""
    _simple_binomial(n::Int)

Return a random B(n,0.5) random value.
"""
function _simple_binomial(n::Int)
    return sum(rand() > 0.5 for _ = 1:n)
end

"""
    cut!(list,idx)

Cut the deck `list` by moving cards `1` through `idx`
(inclusive) to the back of the deck so the first card now was formerly
the one at position `idx+1`.

    cut!(list)

Cut the deck at a random location. If the deck has `n`
cards, then the cut location is given by the binomial random
variable `B(n,1/2)`.
"""
function cut!(list::Vector, idx::Int)
    n = length(list)
    @assert 0 <= idx <= n "Cut index must be between 0 and $n [got $idx]"

    if idx == 0 || idx == n
        return
    end

    A = list[1:idx]
    B = list[idx+1:end]

    na = length(A)
    nb = length(B)

    for j = 1:nb
        list[j] = B[j]
    end

    for j = 1:na
        list[nb+j] = A[j]
    end
    return list
end

function cut!(list::Vector)
    idx = _simple_binomial(length(list))
    cut!(list, idx)
end

"""
    cut(list,idx)
    cut(list)

Use `cut!` to cut a copy of the deck `list`
leaving the original `list` unchanged.
"""
function cut(list::Vector, idx::Int)
    tmp = deepcopy(list)
    cut!(tmp, idx)
end
function cut(list::Vector)
    tmp = deepcopy(list)
    cut!(tmp)
end

import Random: shuffle!, shuffle
export shuffle!, shuffle


using NPZ
using Distributions
using LinearAlgebra

#irit

"""
    generate_gaussian_data(N::Int64, D::Int64, K::Int64,MixtureVar::Number)

Generate `N` observations, generated from `K` `D` dimensions Gaussians, with the Gaussian means sampled from a `Normal` distribution with mean `0` and `MixtureVar` variance.

Returns `(Samples, Labels, Clusters_means, Clusters_cov)`

# Example
```julia
julia> x,y,clusters = generate_gaussian_data(10000,2,6,100.0)
[3644, 2880, 119, 154, 33, 3170]
...
```
"""
function generate_gaussian_data(N::Int64, D::Int64, K::Int64,MixtureVar::Number)
	x = randn(Float32,D,N)
	tpi = rand(Dirichlet(ones(K)))
	tzn = rand(Multinomial(N,tpi))
	tz = zeros(Float32,N)

	tmean = zeros(Float32,D,K)
	tcov = zeros(Float32,D,D,K)

	ind = 1
	println(tzn)
	for i=1:length(tzn)
		indices = ind:ind+tzn[i]-1
		tz[indices] .= i
		tmean[:,i] .= rand(MvNormal(zeros(Float32,D), MixtureVar*Matrix{Float32}(I, D, D)))
		tcov[:,:,i] .= rand(InverseWishart(D+2, Matrix{Float32}(I, D, D)))
		#println(minimum(eigvals(tcov[:,:,i]))>0)
		d = MvNormal(tmean[:,i], tcov[:,:,i])
		for j=indices
			x[:,j] = rand(d)
		end
		ind += tzn[i]
	end
	return x, tz, tmean, tcov
end

"""
     generate_mnmm_data(N::Int64, D::Int64, K::Int64, trials::Int64)

Generate `N` observations, generated from `K` `D` features Multinomial vectors, with `trials` draws from each vector.

Returns `(Samples, Labels, Vectors)`

# Example
```julia
julia> generate_mnmm_data(10000, 10, 5, 100)
...
```
"""
function generate_mnmm_data(N::Int64, D::Int64, K::Int64, trials::Int64)
	clusters = zeros(Float64,D,K)
	x = zeros(Float32,D,N)
	labels = rand(1:K,(N,))
	for i=1:K
		alphas = rand(1:20,(D,))
		alphas[i] = rand(30:100)
		clusters[:,i] = rand(Dirichlet(alphas))
	end
	for i=1:N
		x[:,i] = rand(Multinomial(trials,clusters[:,labels[i]]))
	end
	return x, labels, clusters
end


myParse(x) = parse.(Int, x)
crabPositions = myParse.(split.(readline("input.txt"), ','))

#! Part 1

using Statistics

function findLeastFuelPositionPt1(positions)
    return round(median(positions))
end

totalFuelCostPt1(positions, destination) = sum([abs(x-destination) for x in positions])

print(totalFuelCostPt1(crabPositions, findLeastFuelPositionPt1(crabPositions)))

#! Part 2

function findLeastFuelCostPt2(positions)
    fuel = Inf
    approxPos = Int(round(mean(positions)))
    𝛜 = 3
    for pos in approxPos-𝛜:approxPos+𝛜
        dists = [abs(x - pos) for x in positions]
        f = sum([0.5*(d^2 + d) for d in dists])

        if fuel > f
            fuel = f
        end
    end

    return fuel
end

print(findLeastFuelCostPt2(crabPositions))

# Intuition for the least fuel Position functions: https://www.johnmyleswhite.com/notebook/2013/03/22/modes-medians-and-means-an-unifying-perspective/


# p7.m - accuracy of periodic spectral differentiation

# Compute derivatives for various values of N:
Nmax = 50; allN = 6:2:Nmax; E = zeros(4,length(allN));
for N = 6:2:Nmax
    h = 2*pi/N; x = h*(1:N);
    column = [0; @. .5*(-1)^(1:N-1)*cot((1:N-1)*h/2)];
    D = toeplitz(column,column[[1;N:-1:2]]);
    v = @. abs(sin(x))^3;                     # 3rd deriv in BV
    vprime = @. 3*sin(x)*cos(x)*abs(sin(x));
    j = round(Int,N/2-2);
    E[1,j] = norm(D*v-vprime,Inf);
    v = @. exp(-sin(x/2)^(-2));               # C-infinity
    vprime = @. .5*v*sin(x)/sin(x/2)^4;
    E[2,j] = norm(D*v-vprime,Inf);
    v = @. 1/(1+sin(x/2)^2);                 # analytic in a strip
    vprime = @. -sin(x/2)*cos(x/2)*v^2;
    E[3,j] = norm(D*v-vprime,Inf);
    v = sin.(10*x); vprime = 10*cos.(10*x);   # band-limited
    E[4,j] = norm(D*v-vprime,Inf);
end

# Plot results:
titles = [L"|\sin(x)|^3",L"\exp(-\sin^{-2}(x/2))",L"1/(1+\sin^2(x/2))",L"\sin(10x)"]; clf();
for iplot = 1:4
    subplot(2,2,iplot);
    semilogy(allN,E[iplot,:],".-",markersize=6);
    axis([0,Nmax,1e-16,1e3]); grid(true);
    xticks(0:10:Nmax); yticks(10.0.^(-15:5:0));
    title(titles[iplot]);
    iplot > 2 ? xlabel("N") : nothing;
    iplot%2 > 0? ylabel("error") : nothing;
end


using IntervalArithmetic
using Test

A = [ 2..4   -2..1
     -1..2    2..4]

b = [-2..2
     -2..2]

@testset "Linear algebra with intervals tests" begin

    @test A * b == [-12..12
                    -12..12]

    # Example from Moore et al., Introduction to Interval Analysis (2009), pg. 88:

    @test A \ b == [-5..5
                    -4..4]

end


A = randn(3,3)

x = fill(1.0, (3))
b = A*x

l,u,p = lu(A)

norm(l*u - A)

norm(l*u - A[p,:])

l,u,p = lu(A, Val{false})

norm(l*u - A)

Alu = lufact(A)

Alu[:P]

Alu[:L]

Alu[:U]

# PA = LU
# A = P'LU
# P'LUx = b
# LUx = Pb
# Ux = L\Pb
# x = U\L\Pb
Alu[:U]\(Alu[:L]\(Alu[:P]b))

Alu\b

det(A)

det(Alu)

Atall = randn(3, 2)

Aqr = qrfact(Atall)

Aqr[:Q]

Aqr[:R]

Aqr[:Q]*ones(2)

Aqr[:Q]*ones(3)

Aqr[:Q]*ones(4)

Aqr[:Q][1]

Aqr\b

Atall\b

v = randn(3)
# Tomar el producto exterio de un vector consigo mismo nos da una matriz singular
singmatriz = v * v'

Aqrp = qrfact(singmatriz,Val{true})

Aqrp\b

Asym = A + A'
AsymEig = eigfact(Asym)

AsymEig[:values]

AsymEig[:vectors]

inv(AsymEig)*Asym

eig(Asym)

Asvd = svdfact(A[:,1:2])

Asvd\b

A

Diagonal(diag(A))

Diagonal(A)

LowerTriangular(tril(A))

LowerTriangular(A)

Symmetric(Asym)

SymTridiagonal(diag(Asym),diag(Asym,1))

n = 1000;
A = randn(n,n);

Asym = A + A';

Asym_noisy = copy(Asym); Asym_noisy[1,2] += 5eps();

println("Is Asym symmetric? ", issymmetric(Asym))
println("Is Asym_noisy symmetric? ", issymmetric(Asym_noisy))

@time eigvals(Asym);

@time eigvals(Asym_noisy);

@time eigvals(Symmetric(Asym_noisy));

n = 1_000_000;
A = SymTridiagonal(randn(n), randn(n-1));
@time eigmax(A)

rand(1:10,3,3)*rand(1:10,3,3)

1//2

Ar = convert(Matrix{Rational{BigInt}}, rand(1:10,3,3))/10

x = fill(1, (3))
b = Ar*x

Ar\b

lufact(Ar)

λ1,λ2,λ3 = 1//1,1//2,1//4
v1,v2,v3 = [1,0,0],[1,1,0],[1,1,1]
V,Λ = [v1 v2 v3], Diagonal([λ1,λ2,λ3])
A = V*Λ/V

es-es/intro-to-julia-ES/plutos/11.Factorizaciones y otras diversiones.jl


using ImplicitEquations, Plots

include("project2.jl")
include("helpers.jl")

probname = "simple1"
n_seeds = 3
seed = 50
num_iter = 30

prob = PROBS[probname]

x0 = prob.x0()

# TODO: Contour plot for Rosenbrock function:
# use range of (-3.0, 3.0) for x1, x2
# z(x1, x2) = f([x1, x2])
x1 = range(-3.0,stop=3.0,length=50)
x2 = range(-3.0,stop=3.0,length=50)
funcn = zeros(50,50)
# c1 = zeros(50,50)
# c2 = zeros(50,50)
for i = 1:50
    for j = 1:50
        global funcn
        funcn[i,j] = prob.f([x1[i], x2[j]])
        # c1[i,j] = x1[i] + x2[j]^2 - 1
        # c2[i,j] = -x1[i] -x2[j]
    end
end

c1(x, y) = x + y^2 - 1
c2(x, y) = -x - y
p1 = contour(x1, x2, funcn, colorbar = false,
    c = cgrad(:viridis, rev = true), legend = false, xlims = (-3, 3), ylims = (-3, 3),
    xlabel = "x₁", ylabel = "x₂", aspectratio = :equal)
plot!(c2 ≤ 0)
# contour!(x1, x2, c1, levels = [0], c = :red, fillrange(0,0))
# contour!(x1, x2, c2, levels = [0], c = :black)
# contour!(x1, x2, c1, levels = [0], c = :gray)
# plot!(xhist_nseeds[:,1,1], xhist_nseeds[:,2,1])
# plot!(xhist_nseeds[:,1,2], xhist_nseeds[:,2,2])
# plot!(xhist_nseeds[:,1,3], xhist_nseeds[:,2,3])
# savefig(p1, "rosenbrock_trajectories.pdf")

# Plot x_history on top of function contour

# # CONVERGENCE PLOTS
# fhist_nseeds = zeros(num_iter, n_seeds)
#
# for i = 1:n_seeds
#     Random.seed!(seed+i)
#     _, fhist_nseeds[:,i] = optimize_plot_data(prob.f, prob.g, prob.x0(), prob.n, probname)
# end
#
# p = plot(1:num_iter, fhist_nseeds, xlabel = "Iterations", ylabel = "Function value", title = "Rosenbrock Convergence Plot", label = ["Seed 1" "Seed 2" "Seed 3"], lw = 2)
# savefig(p, "rosenbrock_convergence.pdf")


using NewtonsMethod
using Test

@testset "NewtonsMethod.jl" begin
    # test 1: make sure it is working
        g(x)=(x-1)^3
        g_prime(x)=3*(x-1)^2
        iv = 1.1
        sol = newtonroot(g, g_prime, iv)
        @test sol≈ 1.0000001545213348
    # test 2: make sure that the second dispach works well
        solu = newtonroot(g, iv)
    @test solu ≈ 1.0000001545213348

    # test 3: make sure tolerance works
    solu = newtonroot(g, g_prime, iv, tolerance = 1)
    solut= newtonroot(g, iv, tolerance =  1)
    @test !( solu  ≈ 1.0000001545213348)
    @test !( solut ≈ 1.00000013565)

    # test 4: make sure maxiter works
    soluti  = newtonroot(g, g_prime, iv,maxiter= 5 )
    solutio = newtonroot(g, iv, maxiter= 5 )
    @test soluti  == nothing
    @test solutio == nothing
    #test 5: Non-Convergence
    h(x)=x^2+2
    h_prime(x)=2x
    @test newtonroot(h, h_prime, iv)== nothing
    @test newtonroot(h,  iv)== nothing
    #test BiFloat
    iv=BigFloat(3)
    @test newtonroot(g,g_prime, iv)≈ 1.0000001808754536763325638480
    @test newtonroot(g, iv)≈ 1.0000001808754536763325638480
    # test Int
    iv=3
    @test newtonroot(g,g_prime, iv)≈ 1.0000001808754536763325638480
    @test newtonroot(g, iv)≈ 1.0000001808754536763325638480
    # test complicated function
    k(x)=exp(x)-3+sin(x)
    k_prime(x)=exp(x)+cos(x)
    @test newtonroot(k,k_prime, iv)≈ 0.8194425797965913
    @test newtonroot(k, iv)≈ 0.8194425797965913
end


using Plots

function ackley(x, a=20, b=0.2, c=2π)
    d = length(x);
    return -a * exp(-b * sqrt(sum(x.^2)/d)) -
                exp(sum(cos(c*xi) for xi in x)/d) + a + exp(1);
end

# plot

contour(-30:0.1:30, -30:0.1:30, (x, y)->ackley([x,y]))
heatmap(-30:0.1:30, -30:0.1:30, (x, y)->ackley([x,y]))


function onedElliptic(nElements=40,order=2)
#  Solves a linear elliptic PDE in 1D

  f(x)=x           # right-hand side function definition.

  x,eConn,iU,iC = onedMesh( [0.0,1.0], order, nElements )
  nNodes = size(x,1)

  ide = [1:nNodes;]     # index into unknowns  (set b.c. later in 1D)

  r,w = onedQuadratureRule(5)
  o   = ones(size(r))

  nElDOF = order+1
  nEntries = nElements*nElDOF^2
  II  = zeros(Int64,nEntries) #Array{Int32,1}(undef,nEntries)     # zeros(Int32,nEntries)
  JJ  = zeros(Int64,nEntries) #Array{Int32,1}(undef,nEntries)     # zeros(Int32,nEntries)
  AA  = zeros(Float64,nEntries) #Array{Float64,1}(undef,nEntries)   # zeros(Float64,nEntries)
  b   = zeros(Float64,nNodes) #Array{Float64,1}(undef,nNodes)     # zeros(Float64,nNodes)

  index = 0
  for k=1:nElements
    nLocal = eConn[k,:]
    xLocal = view(x,nLocal)

    xg,wg,phi,p_x,p_xx = onedShape( xLocal, r, w )
    fg     = f(xg)             # forcing function evaluated at quadrature points

    ALoc   = onedBilinear(  o, p_x, p_x, wg )
    bLoc   = onedLinForm(  fg,      phi, wg )

    lDOF = ide[nLocal]
    for nt = 1:nElDOF
      for nu = 1:nElDOF
        index = index + 1
        II[index]   = lDOF[nu]
        JJ[index]   = lDOF[nt]
        AA[index]   = ALoc[nt,nu]
      end
      b[lDOF[nt]] = b[lDOF[nt]] + bLoc[nt]
    end
  end

  A = sparse(II,JJ,AA)

  u = A[iU,iU]\b[iU]

  #  Calculate the exact solution for unit test
  xi = x[iU];

  uExact = -(xi.^3-xi)/6

  return norm(u-uExact)
# using Gaston
# figure(1)
# Gaston.set_terminal("x11");
# Gaston.closeall();
# c = Gaston.CurveConf();
# c.color = "blue";
# c.legend = "FEM";
# Gaston.addcoords(xi,u,c);

# figure(2)
# Gaston.set_terminal("x11");
# c = Gaston.CurveConf();
# c.color = "red";
# c.legend = "Exact";
# c.marker = "fdmd";
# c.plotstyle = "linespoints";
# Gaston.addcoords(xi,-(xi.^3-xi)/6,c);
# Gaston.llplot();

# set_filename("test.png")
# printfigure("png")

#  using PyPlot
#  plot(xi,u,"r",label="FEM solution")
#  plot(xi,-(xi.^3-xi)/6,"go",label="Exact at nodes")
#  legend()
end


"""
    panas(f,c1,c2,a,b,c,m,n)
berisi program untuk mencari solusi
persamaan differensial parsial yaitu persamaan panas. Program ini secara default berisi
8 masukan, yaitu fungsi nilai awal `f(x)`, koefisien nilai batas `c1` dan `c2`, nilai batas solusi
`a` dan `b`, koefisien persamaan gelombang `c`, dan jumlah grid solusi x dan t yaitu `m` dan
`n`.
# Example
```jl
julia> f(x) = 4*x - 4*x.^2;

julia> U,R = panas(f,0,0,1,0.2,1,6,11);

julia> R
0.4999999999999999

julia> U
11×6 Adjoint{Float64,Array{Float64,2}}:
 0.0  0.64       0.96      0.96      0.64       0.0
 0.0  0.48       0.8       0.8       0.48       0.0
 0.0  0.4        0.64      0.64      0.4        0.0
 0.0  0.32       0.52      0.52      0.32       0.0
 ⋮                                              ⋮
 0.0  0.1375     0.2225    0.2225    0.1375     0.0
 0.0  0.11125    0.18      0.18      0.11125    0.0
 0.0  0.09       0.145625  0.145625  0.09       0.0
 0.0  0.0728125  0.117813  0.117813  0.0728125  0.0
```
return solusi persamaan panas `U` dan rasio `R`.
"""
function panas(f,c1,c2,a,b,c,m,n)
  h = a/(m-1);
  k = b/(n-1);
  r = c^2*k/h^2;
  U = zeros(m,n);
  U[1,:] .= c1;
  U[m,:] .= c2;
  U[2:m-1,1] = f.(h:h:(m-2)*h)';
  for j = 2:n
    for i = 2:m-1
      U[i,j]=(1-2*r)*U[i,j-1]+ r*(U[i-1,j-1]+U[i+1,j-1]);
    end
  end
  U=U';
  return U,r
end


"""
   Implementing the ideas in

      @ARTICLE{1993JCoPh.104..211D,
          author = {{Dang-Vu}, H. and {Delcarte}, C.},
           title = "{An Accurate Solution of the Poisson Equation by the Chebyshev Collocation Method}",
         journal = {Journal of Computational Physics},
            year = 1993,
           month = jan,
          volume = {104},
          number = {1},
           pages = {211-220},
             doi = {10.1006/jcph.1993.1021},
          adsurl = {https://ui.adsabs.harvard.edu/abs/1993JCoPh.104..211D},
         adsnote = {Provided by the SAO/NASA Astrophysics Data System}
      }
"""
module ChebyshevSpec

include("CustomTypes.jl")
using .CustomTypes
using SparseArrays

export mat_invD1, mat_invD2

function mat_invD1(nx::myI)
   X = Vector{myF}()
   Y = Vector{myF}()
   V = Vector{myF}()

   c(n) = (n==0)      ? 2.0 : 1.0
   e(n) = (n<=(nx-1)) ? 1.0 : 0.0

   for i=2:(nx-1)
      n=i-1
      push!(X,i)
      push!(Y,i-1)
      push!(V,tomyF(c(n-1)*0.5/n))

      push!(X,i)
      push!(Y,i+1)
      push!(V,tomyF(-e(n+1)*0.5/n))
   end

   return dropzeros(sparse(X,Y,V))
end

function mat_invD2(nx::myI)
   X = Vector{myF}()
   Y = Vector{myF}()
   V = Vector{myF}()

   c(n) = (n==0)      ? 2.0 : 1.0
   e(n) = (n<=(nx-1)) ? 1.0 : 0.0

   for i=3:(nx-2)
      n=i-1
      push!(X,i)
      push!(Y,i-2)
      push!(V,tomyF(c(n-2)*0.25/(n*(n-1))))

      push!(X,i)
      push!(Y,i)
      push!(V,tomyF(-e(n+2)*0.5/((n+1)*(n-1))))

      push!(X,i)
      push!(Y,i+2)
      push!(V,tomyF(e(n+4)*0.25/(n*(n+1))))
   end
   n=nx-1
   push!(X,nx-1)
   push!(Y,nx-3)
   push!(V,tomyF(c(n-2)*0.25/(n*(n-1))))

   n=nx
   push!(X,nx)
   push!(Y,nx-2)
   push!(V,tomyF(c(n-2)*0.25/(n*(n-1))))

   return dropzeros(sparse(X,Y,V))
end

end


function KL(p::AbstractMvNormal, q::AbstractMvNormal)
    # This is the generic implementation for AbstractMvNormal, you might need to specialize for your type
    length(p) == length(q) ||
        throw(DimensionMismatch("Distributions p and q have different dimensions $(length(p)) and $(length(q))"))
    Σp, Σq = cov.((p, q))
    Δμ = mean(p) - mean(q)
    0.5 * (tr(Σq \ Σp) + dot(Δμ, Σq \ Δμ) - length(p) + logdet(Σq) - logdet(Σp))
end

function KL(p::MvNormal, q::MvNormal)
    # We use p.Σ and q.Σ to take the advantage that they are defined as PDMats objects
    length(p) == length(q) ||
        throw(DimensionMismatch("Distributions p and q have different dimensions $(length(p)) and $(length(q))"))
    0.5 * (tr(q.Σ \ p.Σ) + invquad(q.Σ, mean(p) - mean(q)) - length(p) + logdet(q.Σ) - logdet(p.Σ))
end


function demoChainIndepRational()

    println("In this demo we consider the directed graph A->B->C")
    println("The chain is such that A and B are dependent, B and C are dependent, yet A and C are independent.")
    println("To show this we will define the table entries using rationals. Julia will then use rational arithmetic for the subsequent calculations.")

    A,B,C=1,2,3
    pA=PotArray(A,[3//5, 2//5])
    pBgA=PotArray([B A],[1//4 15//40; 1//12 1//8; 2//3 1//2])
    pCgB=PotArray([C B],[1//3 1//2 15//40; 2//3 1//2 5//8])
    pABC=pCgB*pBgA*pA
    pAC=sum(pABC,B)
    pA=sum(pAC,C)
    pC=sum(pAC,A)

    println("pAC-pA*pC=")
    println(pAC-pA*pC)
    println("This shows that A and C are independent.\n")

    println("Contrast this with the same calculation using floating point arithmetic:")

    pA=PotArray(A,[3/5, 2/5])
    pBgA=PotArray([B A],[1/4 15/40; 1/12 1/8; 2/3 1/2])
    pCgB=PotArray([C B],[1/3 1/2 15/40; 2/3 1/2 5/8])
    pABC=pCgB*pBgA*pA
    pAC=sum(pABC,B)
    pA=sum(pAC,C)
    pC=sum(pAC,A)

    println("pAC-pA*pC=")
    println(pAC-pA*pC)
    println("The loss of numerical accuracy means that we cannot confidently declare A and C are independent, even though they actually are.")

end


### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 0dcfd858-f867-11ea-301c-c3ca0a224117
using Plots

# ╔═╡ 1e058ba2-ec94-11ea-09af-7f9f9cc3a233
using PlutoUI

# ╔═╡ 13f6ccac-7ce0-48d7-a0ef-e83489625e1d
using Statistics

# ╔═╡ 78763674-f8fe-11ea-349c-d997f30ac1f6
using LinearAlgebra

# ╔═╡ 35e83a04-f864-11ea-0a8e-9ddf6eec02f3
using Images, ColorSchemes

# ╔═╡ 16887070-f891-11ea-2db3-47b91930e728
using ForwardDiff

# ╔═╡ 3b9941ac-6043-4dc6-850f-4c7b3ae9d9a7
md"""
# Finding structure in data
"""

# ╔═╡ 7365084a-1f37-4897-bca4-fc5855c5ee4e
md"""
In this notebook we will look at one way to analyse, understand and simplify data. We will look at the ideas and intuition behind **principal component analysis**, a technique to find the most important directions and dimensions in a data set. This is very closely related to the SVD (singular-value decomposition) that we saw on Tuesday, and is one way to find and exploit structure in data.
"""

# ╔═╡ ed7ff6b2-f863-11ea-1a59-eb242a8674e3
md"## Flags"

# ╔═╡ fed5845e-f863-11ea-2f95-c331d3c62647
md"Let's start off by recalling the idea of a **multiplication table**, also called an **outer product**:"

# ╔═╡ 0e1a6d80-f864-11ea-074a-5f7890180114
outer(v, w) = [x * y for x in v, y in w]

# ╔═╡ 2e497e30-f895-11ea-09f1-d7f2c1f61193
outer(1:10, 1:10)

# ╔═╡ ab3d55cc-f905-11ea-2f22-5398f3aca803
md"Some flags are a simple example of this:"

# ╔═╡ 13b6c108-f864-11ea-2447-2b0741f15c7b
flag = outer([1, 0.1, 2], ones(6))

# ╔═╡ e66b30a6-f914-11ea-2c0f-35282d45a30a
ones(6)

# ╔═╡ 71d1b12e-f895-11ea-39df-f5c18a7766c3
flag2 = outer([1, 0.1, 2], [1, 1, 1, 3, 3, 3])

# ╔═╡ cdbe1d8e-f905-11ea-3884-efeeef386dda
md"## Rank of a matrix"

# ╔═╡ d9aa9af0-f865-11ea-379e-f16b452bd94c
md"A matrix that can be written exactly as a single multiplication table is called a **rank-1** matrix. If it can be written as the *sum* of *two* multiplication tables, it is called **rank 2**, etc."

# ╔═╡ 2e8ae92a-f867-11ea-0219-1bdd9627c1ea
md"Let's see what a general rank-1 matrix looks like:"

# ╔═╡ 38adc490-f867-11ea-1de5-3b633aff7c97
image = outer([1; 0.4; rand(50)], rand(500));

# ╔═╡ 9cf23f9a-f864-11ea-3a08-af448aceefd8
md"Now what happens if we add a little bit of **noise**, i.e. randomness?"

# ╔═╡ a5b62530-f864-11ea-21e8-71ccfed487f8
noisy_image = image .+ 0.03 .* randn.();

# ╔═╡ c41df86c-f865-11ea-1253-4942bbdbe9d2
md"""The noisy image now has a rank larger than 1. But visually we can see that it is "close to" the original rank-1 matrix.

Given this matrix, how can we discover that it is close to a structured, rank-1 matrix? We would like to be able to find this out and say that the matrix is close to a simple one."""

# ╔═╡ 7fca33ac-f864-11ea-2a8b-933eb382c172
md"## Images as data"

# ╔═╡ 283f5da4-f866-11ea-27d4-957ca2551b92
md"Images are just one example of the many different types of data we come across in the world.

Let's treat the image as a **data matrix**, where each column of the image / matrix is a **vector** representing one observation of data. (In data science it is often the rows that correspond to observations.)

Let's try to visualize those vectors, taking just the first two rows of the image as the $x$ and $y$ coordinates of our data points:"

# ╔═╡ 54977286-f908-11ea-166d-d1df33f38454
image[1:2, 1:20]

# ╔═╡ 7b4e90b4-f866-11ea-26b3-95efde6c650b
begin
	xx = image[1, :]
	yy = image[2, :]
end

# ╔═╡ f574ad7c-f866-11ea-0efa-d9d0602aa63b
md"## Plotting data"

# ╔═╡ 8775b3fe-f866-11ea-3e6f-9732e39a3525
md"We would like to **visualise** this data. There are various plotting packages that we could use. We will use `Plots.jl`:"

# ╔═╡ 7bacf44e-f896-11ea-38be-2b16ae7ca99f
scatter(xx, yy, alpha=0.5, framestyle=:origin, label="original image", leg=:topleft,
		xlabel="x values", ylabel="y values")

# ╔═╡ 1147cbda-f867-11ea-08fa-ef6ed2ae1e93
begin
	scatter(xx, yy,
			leg=:topleft, label="rank-1", ms=3, alpha=0.3,
			size=(500, 400), m=:square, c=:red,
			framestyle=:origin)

	xs = noisy_image[1, :]
	ys = noisy_image[2, :]

	scatter!(xs, ys, label="noisy", m=:., alpha=0.3, c=:blue)
end

# ╔═╡ 8a611e36-f867-11ea-121f-317b7c145fe3
md"We see that the exact rank-1 matrix has columns that **lie along a line**, since they are just multiples of one another.

The approximate rank-1 matrix has columns that **lie *close to* a line**!"

# ╔═╡ f7371934-f867-11ea-3b53-d1566684585c
md"So, given the data, we want to look at it do see if it lies close to a line or not.
How can we do so in an *automatic* way?
"

# ╔═╡ 119dc35c-ec94-11ea-190c-23a750fbe7f4
md"The data are given by pairs $(x_i, y_i)$. We can highlight the $i$th data point in the set to see how the data are spread out. Remember that this is literally scanning through the columns of the image!"

# ╔═╡ 2043d4e6-ec94-11ea-1e1a-c75742eafe71
@bind i Slider(1:length(xs), show_value=true)

# ╔═╡ 2a705962-ec94-11ea-1181-2f001ccf472f
begin
	scatter(xs, ys, ms=4, alpha=0.5, ratio=1, leg=false, size=(500, 400),
			framestyle=:origin)
	scatter!([xs[i]], [ys[i]], ms=8, alpha=0.8, c=:red)
end

# ╔═╡ 987c1f2e-f868-11ea-1125-0d8c02843ae4
md"## Size of the data cloud"

# ╔═╡ 9e78b048-f868-11ea-192e-d903265d1eb5
md"Looking at this cloud of data points, a natural thing to do is to try to *measure* it: How wide is it, and how tall?"

# ╔═╡ 24df1f32-ec90-11ea-1f6d-03c1bfa5df8e
md"""For example, let's think about calculating the width of the cloud, i.e. the range of possible $x$-values of the data. For this, the $y$-values are actually irrelevant.

A first step in analysing data is often to **centre** it data around 0 by subtracting the mean. This is sometimes called "de-meaning":
"""

# ╔═╡ aec46a9b-f743-4cbd-97a7-3ef3cac78b12
begin
	xs_centered = xs .- mean(xs)
	ys_centered = ys .- mean(ys)
end

# ╔═╡ 1b8c743e-ec90-11ea-10aa-e3b94f768f82
scatter(xs_centered, ys_centered, ms=5, alpha=0.5, ratio=1, leg=false, framestyle=:origin)

# ╔═╡ f5358ce4-f86a-11ea-2989-b1f37be89183
md"A common way (but not the only way!) to calculate the width of a data set is the **standard deviation**, i.e. the square distance of the centered data from the origin. We will do this *separately* for $x$ and $y$ by *projecting* onto that direction -- i.e. effectively ignoring the other coordinate:"

# ╔═╡ 870d3efa-f8fc-11ea-1593-1552511dcf86
begin
	scatter(xs_centered, ys_centered, ms=5, alpha=0.5, ratio=1, leg=false, framestyle=:origin)

	scatter!(xs_centered, zeros(size(xs_centered)), ms=5, alpha=0.1, ratio=1, leg=false, framestyle=:origin)

	for i in 1:length(xs_centered)
		plot!([(xs_centered[i], ys_centered[i]), (xs_centered[i], 0)], ls=:dash, c=:black, alpha=0.1)
	end

	plot!()

end

# ╔═╡ 03ab44c0-f8fd-11ea-2243-1f3580f98a65
md"This gives the following approximate extents (standard deviations) of the cloud:"

# ╔═╡ 2c3721da-f86b-11ea-36cf-3fe4c6622dc6
begin
	width = sqrt(mean(xs_centered.^2))
	height = sqrt(mean(ys_centered.^2))
end

# ╔═╡ 6dec0db8-ec93-11ea-24ad-e17870ee64c2
begin
	scatter(xs_centered, ys_centered, ms=5, alpha=0.5, ratio=1, leg=false,
			framestyle=:origin)

	vline!([-2*width, 2*width], ls=:dash, lw=1.5)
	hline!([-2*height, 2*height], ls=:dash, lw=1.5)
end

# ╔═╡ 5fab2c32-f86b-11ea-2f27-ed5feaac1fa5
md"We expect most (~95%) of the data to be contained within the mean $\pm$ (twice the standard deviation)."

# ╔═╡ ae9a2900-ec93-11ea-1ae5-0748221328fc
md"## Correlated data"

# ╔═╡ b81c9db2-ec93-11ea-0dbd-4bd0951cb2cc
md"""However, from the figure we see that $x$ and $y$ are not the correct directions to think about for this data set. It would be more natural to think about other directions: the direction in which the data set is mainly pointing (roughly, the direction in which it's longest) and the approximately perpendicular direction in which it is most narrow.

We need to find from the data *which* directions these are, and the width in those directions.

We cannot get that information by looking at $x$-coordinates and $y$-coordinates separately; rather, it is encoded in the *relationship* between the values of $x_i$ and $y_i$ for those points $(x_i, y_i)$ that are in the data set.

For example, when $x$ is large and negative, $y$ is also quite negative; when $x$ is 0, $y$ is near $0$, and when $x$ is large and positive, so is $y$. We say that $x$ and $y$ are **correlated** -- literally they are mutually ("co") related, such that knowing some information about one of them allows us to predict something about the other.
"""

# ╔═╡ 80722856-f86d-11ea-363d-53fc5f6b8152
md"There are standard ways of calculating this correlation, but we prefer to hone our **intuition** using computational thinking instead!"

# ╔═╡ b8fa6a1c-f86d-11ea-3d6b-2959d737254b
md"We want to think about different *directions*, so let's introduce an angle $\theta$ to describe the direction along which we are looking. We want to calculate the width of the cloud along that direction.

Effectively we are *changing coordinates* to a new coordinate oriented along the line. To do this fully requires more linear algebra than we are assuming in this course, but let's see what it looks like:"

# ╔═╡ 3547f296-f86f-11ea-1698-53d3c1a0bc30
md"## Rotating the data"

# ╔═╡ 7a83101e-f871-11ea-1d87-4946162777b5
md"""By rotating the data we can look in different directions and calculate the width of the data set "along that direction". Again, what we are really doing is **projecting** the data onto that direction."""

# ╔═╡ e8276b4e-f86f-11ea-38be-218a72452b10
M = [xs_centered ys_centered]'

# ╔═╡ d71fdaea-f86f-11ea-1a1f-45e4d50926d3
imax = argmax(M[1, :])

# ╔═╡ 757c6808-f8fe-11ea-39bb-47e4da65113a
svdvals(M)

# ╔═╡ cd9e05ee-f86f-11ea-0422-25f8329c7ef2
R(θ)= [cos(θ) sin(θ)
	  -sin(θ) cos(θ)]

# ╔═╡ 7eb51908-f906-11ea-19d2-e947d81cb743
md"In the following figure, we are rotating the axis (red arrow) around in the left panel. In the right panel we are viewing the data from the point of view of that new coordinate direction, effectively as if we rotated our head so the red vector was horizontal:"

# ╔═╡ 4f1980ea-f86f-11ea-3df2-35cca6c961f3
@bind θ Slider(0:0.01:2π, show_value=true, default=0.0)

# ╔═╡ 3b71142c-f86f-11ea-0d43-47011d00786c
p1 = begin

	scatter(M[1, :], M[2, :], ratio=1, leg=false, ms=2.5, alpha=0.5,
			framestyle=:origin)

	plot!([0.7 .* (-cos(θ), -sin(θ)), 0.7 .* (cos(θ), sin(θ))], lw=2, arrow=true, c=:red, alpha=0.8)
	xlims!(-0.7, 0.7)
	ylims!(-0.7, 0.7)

	scatter!([M[1, imax]], [M[2, imax]],  ms=5, alpha=1, c=:yellow)

	annotate!(0, 1.2, text("align arrow with cloud", :red, 10))
end;

# ╔═╡ 88bbe1bc-f86f-11ea-3b6b-29175ddbea04
p2 = begin
	M2 = R(θ) * M

	scatter(M2[1, :], M2[2, :],ratio=1, leg=false, ms=2.5, alpha=0.5, framestyle=:origin)
	# plot!([(-1, 0), (1, 0)], lw=3, arrow=true, c=:red)

	scatter!([M2[1, imax]], [M2[2, imax]], ms=5, alpha=1, c=:yellow)

	xlims!(-0.7, 0.7)
	ylims!(-0.7, 0.7)

	σ = std(M2[1, :])
	vline!([-2σ, 2σ], ls=:dash, lw=2)
end;

# ╔═╡ 2ffe7ed0-f870-11ea-06aa-390581500ca1
plot(p1, p2)

# ╔═╡ a5cdad52-f906-11ea-0486-755a6403a367
md"Let's plot the variance in a direction $\theta$ as a function of $\theta$:"

# ╔═╡ 0115c974-f871-11ea-1204-054510848849
begin
	f(θ) = var((R(θ) * M)[1,:])
	f(θ::AbstractArray) = f(θ[1])
end

# ╔═╡ 0935c870-f871-11ea-2a0b-b1b824379350
begin
	plot(0:0.01:2π, f, leg=false, size=(400, 300))

	xlabel!("θ")
	ylabel!("variance in direction θ")
end

# ╔═╡ e4af4d26-f877-11ea-1de3-a9f8d389138e
md"""The direction in which the variance is **maximised** gives the most important direction, the first **principal component**. We can quantify how close the data is to being along a single line using the width in the perpendicular direction; if it is "very small" compared to the width in the first principal direction then the data is close to being rank 1."""

# ╔═╡ bf57f674-f906-11ea-08eb-9b50818a025b
md"The simplest way to maximise this function is to evaluate it everywhere and find one of the places where it takes the maximum value:"

# ╔═╡ 17e015fe-f8ff-11ea-17b4-a3aa072cd7b3
begin
	θs = 0:0.01:2π
	fs = f.(θs)

	θmax = θs[argmax(fs)]
	θmin = θs[argmin(fs)]

	fmax = f(θmax)
	fmin = f(θmin)
end

# ╔═╡ 045b9b98-f8ff-11ea-0d49-5b209319e951
begin
	scatter(xs_centered, ys_centered, ms=5, alpha=0.3, ratio=1, leg=false,
			framestyle=:origin)

	plot!([(0, 0), 2*sqrt(fmax) .* (cos(θmax), sin(θmax))], arrow=true, lw=3, c=:red)
	plot!([(0, 0), 2*sqrt(fmin) .* (cos(θmin), sin(θmin))], arrow=true, lw=3, c=:red)

end

# ╔═╡ cfec1ec4-f8ff-11ea-265d-ab4844f0f739
md"Note that the directions that maximise and minimise variance are perpendicular. This is always the case.

We can think of this procedure as effectively *fitting an ellipse* to the data. The widths of the ellipse axes show the relative importance of each direction in the data."

# ╔═╡ e6e900b8-f904-11ea-2a0d-953b99785553
begin
	circle = [cos.(θs) sin.(θs)]'
	stretch = [2 * sqrt(fmax) 0
				0   		2 * sqrt(fmin)]
	ellipse = R(-θmax) * stretch * circle

	plot!(ellipse[1, :], ellipse[2, :], series=:shape, alpha=0.4, fill=true, c=:orange)
end

# ╔═╡ aaff88e8-f877-11ea-1527-ff4d3db663db
md"## Higher dimensions"

# ╔═╡ aefa84de-f877-11ea-3e26-678008e9739e
md"If we now take columns of the first three rows of the original image, we have vectors in 3D.

A rank-1 matrix corresponds to a line in 3D, while a rank-2 matrix gives a **plane** in 3D. Rank-2 + noise gives a noisy cloud lying close to a plane.

Similarly to what we did above, we need to calculate the ellipsoid that best fits the data. The widths of the axes of the ellipsoid tell us how close to being a line or a plane (rank-1 or rank-2) the data is.
"

# ╔═╡ 0bd9358e-f879-11ea-2c83-ed4e7bf9d903
md"In more than 3D we can no longer visualise the data, but the same idea applies. The calculations are done using the SVD.

If the widths of the ellipsoid in some directions are very small, we can ignore those directions and hence reduce the dimensionality of the data, by changing coordinates to the principal components."

# ╔═╡ eb961e36-f899-11ea-39a9-eb33c949b79d
@bind ϕ1 Slider(0:0.1:180, show_value=true, default=30)

# ╔═╡ fdc87844-f899-11ea-1f2f-afe1cd43a68a
@bind ϕ2 Slider(0:0.1:90, show_value=true, default=30)

# ╔═╡ 232454b4-f87a-11ea-1c69-91edfca1e589
md"## Application: A simple recommendation engine"

# ╔═╡ 2b44df7e-f87a-11ea-1690-dd459eae05a3
md"Suppose we have data on movie recommendations. It might look something like the following data matrix. The rows correspond to different people and the columns to different movies. Missing values are denoted by the special value `missing` and are shown in black.
"

# ╔═╡ e5f67376-f917-11ea-1799-4341e3a758d5
missing

# ╔═╡ e7ae7312-f917-11ea-1276-bf8687cc0e57
typeof(missing)

# ╔═╡ e87e74a6-f87a-11ea-02d5-1970d010bde9
md"""If we think of movies as having properties, or **features**, such as being a drama or action movie, or having a certain actor in a lead role, and that each person has certain preferences, it might be reasonable to think that we should be able to approximate the non-missing part of this matrix by a matrix of low rank, say rank 2. If so, we can then **impute** (fill in) the missing data, and hence give each person a recommendation as to how much they might like the movies that they have not yet seen!"""

# ╔═╡ 63bad6ac-f87b-11ea-23ae-31522dfc74d5
md"""We thus want to find the rank-2 matrix that is *closest* to this given data matrix. "Closest" here requires us to define some kind of distance or **loss function** between the rank-2 matrix and the data matrix, and then to **optimise** this function.

This is one of the applications of the SVD, in the case of a matrix with *no* missing entries. However we can also directly apply **optimisation** methods. The simplest method is **gradient descent**.
"""

# ╔═╡ 2d8b13de-f901-11ea-3198-bb513ea1859c
md"The original matrix and the rank-2 approximation are as follows, with missing data shown in black:"

# ╔═╡ da7592da-f902-11ea-2cee-dbaefacdc382
md"Here we compare the non-missing values. The rank-2 approximation is not too good. We could then increase the rank of the approximation:"

# ╔═╡ 1cf3e098-f864-11ea-3f3a-c53017b73490
md"## Appendix"

# ╔═╡ 2917943c-f864-11ea-3ee6-db952ca7cd67
begin
	show_image(M) = get.(Ref(ColorSchemes.rainbow), M ./ maximum(M))
	show_image(x::AbstractVector) = show_image(x')
end

# ╔═╡ 43bff19e-f864-11ea-2315-0f85b532a325
show_image(flag)

# ╔═╡ 79d2c6f4-f895-11ea-30c4-9d1102c99482
show_image(flag2)

# ╔═╡ b183b6ca-f864-11ea-0b34-4dd3f4f5e69d
show_image(image)

# ╔═╡ 5471ddce-f867-11ea-2519-21981f5ea68b
show_image(noisy_image)

# ╔═╡ 1957f71c-f8eb-11ea-0dcf-339bfa7f96fc
show_image(image[1:2, 1:20])

# ╔═╡ 72bb11b0-f88f-11ea-0e55-b1108300f854
loss(M1, M2) = sum( (M1[i] - M2[i])^2 for i in 1:length(M1) if !ismissing(M2[i]) )

# ╔═╡ feeeb24a-f88f-11ea-287f-219e53615f32
function split_up(v, m, n)
	return v[1:m], v[m+1:m+n], v[m+n+1:2m+n], v[2m+n+1:2m+2n]
end

# ╔═╡ 9822b22e-f89a-11ea-3da9-6199f9de033a
nn = 400

# ╔═╡ 690364dc-f89a-11ea-30e0-d52fbc146ef7
M7 = outer([3, 1, 1], rand(nn)) .+ 0.1 .* randn.();

# ╔═╡ 8b6ea690-f899-11ea-2712-51508ae9c53e
M8 = outer([1, 2, 3], rand(nn)) + outer([-1, 3, -1], rand(nn)) .+ 0.1 .* randn.();

# ╔═╡ 9d5591de-f899-11ea-30d4-b1438066cc92
begin
	scatter(M7[1,:], M7[2,:], M7[3,:], camera=(ϕ1, ϕ2), alpha=0.5, label="rank 1")
	scatter!(M8[1,:], M8[2,:], M8[3,:], camera=(ϕ1, ϕ2), alpha=0.5, label="rank 2")
end

# ╔═╡ c66797fe-f899-11ea-094e-6d65bea15a11
randmissing(n, p_m, dims...) = rand([1:n; [missing for _ ∈ 1:n/(1/p_m -1)]], dims...)

# ╔═╡ 4fa77c96-f87a-11ea-2153-4bdf390369a5
begin
	m, n = 20, 5

	M3 = randmissing(5, 0.4, m, n)
end

# ╔═╡ 0bcc8852-f890-11ea-3715-11cbead7f636
function ff(v, m, n)
	v1, w1, v2, w2 = split_up(v, m, n)

	loss(outer(v1, w1) + outer(v2, w2), M3)
end

# ╔═╡ 7040dc72-f893-11ea-3d22-4fbd452faa41
ff2(v) = ff(v, m, n)

# ╔═╡ 20e94d56-f890-11ea-3953-cbd70cec8ebd
total = 2(m + n)

# ╔═╡ 1dbcf15a-f890-11ea-008c-8935edfbdb1c
ff(rand(total), m, n)

# ╔═╡ 1d7e264c-f891-11ea-131d-134cbfff1ac0
function optimise(f)
	x = rand(total)
	η = 0.01

	for i in 1:1000
		x -= η * ForwardDiff.gradient(f, x)
	end

	return x
end

# ╔═╡ 7715e100-f893-11ea-3768-f9a59d8cc06c
begin
	xxx = optimise( v -> ff(v, m, n) )

	(v1, w1, v2, w2) = split_up(xxx, m, n)
	M4 = outer(v1, w1) + outer(v2, w2)
end

# ╔═╡ 53819d1e-f902-11ea-3388-ebc082de7053
begin
	M4_new = float.(M3)
	indices = (!ismissing).(M3)
	M4_new[indices] .= M4[indices]
end

# ╔═╡ 49b83854-f894-11ea-1f07-c95929bf9aea
M5 = replace(M3, missing=>0)

# ╔═╡ 81115c14-f893-11ea-0147-c9fd45b7b777
show_image(replace(M3, missing=>0))

# ╔═╡ 9da0cbaa-f893-11ea-2d94-951c1f947a2d
let
	global M6 = copy(M4)
	M6[ismissing.(M3)] .= 0

	M6
end

# ╔═╡ 1857c66c-f894-11ea-0ba2-efe85cd442aa
[show_image(M5)', show_image(M6)']

# ╔═╡ 54ff9624-f901-11ea-309f-e396acc96f23
function show_image_missing(M)
	colors = show_image(replace(M, missing=>0))
	colors[ismissing.(M)] .= RGB(0, 0, 0)

	colors
end

# ╔═╡ 50d80f8c-f900-11ea-27fb-c5a453928534
show_image_missing(M3)

# ╔═╡ 31ed8eac-f901-11ea-3443-25c0c459803c
[show_image_missing(M3)', show_image_missing(M4)']

# ╔═╡ a0e357a0-f902-11ea-1895-651d395d025d
[show_image_missing(M3)', show_image_missing(M4_new)']

# ╔═╡ b0fadede-f901-11ea-2248-ab8049d719f5
M'

# ╔═╡ 8f599fae-f901-11ea-25e5-11a1f569aef1
show_image_missing(M3)

# ╔═╡ e465ca72-f901-11ea-22f3-318147c8d79a
colors = show_image(replace(M3, missing=>0))

# ╔═╡ f175b60a-f901-11ea-0fcb-01fc17ec2a97
colors[ismissing.(M3)]

# ╔═╡ Cell order:
# ╟─3b9941ac-6043-4dc6-850f-4c7b3ae9d9a7
# ╟─7365084a-1f37-4897-bca4-fc5855c5ee4e
# ╟─ed7ff6b2-f863-11ea-1a59-eb242a8674e3
# ╟─fed5845e-f863-11ea-2f95-c331d3c62647
# ╠═0e1a6d80-f864-11ea-074a-5f7890180114
# ╠═2e497e30-f895-11ea-09f1-d7f2c1f61193
# ╟─ab3d55cc-f905-11ea-2f22-5398f3aca803
# ╠═13b6c108-f864-11ea-2447-2b0741f15c7b
# ╠═e66b30a6-f914-11ea-2c0f-35282d45a30a
# ╠═43bff19e-f864-11ea-2315-0f85b532a325
# ╠═71d1b12e-f895-11ea-39df-f5c18a7766c3
# ╠═79d2c6f4-f895-11ea-30c4-9d1102c99482
# ╟─cdbe1d8e-f905-11ea-3884-efeeef386dda
# ╟─d9aa9af0-f865-11ea-379e-f16b452bd94c
# ╟─2e8ae92a-f867-11ea-0219-1bdd9627c1ea
# ╠═38adc490-f867-11ea-1de5-3b633aff7c97
# ╠═b183b6ca-f864-11ea-0b34-4dd3f4f5e69d
# ╟─9cf23f9a-f864-11ea-3a08-af448aceefd8
# ╟─a5b62530-f864-11ea-21e8-71ccfed487f8
# ╠═5471ddce-f867-11ea-2519-21981f5ea68b
# ╟─c41df86c-f865-11ea-1253-4942bbdbe9d2
# ╟─7fca33ac-f864-11ea-2a8b-933eb382c172
# ╟─283f5da4-f866-11ea-27d4-957ca2551b92
# ╠═1957f71c-f8eb-11ea-0dcf-339bfa7f96fc
# ╠═54977286-f908-11ea-166d-d1df33f38454
# ╠═7b4e90b4-f866-11ea-26b3-95efde6c650b
# ╟─f574ad7c-f866-11ea-0efa-d9d0602aa63b
# ╟─8775b3fe-f866-11ea-3e6f-9732e39a3525
# ╠═0dcfd858-f867-11ea-301c-c3ca0a224117
# ╠═7bacf44e-f896-11ea-38be-2b16ae7ca99f
# ╟─1147cbda-f867-11ea-08fa-ef6ed2ae1e93
# ╟─8a611e36-f867-11ea-121f-317b7c145fe3
# ╟─f7371934-f867-11ea-3b53-d1566684585c
# ╟─119dc35c-ec94-11ea-190c-23a750fbe7f4
# ╟─1e058ba2-ec94-11ea-09af-7f9f9cc3a233
# ╠═2043d4e6-ec94-11ea-1e1a-c75742eafe71
# ╟─2a705962-ec94-11ea-1181-2f001ccf472f
# ╟─987c1f2e-f868-11ea-1125-0d8c02843ae4
# ╟─9e78b048-f868-11ea-192e-d903265d1eb5
# ╟─24df1f32-ec90-11ea-1f6d-03c1bfa5df8e
# ╠═13f6ccac-7ce0-48d7-a0ef-e83489625e1d
# ╠═aec46a9b-f743-4cbd-97a7-3ef3cac78b12
# ╟─1b8c743e-ec90-11ea-10aa-e3b94f768f82
# ╟─f5358ce4-f86a-11ea-2989-b1f37be89183
# ╟─870d3efa-f8fc-11ea-1593-1552511dcf86
# ╟─03ab44c0-f8fd-11ea-2243-1f3580f98a65
# ╠═2c3721da-f86b-11ea-36cf-3fe4c6622dc6
# ╟─6dec0db8-ec93-11ea-24ad-e17870ee64c2
# ╟─5fab2c32-f86b-11ea-2f27-ed5feaac1fa5
# ╟─ae9a2900-ec93-11ea-1ae5-0748221328fc
# ╟─b81c9db2-ec93-11ea-0dbd-4bd0951cb2cc
# ╟─80722856-f86d-11ea-363d-53fc5f6b8152
# ╟─b8fa6a1c-f86d-11ea-3d6b-2959d737254b
# ╟─3547f296-f86f-11ea-1698-53d3c1a0bc30
# ╟─7a83101e-f871-11ea-1d87-4946162777b5
# ╟─e8276b4e-f86f-11ea-38be-218a72452b10
# ╟─3b71142c-f86f-11ea-0d43-47011d00786c
# ╠═d71fdaea-f86f-11ea-1a1f-45e4d50926d3
# ╠═78763674-f8fe-11ea-349c-d997f30ac1f6
# ╠═757c6808-f8fe-11ea-39bb-47e4da65113a
# ╟─88bbe1bc-f86f-11ea-3b6b-29175ddbea04
# ╟─cd9e05ee-f86f-11ea-0422-25f8329c7ef2
# ╟─7eb51908-f906-11ea-19d2-e947d81cb743
# ╠═4f1980ea-f86f-11ea-3df2-35cca6c961f3
# ╟─2ffe7ed0-f870-11ea-06aa-390581500ca1
# ╟─a5cdad52-f906-11ea-0486-755a6403a367
# ╟─0115c974-f871-11ea-1204-054510848849
# ╠═0935c870-f871-11ea-2a0b-b1b824379350
# ╟─e4af4d26-f877-11ea-1de3-a9f8d389138e
# ╟─bf57f674-f906-11ea-08eb-9b50818a025b
# ╠═17e015fe-f8ff-11ea-17b4-a3aa072cd7b3
# ╟─045b9b98-f8ff-11ea-0d49-5b209319e951
# ╟─cfec1ec4-f8ff-11ea-265d-ab4844f0f739
# ╟─e6e900b8-f904-11ea-2a0d-953b99785553
# ╟─aaff88e8-f877-11ea-1527-ff4d3db663db
# ╟─aefa84de-f877-11ea-3e26-678008e9739e
# ╟─0bd9358e-f879-11ea-2c83-ed4e7bf9d903
# ╠═690364dc-f89a-11ea-30e0-d52fbc146ef7
# ╠═8b6ea690-f899-11ea-2712-51508ae9c53e
# ╠═eb961e36-f899-11ea-39a9-eb33c949b79d
# ╠═fdc87844-f899-11ea-1f2f-afe1cd43a68a
# ╟─9d5591de-f899-11ea-30d4-b1438066cc92
# ╟─232454b4-f87a-11ea-1c69-91edfca1e589
# ╟─2b44df7e-f87a-11ea-1690-dd459eae05a3
# ╠═e5f67376-f917-11ea-1799-4341e3a758d5
# ╠═e7ae7312-f917-11ea-1276-bf8687cc0e57
# ╠═4fa77c96-f87a-11ea-2153-4bdf390369a5
# ╠═50d80f8c-f900-11ea-27fb-c5a453928534
# ╟─e87e74a6-f87a-11ea-02d5-1970d010bde9
# ╟─63bad6ac-f87b-11ea-23ae-31522dfc74d5
# ╟─2d8b13de-f901-11ea-3198-bb513ea1859c
# ╠═31ed8eac-f901-11ea-3443-25c0c459803c
# ╟─53819d1e-f902-11ea-3388-ebc082de7053
# ╟─da7592da-f902-11ea-2cee-dbaefacdc382
# ╠═a0e357a0-f902-11ea-1895-651d395d025d
# ╟─1cf3e098-f864-11ea-3f3a-c53017b73490
# ╠═35e83a04-f864-11ea-0a8e-9ddf6eec02f3
# ╠═2917943c-f864-11ea-3ee6-db952ca7cd67
# ╠═72bb11b0-f88f-11ea-0e55-b1108300f854
# ╠═feeeb24a-f88f-11ea-287f-219e53615f32
# ╠═0bcc8852-f890-11ea-3715-11cbead7f636
# ╠═7040dc72-f893-11ea-3d22-4fbd452faa41
# ╠═1dbcf15a-f890-11ea-008c-8935edfbdb1c
# ╠═20e94d56-f890-11ea-3953-cbd70cec8ebd
# ╠═16887070-f891-11ea-2db3-47b91930e728
# ╠═1d7e264c-f891-11ea-131d-134cbfff1ac0
# ╠═7715e100-f893-11ea-3768-f9a59d8cc06c
# ╠═49b83854-f894-11ea-1f07-c95929bf9aea
# ╠═81115c14-f893-11ea-0147-c9fd45b7b777
# ╠═9da0cbaa-f893-11ea-2d94-951c1f947a2d
# ╠═1857c66c-f894-11ea-0ba2-efe85cd442aa
# ╠═9822b22e-f89a-11ea-3da9-6199f9de033a
# ╠═c66797fe-f899-11ea-094e-6d65bea15a11
# ╠═54ff9624-f901-11ea-309f-e396acc96f23
# ╠═b0fadede-f901-11ea-2248-ab8049d719f5
# ╠═8f599fae-f901-11ea-25e5-11a1f569aef1
# ╠═e465ca72-f901-11ea-22f3-318147c8d79a
# ╠═f175b60a-f901-11ea-0fcb-01fc17ec2a97


export ScoreBrier

"""
    ScoreBrier <: ScoringRule

 Calculate Brier Scores for a set of outcomes and predictions

## References
* https://en.wikipedia.org/wiki/Scoring_rule
* https://en.wikipedia.org/wiki/Brier_score
* https://www2.cs.duke.edu/courses/spring17/compsci590.2/proper_scoring.pdf

## Parameters
* `normalise::false`: if true, normalise scores so that for binary choice
     + S( [0.5,0.5], 1) = 0.0
     + S( [0.0,1.0], 1) = 1.0

```
"""
struct ScoreBrier <: ScoringRule
    normalise::Bool
end
ScoreBrier(; normalise::Bool=false) = ScoreBrier(normalise)

function score_direction(srule::ScoreBrier)
    if srule.normalise
        return 1 # normalising puts it in the positive direction
    else
        return -1
    end
end

function scoring_function(srule::ScoreBrier, predicted_probabilities::Array{Float64,1}, outcome::Int)
    # general model allows C classes, so number classes 1,2,...,C
    #    predicted_probabilities is a C-element vector, which must sum to 1
    #    outcome is the index of the event that actually happens
    if abs( sum(predicted_probabilities) - 1.0 ) > 1.0e-2
        error("probabilities must sum to 1")
    end
    C = length(predicted_probabilities)
    e_i = zeros(Float64, C)
    e_i[outcome] = 1.0
    result = sum( (predicted_probabilities - e_i).^2 )
    if srule.normalise
        return 1-2*result
    else
        return result
    end
end


"""
    kahanSumOneValue(a, b, c)

`a`+`b`=`sum` is done, where the lost information in that `sum` is saved in `c` (according to Kahan Summation Algorithm). Each input only represents a number.

# Args

* `a`: it is a number, where `a`>`b` should be true for the appropiate work of the algorithm.
* `b`: the value that will be added to `a`.
* `c`: the first time this function is called in an iteration, the value of `c` should be 0. After that, this value should contain the lost information of the last sum, for its proper function.

# Returns

* `sum`: `a` + `b`.
* `c`: information lost in the sum `a`+`b`.
"""
function kahanSumOneValue(a, b, c)
    sum = a
    y = b - c          # So far, so good: c is zero.
    t = sum + y        # Alas, sum is big, y small, so low-order digits of y are lost.
    c = (t - sum) - y  # (t - sum) cancels the high-order part of y; subtracting y recovers negative (low part of y)
    sum = t            # Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers!
    return sum, c
end

"""
    kahanSumOneValue(a, b, c)

`a`+`b`=`sum` is done, where the lost information in that `sum` is saved in `c` (according to Kahan Summation Algorithm). Each input can represent an array or a matrix.

# Args

* `a`: it is a number, where `a`>`b` should be true for the appropiate work of the algorithm. `sum` values are overwritten here.
* `b`: the value that will be added to `a`.
* `c`: the first time this function is called in an iteration, the value of `c` should be 0. After that, this value should contain the lost information of the last sum, for its proper function. `c` values are overwritten here.
"""
function kahanSum!(a, b, c)

    if length(size(a)) == 1 # Vector
        for i in 1:length(a)
            y = b[i] - c[i]        # So far, so good: c is zero.
            t = a[i] + y           # Alas, a is big, y small, so low-order digits of y are lost.
            c[i] = (t - a[i]) - y  # (t - a) cancels the high-order part of y; subtracting y recovers negative (low part of y)
            a[i] = t               # Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers!
        end
    elseif length(size(a)) == 2 # Matrix
        for i in 1:size(a)[1]
            for j in 1:size(a)[2]
                y = b[i,j] - c[i,j]        # So far, so good: c is zero.
                t = a[i,j] + y           # Alas, a is big, y small, so low-order digits of y are lost.
                c[i,j] = (t - a[i,j]) - y  # (t - a) cancels the high-order part of y; subtracting y recovers negative (low part of y)
                a[i,j] = t               # Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers!
            end
        end
    else
        printf("Warning: kahanSum! works only for arrays and matrices.")
    end

end


# This is Julia code for discretizing AR(1) process by using Rouwenhorst method (1995)
# written on 2017/10/17
#
# Model:
# z' = rho*z+e  e~N(0,(sig_e)^2)
#
# INPUTS
# N:     Number of grids
# rho:   Coeffecient of AR(1) process z'=mew+rho*z+e, e~N(0,sigmasq)
# sig_e: Std of innovation, e, in AR(1) process
#
# OUTPUT
# vZ:    grids
# mPI:   transition prob. matrix

#########################################################################
## 1. Parameters
#########################################################################

# Ex. Standard quarterly calibration for productivity process
# of US economy

const N = 5;            # Number of grids, N
const rho = 0.95;       # AR(1) coefficient
const sig_e = 0.007;    # std of innovation

#########################################################################
## 2. Generating grids && Parameterization
#########################################################################

sig_z = sqrt(sig_e^2/(1-rho^2));    # std of AR(1) r.v.
z_max = sig_z*sqrt(N-1);            # max grid
z_min = -z_max;                     # min grid
p = (1+rho)/2;                      # initial prob
q = p;

vZ = linspace(z_min, z_max, N)      # Grids

#########################################################################
## 3. Transition matrix
#########################################################################

mPI_old = [p 1-p; 1-q q]; # Initial PI
mPI_new = zeros(3,3);

for i in 3:N
    mPI_new  = zeros(i,i);
    mPI_new1 = zeros(i,i);  mPI_new1[1:i-1,1:i-1]    = mPI_old;
    mPI_new2 = zeros(i,i);  mPI_new2[1:i-1,2:i]      = mPI_old;
    mPI_new3 = zeros(i,i);  mPI_new3[2:i,1:i-1]      = mPI_old;
    mPI_new4 = zeros(i,i);  mPI_new4[2:i,2:i]        = mPI_old;

    mPI_new = p*mPI_new1 + (1-p)*mPI_new2 + (1-q)*mPI_new3 + q*mPI_new4;
    mPI_new[2:i-1,:] = 0.5*mPI_new[2:i-1,:]; # devide by 2

    mPI_old = mPI_new;
end

###########################################################
## 4. Display result with 4 decimals
###########################################################

print("Grid points are \n")
for i in 1:N
    @printf "%6.4f" vZ[i]
    print(" ")
end
print("\n")
print("Transition prob. matrix is\n")
for i in 1:N
    for j in 1:N
        @printf "%6.4f" mPI_new[i,j]
        print(" ")
    end
    print("\n")
end


using Plots

function bracket_minimum(f, x=0; s=1e-2, k=2.0)
    a, ya = x, f(x)
    b, yb = a + s, f(a + s)
    if yb > ya
        a, b = b, a
        ya, yb = yb, ya
        s = -s
    end
    while true
        c, yc = b + s, f(b + s)
        if yc > yb
            return a < c ? (a,b, c) : (c, b, a)
        end
        a, ya, b, yb = b, yb, c, yc
        s *= k
    end
end

function quadratic_fit_search(f, a, b, c, n)
    ya, yb, yc = f(a), f(b), f(c)
    for i in 1:n-3
        x = 0.5*(ya*(b^2-c^2)+yb*(c^2-a^2)+yc*(a^2-b^2)) /
            (ya*(b-c) +yb*(c-a) +yc*(a-b))
        yx = f(x)
        if x > b
            if yx > yb
                c, yc = x, yx
            else
                a, ya, b, yb = b, yb, x, yx
            end
        elseif x < b
            if yx > yb
                a, ya = x, yx
            else
                c, yc, b, yb = b, yb, x, yx
            end
        end
    end
    return (a, b, c)
end

#definicao da funcao
f = x-> -(0.4/((x^2+1))^(1/2))+((x^2+1)^(1/2))*(1-0.4/(x^2+1))-x    #multiplicada por -1 para encontrar o minimo
g = x-> -(-(0.4/((x^2+1))^(1/2))+((x^2+1)^(1/2))*(1-0.4/(x^2+1))-x) #funcao original

#definiciao do ponto inicial
x0 = 0
#busca do intervalo
a,b,c = bracket_minimum(f,x0)

#numero de iteracoes
φ = MathConstants.φ
n=(c-a)/(10^-3*log(φ))

am,bm,cm = quadratic_fit_search(f,a,b,c,n)
#(am,f(am))
#am,bm,cm

println("x* = ", cm)

plot(g,a-1.,c+1.,lw=2,draw_arrow="true",label="Function")
scatter!([a,b,c],[g(a),g(b),g(c)],label="interval")
scatter!([cm],[g(cm)],label="x*")


"""Julia program to Find Noble Integer in an array.
Given an array arr, an integer ele is said to be noble integer if
the number of integers greater than ele is equal to ele.
If such an element is not found Return -1.
"""

function search_noble_integer(arr, n)
    # Traverse the entire array
    for i in 1:(n-1)
        if (arr[i] == arr[i+1])
            continue
        end
        #  If arr[i] equals the number of elements after arr[i], it is a noble Integer
        if(arr[i] == (n - i))
            return arr[i]
        end
    end
    if(arr[n] == 0)
        return arr[n]
    end
    return -1
end

print("How many numbers are present in the array? ")
n = readline()
n = parse(Int, n)
if (n <= 0)
    println("Array is Empty!!!")
    exit()
end
arr = Int[]
print("Enter the numbers: ")
arr = [parse(Int, num) for num in split(readline())]
# Sort the array in ascending order
arr = sort(arr)
res = search_noble_integer(arr, n)
if (res == -1)
    print("A Noble Integer is not present in the array.")
else
    print("A Noble Integer $res is  present in the array.")
end

"""
Time Complexity - O(n * log(n)), where 'n' is the size of the array
Space Complexity - O(n)

SAMPLE INPUT AND OUTPUT

SAMPLE I

How many numbers are present in the array? 5
Enter the numbers: 12 34 3 1 99
A Noble Integer 3 is  present in the array.

SAMPLE II

How many numbers are present in the array? 5
Enter the numbers: 5 4 3 2 1
A Noble Integer is not present in the array.

"""


module OptimNewton

export get_root;
export optimize;

"""
	Try to find a root of (fun). You need to provide the first derivative of fun (fder) and an initial x0 value.	Additional parameters are used as stop signals.	xtol, ytol: are the tolerances for x and f(x) respectively, if further iteration do not	improve the result in more that this tolerance values, the method returns. itn: is the max number of iterations that the method will do. If verbose is true, the method will print a status message in every iteration.
"""
function get_root(x0, fun, fder; xtol = 10e-10, ytol = 10e-10,
 itn = 10, verbose = false)

	r = x0;
	lastr = r;
	for i in 1:itn
		verbose && print("iteration $i: initial root $r,");
		r = r - fun(r)/fder(r);
		verbose && println(" oprox root $r");
		abs(lastr - r) <= xtol && break;
		lastr = r;
	end

	return r;

end # get_root

"""
	Try to find a optimus value of (fun). You need to provide the first derivative of fun (fder), de secund derivative of fun (sder) and an initial x0 value. Additional parameters are used as stop signals. xtol, ytol: are the tolerances for x and f(x) respectively, if further iteration do not	improve the result in more that this tolerance values, the method return. itn: is the max number of iterations that the method will do. If verbose is true, the method will print a status message in every iteration.
"""
function optimize(x0, fun, fder, sder; xtol = 10e-10, ytol = 10e-10, itn = 10, verbose = false)

	r = x0;
	lastr = r;
	for i in 1:itn
		verbose && print("iteration $i: before value $r,");
		r = r - fder(r)/sder(r);
		verbose && println(" after value $r");
		abs(lastr - r) <= xtol && break;
		abs(fun(r) - fun(lastr)) <= ytol && break;
		lastr = r;
	end

	return r;

end # optimize

end # module


export wignersurmise,unfoldedeigvals ,unfoldedSpacings

"""
```julia
wignersurmise(s ; beta)
```
- `beta` : default `1`,

    `1` for the spacing density corresponds to GOE(2)

    ``p_1(s)=\\frac{\\pi s}{2} e^{-\\pi s^{2} / 4}``

    `2` for the spacing density corresponds to GUE(2)

    ``p_2(s)=\\frac{32 s^{2}}{\\pi^{2}} e^{-4 s^{2} / \\pi}``

-  For more info see [Wigner surmise on Wikipedia](https://en.wikipedia.org/wiki/Wigner_surmise)

# Examples
Evaluate Wigner surmise for GOE at `2`
```julia
wignersurmise(2)

0.1357605281502967
```

Evaluate Wigner surmise for GUE at `2`
```julia
wignersurmise(2,beta=2)

0.07962814352725955
```
"""
function wignersurmise(s::Real; beta = 1 :: Int)
    if beta == 1
        return (pi/2) * s * exp(-pi * s^2 / 4)
    elseif beta == 2
        return 32*(s/pi)^2 * exp(- 4 * s^2 / pi)
    end
end

"""
```julia
unfoldedeigvals(E; deg, goe)
```
- `E` : The array of eigenvalues
- `deg` : default `10`
- `goe` : default `false`
"""
function unfoldedeigvals(E::Array; deg=10::Int, goe = false::Bool)
    if goe
        return length(E)*cdf(Semicircle(2),E)
    end

    return polyfit(E,(x->sum(E.<x)).(E),deg).(E)

end
function unfoldedSpacings(E::Array;deg=10::Int, goe = false::Bool)
    return diff(unfoldedeigvals(E,deg=deg,goe=goe))
end


#!/usr/bin/env julia

"""
# Problem 5: Smallest multiple

2520 is the smallest number that can be divided by each of the numbers from 1
to 10 without any remainder.

What is the smallest positive number that is evenly divisible by all of the
numbers from 1 to `max_num`?
"""
module Problem005

"""
    solve(; max_num::Integer = 20)

Returns the solution for [`Problem005`](@ref) with the given parameters.

## Preconditions
- `max_num > 0`
"""
function solve(; max_num::Integer = 20)
    return lcm((oneunit(max_num):max_num)...)
end

end


using Plots
using FFTW

tan_taylor(x) = x+( x^3/3 )+( x^5*2/15.0 )+( x^7*17/315.0 )+( x^9*62/2835.0 )+( x^11*1382/155925.0 )+( x^13*21844/6081075.0 )+( x^15*929569/638512875.0 );

x = LinRange(-π/2+0.1, π/2-0.1, 1001);

tanx_taylor = tan_taylor.(x);
tanx_native = tan.(x);

delta = tanx_native-tanx_taylor

# Time domain comparison.
plot( 	x, tanx_native,
	 	label = "Julia native tan",
        size = (1024, 720),)
plot!( 	x, tanx_taylor,
	 	label = "8-term Taylor series",)

savefig("figures/tan/time_domain")


@doc """`care(A, B, Q, R)`

Compute 'X', the solution to the continuous-time algebraic Riccati equation,
defined as A'X + XA - (XB)R^-1(B'X) + Q = 0, where R is non-singular.

Algorithm taken from:
Laub, "A Schur Method for Solving Algebraic Riccati Equations."
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
""" ->
function care(A, B, Q, R)
    G = try
        B*inv(R)*B'
    catch
        error("R must be non-singular.")
    end

    Z = [A  -G;
        -Q  -A']

    S = schurfact(Z)
    S = ordschur(S, real(S.values).<0)
    U = S.Z

    (m, n) = size(U)
    U11 = U[1:div(m, 2), 1:div(n,2)]
    U21 = U[div(m,2)+1:m, 1:div(n,2)]
    return U21/U11
end

@doc """`dare(A, B, Q, R)`

Compute `X`, the solution to the discrete-time algebraic Riccati equation,
defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where A and R
are non-singular.

Algorithm taken from:
Laub, "A Schur Method for Solving Algebraic Riccati Equations."
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
""" ->
function dare(A, B, Q, R)
    G = try
        B*inv(R)*B'
    catch
        error("R must be non-singular.")
    end

    Ait = try
        inv(A)'
    catch
        error("A must be non-singular.")
    end

    Z = [A + G*Ait*Q   -G*Ait;
         -Ait*Q        Ait]

    S = schurfact(Z)
    S = ordschur(S, abs(S.values).<=1)
    U = S.Z

    (m, n) = size(U)
    U11 = U[1:div(m, 2), 1:div(n,2)]
    U21 = U[div(m,2)+1:m, 1:div(n,2)]
    return U21/U11
end

@doc """`dlyap(A, Q)`

Compute the solution "X" to the discrete Lyapunov equation
"AXA' - X + Q = 0".
""" ->
function dlyap(A, Q)
    lhs = kron(A, conj(A))
    lhs = eye(size(lhs, 1)) - lhs
    x = lhs\reshape(Q, prod(size(Q)), 1)
    return reshape(x, size(Q))
end

@doc """`gram(sys, opt)`

Compute the grammian of system `sys`. If `opt` is `:c`, computes the
controllability grammian. If `opt` is `:o`, computes the observability
grammian.""" ->
function gram(sys::StateSpace, opt::Symbol)
    if !isstable(sys)
        error("gram only valid for stable A")
    end
    func = iscontinuous(sys) ? lyap : dlyap
    if opt == :c
        return func(sys.A, sys.B*sys.B')
    elseif opt == :o
        return func(sys.A', sys.C'*sys.C)
    else
        error("opt must be either :c for controllability grammian, or :o for
                observability grammian")
    end
end

@doc """`obsv(A, C)` or `obsv(sys)`

Compute the observability matrix for the system described by `(A, C)` or `sys`.

Note that checking for observability by computing the rank from `obsv` is
not the most numerically accurate way, a better method is checking if
`gram(sys, :o)` is positive definite.""" ->
function obsv(A, C)
    n = size(A, 1)
    ny = size(C, 1)
    if n != size(C, 2)
        error("C must have the same number of columns as A")
    end
    res = zeros(n*ny, n)
    res[1:ny, :] = C
    for i=1:n-1
        res[(1 + i*ny):(1 + i)*ny, :] = res[((i - 1)*ny + 1):i*ny, :] * A
    end
    return res
end
obsv(sys::StateSpace) = obsv(sys.A, sys.C)

@doc """`ctrb(A, B)` or `ctrb(sys)`

Compute the controllability matrix for the system described by `(A, B)` or
`sys`.

Note that checking for controllability by computing the rank from
`obsv` is not the most numerically accurate way, a better method is
checking if `gram(sys, :c)` is positive definite.""" ->
function ctrb(A, B)
    n = size(A, 1)
    nu = size(B, 2)
    if n != size(B, 1)
        error("B must have the same number of rows as A")
    end
    res = zeros(n, n*nu)
    res[:, 1:nu] = B
    for i=1:n-1
        res[:, (1 + i*nu):(1 + i)*nu] = A * res[:, ((i - 1)*nu + 1):i*nu]
    end
    return res
end
ctrb(sys::StateSpace) = ctrb(sys.A, sys.B)

@doc """`P = covar(sys, W)`

Calculate the stationary covariance `P = E[y(t)y(t)']` of an lti-model `sys`, driven by gaussian
white noise 'w' of covariance `E[w(t)w(τ)]=W*δ(t-τ)` where δ is the dirac delta.

The ouput is if Inf if the system is unstable. Passing white noise directly to
the output will result in infinite covariance in the corresponding outputs
(D*W*D.' .!= 0) for contunuous systems.""" ->
function covar(sys::StateSpace, W::StridedMatrix)
    (A, B, C, D) = (sys.A, sys.B, sys.C, sys.D)
    if size(B,2) != size(W, 1) || size(W, 1) != size(W, 2)
        error("W must be a square matrix the same size as `sys.B` columns")
    end
    if !isstable(sys)
        return fill(Inf,(size(C,1),size(C,1)))
    end
    func = iscontinuous(sys) ? lyap : dlyap
    Q = try
        func(A, B*W*B')
    catch
        error("No solution to the Lyapunov equation was found in covar")
    end
    P = C*Q*C'
    if iscontinuous(sys)
        #Variance and covariance infinite for direct terms
        directNoise = D*W*D'
        for i in 1:size(C,1)
            if directNoise[i,i] != 0
                P[i,:] = Inf
                P[:,i] = Inf
            end
        end
    else
        P += D*W*D'
    end
    return P
end

covar(sys::TransferFunction, W::StridedMatrix) = covar(ss(sys), W)

# Note: the following function returns a Float64 when p=2, and a Tuple{Float64,Float64} when p=Inf.
# This varargout behavior might not be a good idea.
# The H∞ norm computation is probably not as accurate as with SLICOT, but this seems to be still
# reasonably ok as a first step
@doc """
`..  norm(sys[, p; tol=...])`

Compute the H`p`-norm of the LTI system `sys`. `p` can be either `2` or `Inf`
(default is 2). For the H∞ norm, the function returns the pair `(val,fpeak)`,
where `fpeak` (in rad/TimeUnit) is where the gain achieves its peak value `val`.

sys is first converted to a state space model if needed.

`tol` is an optional keyword argument, used currently only for the computation of H∞ norms.
It represents the desired relative accuracy for the computed H∞ norm
(default 1e-12 - not an absolute certificate however).

The H∞ norm computation implements the 'two-step algorithm' in:
N.A. Bruinsma and M. Steinbuch, 'A fast algorithm to compute the H∞-norm
of a transfer function matrix', Systems and Control Letters 14 (1990), pp. 287-293.
""" ->
function Base.norm(sys::StateSpace, p::Real=2; tol=1e-12)
    if p == 2
        return sqrt(trace(covar(sys, eye(size(sys.B, 2)))))
    elseif p == Inf
        if sys.Ts == 0
            return normHinf_twoSteps_ct(sys,tol)
        else
            error("H∞ norm computation not yet implemented for discrete-time systems!")
        end
    else
        error("`p` must be either `2` or `Inf`")
    end
end

function Base.norm(sys::TransferFunction, p::Real=2; tol=1e-12)
    return Base.norm(ss(sys),p)
end

function normHinf_twoSteps_ct(sys::StateSpace,tol=1e-12,maxIters=1000,approximag=1e-10)
    # `maxIters`: the maximum  number of iterations allowed in the algorithm (default 1000)
    # approximag is a tuning parameter: what does it mean for a number to be on the imaginary axis
    # Because of this tuning for example, the relative precision that we provide on the norm computation
    # is not a true guarantee, more an order of magnitude
    # outputs: pair of Float64, namely H∞ norm approximation and frequency fpeak at which it is achieved
    p = pole(sys)
    if any(real(p).>=0)
        # N.B.: more efficient than calling isstable, which would recompute the poles
        return Inf
    else
        # Initialization: computation of a lower bound from 3 terms
        lb = maximum(svdvals(sys.D)); fpeak = Inf
        (lb, idx) = findmax([lb, maximum(svdvals(evalfr(sys,0)))])
        if idx == 2
            fpeak = 0
        end
        p = pole(sys)
        if isreal(p)  # only real poles
            omegap = minimum(abs(p))
        else  # at least one pair of complex poles
            tmp = maximum(abs(imag(p)./(real(p).*abs(p))))
            omegap = abs(p[indmax(tmp)])
        end
        (lb, idx) = findmax([lb, maximum(svdvals(evalfr(sys,omegap*1im)))])
        if idx == 2
            fpeak = omegap
        end

        # Iterations
        iter = 1;
        while iter <= maxIters
            res = (1+2tol)*lb
            R = sys.D'*sys.D - res^2*eye(sys.nu)
            S = sys.D*sys.D' - res^2*eye(sys.ny)
            M = sys.A-sys.B*(R\sys.D')*sys.C
            H = [         M              -res*sys.B*(R\sys.B') ;
                   res*sys.C'*(S\sys.C)            -M'            ]
            omegas = eigvals(H)
            omegaps = imag(omegas[ (abs(real(omegas)).<=approximag) & (imag(omegas).>=0) ])
            if isempty(omegaps)
                return (1+tol)*lb, fpeak
            else  # if not empty, omegaps contains at least two values
                ms = [(x+y)/2 for x=omegaps[1:end-1], y=omegaps[2:end]]
                for mval in ms
                    (lb, idx) = findmax([lb, maximum(svdvals(evalfr(sys,mval*1im)))])
                            if idx == 2
                                fpeak = mval
                            end
                end
            end
            iter += 1
        end
        println("The computation of the H-infinity norm did not converge in $maxIters iterations")
    end
end

@doc """`T, B = balance(A[, perm=true])`

Compute a similarity transform `T` resulting in `B = T\\A*T` such that the row
and column norms of `B` are approximately equivalent. If `perm=false`, the
transformation will only scale, and not permute `A`.""" ->
function balance(A, perm::Bool=true)
    n = Base.LinAlg.checksquare(A)
    B = copy(A)
    job = perm ? 'B' : 'S'
    ilo, ihi, scaling = LAPACK.gebal!(job, B)

    S = diagm(scaling)
    for j = 1:(ilo-1)   S[j,j] = 1 end
    for j = (ihi+1):n   S[j,j] = 1 end

    P = eye(Int, n)
    if perm
        if ilo > 1
            for j = (ilo-1):-1:1 cswap!(j, round(Int, scaling[j]), P) end
        end
        if ihi < n
            for j = (ihi+1):n    cswap!(j, round(Int, scaling[j]), P) end
        end
    end
    return S, P, B
end

function cswap!{T<:Number}(i::Integer, j::Integer, X::StridedMatrix{T})
    for k = 1:size(X,1)
        X[i, k], X[j, k] = X[j, k], X[i, k]
    end
end

"""
`sysr, G = balreal(sys::StateSpace)`

Calculates a balance realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal `G`

See also `gram`

Glad, Ljung, Reglerteori: Flervariabla och Olinjära metoder
"""
function balreal(sys::StateSpace)
P = gram(sys, :c)
Q = gram(sys, :o)
Q1 = chol(Q)
U,Σ,V = svd(Q1*P*Q1')
Σ = sqrt(Σ)
Σ1 = diagm((sqrt(Σ)))
T = Σ1\(U'Q1)

Pz = T*P*T'
Qz = inv(T')*Q*inv(T)
if vecnorm(Pz-Qz) > sqrt(eps())
    warn("balreal: Result may be inaccurate")
    println("Controllability gramian before transform")
    display(P)
    println("Controllability gramian after transform")
    display(Pz)
    println("Observability gramian before transform")
    display(Q)
    println("Observability gramian after transform")
    display(Qz)
    println("Singular values of PQ")
    display(Σ)
end

sysr = ss(T*sys.A/T, T*sys.B, sys.C/T, sys.D), diagm(Σ)
end


module DifferencesMatrix

using Test

import LinearAlgebra.diagm

function backwardDiff(dim::Int)
    return diagm(-1 => -ones(dim - 1), 0 => ones(dim))
end

@test backwardDiff(3) == [
  1 0 0
  -1 1 0
  0 -1 1
]

function forwardDiff(dim::Int)
    return diagm(0 => -ones(dim), 1 => ones(dim - 1))
end

@test forwardDiff(3) == [
  -1 1 0
  0 -1 1
  0 0 -1
]

@test backwardDiff(3) * forwardDiff(3) == [
  -1 1 0
  1 -2 1
  0 1 -2
]

function centeredDiff(dim::Int)
    return diagm(-1 => -ones(dim - 1), 0 => zeros(dim), 1 => ones(dim - 1))
end

@test centeredDiff(3) == backwardDiff(3) - (-forwardDiff(3))

export backwardDiff, forwardDiff, centeredDiff

end


function ABCDQR_kfilter_s(y,u,A,B,C,D,Q,R,x10)
	#
	# STATIONARY Kalman filter for model
	#
	# x_{t+1} = A*x_{t} + B*u_{t} + w_{t}
	# y_{t}   = C*x_{t} + D*u_{t} + v_{t}
	#
	# cov(w_{t},v_{t}) = [Q 0;0 R]
	#
	# javier.cara@upm.es, 2015-10
	#

	(ny,nt) = size(y)
	nx = size(A,1)

	# allocation
	xtt = zeros(nx,nt)
	xtt1 = zeros(nx,nt+1)
	et = zeros(ny,nt)
	loglik = 0.0

	# Discrete-time Algebraic Riccati Equation (DARE) solution
	S = zeros(nx,ny)
	Ptt1 = dare(A',C',Q,R,S)

	# variance of innovations
	St = C*Ptt1*C' + R
	Stinv = inv(St)

	# Kalman gain matrix
	Kt = Ptt1*C'*Stinv

	# Var[x_t|y_t]
	Ptt = (Matrix(1.0I,nx,nx)-Kt*C)*Ptt1

	# Filter
	xtt1[:,1] = x10
	for t in 1:nt
		#  innovations
		et[:,t] = y[:,t] - C*xtt1[:,t] - D*u[:,t]

		# filtered values
		xtt[:,t] = xtt1[:,t] + Kt*et[:,t]

		# one-step ahead prediction
		xtt1[:,t+1] = A*xtt[:,t] + B*u[:,t]

		# likelihood
		l0 = et[:,t]'*Stinv*et[:,t] # typeof(l0) = Array{Float64,1}
		loglik = loglik + l0[1]
	end

	loglik = - ny*nt/2*log(2*pi) - nt/2*log(det(St)) - 1/2*loglik

	return xtt,Ptt,xtt1,Ptt1,et,St,Kt,loglik

end


export
    sqrtmod,
    multiplicativeorder,
    linearmod,
    primitiveroot

Nemo.sqrtmod(x::Integer, m::T) where T<:Integer = convert(T, sqrtmod(fmpz(x), fmpz(m)))

#=
http://rosettacode.org/wiki/Multiplicative_order

One possible algorithm that is efficient also for large numbers is the
following: By the Chinese Remainder Theorem, it's enough to calculate the
multiplicative order for each prime exponent p^k of m, and combine the
results with the least common multiple operation. Now the order of a wrt.
to p^k must divide Φ(p^k). Call this number t, and determine it's factors
q^e. Since each multiple of the order will also yield 1 when used as
exponent for a, it's enough to find the least d such that (q^d)*(t/(q^e))
yields 1 when used as exponent.
=#
multiplicativeorder(a::Integer, m::Integer) = multiplicativeorder(promote(a, m)...)
multiplicativeorder(a::T, m::T) where {T<:Integer} = begin
    iscoprime(a, m) || error("$a and $m are not coprime")

    res = one(m)
    for (p, k) in factor(m)
        m = p^k
        t = (m ÷ p) * (p-1)
        for f in factors(t)
            if powermod(a, f, m) == 1
                res = lcm(res, f)
                break
            end
        end
    end
    res
end

##  Solve linear modulo equation a * x = b mod m
# https://github.com/hwborchers/Numbers.jl/blob/master/src/primes.jl
linearmod(a::Integer, b::Integer, m::Integer) = linearmod(promote(a, b, m)...)
linearmod(a::T, b::T, m::T) where {T<:Integer} = begin
    m > 1 || error("Argument 'm' must be an integer greater 1")

    g, u, v = gcdx(a, m)
    mod(b, g) == 0 || return T[]

    x0 = mod(u * (b ÷ g), m)
    [mod(x0 + i * (m ÷ g), m) for i in 0:(g-1)]
end

##  Find a primitive root modulo m
# https://github.com/hwborchers/Numbers.jl/blob/master/src/primes.jl
primitiveroot(m::T) where {T<:Integer} = begin
    isprime(m) || error("Argument 'm' must be a prime number")
    m == 2 && return one(T)

    P = primefactors(m-1)
    for r in 2:(m-1)
        not_found = true
        for p in P
            if powermod(r, (m-1) ÷ p, m) == 1
                not_found = false
                break
            end
        end
        not_found && return r
    end
    zero(T)
end


"""
Assumes x,y points go around the polygon in one direction.
Credits: https://rosettacode.org/wiki/Shoelace_formula_for_polygonal_area#Julia
"""
shoelacearea(x, y) =
    abs(sum(i * j for (i, j) in zip(x, append!(y[2:end], y[1]))) -
        sum(i * j for (i, j) in zip(append!(x[2:end], x[1]), y))) / 2


"""

  Construct the sylvester matrix of degree k from polynomials f and g = f'.
  The matrix has 2k+1 columns. Each column contains a shifted version of f or g.
  Order of columns: k+1 * g, k * f
"""
function sylves1(f::AbstractVector{T}, g::AbstractVector{T}, k::Int) where {T<:Number}
#
#  The k-th sylvester matrix of f(x), f'(x) = g(x)
#
    l = length(f)
    m = l + k - 1
    n = 2 * k + 1
    A = zeros(T, m, n)
	for j = 1:k+1
        A[j:j+l-2,j] = g
    end
    for j = 1:k
        A[j:j+l-1,j+k+1] = f
    end
    A
end

"""
Extract result polynomials u and v if x is a solution of sylves1(f,g) * x == 0.
  u * g + v * f == 0, degree of v is k.
"""
function extract_sylves1(x::AbstractVector{T}) where {T<:Number}
  k = length(x)
  k > 2 && k % 2 == 1 || error("vector too short or not odd length")
  k = (k + 1) ÷ 2
  x[1:k] / x[1], x[k+1:end] / x[k+1]  # u, v
end

"""

  Construct the sylvester matrix of degree k from polynomials f and g = f'.
  The matrix has 2k+1 columns. Each column contains a shifted version of f or g.
  Order of columns: g, k * [f g]
"""
function sylves2(f::AbstractVector{T}, g::AbstractVector{T}, k::Int) where {T<:Number}
#
#  The k-th sylvester matrix of f(x), f'(x) = g(x)
#
    l = length(f)
    m = l + k - 1
    n = 2 * k + 1
    A = zeros(T, m, n)
	for j = 1:k+1
        A[j:j+l-2,j*2-1] = g
    end
    for j = 1:k
        A[j:j+l-1,j*2] = f
    end
    A
end

"""
Extract result polynomials u and v if x is a solution of sylves2(f,g) * x == 0.
  u * g + v * f == 0, degree of v is k.
"""
function extract_sylves2(x::AbstractVector{T}) where {T<:Number}
  k = length(x)
  k > 2 && k % 2 == 1 || error("vector too short or not odd length")
  x[1:2:k] / x[1], x[2:2:k] / x[2]  # u, v
end


function scalematrix(s::Vec{3, T}) where T
    T0, T1 = zero(T), one(T)
    Mat{4}(
        s[1],T0,  T0,  T0,
        T0,  s[2],T0,  T0,
        T0,  T0,  s[3],T0,
        T0,  T0,  T0,  T1,
    )
end

translationmatrix_x(x::T) where {T} = translationmatrix(Vec{3, T}(x, 0, 0))
translationmatrix_y(y::T) where {T} = translationmatrix(Vec{3, T}(0, y, 0))
translationmatrix_z(z::T) where {T} = translationmatrix(Vec{3, T}(0, 0, z))

function translationmatrix(t::Vec{3, T}) where T
    T0, T1 = zero(T), one(T)
    Mat{4}(
        T1,  T0,  T0,  T0,
        T0,  T1,  T0,  T0,
        T0,  T0,  T1,  T0,
        t[1],t[2],t[3],T1,
    )
end

rotate(angle, axis::Vec{3}) = rotationmatrix4(qrotation(convert(Array, axis), angle))
rotate(::Type{T}, angle::Number, axis::Vec{3}) where {T} = rotate(T(angle), convert(Vec{3, T}, axis))

function rotationmatrix_x(angle::Number)
    T0, T1 = (0, 1)
    Mat{4}(
        T1, T0, T0, T0,
        T0, cos(angle), sin(angle), T0,
        T0, -sin(angle), cos(angle),  T0,
        T0, T0, T0, T1
    )
end
function rotationmatrix_y(angle::Number)
    T0, T1 = (0, 1)
    Mat{4}(
        cos(angle), T0, -sin(angle),  T0,
        T0, T1, T0, T0,
        sin(angle), T0, cos(angle), T0,
        T0, T0, T0, T1
    )
end
function rotationmatrix_z(angle::Number)
    T0, T1 = (0, 1)
    Mat{4}(
        cos(angle), sin(angle), T0, T0,
        -sin(angle), cos(angle),  T0, T0,
        T0, T0, T1, T0,
        T0, T0, T0, T1
    )
end

"""
    Create view frustum

    Parameters
    ----------
        left : float
         Left coordinate of the field of view.
        right : float
         Left coordinate of the field of view.
        bottom : float
         Bottom coordinate of the field of view.
        top : float
         Top coordinate of the field of view.
        znear : float
         Near coordinate of the field of view.
        zfar : float
         Far coordinate of the field of view.

    Returns
    -------
        M : array
         View frustum matrix (4x4).
"""
function frustum(left::T, right::T, bottom::T, top::T, znear::T, zfar::T) where T
    (right == left || bottom == top || znear == zfar) && return Mat{4,4,T}(I)
    T0, T1, T2 = zero(T), one(T), T(2)
    return Mat{4}(
        T2 * znear / (right - left), T0, T0, T0,
        T0, T2 * znear / (top - bottom), T0, T0,
        (right + left) / (right - left), (top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), -T1,
        T0, T0, (-T2 * znear * zfar) / (zfar - znear), T0
    )
end

"""
`proj = perspectiveprojection([T], fovy, aspect, znear, zfar)` defines
a projection matrix with a given angular field-of-view `fovy` along
the y-axis (measured in degrees), the specified `aspect` ratio, and
near and far clipping planes `znear`, `zfar`. Optionally specify the
element type `T` of the matrix.
"""
function perspectiveprojection(fovy::T, aspect::T, znear::T, zfar::T) where T
    (znear == zfar) && error("znear ($znear) must be different from tfar ($zfar)")
    h = T(tan(fovy / 360.0 * pi) * znear)
    w = T(h * aspect)
    return frustum(-w, w, -h, h, znear, zfar)
end

function perspectiveprojection(
        ::Type{T}, fovy::Number, aspect::Number, znear::Number, zfar::Number
    ) where T
    perspectiveprojection(T(fovy), T(aspect), T(znear), T(zfar))
end
"""
`proj = perspectiveprojection([T], rect, fov, near, far)` defines the
projection ratio in terms of the rectangular view size `rect` rather
than the aspect ratio.
"""
function perspectiveprojection(wh::Rect2D, fov::T, near::T, far::T) where T
    perspectiveprojection(fov, T(wh.w/wh.h), near, far)
end

function perspectiveprojection(
        ::Type{T}, wh::Rect2D, fov::Number, near::Number, far::Number
    ) where T
    perspectiveprojection(T(fov), T(wh.w/wh.h), T(near), T(far))
end

"""
`view = lookat(eyeposition, lookat, up)` creates a view matrix with
the eye located at `eyeposition` and looking at position `lookat`,
with the top of the window corresponding to the direction `up`. Only
the component of `up` that is perpendicular to the vector pointing
from `eyeposition` to `lookat` will be used.  All inputs must be
supplied as 3-vectors.
"""
function lookat(eyePos::Vec{3, T}, lookAt::Vec{3, T}, up::Vec{3, T}) where T
    zaxis  = normalize(eyePos-lookAt)
    xaxis  = normalize(cross(up,    zaxis))
    yaxis  = normalize(cross(zaxis, xaxis))
    T0, T1 = zero(T), one(T)
    return Mat{4}(
        xaxis[1], yaxis[1], zaxis[1], T0,
        xaxis[2], yaxis[2], zaxis[2], T0,
        xaxis[3], yaxis[3], zaxis[3], T0,
        T0,       T0,       T0,       T1
    ) * translationmatrix(-eyePos)
end

function lookat(::Type{T}, eyePos::Vec{3}, lookAt::Vec{3}, up::Vec{3}) where T
    lookat(Vec{3,T}(eyePos), Vec{3,T}(lookAt), Vec{3,T}(up))
end

function orthographicprojection(wh::Rect2D, near::T, far::T) where T
    orthographicprojection(zero(T), T(wh.w), zero(T), T(wh.h), near, far)
end

function orthographicprojection(
        ::Type{T}, wh::Rect2D, near::Number, far::Number
    ) where T
    orthographicprojection(wh, T(near), T(far))
end

function orthographicprojection(
        left  ::T, right::T,
        bottom::T, top  ::T,
        znear ::T, zfar ::T
    ) where T
    (right==left || bottom==top || znear==zfar) && return Mat{4,4,T}(I)
    T0, T1, T2 = zero(T), one(T), T(2)
    Mat{4}(
        T2/(right-left), T0, T0,  T0,
        T0, T2/(top-bottom), T0,  T0,
        T0, T0, -T2/(zfar-znear), T0,
        -(right+left)/(right-left), -(top+bottom)/(top-bottom), -(zfar+znear)/(zfar-znear), T1
    )
end

function orthographicprojection(::Type{T},
        left  ::Number, right::Number,
        bottom::Number, top  ::Number,
        znear ::Number, zfar ::Number
    ) where T
    orthographicprojection(
        T(left),   T(right),
        T(bottom), T(top),
        T(znear),  T(zfar)
    )
end

mutable struct Pivot{T}
    origin      ::Vec{3, T}
    xaxis       ::Vec{3, T}
    yaxis       ::Vec{3, T}
    zaxis       ::Vec{3, T}
    rotation    ::Quaternion
    translation ::Vec{3, T}
    scale       ::Vec{3, T}
end

GeometryBasics.origin(p::Pivot) = p.origin

rotationmatrix4(q::Quaternion{T}) where {T} = Mat4{T}(q)

function transformationmatrix(p::Pivot)
    translationmatrix(p.origin) * #go to origin
    rotationmatrix4(p.rotation) * #apply rotation
    translationmatrix(-p.origin)* # go back to origin
    translationmatrix(p.translation) #apply translation
end

function transformationmatrix(translation, scale)
    T = eltype(translation)
    T0, T1 = zero(T), one(T)
    Mat{4}(
        scale[1],T0,  T0,  T0,
        T0,  scale[2],T0,  T0,
        T0,  T0,  scale[3],T0,
        translation[1],translation[2],translation[3], T1
    )
end

function transformationmatrix(translation, scale, rotation::Quaternion)
    trans_scale = transformationmatrix(translation, scale)
    rotation = Mat4f0(rotation)
    trans_scale*rotation
end

function transformationmatrix(
        translation::Vec{3}, scale::Vec{3}, rotation::Quaternion,
        align, flip::NTuple{3, Bool}, boundingbox::Nothing
    )
    return transformationmatrix(translation, scale, rotation)
end

function transformationmatrix(
        translation::Vec{3}, scale::Vec{3}, rotation::Quaternion,
        align, flip::NTuple{3, Bool}, boundingbox::Rect3D
    )
    full_width = widths(boundingbox)
    mini = minimum(boundingbox)
    half_width = full_width ./ 2
    to_origin = (half_width + mini)
    if isnan(to_origin)
        to_origin = Vec3f0(0)
    end
    align_middle = translationmatrix(-to_origin)
    align_back = translationmatrix(to_origin)
    flipsign = map(x-> ifelse(x, -1f0, 1f0), Vec{3}(flip))
    flipped = align_back * scalematrix(flipsign) * align_middle
    aligned = flipped #translationmatrix(align .* full_width) * flipped
    trans_scale = transformationmatrix(translation, scale)
    rotation = Mat4f0(rotation)
    aligned * trans_scale * rotation
end

function transformationmatrix(
        translation, scale, rotation::Vec{3,T}, up = Vec{3,T}(0,0,1)
    ) where T
    q = rotation(rotation, up)
    transformationmatrix(translation, scale, q)
end

#Calculate rotation between two vectors
function rotation(u::Vec{3, T}, v::Vec{3, T}) where T
    # It is important that the inputs are of equal length when
    # calculating the half-way vector.
    u, v = normalize(u), normalize(v)
    # Unfortunately, we have to check for when u == -v, as u + v
    # in this case will be (0, 0, 0), which cannot be normalized.
    if (u == -v)
        # 180 degree rotation around any orthogonal vector
        other = (abs(dot(u, Vec{3, T}(1,0,0))) < 1.0) ? Vec{3, T}(1,0,0) : Vec{3, T}(0,1,0)
        return qrotation(normalize(cross(u, other)), T(180))
    end
    half = normalize(u+v)
    return Quaternion(cross(u, half)..., dot(u, half))
end

function to_world(scene::Scene, point::T) where T <: StaticVector
    cam = scene.camera
    x = to_world(
        point,
        inv(transformationmatrix(scene)[]) *
        inv(cam.view[]) *
        inv(cam.projection[]),
        T(widths(pixelarea(scene)[]))
    )
    Point2f0(x[1], x[2])
end

w_component(x::Point) = 1.0
w_component(x::Vec) = 0.0
function to_world(
        p::StaticVector{N, T},
        prj_view_inv::Mat4,
        cam_res::StaticVector
    ) where {N, T}
    VT = typeof(p)
    clip_space = ((VT(p) ./ VT(cam_res)) .* T(2)) .- T(1)
    pix_space = Vec{4, T}(
        clip_space[1],
        clip_space[2],
        T(0), w_component(p)
    )
    ws = prj_view_inv * pix_space
    ws ./ ws[4]
end

function to_world(
        p::Vec{N, T},
        prj_view_inv::Mat4,
        cam_res::StaticVector
    ) where {N, T}
    to_world(Point(p), prj_view_inv, cam_res) .-
        to_world(zeros(Point{N, T}), prj_view_inv, cam_res)
end

function project(scene::Scene, point::T) where T<:StaticVector
    cam = scene.camera
    project(
        cam.projection[] *
        cam.view[] *
        transformationmatrix(scene)[],
        Vec2f0(widths(pixelarea(scene)[])), point
    )
end

function project(matrix::Mat4f0, p::T, dim4 = 1.0) where T <: VecTypes
    p = to_ndim(Vec4f0, to_ndim(Vec3f0, p, 0.0), dim4)
    p = matrix * p
    to_ndim(T, p, 0.0)
end

function project(proj_view::Mat4f0, resolution::Vec2, point::Point)
    p4d = to_ndim(Vec4f0, to_ndim(Vec3f0, point, 0f0), 1f0)
    clip = proj_view * p4d
    p = (clip / clip[4])[Vec(1, 2)]
    p = Vec2f0(p[1], p[2])
    return (((p .+ 1f0) / 2f0) .* (resolution .- 1f0)) .+ 1f0
end

function project_point2(mat4::Mat4, point2::Point2)
    Point2f0(mat4 * to_ndim(Point4f0, to_ndim(Point3f0, point2, 0), 1))
end

function transform(model::Mat4, x::T) where T
    x4d = to_ndim(Vec4f0, x, 0.0)
    to_ndim(T, model * x4d, 0.0)
end


# SIR_v2.jl
# ===============================
using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("style.jl"))
#using DynamicalSystems
using PyPlot, OrdinaryDiffEq
using StaticArrays

function SIR_ODE(u,p,t)
    beta, gamma, N = p
    S, I, R = u
    dS =  - beta * I * S / N
    dI =  beta * I * S / N  - gamma * I
    dR = gamma * I
    return SVector(dS, dI, dR)
end

N = 10000
beta = 0.3
gamma = 0.1
p = beta, gamma, N

u0 = SVector(N-1., 1., 0.)

tspan = (0., 120)

prob = ODEProblem(SIR_ODE, u0, tspan, p)
sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8,tsamp = 0.1)

tvec = sol.t
Svec = [u[1] for u in sol.u]
Ivec = [u[2] for u in sol.u]
Rvec = [u[3] for u in sol.u]

# plot
# ----
fig = figure("SIR",figsize=(0.5*figx,figy))

plot(tvec,Svec/N, c = COLORS[1] )
plot(tvec,Rvec/N, c = COLORS[3] )
plot(tvec,Ivec/N, c = COLORS[2] )

xlabel(L"$t$")
ax = gca()
ax.set_xlim([0.,120.])
# ax.set_ylim([-2.1,2.5])
ax.legend([L"S/N"; L"I/N"; L"R/N"], fontsize = 25)
fig.tight_layout(pad=0.3)

# save
# ----
# dname = "/Users/parlitz/ownCloud/NonlinearDynamicsTextbook/figure_generation/10/SIR/"
# fname = "SIR_model"
# ftype = ".png"

wsave(plotsdir("10", "SIR_model"), fig)


#--------------------------------------------------------------------
# Spacetime Discretization methods in Julia
# Soham 01-2018
# Test summation-by-parts property for our derivative operator
# See Summation by Parts, Projections, and Stability. I Olsson 1995
#--------------------------------------------------------------------

D  = [-1.0  1.0  0.0  0.0;
      -0.5  0.0  0.5  0.0;
       0.0 -0.5  0.0  0.5;
       0.0  0.0 -1.0  1.0]

W  = [ 0.5  0.0  0.0  0.0;
       0.0  1.0  0.0  0.0;
       0.0  0.0  1.0  0.0;
       0.0  0.0  0.0  0.5]

B  = [-1.0  0.0  0.0  0.0;
       0.0  0.0  0.0  0.0;
       0.0  0.0  0.0  0.0;
       0.0  0.0  0.0  1.0]

u = [x^2 for x in linspace(-1, 1, 4)]
v = [x^3 for x in linspace(-1, 1, 4)]
@test W*D + (W*D)' == B
@test u'*W*D*v + u'*(W*D)'*v == u'*B*v

# Now test the spectral discretization operators
@testset "Summation-by-Parts" begin
    @testset "N = $N" for N in 1:6
        D = Float64[chebd(i,j, N) for i in 1:N+1, j in 1:N+1]
        W = diagm(Float64[chebw(i, N) for i in 1:N+1])
        B = zeros(N+1, N+1)
        B[1,1] = 1
        B[end, end] = -1
        u = [x^2 for x in chebgrid(N)]
        v = [x^3 for x in chebgrid(N)]
        @test u'*W*D*v + u'*(W*D)'*v ≈ u'*B*v atol=1e-15
        @test_broken (W*D) + (W*D)' ≈ B atol=1e-15
    end
end;


@testset "Boundaries" begin

    # boundary of a point
    ch = boundary(Simplex(1))
    @test iszero(ch)
    @test dim(ch) == -1

    # different order of faces
    @test Simplex(4,1) == Simplex(1,4)
    ch1 = boundary(Simplex(4,1))
    ch2 = boundary(Simplex(1,4))
    @test ch1 != ch2

    cplx = SimplicialComplex(Simplex(1,2,3),
                            Simplex(2,4),
                            Simplex(3,4),
                            Simplex(5,4),
                            Simplex(1,2,3,5))

    # ch = boundary(Simplex(1,2,3))
    # showchain(cplx, ch)

    # simplex boundary
    ch = Chain(1, Int) + (Simplex(1,2),1) + (Simplex(2,3),1) + (Simplex(1,3),-1)
    @testset "Complex boundary chain" for (a,b) in zip(boundary(Simplex(1,2,3)), ch)
        @test a == b
    end

    # ∂∘∂(c) == 0
    @test iszero( boundary(cplx, boundary(Simplex(1,2,3))) )

    # boundary of a path
    cplx = SimplicialComplex(Simplex(1,2), Simplex(2,3), Simplex(3,4))
    path = Chain(1, Dict(hash(c) => 1 for c in cells(cplx,1)))
    ch = Chain(0, Int) + (Simplex(1), -1) + (Simplex(4), 1)
    @testset "Path boundary" for (a,b) in zip(boundary(cplx, path), ch)
        @test a == b
    end

    # boundary of a loop
    push!(cplx, Simplex(4,1))
    loop = Chain(1, Dict(hash(c) => 1 for c in cells(cplx,1)))
    @test iszero(simplify(boundary(cplx, loop)))

    # coboundary
    ch = Chain(2, Int) + (Simplex(1,3,5),1) + (Simplex(1,2,3),-1)
    @testset "Complex coboundary chain" for (a,b) in zip(coboundary(cplx, Simplex(1,3)), ch)
        @test a == b
    end

    # δ∘δ(c) == 0
    @test iszero( coboundary(cplx, coboundary(cplx, Simplex(1,3))) )

    # cw-cell boundary
    a = Cell()
    b = Cell()
    c = Cell(1, a, b)
    ch = Chain(1, Int) + (a,1) + (b,-1)
    @testset "Complex boundary chain" for (a,b) in zip(boundary(c), ch)
        @test a == b
    end

    # cube boundary
    c = Cube([1,1]) # 2-cube
    ch = Chain(1, Int) + (Cube([0,1]), -1) + (Cube([1,0]), 1)
    @testset "Complex boundary chain" for (a,b) in zip(boundary(c), ch)
        @test a == b
    end

end


# This file includes the theoretical functions of the ber performance cureves.

export berask, berpam, berpsk, berfsk, berqam, plotber

"""
    $SIGNATURES

Returns the probability of symbol error for the snr per bit `γb` and constallation size `M` for ASK and PAM signalling
"""
berask(γb, M) = 2 * (1 - 1 / M) * Q(sqrt(6log2(M) / (M^2 - 1) * dbtoval(γb)))
berpam = berask

"""
    $SIGNATURES

Returns the probability of symbol error for the snr per bit `γb` and constallation size `M` for PSK signalling
"""
berpsk(γs, M) = 1 - quadgk(θ -> pθ(θ, dbtoval(γs)), -π/M, π/M)[1]
pθ(θ, γs) = 1 / (2π) * exp(-γs * sin(θ) * sin(θ)) * quadgk(υ -> integrand(υ, γs, θ), 0, Inf)[1]
integrand(υ, γs, θ) = υ * exp(-1 / 2 * (υ - sqrt(2 * γs) * cos(θ))^2)

"""
    $SIGNATURES

Returns the probability of symbol error for the snr per bit `γb` and constallation size `M` for FSK signalling
"""
function berfsk(γs, M)
    k = log2(M)
    pe = 1 / sqrt(2π) * quadgk(x -> (1 - (1  - Q(x))^(M - 1)) * exp(-1/2 * (x - sqrt(2 * dbtoval(γs)))^2), -Inf, Inf)[1]
    # # Return probability of symbol error, not probability of bits error
    # pb = 2^(k -1) / (2^k - 1) * pe
end

"""
    $SIGNATURES

Returns the probability of symbol error for the snr per bit `γb` and constallation size `M` for QAM signalling
"""
function berqam(γb, M)
    qval = Q(√(3log2(M) / (M - 1) * dbtoval(γb)))
    4 * (1 - 1 / √(M)) * qval * (1 - (1 - 1 / √(M)) * qval)
end

#------------------------ Plots ---------------------------------------
"""
    $SIGNATURES

Plots the probability of symbol error versus snr per bit for the signaling `scheme`. `snr_per_bit_range` is the snr per bit range and `krange` is the symbol size of the M-ary signalling where ``M=2^k``.
"""
function plotber(;scheme="ASK", snr_per_bit_range=collect(-4 : 1 : 20), krange = 1 : 4,
    pltkwargs...)
    # Extract the ber function
    schemes = ["ASK", "PSK", "QAM", "FSK"]
    scheme in schemes || throw(ArgumentError("Expected `$schemes`, got `$scheme` instead"))
    @eval pe_vs_snr = $(Symbol("ber" * lowercase(scheme)))

    # Plot
    plt = plot(xlabel="γb", ylabel="Pe", title="M-$(scheme)", pltkwargs...)
    for k in krange
        M = 2^k
        γs = k * dbtoval.(snr_per_bit_range)
        plot!(snr_per_bit_range,  pe_vs_snr.(γs, M), yscale=:log10, label="M=$M")
    end
    plt
end


# The sum of the squares of the first ten natural numbers is,
#
# 1^2 + 2^2 + ... + 10^2 = 385
#
# The square of the sum of the first ten natural numbers is,
#
# (1 + 2 + ... + 10)^2 = 552 = 3025
#
# Hence the difference between the sum of the squares of the first ten natural
# numbers and the square of the sum is 3025 − 385 = 2640.
#
# Find the difference between the sum of the squares of the first one hundred
# natural numbers and the square of the sum.

using ProjectEulerSolutions

# Brute force solution, using Julia built-ins.
function p006solution_sequence(n::Integer=1)::Integer
    seq = 1:n
    return sum(seq)^2 - sum(seq.^2)
end

# Closed form solution for the sum of a sequence and the sum of a sequence of
# squares.
function p006solution_closedform(n::Integer=1)::Integer
    return (n*(n+1)/2)^2 - (n*(n+1)*(2n+1)/6)
end

p006 = Problems.Problem(Dict("Closed form" => p006solution_closedform,
                             "Sequence" => p006solution_sequence))

Problems.benchmark(p006, 100)


#=
Problem 3
The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143 ?
=#

import Primes.factor

target = 600851475143
factors = collect(keys(factor(target)))

maxFact = maximum(factors)

println("The greatest prime factor is: $(maxFact)")


#numerical 1d integration

#integrate func below
f(x) =sin(x)
#over
xmin = -π
xmax = π
#with number of divisions (linear)
n=1000

#Trapeziod rule
Δ = (xmax - xmin)/n
xseq = range(xmin,xmax,length = n+1)
s_trapeziod = Δ/2 *(sum(f.(xseq))+sum(f.(xseq[2:end-1])))

#error of trapeziod estimation for constinuous second differentiable f
#e_trapeziod = (xmax - xmin)^3/12*n^2 * max(abs(f))
# see this slide for more info
# https://slideplayer.com/slide/6419579/

#Mid-point rule
s_Midp = sum(f.(xseq[2:end] - xseq[1:end-1]))*Δ

# n has to be even, t.s., n+1 is odd, there are n+1 terms to play with
iseven(n)

s_Simpson = Δ/3 * sum([[1]; repeat([4; 2; 4; 1], Int(n/4))] .* f.(xseq))