"""
  Kinetic energy
"""
function Eₖ(uh,h,dΩ)
  0.5*sum(∫(uh⋅uh*h)dΩ)
end

"""
  Potential energy
"""
function Eₚ(a,b,c,dΩ)
  c*sum(∫(a*b)dΩ)
end

"""
  Total energy
"""
function Eₜ(uh,H,hh,g,dΩ)
  Eₖ(uh,H,dΩ)+Eₚ(hh,hh,0.5*g,dΩ)
end

"""
  Kinetic to potential
"""
function compute_kin_to_pot!(w,unv,divvh,hnv)
  mul!(w,divvh,hnv)
  unv⋅w
end

"""
  Potential to kinetic
"""
function compute_pot_to_kin!(w,hnv,qdivu,unv)
   mul!(w,qdivu,unv)
   hnv⋅w
end

"""
  Total Mass
"""
function compute_total_mass!(w,L2MM,hh)
  mul!(w,L2MM,hh)
  sum(w)
end

"""Write scalar diagnostics to csv.
Diagnostics should be added as kwargs: field=values"""
function write_to_csv(csv_file_path; kwargs...)
  df = DataFrame(kwargs... )
  header = names(df)
  CSV.write(csv_file_path, df, header=header)
end

"""Append line to existing csv file.
row should be given as kwargs: field=value"""
function append_to_csv(csv_file_path; kwargs...)
  df = DataFrame(kwargs )
  CSV.write(csv_file_path, df, delim=",", append=true)
end

"""Create a .cvs file header. header should be specified as args"""
function initialize_csv(csv_file_path, args...)
  header = [String(_) for _ in args]
  CSV.write(csv_file_path,[], writeheader=true, header=header)
end

"""Wrapper to get a vector from a csv field. The fieldname argument should be passed as a symbol,
i.e. :<fieldname>"""
function get_scalar_field_from_csv(csv_file_path, fieldname)
  t = CSV.read(csv_file_path, DataFrame)
  getproperty(t, fieldname)
end

"""
  Full diagnostics for the shallow water equations (mass, vorticity, kinetic energy, potential energy, power)
"""

function compute_diagnostics_shallow_water!(h_tmp, w_tmp,
                                         model, dΩ, dω, S, L2MM, H1MM,
					 h, u, w, ϕ, F, h2, c)
  mass_i = compute_total_mass!(h_tmp, L2MM, get_free_dof_values(h))
  vort_i = compute_total_mass!(w_tmp, H1MM, get_free_dof_values(w))
  kin_i  = Eₖ(u,h,dΩ)
  pot_i  = Eₚ(h,h2,c,dΩ)
  pow_i  = sum(∫(ϕ*DIV(F))dω)

  mass_i, vort_i, kin_i, pot_i, pow_i
end

function dump_diagnostics_shallow_water!(h_tmp, w_tmp,
                                         model, dΩ, dω, S, L2MM, H1MM,
                                         h, u, w, ϕ, F, g, step, dt,
                                         output_file, dump_on_screen)

  mass_i, vort_i, kin_i, pot_i, pow_i = compute_diagnostics_shallow_water!(
                                          h_tmp, w_tmp,
                                          model, dΩ, dω, S, L2MM, H1MM,
                                          h, u, w, ϕ, F, h, 0.5*g)

  append_to_csv(output_file;
                time       = step*dt/24/60/60,
                mass       = mass_i,
                vorticity  = vort_i,
                kinetic    = kin_i,
                potential  = pot_i,
                power      = pow_i)

  if dump_on_screen
    @printf("%5d %14.9e %14.9e %14.9e %14.9e %14.9e %14.9e\n",
             step, mass_i, vort_i, kin_i, pot_i, kin_i+pot_i, pow_i)
  end
end

function dump_diagnostics_thermal_shallow_water!(h_tmp, w_tmp,
                                         model, dΩ, dω, S, L2MM, H1MM,
                                         h, u, E, w, ϕ, F, eF, step, dt,
                                         output_file, dump_on_screen)

  mass_i, vort_i, kin_i, pot_i, pow_k2p_i = compute_diagnostics_shallow_water!(
                                              h_tmp, w_tmp,
                                              model, dΩ, dω, S, L2MM, H1MM,
                                              h, u, w, ϕ, F, E, 0.5)

  buoy_i    = compute_total_mass!(h_tmp, L2MM, get_free_dof_values(E))
  pow_k2i_i = 0.5*sum(∫(h*DIV(eF))dω)

  append_to_csv(output_file;
                time       = step*dt/24/60/60,
                mass       = mass_i,
                vorticity  = vort_i,
                buoyancy   = buoy_i,
                kinetic    = kin_i,
                internal   = int_i,
                power_k2p  = pow_k2p_i,
                power_k2i  = pow_k2i_i)

  if dump_on_screen
    @printf("%5d %14.9e %14.9e %14.9e %14.9e %14.9e %14.9e %14.9e %14.9e\n",
             step, mass_i, vort_i, buoy_i, kin_i, int_i, kin_i+int_i, pow_k2p_i, pow_k2i_i)
  end
end


#Grid_vs_Bayes_XOR.jl

#Here we will optimize the learning rate and sigmoid parameter for the XOR neural network

include("XOR_MD.jl")
include("Kernals.jl")
include("gaussian_process.jl")

#Set the random seed:
srand(1234) #Seed for stalzer srand(1234)

"""
We will compare the effect of randomly selecting a learning rate and sigmoid
hyperparemeter vs the use of Bayesian Optimization for finding the optimial LR on the MSE.
Suppose we have limited computing time of 100000 epochs and that we haveju N tries to
minimise the MSE. Let us say that the learning rate is between a and b

"""

#Initialise Layers and params ==========================================

Layer_1=uniform(0,1,2,2)

Layer_2=uniform(0,1,2,1)

epoch_vec=linspace(100,1000,10)   # For the final report up this to 100 and leave for 10 minute to get smoothest graph
epoch_bayes_result=zeros(epoch_vec)
epoch_random_result=zeros(epoch_vec)

a=0.001  #Change to 0.001
b=1

c=0.001 #Was the same as above
d=1 #Change back to what it was before which was same as a, b

N=20

#Curry the sigmoid functions:

function hyper_curry(h)
    return (x->sigmoid(x,h))
end

function hyper_curry_deriv(h)
    return (x->sigmoid_deriv(x,h))
end

Random_Learning_Rates=uniform(a,b,N,1)
Random_Hyperparameters=uniform(c,d,N,1)
Random_Mat=cat(2,Random_Learning_Rates,Random_Hyperparameters)
Random_MSE=zeros(N)

timingsrand=zeros(length(epoch_vec))
timingsbays=zeros(length(epoch_vec))
convergence=zeros(length(epoch_vec))

for p=1:length(epoch_vec)

    epochs=epoch_vec[p]

    # Random Learning Rates Examples ========================================

    #Random_Mat conjoins Random_Learning_Rates and Random_Hyperparameters
    # Random_Mat is a Nx2 matrix where Random_Mat[1,:] is the first entry
    #with LR_1 and hyperparemeter 1.

    tic()
    for i=1:length(Random_Learning_Rates)
        node_function=hyper_curry(Random_Mat[i,2])
        node_deriv=hyper_curry_deriv(Random_Mat[i,2])
        learning_rate=Random_Mat[i,1]
        Random_MSE[i]=Train_Neural_Net_Loop(epochs,Layer_1,Layer_2,learning_rate,node_function,node_deriv)[3]
        println("Epoch Complete")
    end

    println("Random Learning Rates Training Completed")
    q=toc()
    timingsrand[p]=q

    #Bayesian Optimization Examples===================================================

    #Here are the points we can pick from in the Optimization

    LR_Test=linspace(a,b,40)
    HP_Test=linspace(c,d,40)

    #Here is the carteisan product of these written as a vector
    Test=gen_points([LR_Test,HP_Test])[1]

    #We first have to pick a random point to begin bayesian optimization:

    #currently starts with the midpoint, possibly randomise this:
    Bayesian_Points=[Test[Int(round(length(Test)/2))]]

    #Bayesian_Points is an vector of arrays where in each array first entry is LR second entry is Hyper-Parameters:

    #Define hyperparemeter functions:
    node_function=hyper_curry(Bayesian_Points[1][2])
    node_deriv=hyper_curry_deriv(Bayesian_Points[1][2])

    #Define Learning Rate:
    learning_rate=Bayesian_Points[1][1]

    #Run first train before Bayesian Optimization:
    Bayesian_MSE=[Train_Neural_Net_Loop(epochs,Layer_1,Layer_2,learning_rate,node_function,node_deriv)[3]]

    #Begin Bayesian Optimization:
    tic()
    for k=2:N
        D=[(Bayesian_Points[i],Bayesian_MSE[i]) for i=1:length(Bayesian_Points)]
        mu, sigma, D=gaussian_process_chol(std_exp_square_ker,D,1e-6,Test)
        println("Gaussian Process Complete","\r")
        mu=reshape(mu,length(mu));
        sigma=reshape(sigma,length(sigma))

        new_point=findmin(mu-sigma)[2]

        #Here we will need to change the number 2 to k
        Bayesian_Points=cat(1,Bayesian_Points,[Test[new_point]])

        learning_rate=Bayesian_Points[k][1]

        node_function=hyper_curry(Bayesian_Points[k][2])

        node_deriv=hyper_curry_deriv(Bayesian_Points[k][2])

        value_to_be_appended=Train_Neural_Net_Loop(epochs,Layer_1,Layer_2,learning_rate,node_function,node_deriv)[3]

        if value_to_be_appended !=Bayesian_MSE[k-1]
            Bayesian_MSE=cat(1,Bayesian_MSE,[value_to_be_appended])
            println("Epoch Complete")
            if k==N
                convergence[p]=N
            end

        else
            println("Found Optimum on the ", k-1, " iteration of ", N, " iterations")
            Bayesian_Points=Bayesian_Points[1:length(Bayesian_Points)-1]
            convergence[p]=k-1

            break
        end

    end
    q=toc()
    timingsbays[p]=q

    # Bayesian Plotting =========================================================

    println(" The optimium is located at ",Bayesian_Points[end])

    epoch_random_result[p]=minimum(Random_MSE)
    epoch_bayes_result[p]=minimum(Bayesian_MSE)

    #Move this to the bottom
    println("Bayesian_Learning_Rates Training Complete")
    println("The minimum MSE by Bayesian Optimization was", minimum(Bayesian_MSE))
    println("The mininmum MSE by Random Selection was", minimum(Random_MSE))

    println("completed cycle ",p, " out of overall cycle", length(epoch_vec))
end

using PyPlot
# fig = figure("pyplot_subplot_mixed",figsize=(7,7))
# ax=axes()
plot(epoch_vec,epoch_bayes_result,label="Bayesian Optimization")

plot(epoch_vec,epoch_random_result,label="Random Grid Search",alpha=0.7)
title("MSE Plot for different epochs")
xlabel("Epochs")
ylabel("MSE")
legend(loc="upper right",fancybox="true")
grid("on")
show()

using PyPlot

plot(epoch_vec,timingsrand,label="Random Timings")
plot(epoch_vec,timingsbays, label="Bayesian Op Timings")
title("Timings")
xlabel("Epochs")
ylabel("Time (s)")
legend(loc="upper right",fancybox="true")
grid("on")
show()

using PyPlot
plot(epoch_vec,convergence)
title("convergance")
xlabel("Epochs")
ylabel("convergance val")
grid("on")
show()


n, p = 500, 5
((X, y, θ), (X1, y1, θ1)) = generate_continuous(n, p; seed=525)

# NOTE: in these cases, if available, θ_newton is used as reference.
# so if the others are not far, it's assumed all have converged to the
# same (correct) minimizer. The reference J(θ) or J(θ1) is the objective
# at the generating θ; it should usually be >
# these are tests that should be considered as "sanity check" and are not
# stress tests testing corner cases.

@testset "HuberReg" begin
    # No intercept
    δ = 0.01
    λ = 3.0
    hr = HuberRegression(δ, λ; fit_intercept=false)
    J = objective(hr, X, y)
    o = RobustLoss(Huber(δ)) + λ * L2Penalty()
    @test J(θ) == o(y, X*θ, θ)
    θ_newton   = fit(hr, X, y, solver=Newton())
    θ_newtoncg = fit(hr, X, y, solver=NewtonCG())
    θ_lbfgs    = fit(hr, X, y, solver=LBFGS())
    θ_iwls     = fit(hr, X, y, solver=IWLSCG())
    @test (J(θ)          - 10.60971) ≤ 1e-5
    @test (J(θ_newton)   -  7.70113) ≤ 1e-5
    @test (J(θ_newtoncg) -  7.70113) ≤ 1e-5
    @test (J(θ_lbfgs)    -  7.70113) ≤ 1e-5
    @test (J(θ_iwls)     -  7.70113) ≤ 1e-5

    δ = 0.01
    λ = 3.0
    hr = HuberRegression(δ, λ; penalize_intercept=true)
    J = objective(hr, X, y1)
    o = RobustLoss(Huber(δ)) + λ * L2Penalty()
    @test J(θ1) == o(y1, X1*θ1, θ1)
    θ_newton   = fit(hr, X, y1, solver=Newton())
    θ_newtoncg = fit(hr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(hr, X, y1, solver=LBFGS())
    θ_iwls     = fit(hr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 16.36661) ≤ 1e-5
    @test (J(θ_newton)   - 10.51384) ≤ 1e-5
    @test (J(θ_newtoncg) - 10.51384) ≤ 1e-5
    @test (J(θ_lbfgs)    - 10.51384) ≤ 1e-5
    @test (J(θ_iwls)     - 10.51384) ≤ 1e-5

    # don't penalize intercept
    δ = 0.01
    λ = 3.0
    hr = HuberRegression(δ, λ)
    J = objective(hr, X, y1)
    θ_newton   = fit(hr, X, y1, solver=Newton())
    θ_newtoncg = fit(hr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(hr, X, y1, solver=LBFGS())
    θ_iwls     = fit(hr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 8.994865) ≤ 1e-5
    @test (J(θ_newton)   - 6.810135) ≤ 1e-5
    @test (J(θ_newtoncg) - 6.810135) ≤ 1e-5
    @test (J(θ_lbfgs)    - 6.810135) ≤ 1e-5
    @test (J(θ_iwls)     - 6.810135) ≤ 1e-5
end

# NOTE: small difference obtained with NCG and IWLS, might just be
# that the algorithm stops a bit earlier, negligible relative tol anyway.
@testset "AndrewsReg" begin
    δ = 0.1
    λ = 3.0
    rr = RobustRegression(rho=Andrews(δ), lambda=λ; penalize_intercept=true)
    J = objective(rr, X, y1)
    o = RobustLoss(AndrewsRho(δ)) + λ * L2Penalty()
    @test J(θ1) == o(y1, X1*θ1, θ1)
    θ_newton   = fit(rr, X, y1, solver=Newton())
    θ_newtoncg = fit(rr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(rr, X, y1, solver=LBFGS())
    θ_iwls     = fit(rr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 16.11921) ≤ 1e-5
    @test (J(θ_newton)   -  0.49078) ≤ 1e-5
    @test (J(θ_newtoncg) -  0.49078) ≤ 1e-3
    @test (J(θ_lbfgs)    -  0.49078) ≤ 1e-5
    @test (J(θ_iwls)     -  0.49078) ≤ 1e-3
end

@testset "BisquareReg" begin
    δ = 0.1
    λ = 3.0
    rr = RobustRegression(rho=Bisquare(δ), lambda=λ; penalize_intercept=true)
    J = objective(rr, X, y1)
    o = RobustLoss(BisquareRho(δ)) + λ * L2Penalty()
    @test J(θ1) == o(y1, X1*θ1, θ1)
    θ_newton   = fit(rr, X, y1, solver=Newton())
    θ_newtoncg = fit(rr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(rr, X, y1, solver=LBFGS())
    θ_iwls     = fit(rr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 16.54073) ≤ 1e-5
    @test (J(θ_newton)   -  0.82180) ≤ 1e-5
    @test (J(θ_newtoncg) -  0.82180) ≤ 1e-5
    @test (J(θ_lbfgs)    -  0.82180) ≤ 1e-5
    @test (J(θ_iwls)     -  0.82180) ≤ 1e-5
end

@testset "LogisticRReg" begin
    δ = 1.5
    λ = 1.0
    rr = RobustRegression(rho=Logistic(delta=δ), lambda=λ, penalize_intercept=true)
    J = objective(rr, X, y1)
    o = RobustLoss(LogisticRho(δ)) + λ * L2Penalty()
    @test J(θ1) ≈ o(y1, X1*θ1, θ1)
    θ_newton = fit(rr, X, y1, solver=Newton())
    θ_newtoncg = fit(rr, X, y1, solver=NewtonCG())
    θ_lbfgs = fit(rr, X, y1, solver=LBFGS())
    θ_iwls  = fit(rr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 7.69833) ≤ 1e-5
    @test (J(θ_newton)   - 7.66289) ≤ 1e-5
    @test (J(θ_newtoncg) - 7.66289) ≤ 1e-5
    @test (J(θ_lbfgs)    - 7.66289) ≤ 1e-5
    @test (J(θ_iwls)     - 7.66289) ≤ 1e-5
end

@testset "FairReg" begin
    δ = 0.1
    λ = 3.0
    rr = RobustRegression(rho=Fair(δ), lambda=λ, penalize_intercept=true)
    J = objective(rr, X, y1)
    o = RobustLoss(Fair(δ)) + λ * L2Penalty()
    @test J(θ1) ≈ o(y1, X1*θ1, θ1)
    θ_newton   = fit(rr, X, y1, solver=Newton())
    θ_newtoncg = fit(rr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(rr, X, y1, solver=LBFGS())
    θ_iwls     = fit(rr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 17.26553) ≤ 1e-5
    @test (J(θ_newton)   - 16.96553) ≤ 1e-5
    @test (J(θ_newtoncg) - 16.96553) ≤ 1e-5
    @test (J(θ_lbfgs)    - 16.96553) ≤ 1e-5
    @test (J(θ_iwls)     - 16.96553) ≤ 1e-5
end

# NOTE: small difference obtained with NCG and IWLS, might just be
# that the algorithm stops a bit earlier, negligible relative tol anyway.
@testset "TalwarReg" begin
    δ = 0.1
    λ = 3.0
    rr = RobustRegression(rho=Talwar(δ), lambda=λ, penalize_intercept=true)
    J = objective(rr, X, y1)
    o = RobustLoss(Talwar(δ)) + λ * L2Penalty()
    @test J(θ1) == o(y1, X1*θ1, θ1)
    θ_newton   = fit(rr, X, y1, solver=Newton())
    θ_newtoncg = fit(rr, X, y1, solver=NewtonCG())
    θ_lbfgs    = fit(rr, X, y1, solver=LBFGS())
    θ_iwls     = fit(rr, X, y1, solver=IWLSCG())
    @test (J(θ1)         - 17.25562) ≤ 1e-5
    @test (J(θ_newton)   -  2.44343) ≤ 1e-5
    @test (J(θ_newtoncg) -  2.44343) ≤ 2e-3
    @test (J(θ_lbfgs)    -  2.44343) ≤ 1e-5
    @test (J(θ_iwls)     -  2.44343) ≤ 2e-3
end

###########################
## With Sparsity penalty ##
###########################

n, p = 500, 100
((X, y, θ), (X1, y1, θ1)) = generate_continuous(n, p;  seed=51112, sparse=0.1)
# pepper with outliers
y1a  = outlify(y1, 0.1)

@testset "Robust+L1" begin
    δ  = 0.1
    λ  = 5.0
    γ  = 10.0

    # this is a nice case where the robust is actually smooth
    rr = RobustRegression(rho=Huber(δ), lambda=λ, gamma=γ, penalize_intercept=true)
    J  = objective(rr, X, y1a)
    θ_ls    = X1 \ y1a
    θ_fista = fit(rr, X, y1a, solver=FISTA())
    θ_ista  = fit(rr, X, y1a, solver=ISTA())
    @test isapprox(J(θ_ls),    391.66463, rtol=1e-5)
    @test isapprox(J(θ_fista),  99.10028, rtol=1e-5) # <- ref value
    @test isapprox(J(θ_ista),   99.10028, rtol=1e-5) # ista stops a bit early?
    @test nnz(θ_ls)    == 101
    @test nnz(θ_fista) == 6
    @test nnz(θ_ista)  == 6
end


using Multivectors, Combinatorics

for comb in combinations([3, 3x̂, 3x̂ŷ, 3ı̂])
    length(comb) < 2 && continue
    println(comb)
    println("+:\t", +(comb...))
    println("-:\t", -(comb...))
end


using Revise, FourierGPE
gr(titlefontsize=12,size=(500,300),transpose=true,colorbar=false)

# Units
# this example works in oscillator units

function jrphase(x,y)
    return 4x/(1.5 + (2y^2)+x^2)
end
## Initialize simulation

L = (30.0,30.0)
N = (128,128)
sim = Sim(L,N)
@unpack_Sim sim

## simulation parameters
μ = 60.0
ξ = 1/sqrt(μ)
offset = 2.0
# potential
import FourierGPE.V
V1 = 5*μ
sig = 3*ξ
V(x,y,t) = 0.5*(x^2 + y^2) + V1*exp(-((x-offset)^2 + y^2)/sig^2)

# TF state
ψ0(x,y,μ,g) = sqrt(μ/g)*sqrt(max(1.0-V(x,y,0.0)/μ,0.0)+im*0.0)

# make initial state
x,y = X
ψi = ψ0.(x,y',μ,g)
ϕi = kspace(ψi,sim)

@pack_Sim! sim

## evolve and plot final state
sol = runsim(sim)
ϕg = sol[end]
ψg = xspace(ϕg,sim)
showpsi(x,y,ψg)

## imprint and evolve JR for short time
# shift = sig
# ψjr = @. ψg*exp(-im*jrphase((x - offset-shift)/ξ,y'/ξ))
# showpsi(x,y,ψjr)
# ϕi = kspace(ψjr,sim)
# γ = 0.0
# tf = .3
# t = LinRange(ti,tf,Nt)
#
# simjr = Sim(L,N)
# @pack! simjr = tf,t,γ,ϕi

## try jump of obstacle
shift = -2*sig
# ψjr = @. ψg*exp(-im*jrphase((x - offset-shift)/ξ,y'/ξ))

V(x,y,t) = 0.5*(x^2 + y^2) + V1*exp(-((x-offset-shift)^2 + y^2)/sig^2)

ϕi = kspace(ψg,sim)
γ = 0.0
tf = .3
t = LinRange(ti,tf,Nt)

simjr = Sim(L,N)
@pack! simjr = tf,t,γ,ϕi

## evolve
soljr = runsim(simjr)

ϕf = soljr[end]
ψf = xspace(ϕf,simjr)
showpsi(x,y,ψf)

anim = @animate for i=1:Nt-6
    ψ = xspace(soljr[i],simjr)
    showpsi(x,y,ψ)
end

gif(anim,"./examples/jr.gif"),fps=30)


using BrainFlow

# specify logging library to use
BrainFlow.enable_dev_logger(BrainFlow.BOARD_CONTROLLER)

params = BrainFlowInputParams()
board_shim = BrainFlow.BoardShim(BrainFlow.SYNTHETIC_BOARD, params)

BrainFlow.prepare_session(board_shim)
BrainFlow.start_stream(board_shim)
sleep(5)
BrainFlow.stop_stream(board_shim)
data = BrainFlow.get_current_board_data(32, board_shim)
BrainFlow.release_session(board_shim)

eeg_channels = BrainFlow.get_eeg_channels(BrainFlow.SYNTHETIC_BOARD)
sampling_rate = BrainFlow.get_sampling_rate(BrainFlow.SYNTHETIC_BOARD)

data_first_channel = data[eeg_channels[1], :]
println("Original Data First Channel")
println(data_first_channel)
BrainFlow.perform_lowpass(data_first_channel, sampling_rate, 10.0, 3, BrainFlow.BUTTERWORTH, 0.0)
println("After LowPass Filter")
println(data_first_channel)

data_second_channel = data[eeg_channels[2], :]
println("Original Data Second Channel")
println(data_second_channel)
BrainFlow.perform_highpass(data_second_channel, sampling_rate, 5.0, 3, BrainFlow.CHEBYSHEV_TYPE_1, 1.0)
println("After HighPass Filter")
println(data_second_channel)

data_third_channel = data[eeg_channels[3], :]
println("Original Data Third Channel")
println(data_third_channel)
BrainFlow.perform_bandpass(data_third_channel, sampling_rate, 25.0, 20.0, 3, BrainFlow.BESSEL, 0.0)
println("After BandPass Filter")
println(data_third_channel)

data_fourth_channel = data[eeg_channels[4], :]
println("Original Data Fourth Channel")
println(data_fourth_channel)
BrainFlow.perform_bandstop(data_fourth_channel, sampling_rate, 50.0, 2.0, 3, BrainFlow.BESSEL, 0.0)
println("After BandStop Filter")
println(data_fourth_channel)


using Test, Random, LSHFunctions

include(joinpath("..", "utils.jl"))

#========================
Tests
========================#

@testset "Monte Carlo function hashing tests" begin
    Random.seed!(RANDOM_SEED)

    @testset "Construct MonteCarloHash" begin
        # Hash L^2([0,1]) over cosine similarity
        # Sampler μ() chooses a point in [0,1] uniformly at random
        μ() = rand()
        hashfn = MonteCarloHash(cossim, μ)

        @test n_hashes(hashfn) == 1
        @test hashfn.μ == μ
        @test similarity(hashfn) == cossim
        @test hashtype(hashfn) == hashtype(LSHFunction(cossim))

        # Hash L^1([0,1]) over L^1 distance
        hashfn = MonteCarloHash(L1, μ, 10)

        @test n_hashes(hashfn) == 10
        @test similarity(hashfn) == L1
        @test hashtype(hashfn) == hashtype(LSHFunction(ℓ1))
    end

    @testset "Pass invalid similarity function" begin
        # Attempt to construct a MonteCarloHash with an invalid
        # similarity function.
        μ() = rand()

        @test_throws(ErrorException, MonteCarloHash(ℓ1, μ))
        @test_throws(ErrorException, MonteCarloHash(ℓ2, μ))
        @test_throws(ErrorException, MonteCarloHash(jaccard, μ))

        # Construct a hash function (with valid similarity) in the same
        # manner in case the MonteCarloHash API ever changes.
        # This ensures that we won't forget to update these tests.
        _ = MonteCarloHash(L2, μ)
    end

    #==========
    Cosine similarity hashing
    ==========#
    @testset "Hash cosine similarity with trivial inputs" begin
        # Hash over cosine similarity between two functions with cosine similarity
        # zero. The collision rate should be close to 50%.
        f(x) = (0.0 ≤ x ≤ 0.5) ? 1.0 : 0.0;
        g(x) = (0.0 ≤ x ≤ 0.5) ? 0.0 : 1.0;
        hashfn = MonteCarloHash(cossim, rand, 1024)

        @test embedded_similarity(hashfn, f, g) == 0.0

        hf, hg = hashfn(f), hashfn(g)
        @test 0.45 ≤ mean(hf .== hg) ≤ 0.55
    end

    @testset "Hash cosine similarity with nontrivial inputs" begin
        # Test hashing on cosine similarity using step functions defined
        # over [0,N]. The functions are piecewise constant on the intervals
        # [i,i+1], i = 1, ..., N.
        N = 10
        μ() = N * rand()
        hashfn = MonteCarloHash(cossim, μ, 2048)

        @test let success = true, ii = 1
            while success && ii ≤ 128
                f, f_steps = create_step_function(N)
                g, g_steps = create_step_function(N)

                prob = collision_probability(hashfn, cossim(f_steps, g_steps); n_hashes=1)
                hf, hg = hashfn(f), hashfn(g)

                success &= (prob-0.05 ≤ mean(hf .== hg) ≤ prob+0.05)
                ii += 1
            end
            success
        end
    end

    #==========
    L^p distance hashing
    ==========#
    @testset "Hash L^p distance" begin
        # Use step functions to test distances between functions again, since
        # you can map them into R^N isomorphically
        N = 4
        μ() = N * rand()
        hashfn = MonteCarloHash(L1, μ, 1024; volume = N)

        @test let success = true, ii = 1
            while success && ii ≤ 128
                f, f_steps = create_step_function(N)
                g, g_steps = create_step_function(N)

                # Hash collision rate should be close to the probability of
                # collision
                prob = collision_probability(hashfn, ℓ1(f_steps, g_steps); n_hashes=1)
                hf, hg = hashfn(f), hashfn(g)

                success &= (prob-0.05 ≤ mean(hf .== hg) ≤ prob+0.05)
                ii += 1
            end
            success
        end
    end
end


using Gadfly, Distributions

function Q(x) 0.5 * erfc(x / sqrt(2)) end

sdb = [0:0.1:10] # SNR, dB per bit
sb = 10 .^ (sdb / 10) # SNR per bit
sig = sqrt(0.5 ./ sb)
sig1 = 1 ./ sig
PE = Q(sig1)

peplot = plot(x=sdb, y=PE, Geom.line, Scale.y_log10,
              Theme(default_color=color("black")),
              Guide.xticks(ticks=[0:1:10]),
              Guide.xlabel("E/N0 (dB)"), Guide.ylabel("PE"))

draw(PDF("ctKovanikov6.pdf", 4inch, 3inch), peplot)


module QuantizedNoise

export randqn

import Random: GLOBAL_RNG

"""
    randqn([rng,] T::Type{<:Integer}, dims::Integer...; σ=1.0, µ=0.0)
    randqn([rng,] T::Type{<:Integer}, dims::Integer...; std=1.0, mean=0.0)
    randqn([rng,] ::Type{<:Complex{T}}, dims::Integer...; σ=1.0, µ=0.0) where T<:Integer
    randqn([rng,] ::Type{<:Complex{T}}, dims::Integer...; std=1.0, mean=0.0) where T<:Integer

Generate samples from the normal distribution `𝒩(μ,σ²)` quantized and clamped
to the Integer subtype `T`.  Note that the `σ` and `μ` are properties of the
distribution from which the samples are drawn.  They are not necessarily
properties of the returned samples.  For example, quantization will alter the
standard deviation of the output values, so `std(randqn(Int8, 10^6, std=x))` may
be quite different from `x`, especially as `x` approaches (and exceeds)
`typemax(T)`.  Keyword arguments `std` and `mean` may be used instead of `σ` and
`μ`, resp.  If both forms are used, the single character keyword argument takes
precedence.

For complex types, the standard deviation `σ` or `std` may be given as a `Real`
or `Complex`.  If `σ` is given as a `Real`, it specifies the standard deviation
of the complex values (i.e. each real/imaginary component will have a standard
deviarion of `σ/sqrt(2)`). If given as a `Complex`, each real/imaginary
component of `σ` will be the standard deviation of the corresponding components
of the output.  Note that `σ=1.0` is equivalent to `σ=(1+1im)/sqrt(2)`.
Likewise, if `µ` is a `Real`, the complex mean will be `µ+µ*im`.
"""
function randqn(rng, T::Type{<:Integer}, dims::Integer...;
                    std=1.0, σ=std, mean=0.0, µ=mean)
    clamp.(round.(Integer, σ.*Base.randn(rng, Float32, dims...).+µ), T)
end

function randqn(T::Type{<:Integer}, dims::Integer...;
                    std=1.0, σ=std, mean=0.0, μ=mean)
    randqn(GLOBAL_RNG, T, dims...; σ=σ, μ=μ)
end

function randqn(rng, ::Type{<:Complex{T}}, dims::Integer...;
                    std=1.0, σ=std, mean=1.0, µ=mean) where T<:Integer
    σr, σi = (typeof(σ) <: Real) ? (σ, σ)./sqrt(2) : (real(σ), imag(σ))
    µr, µi = (typeof(µ) <: Real) ? (µ, µ) : (real(µ), imag(µ))

    complex.(randqn(rng, T, dims...; σ=σr, μ=μr),
             randqn(rng, T, dims...; σ=σi, μ=μi))
end

function randqn(T::Type{<:Complex{<:Integer}}, dims::Integer...;
                    std=1.0, σ=std, mean=0.0, μ=mean)
    randqn(GLOBAL_RNG, T, dims...; σ=σ, μ=μ)
end

end


using BSON: @save, @load
using PyPlot
using LaTeXStrings
using Optim
using QuadGK
using Statistics

@load "./examples/PhaseTransition/NoisyMeasurement.bson" E
@load "./examples/PhaseTransition/NoiselessMeasurement2.bson" E1000
@load "./examples/PhaseTransition/NoiselessMeasurement500.bson" E3

g(τ) = quadgk(u->(u-τ)^2*sqrt(2/π)*exp(-u^2/2), τ, Inf, rtol=1e-3)[1]
function δ(n, s)
    ρ = s/n
    f(τ) = ρ*(1+τ^2) + (1 - ρ)*g(τ)
    res = optimize(f, 0.0, 1000.0)
    return minimum(res)*n
end

fig, axes = subplots(nrows=1, ncols=1, figsize=(5,5))

# subplot(1,1,1)
# Δ(k, n, s) = max(100, k^2 * (δ(n, s) - n/sqrt(n*s)) );
# tol = 1e-2;
# s = 5;
# ns = collect(50:15:500)
# ticks = [50, 200, 350, 500]
# c = 1.0
# P3 = (E3 .< tol); S3 = sum(P3, dims=3)[:,:,1] ./ 50
# im = imshow(S3, cmap="gray", extent=[50,500,50,500], vmin=0, vmax=1)
# k = 3; ms = map(n->Δ(k,n,s), ns); plot(ns, ms, color="red")
# nl = 50 + mapreduce(x->x<0,+,ns - c*ms)*15
# plot(repeat([nl], 31), ns, color="blue", linestyle="dashed")
# xticks(ticks, fontsize=8)
# yticks(ticks, fontsize=8)
# xlabel("n")
# ylabel("m")
# cb_ax = fig.add_axes([1.55, 0.1, 0.02, 0.8])
# fig.colorbar(im, cax=cb_ax)
# savefig("./examples/PhaseTransition/ralation_m_n.pdf")

# subplot(1,1,1)
# tol = 1e-2;
# Δ(k, n, s) = max(100, k^2 * (δ(n, s) - 0.8*n/sqrt(n*s)) );
# P = (E1000 .< tol); S = sum(P, dims=3)[:,:,1] ./ 10
# im = imshow(S, cmap="gray", vmin=0, vmax=1, extent=[2, 10, 100, 1000], aspect=0.01)
# ks = collect(2:10); n=1000; s=3
# ms = map(k->Δ(k,n,s), ks)
# plot(ks, ms, color="red")
# xticks(collect(2:10), fontsize=8)
# yticks(100*collect(1:10), fontsize=8)
# xlabel("k")
# ylabel("m")
# cb_ax = fig.add_axes([1.55, 0.1, 0.02, 0.8])
# fig.colorbar(im, cax=cb_ax)
# savefig("./examples/PhaseTransition/ralation_m_k.pdf")

subplot(1,1,1)
αs = 0.01 .* collect(1:200)
avg = sum(E, dims=2) ./ 50
s= zeros(200)
for i in 1:200
    s[i] = std(E[i, :],  mean=avg[i])
end
upper = avg + s; lower = avg - s
axes.plot(αs, avg, color="blue")
axes.fill_between(αs, upper[:,1], lower[:,1], facecolor="yellow", alpha=0.5)
xlim([0.0, 2.0])
ylim([0.0, 0.25])
# xticks(collect(2:10), fontsize=8)
# yticks(100*collect(1:10), fontsize=8)
xlabel("noise level, α")
ylabel("maximum absolute error")
savefig("./examples/PhaseTransition/ralation_e_noise.pdf")

display(gcf())


using QuantLib.Time

type FixedRateCoupon{DC <: DayCount} <: Coupon
  couponMixin::CouponMixin{DC}
  paymentDate::Date
  nominal::Float64
  rate::InterestRate
end

FixedRateCoupon{DC <: DayCount}(paymentDate::Date, faceAmount::Float64, rate::InterestRate, accrual_start::Date, accrual_end::Date, ref_start::Date, ref_end::Date, dc::DC, accrual::Float64) =
                FixedRateCoupon(CouponMixin{DC}(accrual_start, accrual_end, ref_start, ref_end, dc, accrual), paymentDate, faceAmount, rate)

## COUPON METHODS ##
amount(coup::FixedRateCoupon) =
        coup.nominal * (compound_factor(coup.rate, accrual_start_date(coup), accrual_end_date(coup), ref_period_start(coup), ref_period_end(coup)) - 1)

calc_rate(coup::FixedRateCoupon) = coup.rate.rate

type FixedRateLeg <: Leg
  coupons::Vector{Union{FixedRateCoupon, SimpleCashFlow}}
  # redemption::SimpleCashFlow
end

function FixedRateLeg(schedule::Schedule, faceAmount::Float64, rate::Float64, calendar::BusinessCalendar, paymentConvention::BusinessDayConvention, dc::DayCount; add_redemption::Bool = true)
  # n = add_redemption ? length(schedule.dates) : length(schedule.dates) - 1
  # coups = Vector{Union{FixedRateCoupon, SimpleCashFlow}}(n)
  #
  # start_date = schedule.dates[1]
  # end_date = schedule.dates[2]
  # payment_date = adjust(calendar, paymentConvention, end_date)
  # #TODO: setup payment adjustments and the like
  # coups[1] = FixedRateCoupon(payment_date, faceAmount, InterestRate(rate, dc, SimpleCompounding(), schedule.tenor.freq), start_date, end_date, start_date, end_date, dc, -1.0)
  #
  # # build coupons
  # count = 2
  # start_date = end_date
  # end_date = count == length(schedule.dates) ? schedule.dates[end] : schedule.dates[count + 1]
  # payment_date = adjust(calendar, paymentConvention, end_date)
  # while start_date < schedule.dates[end]
  #   @inbounds coups[count] = FixedRateCoupon(payment_date, faceAmount, InterestRate(rate, dc, SimpleCompounding(), schedule.tenor.freq), start_date, end_date, start_date, end_date, dc, -1.0)
  #
  #   count += 1
  #   start_date = end_date
  #   end_date = count == length(schedule.dates) ? schedule.dates[end] : schedule.dates[count + 1]
  #   payment_date = adjust(calendar, paymentConvention, end_date)
  # end
  #
  # if add_redemption
  #   @inbounds coups[end] = SimpleCashFlow(faceAmount, end_date)
  # end

  ratesLen = length(schedule.dates) - 1

  ratesVec = fill(rate, ratesLen)

  FixedRateLeg(schedule, faceAmount, ratesVec, calendar, paymentConvention, dc; add_redemption = add_redemption)
end

function FixedRateLeg(schedule::Schedule, faceAmount::Float64, rates::Vector{Float64}, calendar::BusinessCalendar, paymentConvention::BusinessDayConvention, dc::DayCount;
                      add_redemption::Bool = false)
  n = add_redemption ? length(schedule.dates) : length(schedule.dates) - 1
  length(rates) == length(schedule.dates) - 1 || error("mismatch in coupon rates")

  coups = Vector{Union{FixedRateCoupon, SimpleCashFlow}}(n)

  start_date = schedule.dates[1]
  end_date = schedule.dates[2]
  payment_date = adjust(calendar, paymentConvention, end_date)
  #TODO: setup payment adjustments and the like
  coups[1] = FixedRateCoupon(payment_date, faceAmount, InterestRate(rates[1], dc, SimpleCompounding(), schedule.tenor.freq), start_date, end_date, start_date, end_date, dc, -1.0)

  # build coupons
  count = 2
  start_date = end_date
  end_date = count == length(schedule.dates) ? schedule.dates[end] : schedule.dates[count + 1]
  payment_date = adjust(calendar, paymentConvention, end_date)
  while start_date < schedule.dates[end]
    @inbounds coups[count] = FixedRateCoupon(payment_date, faceAmount, InterestRate(rates[count], dc, SimpleCompounding(), schedule.tenor.freq), start_date, end_date, start_date, end_date, dc, -1.0)

    count += 1
    start_date = end_date
    end_date = count == length(schedule.dates) ? schedule.dates[end] : schedule.dates[count + 1]
    payment_date = adjust(calendar, paymentConvention, end_date)
  end

  if add_redemption
    @inbounds coups[end] = SimpleCashFlow(faceAmount, end_date)
  end

  FixedRateLeg(coups)
end

# Coupon methods
get_pay_dates(coups::Vector{Union{FixedRateCoupon, SimpleCashFlow}}) = Date[date(coup) for coup in filter(check_coupon, coups)]
get_reset_dates(coups::Vector{Union{FixedRateCoupon, SimpleCashFlow}}) = Date[accrual_start_date(coup) for coup in filter(check_coupon, coups)]

function accrued_amount(coup::FixedRateCoupon, settlement_date::Date)
  if settlement_date <= accrual_start_date(coup) || settlement_date > coup.paymentDate
    return 0.0
  end

  return coup.nominal *
      (compound_factor(coup.rate, accrual_start_date(coup), min(settlement_date, accrual_end_date(coup)), ref_period_start(coup), ref_period_end(coup)) - 1.0)
end


using Plots

dt = 0.001
T = 20.0
vx_0 = -2
vy_0 = 1
x_0 = 1
y_0 = 1

q1_x = 5
q1_y = 4
q1 = 4
q2_x = -5
q2_y = -5
q2 = 10

function efield()
    t = 0
    x = x_0
    y = y_0
    vx = 0
    vy = 0

    xs = []
    ys = []

    while (t <= T)
        push!(xs, x)
        push!(ys, y)

        t += dt
        rx_1 = q1_x - x
        ry_1 = q1_y - y
        r_1 = sqrt(rx_1^2+ry_1^2)
        vx += ((rx_1*q1)/r_1^3)*dt
        vy += ((ry_1*q1)/r_1^3)*dt

        rx_2 = q2_x - x
        ry_2 = q2_y - y
        r_2 = sqrt(rx_2^2+ry_2^2)
        vx += ((rx_2*q2)/r_2^3)*dt
        vy += ((ry_2*q2)/r_2^3)*dt
        print(vx)

        x += vx * dt
        y += vy * dt

    end

    (xs, ys)
end

xs, ys = efield()

plot(xs, ys, label="", xlabel="x",ylabel="y", color=:cornflowerblue)
savefig("efield.png")


"""
Simple model with 3 variables, 3 shocks and 3 transition equations.
Like E2, but with more lags/leads.

The NL version is artificially made non-linear by taking one of the
equations (pinf[t]=rhs...) and rewriting it as exp(pinf[t])=exp(rhs...).
The solution of E3nl is identical to the linear E3 and can be used for
testing of the linearization solver.
"""
module  E3nl

using ModelBaseEcon

# start with an empty model
model = Model()
model.options.maxiter = 200

# add parameters
@parameters model begin
    cp = [0.5, 0.02]
    cr = [0.75, 1.5, 0.5]
    cy = [0.5, -0.02]
end

# add variables: a list of symbols
@variables model begin
    pinf
    rate
    ygap
end

# add shocks: a list of symbols
@shocks model begin
    pinf_shk
    rate_shk
    ygap_shk
end

# autoexogenize: define a mapping of variables to shocks
@autoexogenize model begin
    pinf = pinf_shk
    rate = rate_shk
    ygap = ygap_shk
end

# add equations: a sequence of expressions, such that
# use y[t+1] for expectations/leads
# use y[t] for contemporaneous
# use y[t-1] for lags
# each expression must have exactly one "="
@equations model begin
    exp(pinf[t])=exp(cp[1]*pinf[t-1]+0.3*pinf[t+1]+0.05*pinf[t+2]+0.05*pinf[t+3]+cp[2]*ygap[t]+pinf_shk[t])
    rate[t]=cr[1]*rate[t-1]+(1-cr[1])*(cr[2]*pinf[t]+cr[3]*ygap[t])+rate_shk[t]
    ygap[t]=cy[1]/2*ygap[t-2]+cy[1]/2*ygap[t-1]+(.98-cy[1])*ygap[t+1]+cy[2]*(rate[t]-pinf[t+1])+ygap_shk[t]
end

# call initialize! to build internal structures
@initialize model

end


"""
    Gauging(level::Real, discharge::Real)

Construct an object of type Gauging
"""
struct Gauging
    level::Real
    discharge::Real
    Gauging(level::T,discharge::T) where {T<:Real} = new(level,discharge)
end

Gauging(level::Real, discharge::Real) = Gauging(promote(level, discharge)...)
Gauging(level::Integer, discharge::Integer,) = Gauging(float(level), float(discharge))
Gauging() = Gauging(0.0, 0.0)

"""
    bootstrap(G::Vector{Gauging})

Resample the gauging for bootstrap.

### Details

Sampling with replacement is performed to compose a gauging sample of the same size.
The smallest gauging is always selected so that the rating curve is valid over the entire gauged range.
"""
function bootstrap(G::Vector{Gauging})

    n = length(G)

    G_bootstrap = sample(G, n-1, replace=true)

    pushfirst!(G_bootstrap, minimum(G))

    return G_bootstrap

end

"""
    crcfit(G::Vector{Gauging})

Fit the compound rating curve corresponding to the gaugings `G`
"""
function crcfit(G::Vector{Gauging})

    h = level.(G)
    q = discharge.(G)

    hs = sort(unique(h))

    fobj(k) = sum( (log.(q) - logdischarge.(crcfit(G,k), h)).^2 )
    res = optimize(fobj, hs[3]+1e-2, hs[end-2]-1e-2)
    k = Optim.minimizer(res)

    crc = crcfit(G, k)

    return crc

end

"""
    crcfit(G::Vector{Gauging},k::Real)

Fit the compound rating curve corresponding to the gaugings `G` using the break-point `k`.

### Details

At least 3 gaugings are necessary by component of the compound rating curve.
"""
function crcfit(G::Vector{Gauging}, k::Real)

    h = level.(G)
    q = discharge.(G)

    ind = h.>k

    h2 = h[ind]
    q2 = q[ind]
    G₂ = G[ind]

    h1 = h[.!(ind)]
    q1 = q[.!(ind)]
    G₁ = G[.!(ind)]

    rc₂ = rcfit(G₂)

    # Continuity constraint at h = k
    rc₁ = rcfit(G₁, [k, discharge(rc₂, k)])

    crc = CompoundRatingCurve([k], [rc₁, rc₂] )

    return crc

end

"""
    discharge(G::Gauging)

Return the discharge of gauging `G`
"""
function discharge(G::Gauging)
    return G.discharge
end

"""
    Compute the rough initial values of the rating curve parameters.

### Details
These are rough estimates obtained by setting `c = 3/2`, `b = .9*min(h)`. The parameter `a` is computed with the median level.
"""
function getinitialvalues(G::Vector{Gauging})

    h = level.(G)
    q = discharge.(G)

    c₀ = 1.5
    b₀ = .9*minimum(h)

    n = length(h)
    p = sortperm(h)

    index = p[floor(Int64, n/2)]

    a₀ = q[index] / (h[index] - b₀)^c₀

    rc = RatingCurve(G, a₀, b₀, c₀)

    return rc

end

"""
    level(G::Gauging)

Return the level of gauging `G`
"""
function level(G::Gauging)
    return G.level
end

"""
    minimum(G::Vector{Gauging})

Return the gauging with the smallest level.
"""
function minimum(G::Vector{Gauging})

    ind = argmin(level.(G))

    return G[ind]
end

"""
    maximum(G::Vector{Gauging})

Return the gauging with the highest level.
"""
function maximum(G::Vector{Gauging})

    ind = argmax(level.(G))

    return G[ind]
end

"""
    rcfit(G::Vector{Gauging})

Fit the rating curve to the gaugings `G`.
"""
function rcfit(G::Vector{Gauging})

    h = level.(G)
    q = discharge.(G)

    rc₀ = RatingCurves.getinitialvalues(G)

    y = log.(q)

    fobj(b) = sum( (y - logdischarge.(rcfit(G,b[]), h)).^2 )

    res = optimize(fobj, [-Inf], [minimum(h)-1e-2], [rc₀.b])
    b = Optim.minimizer(res)[]

    rc = rcfit(G, b)

    return rc

end

"""
    rcfit(G::Vector{Gauging}, b::Real)

Fit the rating curve of parameter `b` to the gaugings `G`.
"""
function rcfit(G::Vector{Gauging}, b::Real)

    h = level.(G)
    q = discharge.(G)

    # @assert b < minimum(h)

    x = log.(h .- b)
    y = log.(q)

    X = hcat(ones(length(x)), x)

    β̂ = X\y

    c = β̂[2]

    a = exp(β̂[1])

    rc = RatingCurve(G, a, b, c)

    return rc

end

"""
    rcfit(G::Vector{Gauging}, constraint::AbstractVector{<:Real})

Fit the rating curve to the gaugings `G` passing through the point (h,q) specified in `constraint`.
"""
function rcfit(G::Vector{Gauging}, constraint::AbstractVector{<:Real})

    h = level.(G)
    q = discharge.(G)

    y = log.(q)

    rc₀ = RatingCurves.getinitialvalues(G)

    fobj(b) = sum( (y - logdischarge.(rcfit(G,b[],constraint), h)).^2 )

    res = optimize(fobj, [-Inf], [minimum(h)-1e-2], [rc₀.b])
    b = Optim.minimizer(res)[]

    rc = rcfit(G, b, constraint)

    return rc

end

"""
    rcfit(G::Vector{Gauging}, b::Real, constraint::AbstractVector{<:Real})

Fit the rating curve with parameter `b` to the gaugings `G` passing through the point (h,q) specified in `constraint`.
"""
function rcfit(G::Vector{Gauging}, b::Real, constraint::AbstractVector{<:Real})

    h = level.(G)
    q = discharge.(G)

    # @assert b <= minimum(h)

    h̃ = constraint[1]
    q̃ = constraint[2]

    x̃ = log(h̃ - b)
    ỹ = log(q̃)

    x = log.(h .- b)
    y = log.(q)

    z = x .- x̃

    c = z\(y .- ỹ)

    a = exp(ỹ - x̃*c)

    rc = RatingCurve(G, a, b, c)

    return rc

end

"""
    sort(G::Vector{Gauging}; rev::Bool=false)

Sort the gauging according to the level.

### Details

Sort in ascending order if `rev=false` (option by default) and in descending order if `rev=true`.
"""
function sort(G::Vector{Gauging}; rev::Bool=false)

    h = level.(G)

    Gs = G[sortperm(h, rev=rev)]

    return Gs

end


import GaussQuadrature
using OffsetArrays
using PyPlot
import FractionalTimeDG.graded_mesh

include("FPDE_problem.jl")

Nx = 20
cutoff_t = 1.0
N_outer = 6
mg = 1 + rt / α # mesh grading
Nquad = 15
u(x, t) = refsoln(x, t, α, Nquad)
rx = 4
rt = 3
Mt = 2rt
Mx = 2rx
ppImax = 20

store = Store(α, rt+1, Mt, ppImax)
x_ = collect(range(0, L, length=Nx+1))
x = OffsetVector(x_, 0:Nx)
grid = SpatialGrid(x, rx, ("essential", "essential"), Mx)

U0 = spatial_L2_projection(u0, grid)
t = graded_mesh(max_t, cutoff_t, N_outer, mg)
Nt = length(t) - 1
U = FPDEDG(1.0, f, U0, grid, t, rt, Mt, store)
τ = range(-1, 1, length=ppImax)
pcwise_t, Uerr = L2error(U, u, t, τ, grid, Mx)

jump = evaluate_jumps(U, U0, t, grid, Mx)
Uhat, jumpU = reconstruction(U, U0, store)
pcwise_t, Uhaterr = L2error(Uhat, u, t, τ, grid, Mx)

figure(6)
line1 = semilogy(t[0:Nt-1], abs.(jump[0:Nt-1]), "C0o")
line2 = semilogy(pcwise_t, Uerr, "C1-")
line3 = semilogy(pcwise_t, Uhaterr, "k:")
legend((line1[1], line2[2], line3[1]),
       (L"jump in $U$", L"error in $U$", L"error in $\widehat U$"))
xlabel(L"$t$")
PyPlot.grid(true)
savefig("fig6b.eps")


#========================================================================================#
#	Laboratory 8
#
# Genetic algorithms and refactoring.
#
# Author: Niall Palfreyman, 16/03/2022
#========================================================================================#
[
	Activity(
		"""
		In this laboratory we will use our Casinos module to implement a genetic algorithm
		(GA). I have already written most of the code for you, but you will see that there
		is still some work left for you to do, so let's get started ...

		Our GA will locate the global minima of a function that we shall call the objective
		of the GA. Objective functions can be very complicated, so we want to be able to view
		them graphically to understand them. I have written a module Objectives to define and
		view objective functions. Run the simple demo1() method of the Objectives module now,
		and tell me to the nearest integer value the x-coordinate of the global minimum of
		the 7-th function in our test-suite of Objective functions:

			include("src/Development/SimpleGAs/Objectives.jl")
			using .Objectives
			Objectives.demo1()
		""",
		"The low values in this contour map are colourd deep blue - look to the top-left! :)",
		x -> x==9
	),
	Activity(
		"""
		Objectives.demo2() shows you how to insert your own function into an Objective. How
		many hills does the Valleys function possess within the displayed domain?
		""",
		"The hills are coloured green-yellow",
		x -> x==3
	),
	Activity(
		"""
		Go to the source code of the Objectives module and look at the Objective type definition.
		the first field of this type is the function itself - whether you take this from the test
		suite or insert your own function. What is the name of the second field?
		""",
		"",
		x -> occursin("domain",lowercase(x))
	),
	Activity(
		"""
		Create several different Objective functions and study the structure of their domains.
		What do you think is the dimension of test suite function 1?
		""",
		"The dimension is the number of number-pairs [lwb,upb] in the domain",
		x -> x==1
	),
	Activity(
		"""
		Use the evaluate() method to find the value of Objective(7) at various locations. Note
		that evaluate() evaluates the objective function at several different points at once,
		so it expects a vector of points. So to check the value for the point [1,2], you will
		need to enter:

			evaluate( obj, [[1,2]])

		What do you now think is the y-coordinate of the global minimum of Objective(7)?
		""",
		"You should find that your earlier answer from graphical inspection is correct",
		x -> x==9
	),
	Activity(
		"""
		Create an instance of the 16-th test suite function. Find out its dimensionality:
		""",
		"You can either inspect the domain or you can use the dim() method",
		x -> x==128
	),
	Activity(
		"""
		Clearly, that is a very large number of dimensions! This makes it very difficult to find
		the global minimum of the function. Indeed, this function is the mepi() (maximally
		epistatic) function created by Robert Watson in 2007 to be maximally difficult for GAs
		to solve. To see how difficult it is to find this function's minimum, you can explore the
		mepi function in any number of dimensions (here 3):

			Objectives.mepi([1,0,1])						# Returns the value 8
			Objectives.mepi.([rand(3) for _ in 1:9])		# Returns nine different mepi values

		Play around with the mepi function to discover its minimum value in 4 dimensions:
		""",
		"Try confining your search to vectors with components either 0 or 1",
		x -> x==4
	),
	Activity(
		"""
		To find out why it is so difficult to find mepi's global minimum, try depicting the
		mepi function in all 128 dimensions. Clearly, you cannot produce a 128-dimensional
		graphic, but you can plot a cross-section of mepi's values in 128 dimensions using the
		command

			depict(Objective(16),[])

		Modify the code of the demo1() method to display this cross-section. Suppose we only had
		8 dimensions. In this case the two minima occur at the vector arguments [0,0,0,0,0,0,0,0]
		and [1,1,1,1,1,1,1,1]. The next lowest local minimum occurs at a vector maximally distant
		from both of these two vectors. What is this vector?
		""",
		"How many 0/1 switches are necessary to convert one of these vectors into the other?",
		x -> x==[0,0,0,0,1,1,1,1] || x==[1,1,1,1,0,0,0,0]
	),
	Activity(
		"""
		OK, now let's look at the module Decoders. Our GA will work with genes consisting of
		0/1 values, but we want to optimise objective functions whose arguments consist of
		real numbers. We therefore need a Decoder to convert our GA chromosomes into input
		numbers for an Objective.

		The method Decoders.demo() demonstrates how to use a Decoder to decode the two
		bit-vectors [1,0,1,0,1,0,1,0,0,0,0,1,0,1,1] and [0,1,0,1,0,1,0,1,1,0,1,0,1,1,0]. How
		many bits does each of these two bit-vectors contain?
		""",
		"Count the bits in the vectors! :)",
		x -> x==15
	),
	Activity(
		"""
		Each bit-vector encodes three floating-point values (for example, the three arguments
		of an objective function). How many bits are used to encode each floating-point number?
		""",
		"Length of the bit-vector divided by the number of float values",
		x -> x==5
	),
	Activity(
		"""
		If you look at where the decoder is constructed in the demo() method, you can see that
		it actually specifies a domain with three dimensions and then the number of bits (5)
		that we want to use for encoding each float value. If you then go up to the definition
		of the Decoder constructor, you will see how it converts these two values into the fields
		inside a Decoder.

		Notice in particular how it uses a functional expression (x->x[1]) to pull out all the
		lower-bound values from the domain and store them in the field lwb. What is the lower
		bound of the third dimension of the domain in our demonstration example?
		""",
		"",
		x -> x==-7
	),
	Activity(
		"""
		Now study the code inside the Decoder constructor - it creates a matrix that will be
		stored in the field coeffs of the Decoder. At the Julia prompt, enter the lines that
		calculate the vector qlevels (quantum levels), using the value 5 instead of nbits.

		Notice how qlevels first contains the sequence [1/2,1/4,1/8,1/16,...], and then these
		numbers are divided by their sum, so that finally the sum of all values in qulevels is
		equal to 1. Is qlevels a row vector or a column vector?
		""",
		"",
		x -> occursin("col",lowercase(x))
	),
	Activity(
		"""
		The vector span contains the span-width of each domain dimension, which we will need
		to decode the bits. Investigate whether span is a row vector or a column vector:
		""",
		"",
		x -> occursin("row",lowercase(x))
	),
	Activity(
		"""
		The Decoder constructor calculates the coeffs matrix by multiplying qlevels * span.
		Notice that qlevels has size (5,1), while span has size (1,3). These numbers are
		important: 5 is the accuracy of our bit-coding of numbers, and 3 is the number of
		different dimensions we need to decode. What is the size of coeffs?
		""",
		"",
		x -> x==(5,3)
	),
	Activity(
		"""
		Now comes your work. The result of decoding our two bit-vectors should be the two
		floating-point vectors [1.67742, 3.51613, -2.74194] and [1.32258, 4.41935,  1.51613].
		But if you run demo(), you will see that it calculates a silly result. The reason for
		this is that I have changed the two important lines of code in the method decode(). it
		is your job now to write the necessary code. You will find hints and information in
		the comments. When you are getting the right answers, come back here and reply
		"Yay!!!" ...
		""",
		"",
		x -> x=="Yay!!!"
	),
	Activity(
		"""
		Great - well done! Now it's time to look at the GA itself - the source code is in the
		module SimpleGAs. Once again, the method demo() shows you how to use the SimpleGA, but
		again it won't give you correct answers yet because I've changed the code in a couple
		of places. So let's just start exploring the code.

		As you can see, in demo(), I simply create an Objective and attach it to a SimpleGA,
		then let it run. The main work of finding the minimum of the Objective is done by the
		method evolve!(), so let's go there next.

		By the way, how many iterations of the GA does demo() perform?
		""",
		"It is the second argument of evolve!()",
		x -> x==5000
	),
	Activity(
		"""
		evolve!() is basically just a loop over lots of generations, and in the loop you can
		see that each generation of the SimpleGA (sga) first measures the current fitness of
		sga's population, then uses this information to (a) sort out any elitists that will
		survive unchanged into the next generation, (b) perform selection (i.e. fitness-
		dependent recombination/crossover) and (c) mutate the children in the next generation.

		If you have already successfully implemented the Casinos module, you should now be
		getting a compile-error inside the method SimpleGAs.mutate!(). Go there now and look at
		the first line of code. Here, we call Casinos.draw() to create a random Boolean matrix
		that will tell us which genes to mutate. However, we haven't yet implemented a draw()
		method with 4 arguments. How many arguments does your current draw() method have?
		""",
		"Look at the signature of the method Casinos.draw()",
		x -> x==3
	),
	Activity(
		"""
		First let's look at how to create a Bernoulli matrix of Boolean values with a particular
		(Bernoulli) probability mu of being true. Enter this line at the Julia prompt:

			rand(5,5) < 0.2

		This will give you a random Boolean matrix. Find the number of true elements like this:

			sum(ans)

		Now enter sum(rand(5,5) < 0.2) many times - what is the average result that you get?
		""",
		"You'll need to do it about 10 times to estimate the average value",
		x -> x==5
	),
	Activity(
		"""
		OK, so we can calculate a Bernoulli matrix from a matrix of random floats between 0
		and 1. But we already have a draw() method that produceds random float matrices, so
		you now need to create a new Casinos.draw() method with 4 arguments that calls the
		3-argument method, compares it with the Bernoulli probability mu, and returns the
		resulting Boolean matrix.

		Do this now, then recompile and run SimpleGAs.demo(). Then give me a "Hi 5!" when
		it runs without errors.
		""",
		"",
		x -> occursin('5',x) || occursin("five",lowercase(x))
	),
	Activity(
		"""
		Notice how good this feels: You have brought a broken program to the stage where it
		runs without errors! Of course, the answer you get is not yet correct, but we will
		fix that now ...

		Go to the method SimpleGAs.recombine!(). First notice the code that selects parents
		for recombination according to their fitness. THIS CODE IS TOTALLY COOL! It is not
		easy to understand, but you will need it if you ever want to implement roulette-wheel
		selection for yourself!

		Further down is a loop that actually generates the individual progeny (children)
		sally and billy from their parents mummy[m] and daddy[m]. You will see there are five
		lines of code that you need to write yourself. Do this now according to the explanations
		in the code.

		You can tell when you have succeeded by looking at the results of the GA: The winning
		individual should be close to [9,9], which was your estimate for the location of the
		global minimum of Objective(7). What is the value (to the nearest integer) of the
		objective function at this point?
		""",
		"",
		x -> x==-16
	),
	Activity(
		"""
		Well done! You have now completed the set work for this laboratory, and you have
		created an up-and-running genetic algorithm capable of optimising multi-variable
		functions. Now try out SimpleGA with various other objective functions from our
		test suite. In particular, find out how close you can get to the global minimum of
		mepi() - or Objective(16) - by tuning sga using the following methods:

			mu!(), temperature!(), elite!() and px!()
		""",
		"",
		x -> true
	),
]


# some simple subtypes of graphons

export erdosrenyi

# erdos renyi graphs
function erdosrenyi(p::Number)
	if p>=0 && p<=1
	    return SBM([1], fill(p,1,1))
	else
		error("connection probability p must be between 0 and 1")
	end
end


"""
    Power(exponent) <: Transform

Raise the data by the given `exponent`.
"""
struct Power <: Transform
    exponent::Real
end

cardinality(::Power) = OneToOne()

_apply(x, P::Power; kwargs...) = x .^ P.exponent


# Effective Rate Factor
ERF(::ContinuousCompounding, r::Float64, t::Float64) = t == 0.0 ? 1.0 : exp(r*t)
ERF(::SimpleCompounding, r::Float64, t::Float64) = t == 0.0 ? 1.0 : 1.0 + r*t
ERF(::ExponentialCompounding, r::Float64, t::Float64) = t == 0.0 ? 1.0 : (1.0+r)^t
ERF(ct::CompoundingType, dcc::DayCountConvention, r::Float64, date_start::Date, date_end::Date) = ERF(ct, r, yearfraction(dcc, date_start, date_end))
ERF(curve::AbstractIRCurve, maturity::Date) = ERF(curve_get_compounding(curve), curve_get_daycount(curve), zero_rate(curve, maturity), curve_get_date(curve), maturity)
ERF(curve::AbstractIRCurve, forward_date::Date, maturity::Date) = ERF(curve, maturity) / ERF(curve, forward_date)

ERF_to_rate(::ContinuousCompounding, ERF::Float64, t::Float64) = log(ERF) / t
ERF_to_rate(::SimpleCompounding, ERF::Float64, t::Float64) = (ERF-1.0) / t
ERF_to_rate(::ExponentialCompounding, ERF::Float64, t::Float64) = ERF^(1.0/t) - 1.0
ERF_to_rate(curve::AbstractIRCurve, ERF::Float64, t::Float64) = ERF_to_rate(curve_get_compounding(curve), ERF, t)

discountfactor_to_rate(c::CompoundingType, _discountfactor_::Float64, t::Float64) = ERF_to_rate(c, 1.0 / _discountfactor_, t)

function discountfactor_to_rate(c::CompoundingType, _discountfactor_vec_::Vector{Float64}, t_vec::Vector{Float64})
    l = length(_discountfactor_vec_)
    @assert l == length(t_vec) "_discountfactor_vec_ and t_vec must have the same length. ($l != $(length(t_vec)))"

    result = Vector{Float64}(undef, l)
    for i in 1:l
        @inbounds result[i] = discountfactor_to_rate(c, _discountfactor_vec_[i], t_vec[i])
    end

    return result
end

# Effective Rate = [Effective Rate Factor] - 1
ER(c::CompoundingType, r::Float64, t::Float64) = ERF(c, r, t) - 1.0
ER(ct::CompoundingType, dcc::DayCountConvention, r::Float64, date_start::Date, date_end::Date) = ER(ct, r, yearfraction(dcc, date_start, date_end))
ER(curve::AbstractIRCurve, maturity::Date) = ERF(curve, maturity) - 1.0
ER(curve::AbstractIRCurve, forward_date::Date, maturity::Date) =  ERF(curve, forward_date, maturity) - 1.0

# [Discount Factor] = 1 / [Effective Rate Factor]
discountfactor(ct::CompoundingType, r::Float64, t::Float64) = 1.0 / ERF(ct, r, t)
discountfactor(ct::CompoundingType, dcc::DayCountConvention, r::Float64, date_start::Date, date_end::Date) = discountfactor(ct, r, yearfraction(dcc, date_start, date_end))
discountfactor(curve::AbstractIRCurve, maturity::Date) = 1.0 / ERF(curve, maturity)

# Optimized vector functions for `ERF` and `discountfactor` functions
for fun in (:ERF, :discountfactor)
    @eval begin
        function ($fun)(curve::AbstractIRCurve, maturity_vec::Vector{Date})
            len = length(maturity_vec)
            _zero_rate_vec_ = zero_rate(curve, maturity_vec)
            result = Vector{Float64}(undef, len)
            for i = 1:len
                @inbounds result[i] = $(fun)(curve_get_compounding(curve), curve_get_daycount(curve), _zero_rate_vec_[i], curve_get_date(curve), maturity_vec[i])
            end
            return result
        end
    end
end


using Distributions
using Random

"""
    pnorm(x)

Julia equivalent of R's `pnorm`
"""
function pnorm(x)
    cdf(Normal(), x)
end

"""
    dnorm(x)

Julia equivalent of R's `dnorm`
"""
function dnorm(x)
    pdf(Normal(), x)
end

# """
#     cond_mean_on_LR(L::T, R::T, a1::T, a2::T, a3::T, μ1::T, μ2::T, μ3::T) where T <: Real

# Conditional mean on (L, R), defined as μ_{L,R} = (∫_L^R f(x) dx)^(-1) / (∫_L^R x f(x) dx)
# """
# function cond_mean_on_LR(L::T, R::T, a1::T, a2::T, a3::T, μ1::T, μ2::T, μ3::T) where T <: Real
#     pL = a1 * pnorm(L-μ1) + a2 * pnorm(L-μ2) + a3 * pnorm(L-μ3)
#     pR = a1 * pnorm(R-μ1) + a2 * pnorm(R-μ2) + a3 * pnorm(R-μ3)

#     ∫_L = -(a1 * dnorm(L-μ1) + a2 * dnorm(L-μ2) + a3 * dnorm(L-μ3)) +
#           a1 * μ1 * pnorm(L-μ1) + a2 * μ2 * pnorm(L-μ2) + a3 * μ3 * pnorm(L-μ3)
#     ∫_R = -(a1 * dnorm(R-μ1) + a2 * dnorm(R-μ2) + a3 * dnorm(R-μ3)) +
#           a1 * μ1 * pnorm(R-μ1) + a2 * μ2 * pnorm(R-μ2) + a3 * μ3 * pnorm(R-μ3)

#     μ_LR = (∫_R - ∫_L)/(pR - pL)

#     (pL, pR, ∫_L, ∫_R, μ_LR)
# end

"""
    cond_mean_on_LR(L, R, a1, a2, a3, μ1, μ2, μ3)

Conditional mean on (L, R), defined as μ_{L,R} = (∫_L^R f(x) dx)^(-1) / (∫_L^R x f(x) dx)
"""
function cond_mean_on_LR(L, R, a1, a2, a3, μ1, μ2, μ3)
    # (∫_l^r f(x) dx) = pR - pL
    pL = a1 * pnorm(L.-μ1) + a2 * pnorm(L.-μ2) #+ a3 * pnorm(L.-μ3)
    pR = a1 * pnorm(R.-μ1) + a2 * pnorm(R.-μ2) #+ a3 * pnorm(R.-μ3)

    # (∫_l^r x f(x) dx) = ∫_R - ∫_L
    ∫_L = -(a1 * dnorm(L.-μ1) + a2 * dnorm(L.-μ2)) + # + a3 * dnorm(L.-μ3)) +
          a1 * μ1 * pnorm(L.-μ1) + a2 * μ2 * pnorm(L.-μ2) #+ a3 * μ3 * pnorm(L.-μ3)
    ∫_R = -(a1 * dnorm(R-μ1) + a2 * dnorm(R-μ2)) + # + a3 * dnorm(R-μ3)) +
          a1 * μ1 * pnorm(R-μ1) + a2 * μ2 * pnorm(R-μ2) #+ a3 * μ3 * pnorm(R-μ3)
    ## adding a3/μ3 terms yields inaccurate result.

    # population conditional mean on (L, R)
    μ_LR = (∫_R .- ∫_L) ./ (pR .- pL)

    (pL, pR, ∫_L, ∫_R, μ_LR)
end

"""
    find_split(d, a1, a2, μ1, μ2, L, R;
               a3=0., μ3=0., find_deltas=false, find_split=!find_deltas)

Split the cluster (L, R) into subclusters that maximizes G_{L,R}.
Returns centered conditional mean (deltas) of the left and right subcluster
if `find_deltas` is true. Otherwise return the split point.
"""
function find_split(d, a1, a2, μ1, μ2, L, R;
                    a3=0., μ3=0., find_deltas=false, find_split=!find_deltas)
    # p, int, int_en evaluated at `d`
    ∫_d = -(a1 * dnorm(d.-μ1) + a2 * dnorm(d.-μ2)) + # + a3 * dnorm(d.-μ3)) +
          a1 * μ1 * pnorm(d.-μ1) + a2 * μ2 * pnorm(d.-μ2) #+ a3 * μ3 * pnorm(d.-μ3)
    p_d = a1 * pnorm(d.-μ1) + a2 * pnorm(d.-μ2) #+ a3 * pnorm(d.-μ3)

    pL, pR, ∫_L, ∫_R, μ_LR = cond_mean_on_LR(L, R, a1, a2, a3, μ1, μ2, μ3)

    # find split point with normlized G'_{L,R} (=h)(supp. p.32)
    h = μ_LR .* (p_d .- pL).^2 + (pL + pR .- 2p_d) .* (∫_d .- ∫_L) - d .* (p_d .- pL) .* (pR .- p_d)
    split = d[2:end][h[2:end] .* h[1:end-1] .< 0]

    if find_deltas && (sum(μ1 .< split .< μ2) == 0)
        return(1., 1.)
    end

    # split point
    s = max(split[μ1 .< split .< μ2]...)

    if find_split
        return(s)
    else
        _, _, _, _, δ_1 = cond_mean_on_LR(L, s, a1, a2, a3, μ1, μ2, μ3)
        δ_1 -= (L + s)/2  # centering

        _, _, _, _, δ_2 = cond_mean_on_LR(s, R, a1, a2, a3, μ1, μ2, μ3)
        δ_2 -= (s + R)/2  # centering

        return(δ_1, δ_2)
    end

end

"""
    find_truncation(a1, a2, μ1, μ2,
                    R_ub=(μ2 + 8), L_lb=(μ1 - 8),
                    tuning_par1=0.01, tuning_par2=0.005,
                    a3=0, μ3=0, σ=1)

Find population right-truncation points for bimodal Normal mixtures.
Inputs:
    a1, a2, a3: weights for Normal distributions. a1 + a2 + a3 should be == 1
    μ1, μ2, μ3: means for normals μ1 = -μ2

Possible returns:
    0: negligible result (not a symmetric problem)
    -1: no truncation
"""
function find_truncation(a2, μ2;
                         a1=1-a2, μ1=-μ2, R_ub=μ2+8., L_lb=μ1-8.,
                         tuning_par1=0.01, tuning_par2=0.005,
                         a3=0., μ3=0., σ=1.)

    r = Array(R_ub:(-tuning_par1):μ1)

    # if the bimodal distn is symmetric, and a1 == a2,
    # then truncate both ends equally
    if a1 == a2 && μ2 == -μ1
        for i=1:length(r)

            # truncate both left and right with the same amount
            R = r[i]
            L = -R

            d = Array(L:0.01:R)
            δ_1, δ_2 = find_split(d, a1, a2, μ1, μ2, L, R; find_deltas=true)

            # L^* is the smallest L that satisfies δ_1 < 0 < δ_2. (supp. p.30)
            # Thus returns right-trunctation point
            if δ_1 < 0 < δ_2
                return(R)
            end
        end
    end

    # possible cases:
    #   a1 != a2 && μ2 != -μ1
    #   a1 != a2 && μ2 == -μ1
    if a1 != a2
        L_old = -1000
        for i=1:length(r)
            R = r[i]
            L = Array{Float64,1}(L_old:tuning_par2:(R-0.05))

            pL, pR, ∫_L, ∫_R, μ_LR = cond_mean_on_LR(L, R, a1, a2, a3, μ1, μ2, μ3)

            # locate R_L using the equation μ_{L,R_L} - (L + R_L)/2.  (supp. p.30)
            temp = sign.(μ_LR .- (L .+ R)./2)

            if sum(temp[2:end] .* temp[1:end-1] .< 0) ≠ 0
                # this is L^* from (supp. p.28)
                L = L[2:end][temp[2:end] .* temp[1:end-1] .< 0][1]

                d = Array(L:0.01:R)
                δ_1, δ_2 = find_split(d, a1, a2, μ1, μ2, L, R; find_deltas=true)

                pL = a1 * pnorm(L-μ1) + a2 * pnorm(L-μ2) + a3 * pnorm(L-μ3)
                pR = a1 * pnorm(R-μ1) + a2 * pnorm(R-μ2) + a3 * pnorm(R-μ3)

                if sign((R-L)/(pR-pL) * (a1 * dnorm(L-μ1) + a2 * dnorm(L-μ2) + a3 * dnorm(L-μ3)) - 1) ≠ -1
                    println("🤔🤔")
                end

                # L^* is the smallest L that satisfies δ_1 < 0 < δ_2. (supp. p.30)
                # Thus returns right-trunctation point
                if δ_1 < 0 < δ_2 && L_old ≤ L

                    # if L^* is achieved at i==1, it means there is no truncation.
                    if i == 1
                        return(-1)
                    end

                    return(R)
                end

                # increment L
                if L_old ≤ L
                    L_old = L
                end

                # if L > 0, it means roughly that L > m_ (m_≤0)  (supp. p.29 table and p.28)
                if L > 0
                    return(-1)
                end

            end
        end
    end

    # If a1 == a2 and μ1 != -μ2, then return 0
    # : do not need to assess in this case, since it can WLOG be reduced to a symmetric case)
    return(0)
end

"""
    clusterpath_pop(a2::T, μ2::T; a1=1-a2, μ1=-μ1, a3=0., μ3=0, σ=1) where T <: Real

Inputs:
    a2: weight on 2nd normal distribution
    μ2: mean of 2nd normal distribution
Returns:
    A dictionary of weights (`a1`, `a2`), means (`μ1`, `μ2`) and
    description of truncation (`L*`, `R*`) and split point (`s`).
"""
function clusterpath_pop(a2::T, μ2::T; a1=1-a2, μ1=-μ2, a3=0., μ3=0, σ=1.) where T <: Real
    # search right truncation point
    R = find_truncation(a2, μ2; a1=a1, μ1=μ1, a3=a3, μ3=μ3, σ=σ)

    # R == 0 or R == -1
    if R == 0 || R == -1
        d = Array(μ1:1e-04:μ2)
        # d[argmin(abs.(a1 * dnorm(d.-μ1) .- a2 * dnorm(d.-μ2)))]  ## ???????
        # d[argmin(a1 * dnorm(d.-μ1) .+ a2 * dnorm(d.-μ2))]

        return(Dict("a1" => a1, "a2" => a2,
                    "mu1" => μ1, "mu2" => μ2,
                    "s" => NaN, "L*" => NaN, "R*" => NaN))
    end

    # If R is a proper right-truncation point, then find split point
    L_lb = -3 * max(R, 3)  # lower bound of searching grid of L
    L_ub = R - 0.01  # upper bound
    L = Array(L_lb:5e-04:L_ub)

    _, _, _, _, μ_LR = cond_mean_on_LR(L, R, a1, a2, a3, μ1, μ2, μ3)
    δs = sign.(μ_LR .- (L .+ R)/2)

    # L^* is the smallest L that satisfies δ_1 < 0 < δ_2
    L = L[2:end][δs[2:end] .* δs[1:end-1] .< 0][1]

    d = Array(L:0.001:R)
    split = find_split(d, a1, a2, μ1, μ2, L, R)

    return(Dict("a1" => a1, "a2" => a2,
                "mu1" => μ1, "mu2" => μ2,
                "s" => split, "L*" => L, "R*" => R))
end


alpha = 0.85
accuracy = 0.00001
edgelist = "test/fixtures/dangling.adj"

rank = Rank{Float64,Int64}(alpha, accuracy, edgelist)
r = stationary_distribution(rank)
count = length(r)

# expected rank is  4, 3, 2, 5, 6, 1,
expected = [0.037375, 0.208173, 0.245344, 0.245344, 0.176404, 0.087356]
precision = 5

@assert isequal(floor(expected, precision), floor(r, precision))


#=

  Euler #48 in Julia.

  Problem 48:
  """
  The series, 1^(1) + 2^(2) + 3^(3) + ... + 10^(10) = 10405071317.

  Find the last ten digits of the series,
  1^(1) + 2^(2) + 3^(3) + ... + 1000^(1000).
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

# 0.00162826s
function euler48a()
    sum = 0
    t = 10_000_000_000
    for i = 1:1_000
        n = i
        for j in 2:i
            n = (n * i) % t
        end
        sum = (sum + n) % t
    end

    return sum
end

run_euler(euler48a)


using Soss
using LaTeXStrings

m = @model x begin
    μ ~ Normal(0,5)
    σ ~ HalfCauchy(3)
    N = length(x)
    x ~ Normal(μ, σ) |> iid(N)
end

f = makeLogdensity(m)
μμ = range(-10,10,length=1000)

normalize(seq) = seq ./ sum(seq)
plot(μμ, [exp(f((μ=μ,σ=1,x=[]))) for μ in μμ] |> normalize, label=L"P(\mu)",legendfontsize=16,dpi=300)
plot!(μμ, [exp(f((μ=μ,σ=1,x=[-5]))) for μ in μμ] |> normalize, label=L"P(\mu|x=-5)")
savefig("/home/chad/prior-posterior.png")

mPrior = prior(m)

rand(mPrior)

particles(mPrior)

nuts(m; x=randn(10))

m2 = @model x,errDist begin
    μ ~ Normal(0,5)
    σ ~ HalfCauchy()
    N = length(x)
    d = LocationScale(μ,σ,errDist)
    x ~ d |> iid(N)
end

nuts(m; x=randn(10), errDist = Normal()).samples[1:3] |> DataFrame

nuts(m; x = randn(10), errDist = TDist(3)).samples[1:3] |> DataFrame

# tdist = @model ν begin
#     w ~ InverseGamma(ν / 2, ν / 2)
#     x ~ Normal(0, w ^ 2)
#     return x
# end

nuts(m; x = randn(10), errDist = tdist, ν=3).samples[1:3] |> DataFrame

p = @model begin
    α ~ Normal(1,1)
    β ~ Normal(α^2,1)
end

q = @model μα,σα,μβ,σβ begin
    α ~ Normal(μα,σα)
    β ~ Normal(μβ,σβ)
end

f = sourceParticleImportance(p,q) |> eval

f = sourceParticleImportance(p,q) |> eval
(ℓ,θ) = f(1000,(μα=1, σα=1, μβ=2, σβ=2.6))
@unpack (α,β) = θ
scatter(α.particles, β.particles
    , alpha=exp(ℓ - maximum(ℓ)).particles
    , legend=false
    , xlim=(-2,3)
    , ylim=(-3,10)
    , xlabel=L"\alpha"
    , ylabel=L"\beta")
savefig("/home/chad/banana.svg")

q = @model μm,μs,σm,σs begin
    μ ~ Normal(μm,μs)
    σ ~ LogNormal(σm,σs)
end

using LaTeXStrings
f = sourceParticleImportance(p,q) |> eval
(ℓ,θ) = f(1000,(μα=1, σα=1, μβ=2, σβ=2.6))
@unpack (α,β) = θ
scatter(α.particles, β.particles
    , alpha=exp(ℓ - maximum(ℓ)).particles
    , legend=false
    , xlim=(-2,3)
    , ylim=(-3,10)
    , xlabel=L"\alpha"
    , ylabel=L"\beta")
savefig("/home/chad/banana.svg")

#############################
# Example problem


using LinearAlgebra
using CompScienceMeshes
using SauterSchwab3D
using StaticArrays

const pI   = point(6,5,3)
const pII  = point(5,2,3)
const pIII = point(7,1,0)
const pIV  = point(3,4,0)

const qIII = point(7,5,6)

const P = simplex(pI,pII,pIV,pIII)
const Q = simplex(pI,qIII,pII)

const sing = SauterSchwab3D.singularity_detection(P,Q)

Accuracy2 = 20
ce_ref = CommonEdge5D(sing,SauterSchwab3D._legendre(Accuracy2,0.0,1.0))

function integrand(x,y)
      return ((x-pI)'*(y-pIV))*exp(-im*1*norm(x-y))/(4pi*norm(x-y))
end

function INTEGRAND(u,v)
   n1 = neighborhood(P,u)
   n2 = neighborhood(Q,v)
   x = cartesian(n1)
   y = cartesian(n2)
   output = integrand(x,y)*jacobian(n1)*jacobian(n2)

   return output
end

print("Ref: ")
ref = sauterschwab_parameterized(INTEGRAND, ce_ref)
println(ref)
println()

res_tp =[]
res_sp =[]
res_gm =[]
n1 = []
n2 = []
n3 = []

for i in 2:1:17
   Accuracy = i
   ce = CommonEdge5D(sing,SauterSchwab3D._legendre(Accuracy,0.0,1.0))

   int_tp = sauterschwab_parameterized(INTEGRAND, ce)

   num_pts = 5*length(ce.qps)^5
   push!(n1,num_pts)

   push!(res_tp,int_tp)

end

for i in 2:1:7
   Accuracy = i
   ce_s = CommonEdge5D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._shunnham2D(Accuracy),
                          SauterSchwab3D._shunnham3D(Accuracy)))

   int_sp = sauterschwab_parameterized(INTEGRAND, ce_s)

   num_pts = 3*length(ce_s.qps[1])*length(ce_s.qps[2])^2 + 2*length(ce_s.qps[2])*length(ce_s.qps[3])
   push!(n2,num_pts)
   push!(res_sp,int_sp)
end

for i in 2:1:12
   Accuracy = i
   ce_gm = CommonEdge5D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._grundmannMoeller2D(2*Accuracy-1),
                          SauterSchwab3D._grundmannMoeller3D(2*Accuracy-1)))

   int_gm = sauterschwab_parameterized(INTEGRAND, ce_gm)

   num_pts = 3*length(ce_gm.qps[1])*length(ce_gm.qps[2])^2 + 2*length(ce_gm.qps[2])*length(ce_gm.qps[3])
   push!(n3,num_pts)
   push!(res_gm,int_gm)
end

err_tp = norm.(res_tp.-ref)/norm(ref)
err_sp = norm.(res_sp.-ref)/norm(ref)
err_gm = norm.(res_gm.-ref)/norm(ref)

println(err_tp)
println(err_sp)
println(err_gm)

using Plots
plot( yaxis=:log, xaxis=:log, fontfamily="Times")
plot!(n1,err_tp, label="Gauss Tensor-Product",markershape=:circle)
plot!(n2,err_sp, label="Simplex Tensor-Product",markershape=:rect)
#plot!(n3,err_gm, label="Simplex-Product GM",markershape=:x)
plot!(xlims=(5e1,1e5),ylims=(1e-6,1))
plot!(xlabel="#Quad. pts/Func. evals", ylabel="Rel. Error.", title="Common Edge 5D",legend=:bottomleft)

using BenchmarkTools

ref = -4.263868529192268 - 0.14818591340482545im

ce = CommonEdge5D(sing,SauterSchwab3D._legendre(6,0.0,1.0))

int_tp = sauterschwab_parameterized(INTEGRAND, ce)

num_pts = 5*length(ce.qps)^5
println("#Pts: ",num_pts)
err_tp = norm.(int_tp.-ref)/norm(ref)
println("Rel Err: ",err_tp)
@time for i in 1:100  sauterschwab_parameterized(INTEGRAND, ce) end

ce_s = CommonEdge5D_S(sing,(SauterSchwab3D._legendre(7,0.0,1.0),
                          SauterSchwab3D._shunnham2D(7),
                          SauterSchwab3D._shunnham3D(7)))

int_sp = sauterschwab_parameterized(INTEGRAND, ce_s)
num_pts = 3*length(ce_s.qps[1])*length(ce_s.qps[2])^2 + 2*length(ce_s.qps[2])*length(ce_s.qps[3])

println("#Pts: ",num_pts)
err_sp = norm.(int_sp.-ref)/norm(ref)
println("Rel Err: ",err_sp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, ce_s) end
#=
 ce_gm = CommonEdge5D_S((SauterSchwab3D._legendre(7,0.0,1.0),
                          SauterSchwab3D._grundmannMoeller2D(2*7-1),
                          SauterSchwab3D._grundmannMoeller3D(2*7-1)))

   int_gm = sauterschwab_parameterized(INTEGRAND, ce_s)

   num_pts = 3*length(ce_gm.qps[1])*length(ce_gm.qps[2])^2 + 2*length(ce_gm.qps[2])*length(ce_gm.qps[3])
println("#Pts: ",num_pts)
err_gm = norm.(int_gm.-ref)/norm(ref)
println("Rel Err: ",err_gm)
=#


using Solvent, Test, LinearAlgebra, Random, CUDA, KernelAbstractions

@kernel function laplace_kernel!(Ax, x, param)
    i = @index(Global, Linear)
    @inbounds begin
        Ax[i] =  x[i]
        if i > 1
            Ax[i] += param*x[i-1]
        end
        if i < length(x)
            Ax[i] += param * x[i+1]
        end
    end
end

"""
    laplace!(AX, X, param=-0.01)

Applies the 1d Laplace operator `A*x`  where
```
A[i,i] = 1
A[i,i-1] = A[i,i+1] = param
```
"""
function laplace!(AX, X, param=eltype(X)(-0.01))
    event = laplace_kernel!(Solvent.array_device(X), 256)(
        AX, X, eltype(X)(param); ndrange=length(X))
    wait(event)
end

for T in (Float32, Float64)
    Random.seed!(44)

    n = 200
    M = 20
    K = 10
    tol = sqrt(eps(T))

    x_ref = CuArray(randn(T,n))
    x = similar(x_ref)
    b = similar(x_ref)
    laplace!(b,x_ref)

    linearsolver = LinearSolver(
        laplace!,
        GeneralizedMinimalResidualMethod(M = M, K = K),
        x;
        rtol = tol,
        atol = tol,
    )

    linearsolve!(linearsolver, x, b)
    @test x ≈ x_ref
end


include("../examples/baker.jl")
using LinearAlgebra
using JLD
using SharedArrays
using Distributed
function unstable_sens_gen(s,nSteps)
	u = 2*pi*rand(2)
	u_trj = step(u, s, nSteps)'
	x, y = view(u_trj,:,1), view(u_trj,:,2)
	dJds = 0.
	N = 20
	nSteps = nSteps + 1
	g = zeros(nSteps)
	J = cos.(4*y)
	dXudx1 = zeros(2,nSteps)
	Xu = [sin.(x) zeros(nSteps)]'
	for i = 1:nSteps-1
		x1, x2 = x[i], y[i]
		z = 2.0 + s[1]*cos(x1)
		dzdx = -s[1]*sin(x1)
		dXudx1[:,i] = [cos(x1)/z, 0]
		g[i+1] = g[i]/z - dzdx/z*z
	end
	Xu = Xu[1,:]
	dXudx1 = dXudx1[1,:]
	for n = 1:N
		J_shift = J[n+1:end]
		nJ = length(J_shift)
		g_shift = g[2:nJ+1]
		dXudx1_shift = dXudx1[1:nJ]
		Xu_shift = Xu[1:nJ]
		dJds -= dot(J_shift,dXudx1_shift)/nJ
		dJds -= dot(J_shift,g_shift.*Xu_shift)/nJ
	end
	#@show le, dJds
	return dJds

end

function unstable_sens(s,nSteps)
	u = 2*pi*rand(2)
	u_trj = step(u, s, nSteps)'
	x, y = view(u_trj,:,1), view(u_trj,:,2)
	dJds = 0.
	N = 4
	nSteps = nSteps + 1
	g = zeros(nSteps)
	J = cos.(4*y)
	dXudx1 = zeros(2,nSteps)
	Xu = [sin.(x) zeros(nSteps)]'
	for i = 1:nSteps-1
		x1, x2 = x[i], y[i]
		z = 2.0 + s[1]*cos(x1)
		dzdx = -s[1]*sin(x1)
		dXudx1[:,i] = [cos(x1)/z, 0]
		g[i+1] = g[i]/z - dzdx/z*z
	end
	Xu = Xu[1,:]
	dXudx1 = dXudx1[1,:]
	for n = 1:N
		J_shift = J[n+1:end]
		nJ = length(J_shift)
		g_shift = g[2:nJ+1]
		dXudx1_shift = dXudx1[1:nJ]
		Xu_shift = Xu[1:nJ]
		dJds -= dot(J_shift,dXudx1_shift)/nJ
		dJds -= dot(J_shift,g_shift.*Xu_shift)/nJ
	end
	#@show le, dJds
	return dJds

end
function get_unstable_sens(s1)
	p = 4
	nSteps = 500000
	s = zeros(p)
	# J = cos(4y)
	n_exps = size(s1)[1]
	dJds = zeros(n_exps)
	n_rep = 160
	dJds_proc = SharedArray{Float64}(n_rep)
	for k=1:n_exps
		s[1] = s1[k]
		dJds_proc .= 0.
		t = @distributed for j=1:n_rep
			dJds_proc[j] = unstable_sens(s,nSteps)/n_rep
		end
		wait(t)
		dJds[k] = sum(dJds_proc)
		@show dJds[k]
	end
	save("../data/unstable_sens/dJds1_test.jld", "s1",
	     s1, "dJds", dJds)
end


# Activation functions
# Author: Philipp Witte, pwitte3@gatech.edu
# Date: January 2020

export ReLU, ReLUgrad
export LeakyReLU, LeakyReLUinv, LeakyReLUgrad
export Sigmoid, SigmoidInv, SigmoidGrad
export GaLU, GaLUgrad

###############################################################################
# Rectified linear unit (ReLU) (not invertible)

"""
    y = ReLU(x)

 Rectified linear unit (not invertible).

 See also: [`ReLUgrad`](@ref)
"""
function ReLU(x)
    return max.(0f0, x)
end

"""
    Δx = ReLUgrad(Δy, x)

 Backpropagate data residual through ReLU function.

 *Input*:

 - `Δy`: data residual

 - `x`: original input (since not invertible)

 *Output*:

 - `Δx`: backpropagated residual

 See also: [`ReLU`](@ref)
"""
function ReLUgrad(Δy, x)
    return Δy.*(sign.(x) .+ 1)/2
end

###############################################################################
# Leaky ReLU (invertible)

"""
    y = LeakyReLU(x; slope=0.01f0)

 Leaky rectified linear unit.

 See also: [`LeakyReLUinv`](@ref), [`LeakyReLUgrad`](@ref)
"""
function LeakyReLU(x; slope=0.01f0)
    return max.(0f0, x) + slope*min.(0f0, x)
end

"""
    x = LeakyReLUinv(y; slope=0.01f0)

 Inverse of leaky ReLU.

 See also: [`LeakyReLU`](@ref), [`LeakyReLUgrad`](@ref)
"""
function LeakyReLUinv(y; slope=0.01f0)
    return max.(0f0, y) + (1f0/slope)*min.(0f0, y)
end

"""
    Δx = ReLUgrad(Δy, y; slope=0.01f0)

 Backpropagate data residual through leaky ReLU function.

 *Input*:

 - `Δy`: residual

 - `y`: original output

 - `slope`: slope of non-active part of ReLU

 *Output*:

 - `Δx`: backpropagated residual

 See also: [`LeakyReLU`](@ref), [`LeakyReLUinv`](@ref)
"""
function LeakyReLUgrad(Δy, y; slope=0.01f0)
    x = LeakyReLUinv(y; slope=slope)  # recompute forward state
    p_mask = (sign.(x) .+ 1f0)/2f0
    return Δy.*p_mask + slope*Δy.*(1f0 .- p_mask)
end

###############################################################################
# Sigmoid (invertible if ouput nonzero)

"""
    y = Sigmoid(x)

 Sigmoid activation function.

 See also: [`SigmoidInv`](@ref), [`SigmoidGrad`](@ref)
"""
function Sigmoid(x)
    y = 1f0 ./ (1f0 .+ exp.(-x))
    return y
end

"""
    x = SigmoidInv(y)

 Inverse of Sigmoid function.

 See also: [`Sigmoid`](@ref), [`SigmoidGrad`](@ref)
"""
function SigmoidInv(y)
    if sum(isapprox.(y, 0f-6)) == 0
        x = -log.((1f0 .- y) ./ y)
    else
        throw("Input contains zeros.")
    end
    return x
end

"""
    Δx = SigmoidGrad(Δy, y; x=nothing)

 Backpropagate data residual through Sigmoid function.

 *Input*:

 - `Δy`: residual

 - `y`: original output

 - `x`: original input, if y not available (in this case, set y=nothing)

 *Output*:

 - `Δx`: backpropagated residual

 See also: [`Sigmoid`](@ref), [`SigmoidInv`](@ref)
"""
function SigmoidGrad(Δy, y; x=nothing)
    if ~isnothing(y) && isnothing(x)
        x = SigmoidInv(y)  # recompute forward state
    end
    Δx = Δy .* exp.(-x) ./ (1f0 .+ exp.(-x)).^2f0
    return Δx
end

###############################################################################
# Gated linear unit (GaLU) (not invertible)
# Adapted from Dauphin et al. (2017)

"""
    y = GaLU(x)

 Gated linear activation unit (not invertible).

 See also: [`GaLUgrad`](@ref)
"""
function GaLU(x::AbstractArray{Float32, 4})
    k = Int(size(x, 3) / 2)
    y = x[:, :, 1:k, :] .* Sigmoid(x[:, :, k+1:end, :])
    return y
end

function GaLU(x::AbstractArray{Float32, 5})
    k = Int(size(x, 4) / 2)
    y = x[:, :, :, 1:k, :] .* Sigmoid(x[:, :, :, k+1:end, :])
    return y
end

"""
    Δx = GaLUgrad(Δy, x)

 Backpropagate data residual through GaLU activation.

 *Input*:

 - `Δy`: residual

 - `x`: original input (since not invertible)

 *Output*:

 - `Δx`: backpropagated residual

 See also: [`GaLU`](@ref)
"""
function GaLUgrad(Δy::AbstractArray{Float32, 4}, x::AbstractArray{Float32, 4})
    k = Int(size(x, 3) / 2)
    x1 = x[:, :, 1:k, :]
    x2 = x[:, :, k+1:end, :]
    Δx = 0f0.*x
    Δx[:, :, 1:k, :] = Sigmoid(x2) .* Δy
    Δx[:, :, k+1:end, :] = SigmoidGrad(Δy, nothing; x=x2) .* x1
    return Δx
end

function GaLUgrad(Δy::AbstractArray{Float32, 5}, x::AbstractArray{Float32, 5})
    k = Int(size(x, 4) / 2)
    x1 = x[:, :, :, 1:k, :]
    x2 = x[:, :, :, k+1:end, :]
    Δx = 0f0.*x
    Δx[:, :, :, 1:k, :] = Sigmoid(x2) .* Δy
    Δx[:, :, :, k+1:end, :] = SigmoidGrad(Δy, nothing; x=x2) .* x1
    return Δx
end


"""
    AbstractStar{N}

Abstract supertype for all star set types.

### Notes

A set ``X`` is star-like (also known as generalized star) if it can be represented
by a center ``x₀ ∈ \\mathbb{R}^n`` and ``m`` vectors ``v₁, …, vₘ`` forming the basis,
and a predicate ``P : \\mathbb{R}^n → \\{⊤, ⊥\\}`` such that

```math
    X = \\{x ∈ \\mathbb{R}^n : x = x₀ + \\sum_{i=1}^m α_i v_i,,~~\\textrm{s.t. } P(α) = ⊤ \\}.
```
"""
abstract type AbstractStar{N} end

isoperationtype(::Type{<:AbstractStar}) = false
isconvextype(::Type{<:AbstractStar}) = false


type DiscreteHopfieldNet <: HopfieldNet
    s::Vector{Float64} # State -- could have used Int's
    W::Matrix{Float64} # Weights
end

function DiscreteHopfieldNet(n::Integer)
    s = ones(n)
    W = zeros(n, n)
    DiscreteHopfieldNet(s, W)
end

# Perform one asynchronous update on randomly selected neuron
function update!(net::DiscreteHopfieldNet)
    i = rand(1:length(net.s))
    net.s[i] = dot(net.W[:, i], net.s) > 0 ? +1 : -1
    return
end

function Base.show(io::IO, net::DiscreteHopfieldNet)
    @printf io "A discrete Hopfield net with %d neurons\n" length(net.s)
end


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ d613968d-7549-401a-9f25-8169660f121f
using SymPy, PlutoUI

# ╔═╡ a5797465-247a-4e33-b733-46ad9a58ad00
TableOfContents(title="📚 Sadržaj", aside=true)

# ╔═╡ 71bf3e90-8277-11eb-188f-0f6601464f27
md"
# Integrali iracionalnih funkcija
"

# ╔═╡ 90465d99-b8e8-4ed3-9943-64aec908d6f7
md"""
## Eulerove i trigonometrijske supstitucije
"""

# ╔═╡ 37981771-05ce-40b5-a2a9-8031d41cdb6b
x=symbols("x",real=true)

# ╔═╡ 1dee94b6-fa66-462e-bbb3-0c941a59dd8e
# Ne može izračunati
integrate(1/(x+√(x^2+6*x+10)),x)

# ╔═╡ cc61e680-8110-11eb-2211-1d212f28c36d
t=symbols("t",real=true)

# ╔═╡ dacfbf80-8110-11eb-2981-4d7c579c8e48
I₀=1/(x+√(x^2+6x+10))

# ╔═╡ 2835b6ea-9c18-4259-82b9-f3eefea58772
I₁=simplify(subs(I₀,x,t-3))

# ╔═╡ 4b0bc7e5-c399-483b-8a4a-c3b4b35fc543
z=symbols("z",real=true)

# ╔═╡ a31fc5bd-cf3b-4248-8994-ce8adb4dd02f
# Eulerova supstitucija
I₂=subs(I₁,√(t^2+1),t+z)

# ╔═╡ 6594a67e-8111-11eb-29a5-eb1fd173be05
I₃=subs(I₂,t,(1-z^2)/(2*z))

# ╔═╡ d7184d11-2e04-434a-bb68-3231aa4a59e2
# Množi s dx
I₄=simplify(I₃*(-1)*(1+z^2)/(2*z^2))

# ╔═╡ f9ec108c-b5dd-46ef-b4ac-b6d97bbc2162
# Integriramo
I₅=integrate(I₄,z)

# ╔═╡ dc3b1f9b-5dde-41f5-9dda-07eb70b460dd
# Vratimo supstitucije
I₆=subs(I₅,z,√(t^2+1)-t)

# ╔═╡ 2bc4c1bf-d574-43f1-8788-3dcecdfd8218
I₇=subs(I₆,t,x+3)

# ╔═╡ 777be0b5-8d94-4347-b61c-d0827a0bf774
# Primjer 1.12
integrate(x^2*sqrt(4*x^2+9))

# ╔═╡ 08e1571e-3f23-456b-9eea-6f228c44980c
md"""
## Binomni integral
"""

# ╔═╡ 098a9b62-9ea4-4547-b6fb-f4fb6d1ee6cf
# Primjer 1.13
I₈=((3*x-x*x*x)^(1//3))

# ╔═╡ 94c8f287-8031-4cd3-a67a-186a5eebf09e
integrate(I₈,x)

# ╔═╡ 0cbc9b1a-7282-4075-a0a2-2b92595bc7c3
md"""
## Integriranje razvoja u red potencija
"""

# ╔═╡ ecf4a097-2e3f-496c-8e65-004e7f0740d1
s₁=series(exp(x),x)

# ╔═╡ 4ddece7e-33ee-4586-b08d-33cd39bb1e30
s₂=series(exp(x),x,0,10)

# ╔═╡ 30f2cb1b-5bb3-4db8-9239-bcde4ec2962f
md"""
Razvoj u red funkcije $f(x)=e^{-x^2}$
"""

# ╔═╡ 4b02de9e-ae81-4324-92e3-20f4b882a8d4
s₃=subs(s₂,x,(-x^2))

# ╔═╡ da968a92-6a56-4dff-97f6-5e87616d4291
s₄=integrate(s₃,x)

# ╔═╡ e8bcf956-422d-4829-9ab6-fda047805a5b
# Uklonimo član O(x^21)
s₆=s₄.removeO()

# ╔═╡ 58468833-ef55-4983-b637-cadd7bcdf9f3
# Izračunajmo vrijednost u točki x=0.1
subs(s₆,x,0.1)

# ╔═╡ 2ac4782d-6c74-4402-b6eb-17554ef27d03
md"""
###  Primjer 1.14 b)
"""

# ╔═╡ 17c63bc5-611b-4cfc-a916-c0018dd71ecb
# Maclaurin-ov red za sin(x)
T₁=series(sin(x),x,0,10)

# ╔═╡ 4375ec68-a2d1-4cf8-a2ab-88bb69717406
# Razvoj u red za sin(x)/x
T₂=simplify(s₁/x)

# ╔═╡ b9b5d733-931d-4f69-8f44-6bde41f7458b
# Ukonimo O() clan
T₃=s₂.removeO()

# ╔═╡ 85a68267-73a0-4c16-ae9f-db1d3ceac7bb
# Integrirajmo
T₄=integrate(s₃,x)

# ╔═╡ 3e684e89-3216-4ed3-a60c-782ab7234fcf

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"

[compat]
PlutoUI = "~0.7.14"
SymPy = "~1.0.52"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "a325370b9dd0e6bf5656a6f1a7ae80755f8ccc46"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.7.2"

[[CommonEq]]
git-tree-sha1 = "d1beba82ceee6dc0fce8cb6b80bf600bbde66381"
uuid = "3709ef60-1bee-4518-9f2f-acd86f176c50"
version = "0.2.0"

[[CommonSolve]]
git-tree-sha1 = "68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.0"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Conda]]
deps = ["JSON", "VersionParsing"]
git-tree-sha1 = "299304989a5e6473d985212c28928899c74e9421"
uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
version = "1.5.2"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "34dc30f868e368f8a17b728a1238f3fcda43931a"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.3"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "a8709b968a1ea6abc2dc1967cb1db6ac9a00dfb6"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.5"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "d1fb76655a95bf6ea4348d7197b22e889a4375f4"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.14"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[PyCall]]
deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
git-tree-sha1 = "169bb8ea6b1b143c5cf57df6d34d022a7b60c6db"
uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.92.3"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "793793f1df98e3d7d554b65a107e9c9a6399a6ed"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.7.0"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[SymPy]]
deps = ["CommonEq", "CommonSolve", "LinearAlgebra", "Markdown", "PyCall", "RecipesBase", "SpecialFunctions"]
git-tree-sha1 = "1ef257ecbcab8058595a68ca36a6844b41babcbd"
uuid = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
version = "1.0.52"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[VersionParsing]]
git-tree-sha1 = "80229be1f670524750d905f8fc8148e5a8c4537f"
uuid = "81def892-9a0e-5fdd-b105-ffc91e053289"
version = "1.2.0"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╠═d613968d-7549-401a-9f25-8169660f121f
# ╠═a5797465-247a-4e33-b733-46ad9a58ad00
# ╟─71bf3e90-8277-11eb-188f-0f6601464f27
# ╟─90465d99-b8e8-4ed3-9943-64aec908d6f7
# ╠═37981771-05ce-40b5-a2a9-8031d41cdb6b
# ╠═1dee94b6-fa66-462e-bbb3-0c941a59dd8e
# ╠═cc61e680-8110-11eb-2211-1d212f28c36d
# ╠═dacfbf80-8110-11eb-2981-4d7c579c8e48
# ╠═2835b6ea-9c18-4259-82b9-f3eefea58772
# ╠═4b0bc7e5-c399-483b-8a4a-c3b4b35fc543
# ╠═a31fc5bd-cf3b-4248-8994-ce8adb4dd02f
# ╠═6594a67e-8111-11eb-29a5-eb1fd173be05
# ╠═d7184d11-2e04-434a-bb68-3231aa4a59e2
# ╠═f9ec108c-b5dd-46ef-b4ac-b6d97bbc2162
# ╠═dc3b1f9b-5dde-41f5-9dda-07eb70b460dd
# ╠═2bc4c1bf-d574-43f1-8788-3dcecdfd8218
# ╠═777be0b5-8d94-4347-b61c-d0827a0bf774
# ╟─08e1571e-3f23-456b-9eea-6f228c44980c
# ╠═098a9b62-9ea4-4547-b6fb-f4fb6d1ee6cf
# ╠═94c8f287-8031-4cd3-a67a-186a5eebf09e
# ╟─0cbc9b1a-7282-4075-a0a2-2b92595bc7c3
# ╠═ecf4a097-2e3f-496c-8e65-004e7f0740d1
# ╠═4ddece7e-33ee-4586-b08d-33cd39bb1e30
# ╟─30f2cb1b-5bb3-4db8-9239-bcde4ec2962f
# ╠═4b02de9e-ae81-4324-92e3-20f4b882a8d4
# ╠═da968a92-6a56-4dff-97f6-5e87616d4291
# ╠═e8bcf956-422d-4829-9ab6-fda047805a5b
# ╠═58468833-ef55-4983-b637-cadd7bcdf9f3
# ╟─2ac4782d-6c74-4402-b6eb-17554ef27d03
# ╠═17c63bc5-611b-4cfc-a916-c0018dd71ecb
# ╠═4375ec68-a2d1-4cf8-a2ab-88bb69717406
# ╠═b9b5d733-931d-4f69-8f44-6bde41f7458b
# ╠═85a68267-73a0-4c16-ae9f-db1d3ceac7bb
# ╠═3e684e89-3216-4ed3-a60c-782ab7234fcf
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


@testset "optimal transport" begin

    P = randn(4, 5).^2 |> x -> x .= x / sum(x)

    M = 0.1randn(size(P))

    a, b = marginals(P)

    @test isprobability(P)
    @test !isprobability(P .+ 0.1)
    @test isprobability(a)
    @test isprobability(b)

    @test entropy(P[1,:] |> OTSIN.normalize) ≈ entropy(P[1,:], norm=true)

    @testset "free OT" begin
        Q = optimaltransport(M, λ=1.0)
        @test isprobability(Q)
        @test entropy(Q) > entropy(optimaltransport(M, λ=10.0))
        @test relative_entropy(P, Q) > 0
        @test Q ≈ optimaltransport(M, nothing, nothing, λ=1.0)
    end

    @testset "one marginal OT" begin
        Q = optimaltransport(M, a, λ=1.0)
        @test isprobability(Q)
        @test marginals(Q)[1] ≈ a
        @test Q ≈ optimaltransport(M, a, nothing, λ=1.0)
    end

    @testset "Sinkhorn" begin
        Q = optimaltransport(M, a, b, λ=1.0, ϵ=1e-10)
        @test isprobability(Q)
        @test all(marginals(P) .≈ marginals(Q))
        @test optimaltransport(M, a, b, λ=0) ≈ optimaltransport(a, b)
        @test Q ≈ optimaltransport(M, a, b, λ=1.0, ϵ=1e-10)
    end

    #  check if some elements of a and b can be zero
    @testset "Sinkhorn sparse" begin
        Q = optimaltransport(M, [0.5, 0.2, 0.0, 0.3],
                    [0.1, 0.1, 0.1, 0.0, 0.7], λ=1.0, ϵ=1e-10)
        a_, b_ = marginals(Q)
        @assert a_[3] < 1e-6
        @assert b_[4] < 1e-6
        @assert b_[1] ≈ 0.1
        @assert sum(Q) ≈ 1.0
    end

    @testset "perturbation" begin
        ∇af, ∇bf = perturbation(Q -> sum(Q.^2), M, a, b)
        @test ∇af ⋅ ones(size(∇af)) < 1e-6
        @test ∇bf ⋅ ones(size(∇bf)) < 1e-6
    end

end


using Seismic, PyCall
@pyimport matplotlib.pyplot as pl

#Generate seismic data

nt = 256;
nx = convert(Int64,128);
h = Array{Header}(nx);
for ix = 1:nx
  h[ix] = Seismic.InitSeisHeader();
  h[ix].tracenum = ix;
  h[ix].d1 = 0.004;
  h[ix].n1 = nt;
  h[ix].imx = ix;
  h[ix].imy = 0;
  h[ix].ihx = 0;
  h[ix].ihy = 0;
end

dt=0.004;
dx=5;
f_niquest=Int(floor(1/dt))

Tmax=(nt-1)*dt

data,ext=SeisLinearEvents(nt=nt,dt=dt,nx1=nx,dx1=dx,tau=[Tmax/4., Tmax/3])#,p1=[0.0001])

#Transform to FK domain
m=fft(data,1)/sqrt(size(data,1));
m=fft(m,2)/sqrt(size(data,2))
m=fftshift(m)

m=m[Int(floor(f_niquest/2)):f_niquest,:]

# apply FK filter
data_fk= SeisFKFilter(data;dt=dt,dx=dx,va=-1500,vb=-2500,vc=2500,vd=1500)

#Transform back to TX domain.

m=fft(data_fk,1)/sqrt(size(data_fk,1));
m=fft(m,2)/sqrt(size(data_fk,2))
m=fftshift(m)

m=m[Int(floor(f_niquest/2)):f_niquest,:]

#>>>>>>>>>>>>>>>Plot<<<<<<<<<<<<<<<<

f1 = pl.figure(1)
pl.imshow(data[1:nt,:],cmap="PuOr",aspect="auto",vmin=-1,vmax=1)
pl.title("data before fk filter")
pl.xlabel("X (m)")
pl.ylabel("T (s)")
pl.show()

f2 = pl.figure(2)
pl.imshow(abs.(m),cmap="PuOr",aspect="auto",vmin=-1,vmax=1)
pl.title("fk spectrum before filter")
pl.xlabel("K (1/m)")
pl.ylabel("W (1/s)")
pl.show()

f3 = pl.figure(3)
pl.imshow(data_fk[1:nt,:],cmap="PuOr",aspect="auto",vmin=-1,vmax=1)
pl.title("data after fk filter")
pl.xlabel("X (m)")
pl.ylabel("T (s)")
pl.show()

f4 = pl.figure(4)
pl.imshow(abs.(m),cmap="PuOr",aspect="auto",vmin=-1,vmax=1)
pl.title("fk spectrum after filter")
pl.xlabel("K (1/m)")
pl.ylabel("W (1/s)")
pl.show()

pl.show()


# Computes the s_2 function.
include("ellpic_bulirsch.jl")

function s2(r::T,b::T,Kofk::T,Eofk::T) where {T <: Real}
# For now, just compute linear component:
Lambda1 = zero(typeof(b))
if b > 1.0+r ||  r == 0.0
  # No occultation:
  Lambda1 = 0  # Case 1
elseif b < r-1.0
  # Full occultation:
  Lambda1 = 0  # Case 11
else
  if b == 0
    Lambda1 = -2/3*(1.0-r^2)^(3/2) # Case 10
  elseif b==r
    if r == 0.5
      Lambda1 = 1/3-4/(9pi) # Case 6
    elseif r < 0.5
#      Kofk = ellk_bulirsch(4r^2); Eofk = ellec_bulirsch(4r^2)
#      Lambda1 = 1/3+2/(9pi)*(4*(2r^2-1)*ellec_bulirsch(4r^2)+(1-4r^2)*ellk_bulirsch(4r^2)) # Case 5
      Lambda1 = 1/3+2/(9pi)*(4*(2r^2-1)*Eofk+(1-4r^2)*Kofk) # Case 5
    else
#      Kofk = ellk_bulirsch(inv(4r^2)); Eofk = ellec_bulirsch(inv(4r^2))
#      Lambda1 = 1/3+16r/(9pi)*(2r^2-1)*ellec_bulirsch(inv(4r^2))-(1-4r^2)*(3-8r^2)/(9pi*r)*ellk_bulirsch(inv(4r^2)) # Case 7
      Lambda1 = 1/3+16r/(9pi)*(2r^2-1)*Eofk-(1-4r^2)*(3-8r^2)/(9pi*r)*Kofk # Case 7
    end
  else
    k2 = 1.0+(1.0-(b+r)^2)/(4*b*r)
    if (b+r) > 1.0 # k^2 < 1, Case 2, Case 8
#      Kofk = ellk_bulirsch(k2)
#      Eofk = ellec_bulirsch(k2)
      Piofnk = ellpic_bulirsch(-k2*(b+r)^2,k2)
      xi = 2*b*r*(4-7r^2-b^2)
      Lambda1 = (((r+b)^2-1)/(r+b)*(-2r*(2*(r+b)^2+(r+b)*(r-b)-3)*Kofk+3*(b-r)*Piofnk)
            -2*xi*Eofk)/(9*pi*sqrt(b*r))
    elseif (b+r) < 1.0  # k^2 > 1, Case 3, Case 9
      k2inv = inv(k2)
#      Kofk = ellk_bulirsch(k2inv)
#      Eofk = ellec_bulirsch(k2inv)
      if abs(b-r) != 1.0
        Piofnk = ellpic_bulirsch(-inv(k2*(b+r)^2),k2inv)/sqrt(1-(b-r)^2)
      else
        Piofnk = 0.0
      end
      Lambda1 = 2*((1-(r+b)^2)*(sqrt(1-(b-r)^2)*Kofk+3*(b-r)/(b+r)*Piofnk)
            -sqrt(1-(b-r)^2)*(4-7r^2-b^2)*Eofk)/(9*pi)
    else
      # b+r = 1 or k^2=1, Case 4 (extending r up to 1)
      Lambda1 = 2/(3pi)*acos(1.-2*r)-4/(9pi)*(3+2r-8r^2)*sqrt(r*b)-2/3*convert(typeof(b),r>.5)
    end
  end
end
s2 = 1.0-1.5*Lambda1-convert(typeof(b),r>b)
return s2*2pi/3
end


@testset "Power Residue" begin
    R = ResidueRing(ZZ,7)
    Rx,x = R["x"]
    b = x^4+x+1 #irreducible
    a = Base.rand(Rx, 10)
    a = div(a, gcd(a, b))
    d = fmpz(3)
    @test power_residue(b^2,b,d,fmpz(7)) == 0
    @test power_residue(a,b,d,fmpz(7)) == mod(a^(div(7^degree(b)-1, 3)), b)
end