module Day5

export day5

function findposition(str, upper)
    reduce(str, init = 0) do acc c
        c == upper ? acc+1 : acc
    end
end

function day5(::Val{:a}, filename)
    highest = 0
    for line in readlines(filename)
        line = strip(line)
        row_position = line[1:7]
        seat_position = line[8:end]
        row = findposition(row_position, 'B')
        seat = findposition(seat_position, 'R')
        seatid = row * 8 + seat
        if seatid > highest
            highest = seatid
        end
    end
    highest
end

function day5(::Val{:b}, filename)
    seats = Set{Int}()
    for line in readlines(filename)
        line = strip(line)
        row_position = line[1:7]
        seat_position = line[8:end]
        row = findposition(row_position, 'B')
        col = findposition(seat_position, 'R')
        push!(seats, row * 8 + col)
    end
    miss = setdiff(Set{Int}(0:127*8-1), seats)
    for seat in miss
        if in(seat-1, seats) && in(seat+1, seats)
            println(seat)
        end
    end
end

end


using FastGaussQuadrature, Memoization, SpecialFunctions

const RTOL = eps()

abstract type AbstractShape end
struct GaussianShape <: AbstractShape
  σ::Float64
  function GaussianShape(σ)
    σ > 0 || throw(error(ArgumentError("GuassianShape σ must be > 0 but is $σ")))
    return new(σ)
  end
end
width(s::GaussianShape) = 12.5 .* s.σ
sigma(s::GaussianShape) = s.σ
(s::GaussianShape)(x, centre) = exp(-(x-centre)^2 / s.σ^2) / √π / s.σ
knots(s::GaussianShape) = 0:1

struct BSpline{N} <: AbstractShape
  Δ::Float64
  function BSpline{N}(Δ) where {N}
    Δ > 0 || throw(error(ArgumentError("BSpline{$N} Δ must be > 0 but is $Δ")))
    return new{N}(Δ)
  end
end
width(s::BSpline{N}) where N = (N + 1) * s.Δ

function (s::BSpline{0})(x, centre)
  return (-s.Δ/2 <= x - centre < s.Δ/2) / s.Δ
end
const TopHatShape = BSpline{0}

function (s::BSpline{1})(x, centre)
  z = 2 * (x - centre + 1 * s.Δ) / width(s) # z is between 0 and 2
  value = if 0 <= z < 1
    z
  elseif 1 <= z < 2
    (2 - z)
  else
    zero(z)
  end
  return value / s.Δ
end
const TentShape = BSpline{1}

function (s::BSpline{2})(x, centre)
  z = 3 * (x - centre + 1.5 * s.Δ) / width(s) # z is between 0 and 3
  value = if 0 <= z < 1
    z^2 / 2
  elseif 1 <= z < 2
    3/4 - (1.5 - z)^2
  elseif 2 <= z < 3
    (3 - z)^2 / 2
  else
    zero(z)
  end
  return value / s.Δ
end
const QuadraticBSplineShape = BSpline{2}

knots(b::BSpline{N}) where N = 0:1/(N + 1):1

struct DeltaFunctionShape <: AbstractShape end
DeltaFunctionShape(_) = DeltaFunctionShape() # so has same ctor as others
width(s::DeltaFunctionShape) = 0

mutable struct BasisFunction{S<:AbstractShape, T}
  shape::S
  centre::Float64
  weight::T
end
BasisFunction(s::AbstractShape, centre) = BasisFunction(s, centre, 0.0)

lower(b::BasisFunction) = b.centre - width(b.shape) / 2
upper(b::BasisFunction) = b.centre + width(b.shape) / 2
lower(a::BasisFunction, b::BasisFunction) = max(lower(a), lower(b))
upper(a::BasisFunction, b::BasisFunction) = min(upper(a), upper(b))
width(b::BasisFunction) = width(b.shape)
weight(b::BasisFunction) = b.weight
centre(b::BasisFunction) = b.centre
shape(b::BasisFunction) = b.shape
knots(b::BasisFunction) = knots(shape(b)) .* width(b) .+ lower(b)
function getkey(u::BasisFunction, v::BasisFunction)::UInt64
  hashtuple = (width(u), width(v), trunc((centre(u) - centre(v)), digits=14))
  return foldr(hash, hashtuple; init=hash(typeof(u), hash(typeof(v))))
end
function translate(b::BasisFunction, x::Number)
  @assert isfinite(x)
  translated = deepcopy(b)
  translated.centre += x
  return translated
end
function translate!(b::BasisFunction, x::Number, bc::AbstractBC)
  b.centre = bc(b.centre + x)
  return b
end
Base.:+(b::BasisFunction, x) = (@assert isfinite(x); b.weight += x; b)

zero!(b::BasisFunction) = (b.weight *= false)
function Base.in(x::Number, b::BasisFunction)
  return (b.centre - width(b)/2 <= x < b.centre + width(b)/2)
end

function knots(a::BasisFunction, b::BasisFunction)
  overlap(a, b) || return output
  ka = knots(a)
  kb = knots(b)
  lo = max(ka[1], kb[1])
  hi = min(ka[end], kb[end])
  output = filter(x->lo <= x <= hi, unique(vcat(ka, kb)))
  sort!(output)
  return output
end

function translate(a::BasisFunction{S1}, b::BasisFunction{S2},
    p::PeriodicGridBC) where {S1<:AbstractShape, S2<:AbstractShape}
  lower(p) <= lower(a) && upper(a) < upper(p) && lower(p) <= lower(b) && upper(b) < upper(p) && return (a, b)
  in(a, b) && return (a, b)
  t = translate(a, length(p)); in(t, b) && return (t, b)
  t = translate(a,-length(p)); in(t, b) && return (t, b)
  return (a, b)
end

(b::BasisFunction)(x::Number) = b.shape(x, b.centre)
function (b::BasisFunction)(x::Number, p::PeriodicGridBC)
  in(x, b) && return b(x)
  t = translate(b, length(p)); in(x, t) && return t(x)
  t = translate(b,-length(p)); in(x, t) && return t(x)
  return b(x)
end

function overlap(a::BasisFunction, b::BasisFunction)
  return lower(a) == lower(b) || (lower(a) < upper(b) && upper(a) > lower(b))
end
Base.in(a::BasisFunction, b::BasisFunction) = overlap(a, b)
function overlap(u::BasisFunction, v::BasisFunction, p::PeriodicGridBC)
  return in(translate(u, v, p)...)
end

function integral(b::BasisFunction{DeltaFunctionShape}, f::F, _::AbstractBC) where {F<:Function}
  return f(centre(b))
end

function integral(b::BasisFunction, f::F, _::AbstractBC) where {F}
  ks = knots(b)
  return mapreduce(i->
    QuadGK.quadgk(x->b(x) * f(x), ks[i], ks[i+1]; order=7, atol=eps(), rtol=RTOL)[1],
    +, 1:length(ks)-1)
end

function integral(u::BasisFunction{S1},
                  v::BasisFunction{S2},
                  p::PeriodicGridBC) where {S1<:AbstractShape, S2<:AbstractShape}
  u, v = translate(u, v, p)
  return in(u, v) ? integral(u, v) : 0.0
end

function integral(u::BasisFunction{BSpline{N1}}, v::BasisFunction{BSpline{N2}}
    ) where {N1, N2}
  output = 0.0
  kn = knots(u, v)
  x, w = FastGaussQuadrature.gausslegendre((N1 + 1) * (N2 + 1))
  for i ∈ 1:length(kn)-1
    a, b = kn[i], kn[i+1]
    for j ∈ eachindex(x)
      xj = (x[j] + 1) / 2 * (b - a) + a
      wj = w[j] / 2 * (b - a)
      output += wj * u(xj) * v(xj)
    end
  end
  return output
end
function integral(u::BasisFunction{GaussianShape, T1},
                  v::BasisFunction{BSpline{N}, T2}) where {T1, N, T2}
  ks = knots(v)
  return mapreduce(i->
    QuadGK.quadgk(x->u(x) * v(x), ks[i], ks[i+1]; order=7, atol=eps(), rtol=RTOL)[1],
    +, 1:length(ks)-1)
end
function integral(u::BasisFunction{BSpline{N}, T1},
                  v::BasisFunction{GaussianShape, T2}) where {N, T1, T2}
  return integral(v, u)
end

function integral(u::BasisFunction{TopHatShape}, v::BasisFunction{GaussianShape})
  return integral(v, u)
end
function integral(u::BasisFunction{GaussianShape}, v::BasisFunction{TopHatShape})
  return (erf(-(u.centre - upper(v)) / u.shape.σ) -
          erf(-(u.centre - lower(v)) / u.shape.σ)) / 2 / width(v)
end
function integral(u::BasisFunction{TopHatShape},
                  v::BasisFunction{TopHatShape})
  w = upper(u, v) - lower(u, v)
  h = 1 / (u.shape.Δ * v.shape.Δ)
  return w * h * overlap(u, v)
end
integral(a::BasisFunction{DeltaFunctionShape}, b::BasisFunction) = b(a.centre)
integral(a::BasisFunction, b::BasisFunction{DeltaFunctionShape}) = a(b.centre)

function integral(a::BasisFunction{GaussianShape},
                  b::BasisFunction{GaussianShape})
  σ₁, σ₂ = a.shape.σ, b.shape.σ
  μ₁, μ₂ = a.centre, b.centre
  σ² = σ₁^2 + σ₂^2
  return exp(-(μ₁ - μ₂)^2 / σ²) / √(π * σ²)
end

function integral(a::BasisFunction{GaussianShape}, b::BasisFunction{TentShape})
  σ = a.shape.σ
  μ = centre(a)
  Δ = b.shape.Δ
  c = centre(b)
  foo(l, m) = (σ * exp(-(m - l)^2/σ^2)/√π + m * erf((m - l)/σ)) / 2Δ^2
  return foo(0, μ - c + Δ) - foo(Δ, μ - c + Δ) +
    foo(0, μ - c - Δ) - foo(-Δ, μ - c - Δ)
end


# GRADIENT OPERATOR WITH DIMENSION 1/Δ

function GTx_nonperiodic!(dTdx::Matrix{Numtype},T::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the T-grid.
    The result dTdx sits on the u-grid. =#

    dTdx[:,:] = one_over_Δ*(T[2:end,:] - T[1:end-1,:])
end

function GTx_periodic!(dTdx::Matrix{Numtype},T::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the T-grid.
    The result dTdx sits on the u-grid. =#

    dTdx[1,:] = one_over_Δ*(T[1,:] - T[end,:])
    dTdx[2:end,:] = one_over_Δ*(T[2:end,:] - T[1:end-1,:])
end

function GTy!(dTdy::Matrix{Numtype},T::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the T-grid.
    The result dTdy sits on the v-grid. =#

    dTdy[:,:] = one_over_Δ*(T[:,2:end] - T[:,1:end-1])
end

function Gux_nonperiodic!(dudx::Matrix{Numtype},u::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the u-grid.
    The result dudx sits on the T-grid. =#

    dudx[1,:] = one_over_Δ*u[1,:]              # -0, kinematic bc
    dudx[2:end-1,:] = one_over_Δ*(u[2:end,:] - u[1:end-1,:])
    dudx[end,:] = (-one_over_Δ)*u[end,:]       # +0, kinematic bc
end

function Gux_periodic!(dudx::Matrix{Numtype},u::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the u-grid.
    The result dudx sits on the T-grid. =#

    dudx[1:end-1,:] = one_over_Δ*(u[2:end,:]-u[1:end-1,:])
    dudx[end,:] = one_over_Δ*(u[1,:]-u[end,:])
end

function Gvy!(dvdy::Matrix{Numtype},v::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the v-grid.
    The result dvdy sits on the T-grid. =#

    dvdy[:,2:end-1] = one_over_Δ*(v[:,2:end] - v[:,1:end-1])
    dvdy[:,1] = one_over_Δ*v[:,1]              # -0, kinematic bc
    dvdy[:,end] = (-one_over_Δ)*v[:,end]       # +0, kinematic bc
end

function Guy_nonperiodic!(dudy::Matrix{Numtype},u::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the u-grid.
    The result dudy sits on the q-grid. =#

    dudy[2:end-1,2:end-1] = one_over_Δ*(u[:,2:end] - u[:,1:end-1])
    dudy[2:end-1,1] = α_over_Δ*u[:,1] #  α is the lateral boundary condition parameter
    dudy[2:end-1,end] = -α_over_Δ*u[:,end]
    dudy[1,:] .= zeero       #TODO probably redundant if initialised with zeros
    dudy[end,:] .= zeero
end

function Guy_periodic!(dudy::Matrix{Numtype},u::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the u-grid.
    The result dudy sits on the q-grid. =#

    dudy[:,2:end-1] = one_over_Δ*(u[:,2:end] - u[:,1:end-1])
    dudy[:,1] = α_over_Δ*u[:,1] #  α is the lateral boundary condition parameter
    dudy[:,end] = -α_over_Δ*u[:,end]
end

function Gvx_nonperiodic!(dvdx::Matrix{Numtype},v::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the v-grid.
    The result dvdx sits on the q-grid. =#

    dvdx[2:end-1,2:end-1] = one_over_Δ*(v[2:end,:] - v[1:end-1,:])
    dvdx[1,2:end-1] = α_over_Δ*v[1,:] #  α is the lateral boundary condition parameter
    dvdx[end,2:end-1] = -α_over_Δ*v[end,:]
    dvdx[:,1] .= zeero      #TODO redundant if initialised with zeros
    dvdx[:,end] .= zeero
end

function Gvx_periodic!(dvdx::Matrix{Numtype},v::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the v-grid.
    The result dvdx sits on the q-grid. =#

    dvdx[2:end,2:end-1] = one_over_Δ*(v[2:end,:] - v[1:end-1,:])
    dvdx[1,2:end-1] = one_over_Δ*(v[1,:]-v[end,:])
    dvdx[:,1] .= zeero      #TODO redundant if initialised with zeros
    dvdx[:,end] .= zeero
end

function Gqx_nonperiodic!(dqdx::Matrix{Numtype},q::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the q-grid.
    The result dqdx sits on the v-grid. =#

    dqdx[:,:] = one_over_Δ*(q[2:end,2:end-1] - q[1:end-1,2:end-1])
end

function Gqx_periodic!(dqdx::Matrix{Numtype},q::Matrix{Numtype})
    #= Calculates the gradient in x-direction on the q-grid.
    The result dqdx sits on the v-grid. =#

    dqdx[1:end-1,:] = one_over_Δ*(q[2:end,2:end-1] - q[1:end-1,2:end-1])
    dqdx[end,:] = one_over_Δ*(q[1,2:end-1] - q[end,2:end-1])
end

function Gqy_nonperiodic!(dqdy::Matrix{Numtype},q::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the q-grid.
    The result dqdy sits on the u-grid. =#

    dqdy[:,:] = one_over_Δ*(q[2:end-1,2:end] - q[2:end-1,1:end-1])
end

function Gqy_periodic!(dqdy::Matrix{Numtype},q::Matrix{Numtype})
    #= Calculates the gradient in y-direction on the q-grid.
    The result dqdy sits on the u-grid. =#

    dqdy[:,:] = one_over_Δ*(q[:,2:end] - q[:,1:end-1])
end


for N in [Float64, Float32, Rational{Int}]
    # unit vector
    for n in 1:3
        for i in 1:n
            e = SingleEntryVector(i, n, one(N))
            for j in 1:n
                @test e[j] == (i == j ? one(N) : zero(N))
            end
        end
    end

    # check that #1359 is fixed
    e = SingleEntryVector(2, 3, N(1)) # vector [0, 1, 0]
    D = Diagonal(N[1, 2, 3])
    De = D * e
    @test De isa SingleEntryVector && Vector(De) == N[0, 2, 0]

    # template direction approximation
    for n in 1:3
        B = BallInf(zeros(N, n), N(2))
        A = Matrix{N}(2I, n, n) + ones(N, n, n)
        X = A * B

        # box directions
        dir = BoxDirections{N}(n)
        @test isbounding(dir)
        @test dim(dir) == n
        box = overapproximate(X, dir)
        @test length(dir) == length(box.constraints) == 2*n
        box = overapproximate(X, BoxDirections)
        @test length(dir) == length(box.constraints) == 2*n

        # octagon directions
        dir = OctDirections{N}(n)
        @test isbounding(dir)
        @test dim(dir) == n
        oct = overapproximate(X, dir)
        @test length(dir) == length(oct.constraints) == 2 * n^2
        oct = overapproximate(X, OctDirections)
        @test length(dir) == length(oct.constraints) == 2 * n^2

        # box-diagonal directions
        dir = BoxDiagDirections{N}(n)
        @test isbounding(dir)
        @test dim(dir) == n
        boxdiag = overapproximate(X, dir)
        @test length(dir) == length(boxdiag.constraints) ==
              (n == 1 ? 2 : 2^n + 2*n)
        boxdiag = overapproximate(X, BoxDiagDirections)
        @test length(dir) == length(boxdiag.constraints) ==
              (n == 1 ? 2 : 2^n + 2*n)

        # spherical directions approximation
        if n == 2 && N in [Float32, Float64]
            dir = PolarDirections{N}(2)
            @test !isbounding(dir)
            dir = PolarDirections{N}(5)
            @test isbounding(dir)
            @test dim(dir) == 2
            polar = overapproximate(X, dir)
        end

        # spherical directions approximation
        if n == 3 && N in [Float32, Float64]
            dir = SphericalDirections{N}(2, 2)
            @test !isbounding(dir)
            dir = SphericalDirections{N}(5, 5)
            @test isbounding(dir)
            @test dim(dir) == 3
            spherical = overapproximate(X, dir)
        end

        # overapproximate lazy polyhedral intersections
        if N in [Float64]
            Y = B ∩ Ball1(zeros(N, n), N(1))
            Z = B ∩ HalfSpace(ones(N, n), N(1))
            for dir in [BoxDirections, OctDirections, BoxDiagDirections]
                overapproximate(Y, dir)
                overapproximate(Z, dir)
            end
        end

        # custom directions
        # empty list of directions
        dir = CustomDirections(Vector{N}[]; n=n)
        @test dim(dir) == n
        P = overapproximate(X, dir)
        @test isempty(constraints_list(P))
        # minimal number of bounded directions
        if n == 1
            dirs = [N[-1], N[1]]
        elseif n == 2
            dirs = [N[-1, 0], N[0, -1], N[1, 1]]
        elseif n == 3
            dirs = [N[-1, 0, 0], N[0, -1, 0], N[0, 0, -1], N[1, 1, 1]]
        end
        dir = CustomDirections(dirs)
        @test isbounding(dir)
        @test !isbounding(CustomDirections(dirs[1:end-1]))
        P = overapproximate(X, dir)
        @test P isa HPolytope && length(constraints_list(P)) == length(dirs)
    end
end

# default Float64 constructors
BoxDirections(3)
OctDirections(3)
BoxDiagDirections(3)
SphericalDirections(3)


using DSP
function ExtractFlattedGroundRoll(AShot,datainfo)
    ### Input:
	### AShot: one shot gather (nt,nrx,nry)
	### datainfo: a Dictionary contains info for one shot gather
	### Output:
	### A shot gather with only the flatted ground roll estimatation
	nrx = datainfo["nrx"]; #number of receiver x
	nry = datainfo["nry"]; #number of receiver y
	Rgrid = datainfo["Rgrid"]; #receiver grid space
	dt = datainfo["dt"]; # number of time
	# Pad zeros in time domain for the shot data
	AShot = [zeros(300,nrx,nry);AShot;zeros(100,nrx,nry)]

	vel = 1415; #velocity sand(dry) 0.2-1 Km/s, sand(water saturated 1.5-2 Km/s)

	# find the center position of receiver = source position
	CentX = (nrx+1)/2;
	CentY = (nry+1)/2;

	# calculate the shift grid for each receiver and save them in the matrix shiftT
	a = 1:nrx;
	b = 1:nry;
	row = reshape(repeat(a,outer=nry),nrx,nry);
	col = reshape(repeat(b,inner=nry),nrx,nry);
	shiftT = round.(sqrt.((row .-CentX).^2 .+ (col .-CentY).^2) .*(Rgrid/vel/(dt*1e-3)));

	Shift_Data = zeros(size(AShot));
	nt1 =  datainfo["nt"] + 300 + 100;
	for i = 1:nrx
	    for j = 1:nry
		Shift_Data[1:Int(nt1-shiftT[i,j]),i,j] = AShot[Int(shiftT[i,j]+1):nt1,i,j];
	    end
	end

	# define a filter in time domain to mute the signal besides groundroll
	A = zeros(nt1,1);
	band = 10;
	low = 230;
	high = 540;

	A[low+band+1:high-band-1] .= DSP.Windows.tukey(high-band-1-(low+band+1)+1,0.6;padding=0,zerophase=false)

	Shift_taper_Data = zeros(size(Shift_Data));
	for i = 1:nrx
	    for j = 1:nry
		Shift_taper_Data[:,i,j] = Shift_Data[:,i,j] .* A;
	    end
	end
	return Shift_taper_Data;
end


using Test, bad

α, β        = 0.05, .2
pnull, palt = .2, .4
n_required  = 35 # for corresponding single stage test
p           = Beta(1, 1) # uniform prior, only used to make conditioning valid for all p ∈ [0, 1]

# check under expected sample sise
problem = Problem(
    minimise(
        SampleSize(p | palt)
    ),
    Power(p | pnull) <= α,
    Power(p | palt)  >= 1 - β,
    # make search space large enough to incorporate single-stage solution
    nmax                    = n_required + 10,
    n1max                   = n_required + 1,
    type                    = :OneStage
)
design = optimise(problem; verbosity = 0)
@test problem.toer.score(design) .<= α
@test problem.power.score(design) .>= 1 - β
@test all( n2.(design, 0:n1(design)) .== 0 )
@test n1(design) == n_required

# check under minimax sample size
problem = Problem(
    MiniMaxSampleSize(.33, p),
    Power(p | pnull) <= α,
    Power(p | palt)  >= 1 - β,
    # make search space large enough to incorporate single-stage solution
    nmax                    = n_required + 10,
    n1max                   = n_required + 1,
    type                    = :OneStage
)
design = optimise(problem; verbosity = 0)
@test problem.toer.score(design) .<= α
@test problem.power.score(design) .>= 1 - β
@test all( n2.(design, 0:n1(design)) .== 0 )
@test n1(design) == n_required


using Photon
import Knet

# This file contains an example how to train a network on the MNIST dataset and
# plot some metrics afterwards (if you run this example in Juno or a
# Jupyter notebook).
#
# The MNIST dataset contains small (28x28) gray scale images that depict a
# handwritten digit. So the goal is to predict what the digit is.

# Load the MNIST data set
include(Knet.dir("data", "mnist.jl"))
trndata, tstdata = mnistdata()

# Create a simple Convolutional network.
model = Sequential(
      Conv2D(16, 3, relu),
      Conv2D(16, 3, relu),
      MaxPool2D(),
      Dense(64, relu),
      Dense(10)
)

# Create a workout containing the model, a loss function and
# the optimizer (default SGD) and register the accuracy.
workout = Workout(model, nll, acc=OneHotBinaryAccuracy())

# Run the training for 10 epochs and we don't need a convertor since
# mnistdata function already does that work.
fit!(workout, trndata, tstdata; epochs=10, convertor=identity)

println("\nTrained the model in $(workout.epochs) epochs.")

# Now let's plot some results. If you haven't installed Plots yet, you'll
# need to run:  using Pkg; Pkg.add("Plots")
import Plots
plotmetrics(Plots, workout, [:loss, :val_loss, :val_acc])


using PyPlot
using Polynomials

# Compute PSD for a two-component Lorentzian with
# both complex coefficients.  Check that PSD is positive
# and plot it.

#psd_check(x)
x = rand(8)
# x=[ar1,ai1,br1,bi1,ar2,ai2,br2,bi2]

ar1=x[1]
ai1=x[2]
br1=x[3]
bi1=x[4]
ar2=x[5]
ai2=x[6]
br2=x[7]
bi2=x[8]
# Compute d=0 case 10% of time:
if rand() < 0.1
  bi2 = (ar1*br1+ar2*br2-ai1*bi1)/ai2
end

d = ar1*br1 - ai1*bi1 + ar2*br2 - ai2*bi2

a = (2(br2^2-bi2^2)*(ar1*br1-ai1*bi1)+2(br1^2-bi1^2)*(ar2*br2-ai2*bi2)
 +(bi1^2+br1^2)*(ai1*bi1+ar1*br1)+(bi2^2+br2^2)*(ai2*bi2+ar2*br2))

b = (2(br2^2-bi2^2)*(bi1^2+br1^2)*(ai1*bi1+ar1*br1)
+2(br1^2-bi1^2)*(bi2^2+br2^2)*(ai2*bi2+ar2*br2)
+(bi2^2+br2^2)^2*(ar1*br1-ai1*bi1)
+(bi1^2+br1^2)^2*(ar2*br2-ai2*bi2))

c = ((bi2^2+br2^2)^2*(bi1^2+br1^2)*(ai1*bi1+ar1*br1)
+(bi1^2+br1^2)^2*(bi2^2+br2^2)*(ai2*bi2+ar2*br2))

if abs(d) < eps()
  d = 0.0
  println("Reached quadratic test")
  disc = b^2-4a*c
  if disc < 0
# there is no z_max, so set it to a negative value:
    zmax = -1.0
  else
    if a > 0
      zmax = (-b+sqrt(disc))/(2*a)
    else
      zmax = (-b-sqrt(disc))/(2*a)
    end
  end
else

  a /= d
  b /= d
  c /= d

  delta = 18a*b*c-4a^3*c+a^2*b^2-4b^3-27c^2

  if delta >= 0
    disc = sqrt(a^2-3b)
    theta = acos(0.5*(2a^3-9a*b+27c)/disc^3)
    zmax = -2/3*disc*cos((mod((theta+pi),2pi)+pi)/3)-a/3
  else
    r = (2a^3-9a*b+27c)/54
    q = (a^2-3b)/9
    aa = -(abs(r)+sqrt(r^2-q^3))^(1/3)
    if r < 0
      aa = -aa
    end
    if aa == 0
      bb = 0
    else
      bb = q/aa
    end
    zmax = aa+bb-a/3
  end
end

if zmax < 0
  println("Positive definite spectrum.  Maximum omega^2: ",zmax)
else
  println("Negative spectrum.  Maximum omega^2: ",zmax)
end

omega = linspace(0,20,100000)

psd = sqrt(2/pi)*((omega.^2.*(ar1*br1-ai1*bi1)+(ai1*bi1+ar1*br1)*(bi1^2+br1^2))./(omega.^4+2(br1^2-bi1^2).*omega.^2 + (bi1^2+br1^2)^2)
+(omega.^2.*(ar2*br2-ai2*bi2)+(ai2*bi2+ar2*br2)*(bi2^2+br2^2))./(omega.^4+2(br2^2-bi2^2).*omega.^2 + (bi2^2+br2^2)^2))

fig,axes = subplots(2,1)
ax = axes[1]
ax[:plot](omega,psd)
if zmax > 0
  ax[:scatter](sqrt(zmax),0)
end
# Now plot ACF:

tau = linspace(0,10,100000)
acf = (exp(-br1.*tau).*(ar1.*cos(bi1.*tau)+ai1.*sin(bi1.*tau))
+ exp(-br2.*tau).*(ar2.*cos(bi2.*tau)+ai2.*sin(bi2.*tau)))

ax = axes[2]
ax[:plot](tau,acf)

println(minimum(psd))

# Now, try Sturm's approach:

f0 = Poly([c,b,a,1])
f1 = polyder(f0)
f2 = -rem(f0,f1)
f3 = -rem(f1,f2)
#f4 = -rem(f2,f3)

f_of_0 = [f0(0),f1(0),f2(0),f3(0)]
f_of_inf = [f0[3],f1[2],f2[1],f3[0]]
sig_0 = 0
for i=1:3
  if f_of_0[i+1]*f_of_0[i] < 0
    sig_0 +=1
  end
end
sig_inf = 0
for i=1:3
  if f_of_inf[i+1]*f_of_inf[i] < 0
    sig_inf +=1
  end
end
println("Number of positive roots: ",sig_0-sig_inf)


export SolverProjGradDesc

struct SolverProjGradDesc{T<:Real} <:AbstractNextBasisSolver
    maxiter::Int
    batchsize::Int
    projiter::Int
    stepsize::T
    ftol::T
end

function SolverProjGradDesc(;
    maxiter::Int=100,
    batchsize::Int=20,
    projiter::Int=10,
    stepsize::T=.01,
    ftol::T=1e-4
    ) where T
    SolverProjGradDesc{T}(maxiter, batchsize, projiter, stepsize, ftol)
end

function (self::SolverProjGradDesc{T})(prob::Problem{L, B, CS},
                            r::Vector{T},
                            uinit::Vector{T}) where {L, B<:AbUniDirBasis, CS, T}
    u = copy(uinit)
    n = sizen(prob)
    obj = r'*calc(B, prob.X, u)
    iter = 0

    for i in 1:self.maxiter
        for j in 1:self.batchsize:n
            j2 = min(n, j+self.batchsize-1)
            X = view(prob.X, j:j2, :)
            J = jacobian(B, X, u)
            u -= self.stepsize * J' * r[j:j2]

            # Project
            if (iter .% self.projiter == 0) || (j2 == n)
                u = projectΩ(prob, u)
            end
            iter += 1
        end
        # Stopping
        Δobj = r'*calc(B, prob.X, u) - obj
        obj += Δobj
        abs(Δobj) < self.ftol && break
        printfmtln("\t\t\t obj={:8.4f}", obj)
    end
    u, obj
end


# small test with one tree quartet for the NLopt
# Claudia January 2015

treal = 0.1
obsCF=[1-2/3*exp(-treal),1/3*exp(-treal),1/3*exp(-treal)]
using NLopt

type ToyQuartet
    t::Float64
end

function objective(x::Float64,obsCF::Vector{Float64})
    length(obsCF) == 3 || error("obsCF must be size 3")
    val = obsCF[1]*log(1-2/3*exp(-x)) + obsCF[2]*log(1/3*exp(-x)) + obsCF[3]*log(1/3*exp(-x))
    return -val
end

function optimizar(obsCF::Vector{Float64}, q::ToyQuartet)
    t = q.t
    k = length(t)
    opt = NLopt.Opt(:LN_BOBYQA,k) # :LD_MMA if use gradient
    # criterion based on prof Bates code
    NLopt.ftol_rel!(opt,1e-12) # relative criterion
    NLopt.ftol_abs!(opt,1e-8) # absolute critetion
    NLopt.xtol_abs!(opt,1e-10) # criterion on parameter value changes
    NLopt.lower_bounds!(opt, zeros(k))
    NLopt.upper_bounds!(opt, Inf)
    count = 0
    function obj(x::Vector{Float64},g::Vector{Float64}) # added g::Vector{Float64} for gradient, ow error
        count += 1
        println("t is $(t) initially, x: $(x)")
        t = deepcopy(x)
        println("t is now $(t), x: $(x)")
        val = objective(x[1],obsCF)
        println("f_$count: $(round(val,5)), x: $(x)")
        return val
    end
    NLopt.min_objective!(opt,obj)
    fmin, xmin, ret = NLopt.optimize(opt,[t])
    return fmin,xmin
end

q = ToyQuartet(1.0)
fmin,xmin = optimizar(obsCF,q)


# https://fenicsproject.org/docs/dolfin/latest/python/demos/auto-adaptive-poisson/demo_auto-adaptive-poisson.py.html

module auto_adaptive_poisson

using FenicsPy

# Create mesh and define function space
mesh = UnitSquareMesh(8, 8)
V = FunctionSpace(mesh, "Lagrange", 1)

# Define boundary condition
u0 = FeFunction(V)
bc = DirichletBC(V, u0, "x[0] < $DOLFIN_EPS || x[0] > 1.0 - $DOLFIN_EPS")

# Define variational problem
u = TrialFunction(V)
v = TestFunction(V)
f = Expression("10*exp(-(pow(x[0] - 0.5, 2) + pow(x[1] - 0.5, 2)) / 0.02)",
	                      degree=1)
g = Expression("sin(5*x[0])", degree=1)
a = inner(grad(u), grad(v))*dx()
L = f*v*dx() + g*v*ds()

# Define function for the solution
u = FeFunction(V)

# Define goal functional (quantity of interest)
M = u*dx()

# Define error tolerance
tol = 1.e-5

# Solve equation a = L with respect to u and the given boundary
# conditions, such that the estimated error (measured in M) is less
# than tol
problem = LinearVariationalProblem(a, L, u, bc)
solver = AdaptiveLinearVariationalSolver(problem, M)
solver.parameters["error_control"]["dual_variational_solver"]["linear_solver"] = "cg"
solver.parameters["error_control"]["dual_variational_solver"]["symmetric"] = true
solver.solve(tol)

solver.summary()

# Plot solution(s)
#plot(u.root_node(), title="Solution on initial mesh")

#plot(u.leaf_node(), title="Solution on final mesh")

end # module auto_adaptive_poisson


# Test the multivariate distribution support for HMC sampler

using Turing, Distributions

# Define helper functions
function sigmoid(t)
  return 1 / (1 + exp.(-t))
end

# Define NN flow
function nn(x, b1, w11, w12, w13, bo, wo)
  h = tanh.([w11' * x + b1[1]; w12' * x + b1[2]; w13' * x + b1[3]])
  return sigmoid((wo' * h)[1] + bo)
end

# Generating training data
N = 20
M = round(Int64, N / 4)
x1s = rand(M) * 5
x2s = rand(M) * 5
xt1s = Array([[x1s[i]; x2s[i]] for i = 1:M])
append!(xt1s, Array([[x1s[i] - 6; x2s[i] - 6] for i = 1:M]))
xt0s = Array([[x1s[i]; x2s[i] - 6] for i = 1:M])
append!(xt0s, Array([[x1s[i] - 6; x2s[i]] for i = 1:M]))

xs = [xt1s; xt0s]
ts = [ones(M); ones(M); zeros(M); zeros(M)]

# Define model

alpha = 0.16            # regularizatin term
var = sqrt(1.0 / alpha) # variance of the Gaussian prior

@model bnn(ts) = begin
  b1 ~ MvNormal([0 ;0; 0], [var 0 0; 0 var 0; 0 0 var])
  w11 ~ MvNormal([0; 0], [var 0; 0 var])
  w12 ~ MvNormal([0; 0], [var 0; 0 var])
  w13 ~ MvNormal([0; 0], [var 0; 0 var])
  bo ~ Normal(0, var)

  wo ~ MvNormal([0; 0; 0], [var 0 0; 0 var 0; 0 0 var])
  for i = rand(1:N, 10)
    y = nn(xs[i], b1, w11, w12, w13, bo, wo)
    ts[i] ~ Bernoulli(y)
  end
  b1, w11, w12, w13, bo, wo
end

# Sampling
chain = sample(bnn(ts), HMC(10, 0.1, 5))


module LaverySpline

import PPInterpolation: PP, computeDEF
using JuMP
using Cbc
#using GLPK
export makeLaverySpline, makeQuinticLaverySpline

#TODO remove the various println, move to an extra param optional

function makeLaverySpline(x::AbstractArray{TX}, y::AbstractArray{T}) where {T,TX}
    pp = PP(3, T, TX, length(y))
    computeLaverySpline(pp, x, y)
    return pp
end

function computeLaverySpline(pp::PP{3,T,TX}, x::AbstractArray{TX}, z::AbstractArray{T}) where {T,TX}
    h = x[2:end] - x[1:end-1] #h1 = x2-x1
    dz = (z[2:end] - z[1:end-1]) ./ h
    size = length(z)
    nIntervals = 10
    firstSize = (size - 1) * nIntervals #first sum from 1 to size-1 (included) and interior sum from 1 to nInterval
    #1,1 1,2, ..., 1,nIntervals ...,size-1,1, size-1,2, ..., size-1,nIntervals
    eps = 1e-4
    m = Model(with_optimizer(Cbc.Optimizer))
    # m = Model(with_optimizer(GLPK.Optimizer, tm_lim = 60.0, msg_lev = GLPK.OFF))
    @variable(m, bv[1:size])
    @variable(m, s0[1:firstSize] >= 0) #first slack
    # @objective(m, Min, sum(s0))

    @variable(m, s1[1:size-2] >= 0) #second slack
    @objective(m, Min, sum(s0) + eps * sum(s1)) #s0 and s1 don't have the same size! => add s0 to s1 elements! or expand s0 and s1
    @constraint(m, s1 .>= bv[2:size-1])
    @constraint(m, s1 .>= -bv[2:size-1])
    deltatk = 1.0 / (nIntervals)

    for k = 1:nIntervals
        for i = 1:size-1
            dzi = dz[i]
            tk = -0.5 + (k - 0.5) / nIntervals
            @constraint(m, s0[k+(i-1)*nIntervals] >= ((-1 + 6 * tk) * bv[i] + (1 + 6 * tk) * bv[i+1] - 12 * dzi * tk) * deltatk)
            @constraint(m, s0[k+(i-1)*nIntervals] >= -((-1 + 6 * tk) * bv[i] + (1 + 6 * tk) * bv[i+1] - 12 * dzi * tk) * deltatk)
        end
    end
    @constraint(m, bv[1] == dz[1] - 0.5 * (bv[2] - dz[1]))
    @constraint(m, bv[size] == dz[size-1] - 0.5 * (bv[size-1] - dz[size-1]))
    optimize!(m)

    println("Objective: ", objective_value(m))

    b = zeros(size)

    for i = 1:size
        println("b $i: ", value(bv[i]))
        b[i] = value(bv[i])
    end
    #b[1] = dz[1]-0.5*(b[2]-dz[1]) #natural BC
    #b[end] = dz[end]-0.5*(b[end-1]-dz[end]) #natural BC

    c = pp.c
    for i = 1:n-1
        c[1, i] = (3 * dz[i] - b[i+1] - 2 * b[i]) / h[i]
        c[2, i] = (b[i+1] + b[i] - 2 * dz[i]) / (h[i]^2)
    end
    pp.a[1:end] = z
    pp.b[1:end] = b
    pp.x[1:end] = x
end

function makeQuinticLaverySpline(x::AbstractArray{TX}, z::AbstractArray{T}, eps1 = 1e-3, eps2 = 1e-3, order = 3) where {T,TX}
    h = x[2:end] - x[1:end-1] #h1 = x2-x1
    dz = (z[2:end] - z[1:end-1]) ./ h
    size = length(z)
    nIntervals = 10
    firstSize = (size - 1) * nIntervals #first sum from 1 to size-1 (included) and interior sum from 1 to nInterval
    #1,1 1,2, ..., 1,nIntervals ...,size-1,1, size-1,2, ..., size-1,nIntervals

    #first: compute slopes for a cubic.
    m = Model(with_optimizer(Cbc.Optimizer))
    # m = Model(with_optimizer(GLPK.Optimizer, tm_lim = 60.0, msg_lev = GLPK.OFF))
    @variable(m, bv[1:size])
    @variable(m, s0[1:firstSize] >= 0) #first slack
    # @objective(m, Min, sum(s0))

    @variable(m, s1[1:size-2] >= 0) #second slack
    @objective(m, Min, sum(s0) + eps1 * sum(s1)) #s0 and s1 don't have the same size! => add s0 to s1 elements! or expand s0 and s1
    @constraint(m, s1 .>= bv[2:size-1])
    @constraint(m, s1 .>= -bv[2:size-1])
    deltatk = 1.0 / (nIntervals)

    for k = 1:nIntervals
        for i = 1:size-1
            dzi = dz[i]
            tk = -0.5 + (k - 0.5) / nIntervals
            @constraint(m, s0[k+(i-1)*nIntervals] >= ((-1 + 6 * tk) * bv[i] + (1 + 6 * tk) * bv[i+1] - 12 * dzi * tk) * deltatk)
            @constraint(m, s0[k+(i-1)*nIntervals] >= -((-1 + 6 * tk) * bv[i] + (1 + 6 * tk) * bv[i+1] - 12 * dzi * tk) * deltatk)
        end
    end
    @constraint(m, bv[1] == dz[1] - 0.5 * (bv[2] - dz[1]))
    @constraint(m, bv[size] == dz[size-1] - 0.5 * (bv[size-1] - dz[size-1]))
    optimize!(m)

    println("Objective: ", objective_value(m))

    b = zeros(T, size)

    for i = 1:size
        # println("b $i: ", value(bv[i]))
        b[i] = value(bv[i])
    end

    #second: compute second derivative for quintic

    m = Model(with_optimizer(Cbc.Optimizer))
    # m = Model(with_optimizer(GLPK.Optimizer, tm_lim = 60.0, msg_lev = GLPK.OFF))
    @variable(m, cv[1:size])
    @variable(m, s0[1:firstSize] >= 0) #first slack
    @variable(m, s1[1:size-2] >= 0) #second slack
    @objective(m, Min, sum(s0) + eps2 * sum(s1)) #s0 and s1 don't have the same size! => add s0 to s1 elements! or expand s0 and s1
    @constraint(m, s1 .>= cv[2:size-1])
    @constraint(m, s1 .>= -cv[2:size-1])

    if order == 1
        for k = 1:nIntervals
            for i = 1:size-1
                deltatk = (x[i+1] - x[i]) / (nIntervals)
                hk = deltatk * (k - 0.5)
                @constraint(m, s0[k+(i-1)*nIntervals] >= (b[i] + hk * cv[i] + 3 * hk^2 * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 4 * hk^3 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 5 * (hk^4) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
                @constraint(m, -s0[k+(i-1)*nIntervals] <= (b[i] + hk * cv[i] + 3 * hk^2 * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 4 * hk^3 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 5 * (hk^4) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
            end
        end
    elseif order == 2
        #second der constraint
        for k = 1:nIntervals
            for i = 1:size-1
                deltatk = (x[i+1] - x[i]) / (nIntervals)
                hk = deltatk * (k - 0.5)
                @constraint(m, s0[k+(i-1)*nIntervals] >= (cv[i] + 6 * hk * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 12 * hk^2 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 20 * (hk^3) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
                @constraint(m, -s0[k+(i-1)*nIntervals] <= (cv[i] + 6 * hk * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 12 * hk^2 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 20 * (hk^3) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
            end
        end
        #third der
    elseif order == 3
        for k = 1:nIntervals
            for i = 1:size-1
                deltatk = (x[i+1] - x[i]) / (nIntervals)
                hk = deltatk * (k - 0.5)
                @constraint(m, s0[k+(i-1)*nIntervals] >= (6 * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 24 * hk * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 60 * (hk^2) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
                @constraint(m, -s0[k+(i-1)*nIntervals] <= (6 * ((cv[i+1] - 3 * cv[i]) / (2 * h[i]) + 2 * (5 * dz[i] - 3 * b[i] - 2 * b[i+1]) / (h[i]^2)) + 24 * hk * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 60 * (hk^2) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
            end
        end
    elseif order == 4
        for k = 1:nIntervals
            for i = 1:size-1
                deltatk = (x[i+1] - x[i]) / (nIntervals)
                hk = deltatk * (k - 0.5)
                @constraint(m, s0[k+(i-1)*nIntervals] >= (24 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 120 * (hk) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
                @constraint(m, -s0[k+(i-1)*nIntervals] <= (24 * ((3 * cv[i] - 2 * cv[i+1]) / (2 * h[i]^2) + (8 * b[i] + 7 * b[i+1] - 15 * dz[i]) / (h[i]^3)) + 120 * (hk) * ((cv[i+1] - cv[i]) / (2 * h[i]^3) + 3 * (2 * dz[i] - b[i+1] - b[i]) / (h[i]^4))) * deltatk)
            end
        end
    end
    @constraint(m, cv[1] == 0)
    @constraint(m, cv[size] == 0)
    optimize!(m)

    println("Objective: ", objective_value(m))

    y2 = zeros(T, size)

    for i = 1:size
        #        println("c $i: ", value(cv[i]))
        y2[i] = value(cv[i])
    end

    #b[1] = dz[1]-0.5*(b[2]-dz[1]) #natural BC
    #b[end] = dz[end]-0.5*(b[end-1]-dz[end]) #natural BC

    c = zeros(T, (4, size))
    computeDEF(c, b, y2, dz, h)
    return PP(5, copy(z), b, c, copy(x))
end
end


module MinimumVolumeEllipsoids

using Distributions
using LinearAlgebra
using PDMats
using SparseArrays
using SpecialFunctions

export Ellipsoid

export minimum_volume_ellipsoid
export rand
export volume

struct Ellipsoid
    H::AbstractPDMat
    x::AbstractVector
end

function minimum_volume_ellipsoid(
    X::AbstractMatrix,
    tol::Real=1e-7,
    KKY::Integer=0,
    maxit::Integer=100000;
    centered::Bool=false,
)
    n, m = size(X)

    if centered
        _, R, ϕ = _minvol(X, tol, KKY, maxit)

        H = inv(R.L * R.L' / ϕ)
        x = zeros(n)
    else
        Y = [X; ones(1, m)]

        u, R, ϕ = _minvol(Y, tol, KKY, maxit)

        H = inv(R.L * R.L' / ϕ)[1:n, 1:n]
        x = X * u
    end

    H = (H + H') / 2
    H = PDMat(H)

    return Ellipsoid(H, x)
end

function _minvol(X::AbstractMatrix, tol::Real=1e-7, KKY::Integer=0, maxit::Integer=100000)
    n, m = size(X)

    n100 = max(n, 100)
    n50000 = max(n, 50000)

    iter = 0

    if KKY >= 1
        u = (1 / m) * ones(m)
    else
        u = initwt(X)
    end

    # Initialize Cholesky Factor
    upos = findall(u .> 0)
    R, var = _compute_R_and_var(u, X, upos)
    ϕ = 1

    ω₊ = maximum(var)

    # Use the Harman-Pronzato test to see if columns of X can be eliminated.
    essential_indices = _harman_pronzato_elimination(ω₊, n, var, u)
    XX = X[:, essential_indices]

    # If only n columns remain, recompute u and R
    if length(essential_indices) == n
        u = (1 / n) * ones(n)
        upos = findall(u .> 1e-8)
        R, var = _compute_R_and_var(u, XX, upos)
    else
        var = var[essential_indices]
        u = u[essential_indices] / sum(u[essential_indices])
        upos = findall(u .> 1e-8)
    end

    # Find "furthest" and "closest" points
    ω₊, j = findmax(var)
    ω₋, i = findmin(var[upos])
    i = upos[i]

    while ((ω₊ > (1 + tol) * n) || (ω₋ < (1 - tol) * n)) && iter < maxit
        iter += 1

        (j, ω) = ω₊ + ω₋ > 2 * n ? (j, ω₊) : (i, ω₋)

        # compute new λ
        λ = (ω - n) / ((n - 1) * ω)
        λ = max(λ, -u[j])

        # Update u and make sure it stays nonnegative
        uold = u
        u[j] = max(u[j] + λ, 0)
        u = (1 / (1 + λ)) * u
        upos = findall(u .> 1e-8)

        Rxj = R.U' \ XX[:, j]
        x = ϕ * (R.U \ Rxj)
        ωⱼ = ϕ * (Rxj' * Rxj)

        recompute_R = abs(ωⱼ - ω) / max(1, ω) > 1e-8

        ω = ωⱼ

        # Every 50.000 iterations: Check if R should be recomputed
        if mod(iter, n50000) == 0
            upos = findall(uold .> 0)
            M = XX[:, upos] * Diagonal(uold[upos]) * XX[:, upos]'
            if norm(ϕ * M - R.L * R.L') / (ϕ * norm(M)) > 1e-8
                recompute_R = true
            end
        end

        if recompute_R
            upos = findall(u .> 0)
            R, var = _compute_R_and_var(u, XX, upos)
            ϕ = 1
        else
            xx = sqrt(abs(λ) * ϕ) * XX[:, j]
            if λ > 0
                lowrankupdate!(R, xx)
            else
                lowrankdowndate!(R, xx)
            end
            ϕ *= (1 + λ)
            mult = λ / (1 + λ * ω)
            var = (1 + λ) * (var - mult * transpose((transpose(x) * XX)) .^ 2)
        end

        ω₊, j = findmax(var)

        # Every 100 iterations: Check if more points can be eliminated
        if mod(iter, n100) == 0
            essential = _harman_pronzato_elimination(ω₊, n, var, u)
            if length(essential) < length(essential_indices)
                essential_indices = essential_indices[essential]
                XX = XX[:, essential]
                if length(essential_indices) == n
                    u = (1 / n) * ones(n, 1)
                    R, var = _compute_R_and_var(u, XX, upos)
                    ϕ = 1
                else
                    var = var[essential]
                    u = u[essential] / sum(u[essential])
                end
                ω₊, j = findmax(var)
            end
        end

        upos = findall(u .> 0)
        ω₋, i = findmin(var[upos])
        i = upos[i]
    end

    uu = zeros(m)
    uu[essential_indices] = u
    u = uu

    return u, R, ϕ
end

function _harman_pronzato_elimination(
    ω₊::Float64, n::Integer, var::AbstractVector, u::AbstractVector
)
    δn = ω₊ - n
    # TODO: Figure out when and why this happens
    if δn < 0
        δn = 0
    end
    threshold = n * (1 + δn / 2 - √(δn - δn / n + ((δn / n)^2 * n^2) / 4))
    return findall((var .> threshold) .| (u .> 1e-8))
end

function _compute_R_and_var(u::AbstractVector, X::AbstractMatrix, upos::AbstractVector)
    A = spdiagm(sqrt.(u[upos])) * X[:, upos]'
    F = qr(A)
    R = Cholesky(F.R, :U, 0)
    RX = R.U' \ X
    var = vec(sum(RX .* RX; dims=1))
    return R, var
end

"""
    initwt(X)

Obtain the initial weights `u` using the Kumar-Yildirim algorithm, taking into account that
`X` represents [X, -X].
"""
function initwt(X::AbstractMatrix)
    n, m = size(X)
    u = zeros(m)
    Q = 1.0 * I(n)
    d = Q[:, 1]

    for j in 1:n
        # compute the maximizer of | d'*x | over the columns of X.
        dX = vec(abs.(d' * X))
        _, ind = findmax(dX)
        u[ind] = 1

        j == n && break

        # update Q
        y = X[:, ind]
        z = Q' * y

        if j > 1
            z[1:(j - 1)] = zeros(j - 1, 1)
        end

        ζ = norm(z)
        zj = z[j]
        if zj < 0
            ζ = -ζ
        end
        zj += ζ
        z[j] = zj
        Q = Q - (Q * z) * ((1 / (ζ * zj)) * z')
        d = Q[:, j + 1]
    end

    u /= n
    return u
end

function Base.rand(ϵ::Ellipsoid, m::Integer)
    n = size(ϵ.H, 1)
    X = randn(n, m)
    X = X ./ kron(ones(n, 1), sqrt.(sum(X .^ 2; dims=1)))
    R = ones(n, 1) * rand(1, m) .^ (1 / n)
    sphere = R .* X
    ellipsoid = sqrt(n) * cholesky(inv(ϵ.H)).L * sphere + ϵ.x .* ones(1, m)
    return ellipsoid
end

include("volume.jl")

end # module


import Test
import PredictMD

table1 = [20 5; 10 15]
Test.@test(
    isapprox(PredictMD.cohen_kappa(table1), 0.4; atol = 0.00000000001)
    )

table2 = [45 15; 25 15]
Test.@test(
    isapprox(PredictMD.cohen_kappa(table2), 0.1304; atol = 0.0001)
    )

table3 = [25 35; 5 35]
Test.@test(
    isapprox(PredictMD.cohen_kappa(table3), 0.2593; atol = 0.0001)
    )

table4 = [9 3 1; 4 8 2 ; 2 1 6]
Test.@test(
    isapprox(PredictMD.cohen_kappa(table4), 0.45; atol = 0.001)
    )


"""Find all neighbors of vertex `u`` within `G` that precede it in the given ordering"""
function lowernbrs(G::AbstractComplex, uidx::IX, E) where {IX <: Integer}
    upos = position(G, uidx, 0)
    nbrs = Set{IX}()
    # get all edges of u: select {v} s.t. u > v for all edges {u,v}
    for v in cells(G,0)
        vidx = hash(v)
        vpos = position(G, vidx, 0)
        upos > vpos && E[vpos, upos] && push!(nbrs, vidx)
    end
    return nbrs
end

"""Inductive construction of VR complex from neighborhood graph"""
function inductive!(cplx, k, E)
    C0 = cells(cplx, 0)
    for i in 1:k-1
        cls = cells(cplx, i)
        cls === nothing && continue
        for τ in cls
            # N = ∩ᵤₜlowernbrs(G,u)
            V = vertecies(τ)
            N = lowernbrs(cplx, V[1], E)
            for i in 2:length(V)
                intersect!(N, lowernbrs(cplx, V[i], E))
            end
            #K ⟵ K ∪ {τ ∪ {v}}
            for vidx in N
                vpos = position(cplx, vidx, 0)
                σ = τ ∪ C0[vpos]
                push!(cplx, σ)
            end
        end
    end
end

"""Add all simplices of the `k`-skeleton whose maximal vertex is `τ`"""
function addcofaces!(cplx, k, τ, N, E)
    # V ← V ∪ {τ}
    τ ∉ cplx && addsimplex!(cplx, τ)
    # stop recursion
    dim(τ) >= k && return
    for vidx in N
        # σ ← τ ∪ {v}
        vpos = position(cplx, vidx, 0)
        σ = τ ∪ cells(cplx, 0)[vpos]
        # M ← N ∩ lowernbrs(G, v)
        M = intersect(N, lowernbrs(cplx, vidx, E))
        addcofaces!(cplx, k, σ, M, E)
    end
end

"""Incremental construction of VR complex from neighborhood graph"""
function incremental!(cplx, k, E)
    # construct VR complex
    for u in cells(cplx, 0)
        N = lowernbrs(cplx, hash(u), E)
        addcofaces!(cplx, k, u, N, E)
    end
end

"""Weight function for VR complex"""
function weight(σ, w, cplx)
    k = dim(σ)
    k == 0 && return 0.
    k == 1 && return w[1][cplx[σ]]
    return maximum([w[k-1][position(cplx, τ)] for τ in faces(σ)])
end

function expand(method, cplx, w, kmax, E)
    # perform nerve expansion
    if method == :incremental
        incremental!(cplx, kmax, E)
    elseif method == :inductive
        inductive!(cplx, kmax, E)
    else
        throw(ArgumentError("Invalid method name $(method)"))
    end

    if w !== nothing
        for k in 2:dim(cplx)
            w[k] = zeros(size(cplx,k))
            for σ in cells(cplx,k)
                w[k][position(cplx, σ)] = weight(σ, w, cplx)
            end
        end
    end
    return
end

"""
    vietorisrips(X, ɛ[, weights])

Construction of a Vietoris–Rips complex

# Arguments
- `X::AbstractMatrix{T}`: the dataset where each column defines a data point.
- `ɛ::Real`: the maximal scale value.
- `weights::Bool`: this flag indicates if the filteration weight should be calculated. Default value is `true`.

This is an inefficient implementation (based on distance matrix) of the VRC algorithm from:

    Afra Zomorodian, Fast construction of the Vietoris-Rips complex, 2010
"""
function vietorisrips(X::AbstractMatrix{T}, ɛ::Real, weights = true;
                      expansion = :incremental,
                      distance  = Distances.Euclidean(),
                      maxoutdim = size(X,2)-1) where T <: Real
    d, n = size(X)

    # add zero-dimensional simplices to complex
    splxs = map(Simplex, 1:n)
    cplx = SimplicialComplex(splxs...)

    # calculate cross distances for each point in dataset
    D = Distances.pairwise(distance, X)

    # build 1-skeleton (neighborhood graph)
    E = spzeros(Bool, n, n) # adjacency matrix
    for i in 1:n
        E[i,i] = true
        for j in findall(d-> 0 < d <= ɛ, view(D, :, i))
            s = Simplex(i,j)
            # check if simplex is already added
            s in cplx && continue
            # add simplex to complex
            push!(cplx, s)
            # fill adjacency matrix
            E[i,j] = true
            E[j,i] = true
        end
    end

    # determine maximal dimension
    kmax = min(maxoutdim, maximum(mapslices(c->count(d-> 0.0 < d ≤ ɛ, c), D, dims=1)))

    # calculate weights of nerve
    w = nothing
    if weights
        w = Dict{Int, Vector{T}}()
        w[0] = zeros(size(cplx,0))
        if size(cplx, 1) > 0
            w[1] = zeros(size(cplx,1))
            for e in cells(cplx,1)
                w[1][position(cplx, e)] = D[map(v->position(cplx, v), faces(e))...]
            end
        end
    end

    # perform expansion
    expand(expansion, cplx, w, kmax, E)

    return cplx, w
end

"""
Landmark selection for witness complex
"""
function landmarks(X::AbstractMatrix, l::Int; method = :minmax,
                   distance = Distances.Euclidean(), firstpoint = 0)
    d, n = size(X)
    if method == :random
        return Random.randperm(n)[1:l]
    elseif method == :minmax
        idxs = firstpoint > 0 ? [firstpoint] : rand(1:n, 1)
        Z = view(X, :, idxs)
        for i in 2:l
            maxv = -Inf
            maxvidx = 0
            for j in 1:n
                if j ∉ idxs
                    v, _ = findmin(Distances.colwise(distance, X[:,j], Z))
					if v > maxv
					    maxv = v
                        maxvidx = j
                    end
                end
            end
            push!(idxs, maxvidx)
        end
        return idxs
    else
        throw(ArgumentError("Invalid method name $(method)"))
    end
end

"""
    witness(X, l, ɛ[, weights])

Construction of a witness complex.

# Arguments
- `X::AbstractMatrix{T}`: the dataset where each column defines a data point.
- `l::Int`: the number of landmarks.
- `ɛ::Real`: the maximal scale value.
- `weights::Bool`: this flag indicates if the filteration weight should be calculated. Default value is `true`.

# Keyword arguments
For parameter ν = 0, 1, 2 determines size of landmark radius.
- If ν = 0, then for i = 1, 2, . . . , N define m_i = 0.
- If ν > 0, then for i = 1, 2, . . . , N define m_i to be the ν-th smallest entry of the i-th column of D.
"""
function witness(X::AbstractMatrix{T}, l::Int, ɛ::Real, weights = true;
                 landmark = :minmax, distance = Distances.Euclidean(),
                 expansion = :incremental, ν::Int = 2, maxoutdim = size(X,1)-1,
                 firstpoint = 0) where T <: Real

    # get landmarks
    L = landmarks(X, l, method = landmark, distance = distance, firstpoint=firstpoint)

    # get distances to landmarks
    D = Distances.pairwise(distance, X[:,L], X)

    # simplexes contain indexes to landamarks
    cplx, w = witness(D, ɛ, weights, expansion=expansion, ν=ν, maxoutdim=maxoutdim, firstpoint=firstpoint)

    return cplx, w
end

"""
    witness(D, ɛ[, weights])

Construction of a witness complex from the distance matrix `D` with a maximum scale value `ɛ`.
"""
function witness(D::AbstractMatrix, ɛ::Real, weights = true;
                 expansion = :incremental,
                 ν::Int = 2,
                 maxoutdim = size(D,1)-1,
                 firstpoint = 0) where T <: Real

    @assert ν < 3 "ν only can take values of 0, 1 or 2"
    @assert maxoutdim > 0 "Maximum dimension should be more then 0"

    l, n = size(D)

    # get landmark radius extension m_i
    m = zeros(eltype(D), n)
    if ν > 0
        idxs = zeros(Int, l)
        for i in 1:n
            sortperm!(idxs, D[:,i])
            m[i] = D[idxs[ν], i]
        end
    end

    # construct 1-seleton edges
    splxs = map(Simplex, 1:l)
    cplx = SimplicialComplex(splxs...)
    w = nothing
    if weights
        w = Dict{Int, Vector{Float64}}()
        w[0] = zeros(size(cplx,0))
        w[1] = zeros(0)
    end

    # the edge σ = [ab] belongs to W(D; ɛ, ν) iff there exists a witness i ∈ {1, 2, ..., N} such that:
    # max(D(a, i), D(b, i)) ≤ ɛ + m_i
    E = spzeros(Bool, l, l) # adjacency matrix
    for i in 1:l
        for j in i+1:l
            e = Inf
            @inbounds for k in 1:n
                d = max(D[i, k], D[j, k])
                d = d < m[k] ? 0.0 : d - m[k]
                if d < e
                    e = d
                end
            end

            if e ≤ ɛ
                push!(cplx, Simplex(i, j))
                weights && push!(w[1], e)
                E[i, j] = true
            end
        end
    end

    # determine maximal dimension
    Rmax = ɛ+maximum(m)
    kmax = min(maxoutdim, maximum(mapslices(c->count(d->0.0<d≤Rmax, c), D, dims=1)))

    # perform expansion
    expand(expansion, cplx, w, kmax, E)

    return cplx, w
end

"""
    welzl(P, R)

Computes the smallest enclosing circle of the union of `P` and `R`.
"""
function welzl(P, R)
    p = length(P)
    r = length(R)
    if p == 0 || r >= 3
        if r == 1
            return R[1], 0.0
        elseif r == 2
            p1 = R[1]
            p2 = R[2]
            m = (p1.+p2) ./ 2.
            d = sqrt(sum(abs2, (x .- y)))
            return m, d / 2.
        elseif true
            @warn "the points of R are cocircular"
            return [], 0.0
        else
            @error "Undefined"
            return [], -1.0
       end
    end
    i = rand(1:p)
    x = P[i]
    c, r = welzl(deleteat!(P, i), R)
    norm(x - c) <= r && return (c, r)
    return welzl(P, push!(R, x))
end

"""
    čech(X, ɛ[, weights])
    cech(X, ɛ[, weights])

Construction of a Čeck complex.

# Arguments
- `X::AbstractMatrix{T}`: the dataset where each column defines a data point.
- `ɛ::Real`: the maximal scale value.
- `weights::Bool`: this flag indicates if the filteration weight should be calculated. Default value is `true`.

"""
function čech(X::AbstractMatrix{T}, ɛ::Real, weights = true;
              distance  = Distances.Euclidean(),
              maxoutdim = size(X,2)-1) where T <: Real

    d, n = size(X)

    splxs = map(Simplex, 1:n) # construct 0-simplices
    cplx = SimplicialComplex(splxs...) # and 0-complex

    w = nothing
    if weights
        w = Dict{Int, Vector{Float64}}()
        w[0] = zeros(size(cplx,0))
    end

    for h in 1:maxoutdim
        idx = collect(h+1:-1:1)
        weights && setindex!(w, zeros(T, 0), h)

        while idx[end] <= n-h
            # find radius of bounding sphere
            c, r = BoundingSphere.boundingsphere([X[:, i] for i in idx])

            # add simplex in radius less then maximal filtration value
            if r <= ε
                s = Simplex(reverse(idx))
                addsimplex!(cplx, s)
                weights && push!(w[h], r)
            end

            # iterate through all combinations of point indecies
            for i in 1:h+1
                idx[i] += 1
                if idx[i] <= n-i+1
                    for j in i-1:-1:1
                        idx[j] = idx[j+1] + 1
                    end
                    break
                end
            end
        end
    end

    return cplx, w
end
const cech = čech


import DataStructures: PriorityQueue, enqueue!, peek, dequeue!

function readinstance()
    n, m, x, y = [parse(Int, x) for x in split(readline())]
    edges = Dict{Int, Set{Tuple{Int, Int, Int}}}()
    for i=1:n
        edges[i] = Set{Tuple{Int, Int, Int}}()
    end
    for i=1:m
        a, b, t, k = [parse(Int, x) for x in split(readline())]
        push!(edges[a], (b, t, k))
        push!(edges[b], (a, t, k))
    end
    n, m, x, y, edges
end

function solve()
    n, m, x, y, edges = readinstance()
    fixed = Dict{Int, Int}()
    unfixed = PriorityQueue{Int, Int}()
    for i=1:n
        enqueue!(unfixed, i, i == x ? 0 : Inf)
    end

    while length(unfixed) != 0
        v, d = peek(unfixed)
        dequeue!(unfixed)
        if d == Inf
            return -1
        end
        if v == y
            return d
        end
        fixed[v] = d
        for (b, t, k) in edges[v]
            if b ∉ keys(fixed)
                dd = ceil(Int, d / k) * k + t
                if dd < unfixed[b]
                    unfixed[b] = dd
                end
            end
        end
    end

    y ∈ keys(fixed) ? fixed[y] : -1
end

println(solve())


using Seismic,PyCall
@pyimport matplotlib.pyplot as plt

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=25;
f_niquest=int(floor(1/dt))
param=["dt"=>dt, "dx"=>dx]

data=rand(nt,nx)

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

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,:]

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

# apply FK filter
data_fk,fk_spec,h = FKFilter(data,h,param)

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

f21 = plt.figure(21)
plt.imshow(fk_spec,cmap="PuOr",aspect="auto",vmin=-1,vmax=1)
plt.title("fk spectrum after filter")
plt.xlabel("K (1/m)")
plt.ylabel("W (1/s)")
plt.show()


@testset "Binning" begin
    @test bin([1 2 3 4; 5 6 7 8], (2, 2)) == [14 22]
    @test bin(ones((4, 4)), (2, 4)) ≈ [8.0; 8.0]
    @test bin([1, 2, 3, 4, 5], (2,)) == [3, 7]
    @test bin(ones((2, 2, 2)), (2,)) ≈ ones((1, 2,2)) .* 2
    @test bin(ones((2, 2, 2)), (2,)) == bin(ones((2, 2, 2)), (2, 1, 1))

    x = randn(Float32, (11, 4, 5))
    r2 = bin(x, (1, 3, 2))
    @tullio r[i,j,k] := x[i, 3j, 2k] + x[i, 3j-1, 2k] + x[i, 3j-2, 2k] + x[i, 3j, 2k-1] + x[i, 3j-1, 2k-1] + x[i, 3j-2, 2k-1]

    @test typeof(r2) == typeof(r)

    x_3D = randn((11, 10, 3))
    @test MicroscopyTools.bin2(x_3D) ≈ bin(x_3D, (2,2))
    x_2D = randn((11, 10))
    @test MicroscopyTools.bin2(x_2D) ≈ bin(x_2D, (2,2))

    x = [1 2 3 4; 5 6 7 8]
    @test bin2(x) ≈ bin(x, (2,2))
end


include(joinpath("..", "common", "utils.jl"))
using LinearAlgebra: Diagonal, diag

⊗ = kron

function element_operators(N, T = Float64)
    ξ, ω = lglpoints(BigFloat, N)
    D = spectralderivative(ξ)
    return (ξ = T.(ξ), ω = T.(ω), D = T.(D))
end

wave_speed(ρ, p, γ) = sqrt(γ * p ./ ρ)

function create_mesh(K, elem, z0, z1)
    ξ = elem.ξ

    # Number of pointss in element and polynomial order
    Nq = length(ξ)
    N = Nq - 1

    # Element size
    Δz = (z1 - z0) / K
    J = Δz / (ξ[Nq] - ξ[1])

    # shift ξ to go (0, 1)
    ξ01 = (ξ[1:(end - 1)] .- ξ[1]) / (ξ[Nq] - ξ[1])

    # cg to dg scatter matrix
    Q = scatter_matrix(N, K)

    # CG DOF locations
    zcg = [z0 .+ Δz * (ξ01 .+ (0:(K - 1))')[:]; z1]

    # DG DOF locations
    zdg = reshape(Q * zcg, Nq, K)

    W = Diagonal(elem.ω)
    I_KK = sparse(I, K, K)
    Wcg = Array(diag(Q' * (I_KK ⊗ (J * W)) * Q))

    return (zcg = zcg, zdg = zdg, scatter = Q, J = J, Δz = Δz, Wcg = Wcg)
end

function dg2cg_scatter(q, mesh, elem)
    Q = mesh.scatter
    ω = elem.ω
    if q isa Array
        q[:] .= Q * ((Q' * (mesh.J * ω .* q)[:]) ./ mesh.Wcg)
    elseif q isa Tuple || q isa NamedTuple
        for i in 1:length(q)
            q[i][:] .= Q * ((Q' * (mesh.J * ω .* q[i])[:]) ./ mesh.Wcg)
        end
    end
    return q
end

function legendre_polynomial(ξ, P)
    T = eltype(ξ)
    N = length(ξ) - 1
    coef = GaussQuadrature.legendre_coefs(T, max(1, P))
    v = GaussQuadrature.orthonormal_poly(ξ, coef...)[:, P + 1]
    return v
end

function balanced_rho(p, grav, mesh, elem)
    Nq = length(elem.ξ)
    N = Nq - 1

    # Compute the derivative
    ρ = -elem.D * p / (mesh.J * grav)
    ρ0 = copy(ρ)

    # make continuous by adding a higher mode on each element
    v = legendre_polynomial(elem.ξ, N)
    v ./= v[1]
    for e in 2:size(ρ, 2)
        Δρ = ρ[1, e] - ρ[Nq, e - 1]
        ρ[:, e] .-= Δρ * v
    end

    return ρ
end

"""
    element_tendency!(∂q, q, elem_operator, problem, z_elem)

Evaluate the element rhs `∂q` associated with `q` for the
`problem::AbstractProblem` using the DG `elem_operator`.
`q` and `∂q` are taken to be `NamedTuple`s with fields `(ρ, w, E)`
"""
function element_tendency!(∂q, q, O, J, z, grav, p_, Pr = I)
    ρ, w, ρe = q.ρ, q.w, q.ρe
    p = p_.(ρ, w, ρe, z)

    # ∫ ϕ J ∂ρ = ∫ (∂_ξ ϕ) ρ w
    ∂q.ρ .= (O.D' * (O.ω .* ρ .* w)) ./ (J .* O.ω)
    # ∂q.ρ .= (ρ .* (O.D' * (O.ω .* w))) ./ (J .* O.ω)
    # ∂q.ρ .-= w .* (O.D * ρ) ./ J

    #  ∫ ϕ ∂w = - ∫ ϕ (g - (∂_ξ p) / ρ̂) + ∫ (∂_ξ ϕ) w^2/2
    #  where ρ̂ has the projection operator Pr applied
    ∂q.w .= -grav .- (O.D * p) ./ (J .* Pr * ρ)

    #  ∫ ϕ ∂w = - ∫ ϕ ((g ρ̂  - ∂_ξ p) / ρ) + ∫ (∂_ξ ϕ) w^2/2
    #  where ρ̂ has the projection operator Pr applied
    # ∂q.w .= -(grav * (Pr * ρ) + (O.D * p) / J) ./ ρ
    ∂q.w .+= (O.D' * (O.ω .* w .^ 2 / 2)) ./ (J .* O.ω)

    #  ∫ ϕ ∂ρe = ∫ (∂_ξ ϕ) (ρe + p) w
    ∂q.ρe .= (O.D' * (O.ω .* (ρe + p) .* w)) ./ (J .* O.ω)

    return ∂q
end

function tendency!(
    ∂q,
    q,
    t,
    mesh,
    elem,
    grav,
    pres,
    Pr = I,
    bc = (nothing, nothing),
    forcing = nothing,
)
    # Evaluate the volume terms
    element_tendency!(∂q, q, elem, mesh.J, mesh.zdg, grav, pres, Pr)

    # Add in MMS formcing
    if !isnothing(forcing)
        ∂q.δρ .+= forcing.ρ.(mesh.zdg, t)
        ∂q.δw .+= forcing.w.(mesh.zdg, t)
        ∂q.δE .+= forcing.ρe.(mesh.zdg, t)
    end

    # Use upwind boundary treatment
    if !isnothing(bc[1])
        error("not implemented yet")
    end

    if !isnothing(bc[2])
        error("not implemented yet")
    end

    # DG -> CG projection and then scatter for storage
    dg2cg_scatter(∂q, mesh, elem)
end


# Econometric Notes - Matlab code
#  Matteo Courthoud
#  01/01/2017

#import Pkg
#Pkg.add("LinearAlgebra")
#Pkg.add("Random")
#Pkg.add("Statistics")
#Pkg.add("Distributions")
#Pkg.add("DataFrames")
#Pkg.add("CSV")
#Pkg.add("Optim")

using LinearAlgebra
using Random
using Statistics
using Distributions
using DataFrames
using CSV
using Optim
using Plots

## Lecture 1

# Set seed
Random.seed!(123);

# Set the number of observations
n = 100;

# Set the dimension of X
k = 2;

# Draw a sample of explanatory variables
X = rand(Uniform(0,1), n, k);

# Draw the error term
σ = 1;
ε = rand(Normal(0,1), n, 1) * sqrt(σ);

# Set the parameters
β = [2; -1];

# Calculate the dependent variable
y = X*β + ε;

# Estimate beta
β_hat = inv(X'*X)*(X'*y)

# Equivalent but faster formulation
β_hat = (X'*X)\(X'*y);

# Even faster (but less intuitive) formulation
β_hat = X\y;

# Note that is generally not equivalent to Var(X)^-1 * Cov(X,y)...
β_wrong = inv(cov(X)) * cov(X, y)

# ...unless you include a constant
α = 3;
y1 = α .+ X*β + ε;
β_hat1 = [ones(n,1) X] \ y1
β_correct1 = inv(cov(X)) * cov(X, y1)

# Predicted y
y_hat = X*β_hat;

# Residuals
ε_hat = y - X*β_hat;

# Projection matrix
P = X * inv(X'*X) * X';

# Annihilator matrix
M = I - P;

# Leverage
h = diag(P);

# Biased variance estimator
σ_hat = ε_hat'*ε_hat / n;

# Unbiased estimator 1
σ_hat_2 = ε_hat'*ε_hat / (n-k);

# Unbiased estimator 2
σ_hat_3 = mean( ε_hat.^2 ./ (1 .- h) );

# R squared - uncentered
R2_uc = (y_hat'*y_hat)/ (y'*y);

# R squared
y_bar = mean(y);
R2 = ((y_hat .- y_bar)'*(y_hat .- y_bar))/ ((y .- y_bar)'*(y .- y_bar));

# Ideal variance of the OLS estimator
var_β = σ * inv(X'*X);

# Standard errors
std_β = sqrt.(diag(var_β));

## Lecture 2

# Set seed
Random.seed!(123);

# Homoskedastic standard errors
std_h = var(ε_hat) * inv(X'*X);

# HC0 variance and standard errors
Ω_hc0 = X' * (I(n) .* ε_hat.^2) * X;
std_hc0 = sqrt.(diag(inv(X'*X) * Ω_hc0 * inv(X'*X)))

# HC1 variance and standard errors
Ω_hc1 = n/(n-k) * X' * (I(n) .* ε_hat.^2) * X;
std_hc1 = sqrt.(diag(inv(X'*X) * Ω_hc1 * inv(X'*X)))

# HC2 variance and standard errors
Ω_hc2 = X' * (I(n) .* ε_hat.^2 ./ (1 .- h)) * X;
std_hc2 = sqrt.(diag(inv(X'*X) * Ω_hc2 * inv(X'*X)))

# HC3 variance and standard errors
Ω_hc3 = X' * (I(n) .* ε_hat.^2 ./ (1 .- h).^2) * X;
std_hc3 = sqrt.(diag(inv(X'*X) * Ω_hc3 * inv(X'*X)))

# Note what happens if you allow for full autocorrelation
omega_full = X'*ε_hat*ε_hat'*X;

# t-test for beta=0
t = abs.(β_hat ./ (std_hc1));

# p-value
p_val = 1 .- cdf.(Normal(0,1), t);

# F statistic of joint significance
SSR_u = ε_hat'*ε_hat;
SSR_r = y'*y;
F = (SSR_r - SSR_u)/k / (SSR_u/(n-k));

# 95# confidente intervals
conf_int = [β_hat - 1.96*std_hc1, β_hat + 1.96*std_hc1];

## Lecture 3

# Set seed
Random.seed!(123);

# Set the dimension of Z
l = 3;

# Draw instruments
Z = rand(Normal(0,1), n,l);

# Correlation matrix for error terms
S = [1 0.8; 0.8 1];

# Endogenous X
γ = [2 0; 0 -1; -1 3];
ε = rand(Normal(0,1), n, 2) * cholesky(S).U;
X = Z*γ .+ ε[:,1];

# Calculate y
y = X*β .+ ε[:,2];

# Estimate beta OLS
β_OLS = (X'*X)\(X'*y)

# IV: l=k=2 instruments
Z_IV = Z[:,1:k];
β_IV = (Z_IV'*X)\(Z_IV'*y)

# Calculate standard errors
ε_hat = y - X*β_IV;
V_NHC_IV = var(ε_hat) * inv(Z_IV'*X)*Z_IV'*Z_IV*inv(Z_IV'*X);
V_HC0_IV = inv(Z_IV'*X)*Z_IV' * (I(n) .* ε_hat.^2) * Z_IV*inv(Z_IV'*X);

# 2SLS: l=3 instruments
Pz = Z*inv(Z'*Z)*Z';
β_2SLS = (X'*Pz*X)\(X'*Pz*y)

# Calculate standard errors
ε_hat = y - X*β_2SLS;
V_NCH_2SLS = var(ε_hat) * inv(X'*Pz*X);
V_HC0_2SLS = inv(X'*Pz*X)*X'*Pz * (I(n) .* ε_hat.^2) *Pz*X*inv(X'*Pz*X);

# GMM 1-step: inefficient weighting matrix
W_1 = I(l);

# Objective function
gmm_1(b) = ( y - X*b )' * Z * W_1 *  Z' * ( y - X*b );

# Estimate GMM
β_gmm_1 = optimize(gmm_1, β_OLS).minimizer
ε_hat = y - X*β_gmm_1;

# Standard errors GMM
S_hat = Z' * (I(n) .* ε_hat.^2) * Z;
d_hat = -X'*Z;
V_gmm_1 = inv(d_hat * inv(S_hat) * d_hat');

# GMM 2-step: efficient weighting matrix
W_2 = inv(S_hat);

# Objective function
gmm_2(b) = ( y - X*b )' * Z * W_2 *  Z' * ( y - X*b );

# Estimate GMM
β_gmm_2 = optimize(gmm_2, β_OLS).minimizer

# Standard errors GMM
ε_hat = y - X*β_gmm_2;
S_hat = Z' * (I(n) .* ε_hat.^2) * Z;
d_hat = -X'*Z;
V_gmm_2 = inv(d_hat * inv(S_hat) * d_hat');

## Lecture 5

# Set seed
Random.seed!(123);

# Define Model Parameters
n = 1000;
α = 1;
β = .1;
γ = -4;

# Set number of simulations
n_simulations = 10000;

# Preallocate simulation results
α_short = zeros(n_simulations,1);
α_long = zeros(n_simulations,1);
α_pretest = zeros(n_simulations,1);

# Loop over simulations
for sim = 1:n_simulations

    # Generate Data
    Z = randn(n,1);
    X = γ*Z + randn(n,1);
    ε = randn(n,1);
    y = α*X + β*Z + ε;

    # Alpha estimate from long regression
    α_short[sim] = ((X'*X)\(X'*y))[1,1];

    # Alpha estimate from long regression
    estimates_temp = ([X Z]'*[X Z]) \ ([X Z]'*y);
    α_long[sim] = estimates_temp[1];

    # Compute test statistic
    e = y - [X Z]*estimates_temp;
    t = estimates_temp[2]/sqrt(var(ε)*([0 1]*inv([X Z]'*[X Z])*[0;1])[1]);

    # Pick estimate depending on test statistic
    if abs(t)>1.96
        α_pretest[sim] = α_long[sim];
    else
        α_pretest[sim] = α_short[sim];
    end
end

# Plot results
p1 = histogram(α_long, bins=30, title = "alpha long")
plot!([1], seriestype="vline", linewidth = 2)
p2 = histogram(α_short, bins=30, title = "alpha short")
plot!([1], seriestype="vline", linewidth = 2)
p3 = histogram(α_pretest, bins=30, title = "alpha pretest")
plot!([1], seriestype="vline", linewidth = 2)
p = plot(p1, p2, p3, layout=(1, 3), legend=false, size=(800, 300))
savefig(p, "../img/Fig_621.png")

# Sequence of sample sizes
n_sequence = [100,1000,10000,50000,100000];

# Initialize figure
figure
set(gcf,'position',[0,0,2000,1000])
n_fig = 1;

# Loop over data generating processes
for dgp=0:1
    for n=n_sequence
        for sim=1:n_simulations

            # Select beta according to dgp
            if dgp==0
                beta_n = beta;
            else
                beta_n = beta/sqrt(n)*sqrt(100);
            end

            # Generate Data
            Z = randn(n,1);
            X = γ*Z + randn(n,1);
            e = randn(n,1);
            y = alpha*X+ beta_n*Z + e;

            # Alpha estimate from long regression
            α_short[sim] = inv(X'*X)*X'*y;

            # Alpha estimate from long regression
            estimates_temp = inv([X,Z]'*[X,Z])*[X,Z]'*y;
            α_long[sim] = estimates_temp[1];

            # Compute test statistic
            e = y - [X,Z]*estimates_temp;
            t = estimates_temp[2]/sqrt(var(e)*([0,1]*inv([X,Z]'*[X,Z])*[0;1]));

            # Pick estimate depending on test statistic
            if abs(t)>1.96
                α_pretest[sim] = α_long[sim];
            else
                α_pretest[sim] = α_short[sim];
            end
        end

        # Add subplot
        subplot(2,length(n_sequence),n_fig)
        hist((α_pretest-alpha)*sqrt(n/100), 12)
        title(['beta=',num2str(beta_n)],'fontsize',10)
        xlabel(['n=',num2str(n)], 'fontsize',10)
        n_fig = n_fig+1;

    end
end
saveas(gcf,'figures/Fig_624.png')


# DiscontinuousGalerkinScheme
# Based on:
# Hesthaven, Warburton, Nodal Discontinuous Galerkin Methods Algorithms, Analysis and applications

mutable struct DiscontinuousGalerkinScheme{T,vType} <: AbstractFEAlgorithm
  basis::PolynomialBasis
  riemann_solver::Function
  max_w_speed :: Function
  D::AbstractArray{T,2}
  Ma::AbstractArray{T,2}    #Mass Matrix on reference element
  S::AbstractArray{T,2}     #Stiffness matrix on reference element
  ν::vType                  #Max wave speed
end

"DiscontinuousGalerkinScheme constructor"
function DiscontinuousGalerkinScheme(basis::PolynomialBasis, riemann_solver;max_w_speed = nothing)
    if max_w_speed == nothing
        max_w_speed = maxfluxρ
    end
    D = basis.dφ*basis.invφ
    Ma = inv(basis.φ*basis.φ')
  DiscontinuousGalerkinScheme(basis, riemann_solver, max_w_speed, D, Ma, Ma*D, 0.0)
end

"Reconstruc solution from basis space"
function reconstruct_u(u::AbstractArray{T,2}, φ::AbstractArray{T2,2}, NC::Int) where {T, T2}
  uh = myblock(φ,NC)*u
  NN = size(φ,1); Nx = size(u,2)
  uₕ = zeros(T, NN*Nx,NC)
  for j in 1:NC
    uₕ[:,j] = uh[(j-1)*NN+1:j*NN,:][:]
  end
  return uₕ
end

"Flat x nodes on u matrix"
function flat_u(u::AbstractArray{T,2}, order::Int, NC::Int) where {T}
  NN = order + 1
  uh = zeros(T, size(u,2)*NN, NC)
  for j in 1:NC
    uh[:,j] = u[(j-1)*NN+1:j*NN,:][:]
  end
  return uh
end

"Get cell face values for each variable"
function get_dg_face_values(u::AbstractArray{T,2}, order::Int, NC::Int) where {T}
    NN = order + 1
    #TODO: number of faces depend on dimension
    us = zeros(T, 2*NC, size(u,2))
    for j in 1:size(u,2)
      for k in 1:NC
          us[(2*k-1):k*2,j] = u[[1+NN*(k-1),NN+NN*(k-1)],j]
      end
    end
    return us
end

"Update dt based on CFL condition"
function update_dt(alg::DiscontinuousGalerkinScheme,u::AbstractArray{T2,2},Flux,
    CFL,mesh::Uniform1DFVMesh) where {T2}
    alg.ν = alg.max_w_speed(u, Flux)
    dx = maximum(cell_volumes(mesh))
    dx * CFL*min(abs(alg.basis.nodes[1]-alg.basis.nodes[2])) / alg.ν
end

"Compute right hand side for time integration"
function residual!(H::AbstractArray{T,2}, u::AbstractArray{T,2}, basis::PolynomialBasis, mesh::Uniform1DFVMesh, alg::DiscontinuousGalerkinScheme, f, riemann_solver, NC, ::Type{Val{false}}) where {T}
    NN = basis.order + 1

    us = get_dg_face_values(u, basis.order, NC)
    #Apply boundary conditions TODO: Other boundary types
    us = apply_boundary(us, mesh)
    q = zeros(u)
    F = zeros(u)
    ur=us[1:2:end,:]
    ul=us[2:2:end,:]
    for i = 1:numcells(mesh)
        # Evaluate edge fluxes
        q[NN:NN:end,i] = riemann_solver(ul[:,i+1],ur[:,i+2], f, alg.ν)
        q[1:NN:end,i] = -riemann_solver(ul[:,i],ur[:,i+1], f, alg.ν)
        # Integrate interior fluxes ∫f(uₕ)φ'(ξ)dξ
        for k = 1:NN
            F[k:NN:end,i] = f(u[k:NN:end,i])
        end
    end

    # Compute right hand size
    ru = myblock(alg.S',NC)*F - q
    h = maximum(cell_volumes(mesh))
    H[:,:] = (h/2*myblock(alg.Ma,NC))\ru;
    if isleftdirichlet(mesh); H[:,1] = 0.0; end
   if isrightdirichlet(mesh); H[:,end] = 0.0; end
end

"Apply boundary conditions on scalar problems"
function apply_boundary(u::AbstractArray{T,2}, mesh::AbstractFVMesh1D) where {T}
  #Add ghost cells
  uh = hcat(u[:,1],u,u[:,end])
  if isleftperiodic(mesh)
      uh[:,1] = uh[:,end-1]
  elseif  isleftzeroflux(mesh)
      uh[:,1] = uh[:,2]
  end
  if isrightperiodic(mesh)
      uh[:,end] = uh[:,2]
  elseif  isrightzeroflux(mesh)
      uh[:,end] = uh[:,end-1]
  end
  return uh
end

@inline function fluxρ(uj::Vector,f)
  maximum(abs,eigvals(f(Val{:jac}, uj)))
end

"build a block diagonal matrix by repeating a matrix N times"
function myblock(A::AbstractArray{T,2},N::Int) where {T}
  M = size(A,1)
  Q = size(A,2)
  B = zeros(T,M*N,Q*N)
  for i = 1:N
    B[(i-1)*M+1:i*M,(i-1)*Q+1:i*Q] = A
  end
  B
end

"Solve scalar 1D conservation laws problems with DG Scheme"
function solve(
  prob::AbstractConservationLawProblem,
  alg::AbstractFEAlgorithm;
  TimeIntegrator::OrdinaryDiffEqAlgorithm = SSPRK22(),use_threads = false, kwargs...)

  # Unpack some useful variables
  @unpack basis, riemann_solver = alg
  #Unroll some important constants
  @unpack tspan,f,f0, mesh = prob

  N = numcells(mesh)
  NC = prob.numvars
  NN = basis.order+1
  #Assign Initial values
  xg = zeros(NN,N)
  for i in 1:N
    b = cell_faces(mesh)[i+1]; a=cell_faces(mesh)[i]
    xg[:,i] = reference_to_interval(basis.nodes,(a,b))
  end

  u0 = zeros(eltype(f0(cell_faces(mesh)[1])),NN*NC, N)
  for i = 1:N
      for k = 1:NN
        u0[k:NN:NN*NC,i] = f0(xg[k,i])
      end
  end
  #Time loop
  #First dt
  u0ₕ = flat_u(u0, basis.order, NC)
  dt = update_dt(alg, u0ₕ, f, prob.CFL, mesh)
  # Setup time integrator
  semidiscretef(du,u,p,t) = residual!(du, u, basis, mesh, alg, f, riemann_solver, NC, Val{use_threads})
  ode_prob = ODEProblem(semidiscretef, u0, prob.tspan)
  timeIntegrator = init(ode_prob, TimeIntegrator;dt=dt, kwargs...)
  @inbounds for i in timeIntegrator
    uₕ = flat_u(timeIntegrator.u, basis.order,NC)
    dt = update_dt(alg, uₕ, f, prob.CFL, mesh)
    set_proposed_dt!(timeIntegrator, dt)
  end
  if timeIntegrator.sol.t[end] != prob.tspan[end]
    savevalues!(timeIntegrator)
  end
  return build_solution(timeIntegrator.sol,xg,basis,prob, NC)
end


function dotAfterFoldAlongX(lineX, dot) # Vertical Line
    if (dot[1] > lineX)
        return [lineX - abs(lineX-dot[1]), dot[2]]
    end

    return dot
end

function dotAfterFoldAlongY(lineY, dot) # Horizontal Line
    if (dot[2] > lineY)
        return [dot[1], lineY - abs(lineY-dot[2])]
    end

    return dot
end

content = readlines("input.txt")
splitIndex = findfirst(isempty, content)

atEnd(vec) = vec[end]

myParse(x) = parse.(Int, x)

dots, instructions = myParse.(split.(content[1:splitIndex-1], ",")), split.(atEnd.(split.(content[splitIndex+1:end], " ")), "=")

function doFold(instruction)
    if instruction[1] == "x"
        return Set([dotAfterFoldAlongX(parse(Int, instruction[2]), dot) for dot in dots])
    else
        return Set([dotAfterFoldAlongY(parse(Int, instruction[2]), dot) for dot in dots])
    end
end

#! Part 1

length(doFold(instructions[1]))

#! Part 2

for instruction in instructions
    dots = doFold(instruction)
end

using Plots

plot([(dot[1], dot[2]) for dot in dots], seriestype = :scatter, yflip = true, aspect_ratio=:equal, c=:black)


using Random, DataFrames

rv1 = RandomVariable(Exponential(1), :x)
rv2 = RandomVariable(Exponential(1 / 2), :y)

marginals = [rv1 rv2]
copula = GaussianCopula([1 0.8; 0.8 1])

@testset "JointDistribution" begin
    @testset "Constructor" begin
        @test isa(JointDistribution(marginals, copula), JointDistribution)

        jd = JointDistribution(marginals, copula)
        @test jd.marginals == marginals
        @test jd.copula == copula

        @test_throws ErrorException("Dimension mismatch between copula and marginals") JointDistribution(
            marginals, GaussianCopula([1 0 0; 0 1 0; 0 0 1])
        )
    end

    @testset "sample" begin
        jd = JointDistribution(marginals, copula)
        @test size(sample(jd, 10)) == (10, 2)
        @test size(sample(jd)) == (1, 2)
    end

    @testset "names" begin
        jd = JointDistribution(marginals, copula)
        @test names(jd) == [:x, :y]
    end

    @testset "mean" begin
        jd = JointDistribution(marginals, copula)
        @test mean(jd) == DataFrame(:x => 1.0, :y => 0.5)
    end

    @testset "to_standard_normal_space" begin
        jd = JointDistribution(marginals, copula)

        Random.seed!(8128)

        samples = sample(jd, 10^5)

        @test isapprox(mean(samples.x), 1.0; atol=0.01)
        @test isapprox(mean(samples.y), 0.5; atol=0.01)

        @test round(cor(samples.x, samples.y); digits=2) == 0.77

        to_standard_normal_space!(jd, samples)

        @test isapprox(abs(mean(samples.x)), 0.0; atol=0.01)
        @test isapprox(abs(mean(samples.y)), 0.0; atol=0.01)

        @test isapprox(std(samples.x), 1.0; atol=0.01)
        @test isapprox(std(samples.y), 1.0; atol=0.01)

        @test round(abs(cor(samples.x, samples.y)); digits=2) == 0.0

        Random.seed!()
    end

    @testset "to_physical_space" begin
        jd = JointDistribution(marginals, copula)

        Random.seed!(8128)

        samples = DataFrame(:x => rand(Normal(), 10^5), :y => rand(Normal(), 10^5))

        to_physical_space!(jd, samples)

        @test isapprox(mean(samples.x), 1.0; atol=0.01)
        @test isapprox(mean(samples.y), 0.5; atol=0.01)

        @test round(cor(samples.x, samples.y); digits=2) == 0.77

        Random.seed!()
    end
end


# Before far field patterns and the pair field patterns are calculated here. These are needed for the effective wavenumber and reflection for low volume fraction.

d2D(x,m) = diffbesselj(m,x)*diffhankelh1(m,x) + (1.0 - (m/x)^2)*besselj(m,x)*hankelh1(m,x)

d3D(x,m) = x * diffsbesselj(m,x) * (x * diffshankelh1(m,x) + shankelh1(m,x)) + (x^2 + - m * (m+1)) * sbesselj(m,x)^2

function far_field_pattern(ω::T, medium::Acoustic{T,2}, species::Species{T,2}; basis_order = 2) where T<:Number

    Zs = - get_t_matrices(medium, species, ω, basis_order)
    num_density_inv = one(T)/sum(number_density.(species))

    far_field(θ::T) where T <: Number = -num_density_inv*sum(
        number_density(species[i])*Zs[i][n+basis_order+1,n+basis_order+1]*exp(im*θ*n)
    for i in eachindex(species), n = -basis_order:basis_order)

    return far_field
end

function diff_far_field_pattern(ω::T, medium::Acoustic{T,2}, species::Species{T}; tol=1e-6, basis_order = 2, verbose = false, kws...) where T<:Number

    Zs = - get_t_matrices(medium, species, ω, basis_order)
    num_density_inv = one(T) / sum(number_density.(species))

    far_field(θ::T) where T <: Number = -num_density_inv*sum(
        number_density(species[i])*Zs[i][n+basis_order+1,n+basis_order+1]*im*n*exp(im*θ*n)
    for i in eachindex(species), n=-basis_order:basis_order)

    return far_field
end

function pair_field_pattern(ω::T, medium::Acoustic{T,2}, species::Species{T}; tol::T = T(1e-6), basis_order = 2) where T<:Number

    Zs = - get_t_matrices(medium, species, ω, basis_order)
    num_density_inv = one(T)/sum(number_density.(species))

    pair_field(θ::T) where T <: Number = -T(π)*num_density_inv^(2.0)*sum(
        begin
            a12 = (outer_radius(species[i]) * species[i].exclusion_distance + outer_radius(species[j]) * species[j].exclusion_distance )
            number_density(species[i]) * number_density(species[j]) * a12^2.0 * d2D(a12*ω/medium.c,m-n) * Zs[i][n+basis_order+1,n+basis_order+1] * Zs[j][m+basis_order+1,m+basis_order+1]*exp(im*m*θ)
        end
    for i in eachindex(species), j in eachindex(species),
    n = -basis_order:basis_order, m = -basis_order:basis_order)

    return pair_field
end


compute_moments(basis::Dictionary) = [moment(basis,i) for i in eachindex(basis)]

function apply_quad(w, x, f)
    z = w[1] * f(x[1])
    for i in 2:length(w)
        z += w[i] * f(x[i])
    end
    z
end

compute_weights(x, basis, B) = interpolation_matrix(basis, x)' \ B

"""
A container to collect information about a quadrature rule, mainly for use
in types describing exactness equations.
"""
struct QuadRuleData{B <: Dictionary,T}
    "The basis for which the rule is exact."
    basis   ::  B
    "The length of the quadrature rule"
    len     ::  Int
    "Moments of the basis with respect to some measure."
    moments ::  Vector{T}
end

eltype(::QuadRuleData{B,T}) where {B,T} = T

function QuadRuleData(basis::Dictionary{S,T}, len::Int, moments = compute_moments(basis)) where {S,T}
    QuadRuleData{typeof(basis),T}(basis, len, moments)
end

length(d::QuadRuleData) = d.len

basis(d::QuadRuleData) = d.basis

moments(d::QuadRuleData) = d.moments

"A type that describes the nonlinear equations of exactness of a quadrature rule, for use in Newton's method."
abstract type QuadRuleSystem{T} <: NonlinearSystem end

eltype(::QuadRuleSystem{T}) where {T} = T

data(sys::QuadRuleSystem) = sys.data

for op in (:basis, :moments)
    @eval $op(sys::QuadRuleSystem) = $op(sys.data)
end

length(rule::QuadRuleSystem) = dofs(rule)

size(rule::QuadRuleSystem) = (length(rule), dofs(rule))

quadlength(rule::QuadRuleSystem) = length(rule.data)

# Evaluate the residual of the system of equations
function residual!(result, sys::QuadRuleSystem, newton_x)
    w,x = newton_to_quad(sys, newton_x)
    residual!(result, sys, w, x, basis(sys), moments(sys))
end

function residual!(result, sys::QuadRuleSystem, w, x, basis, moments)
    for (i,ϕ) in enumerate(basis)
        result[i] =  apply_quad(w, x, ϕ) - moments[i]
    end
    result
end

# Evaluate the Jacobian of the system of equations
function jacobian!(J, sys::QuadRuleSystem, newton_x)
    w,x = newton_to_quad(sys, newton_x)
    jacobian!(J, sys, w, x, basis(sys))
end

function newton_to_quad(sys::QuadRuleSystem, newton_x)
    T = eltype(newton_x)
    x = zeros(T, length(sys.data))
    w = zeros(T, length(sys.data))
    newton_to_quad!(sys, w, x, newton_x)
end

function quad_to_newton(sys::QuadRuleSystem, w, x)
    T = promote_type(eltype(w),eltype(x))
    newton_x = zeros(T, dofs(sys))
    quad_to_newton!(sys, w, x, newton_x)
end

"System of equations for a quadrature rule where all points are free."
struct QuadRuleFreePoints{B,T} <: QuadRuleSystem{T}
    data    ::  QuadRuleData{B,T}
end

"The total number of degrees of freedom in the system."
dofs(rule::QuadRuleFreePoints) = 2*length(rule.data)

"System of equations for a quadrature rule with some points fixed."
struct QuadRuleFixedPoints{B,T} <: QuadRuleSystem{T}
    "Data about the quadrature rule: basis, moments, etcetera."
    data        ::  QuadRuleData{B,T}
    "Indices of the fixed points."
    fixed_idxs  ::  Vector{Int}
    "Values of the fixed points."
    fixed_pts   ::  Vector{T}
    "Indices of the free points."
    free_idxs   ::  Vector{Int}

    function QuadRuleFixedPoints{B,T}(data, fixed_idxs, fixed_pts) where {B,T}
        P = length(fixed_idxs)
        @assert length(fixed_pts) == P
        @assert P > 0
        # Make sure indices and points are sorted.
        for i in 1:P-1
            @assert fixed_idxs[i] < fixed_idxs[i+1]
            @assert fixed_pts[i] < fixed_pts[i+1]
        end
        new(data, fixed_idxs, fixed_pts, setdiff(1:length(data), fixed_idxs))
    end
end

QuadRuleFixedPoints(data::QuadRuleData{B,T}, fixed_idxs, fixed_pts) where {B,T} =
    QuadRuleFixedPoints{B,T}(data, fixed_idxs, fixed_pts)

"The number of fixed points in the quadrature rule."
nbfixed(rule::QuadRuleFixedPoints) = length(rule.fixed_pts)

"The number of free points in the quadrature rule."
nbfree(rule::QuadRuleFixedPoints) = length(rule.free_idxs)

dofs(rule::QuadRuleFixedPoints) = 2*length(rule.data) - nbfixed(rule)

function setfixedpoint!(rule::QuadRuleFixedPoints, idx, xstar)
    rule.fixed_pts[rule.fixed_idxs[idx]] = xstar
end

# newton_x contains l weights, followed by l points
function newton_to_quad!(sys::QuadRuleFreePoints, w, x, newton_x)
    @assert length(newton_x) == dofs(sys)
    l = length(w)
    for i in 1:l
        w[i] = newton_x[i]
        x[i] = newton_x[l+i]
    end
    w,x
end

function quad_to_newton!(sys::QuadRuleFreePoints, w, x, newton_x)
    @assert length(newton_x) == dofs(sys)
    l = length(w)
    for i in 1:l
        newton_x[i] = w[i]
        newton_x[l+i] = x[i]
    end
    newton_x
end

# newton_x contains l weights, followed by l-nbfixed(rule) free points (the other points are fixed)
function newton_to_quad!(sys::QuadRuleFixedPoints, w, x, newton_x)
    @assert length(newton_x) == dofs(sys)

    l = length(w)
    P = nbfixed(sys)

    for i in 1:l
        w[i] = newton_x[i]
    end

    # Copy the free points into x
    for (j,i) in enumerate(sys.free_idxs)
        x[i] = newton_x[l+j]
    end
    # and copy the fixed points into x
    for (i,xi) in zip(sys.fixed_idxs, sys.fixed_pts)
        x[i] = xi
    end
    w,x
end

function quad_to_newton!(sys::QuadRuleFixedPoints, w, x, newton_x)
    @assert length(newton_x) == dofs(sys)

    l = length(w)
    P = nbfixed(sys)

    for i in 1:l
        newton_x[i] = w[i]
    end

    # Copy the free points
    for (j,i) in enumerate(sys.free_idxs)
        newton_x[l+j] = x[i]
    end
    newton_x
end

function jacobian!(J, sys::QuadRuleFreePoints, w, x, basis)
    l = length(w)

    # We are computing the derivative of \sum_{i=1}^l w_i ϕ_j(x_i) wrt w_i and x_i
    # - First the derivatives with respect to the weight w[i]
    for j in 1:length(basis)
        for i in 1:l
            J[j,i] = BasisFunctions.unsafe_eval_element(basis, j, x[i])
        end
    end
    # - Then the weight times the derivatives of the basis functions
    for j in 1:length(basis)
        for i in 1:l
            J[j,l+i] = w[i] * BasisFunctions.unsafe_eval_element_derivative(basis, j, x[i], 1)
        end
    end
    J
end

function jacobian!(J, sys::QuadRuleFixedPoints, w, x, basis)
    l = length(w)
    P = nbfixed(sys)

    # We are computing the derivative of \sum_{i=1}^l w_i ϕ_j(x_i) wrt w_i and x_i
    # as before, but now some of the x_i's are fixed and we have to skip them.
    # - First the derivatives with respect to the weight w[i]: same as before
    for j in 1:length(basis)
        for i in 1:l
            J[j,i] = BasisFunctions.unsafe_eval_element(basis, j, x[i])
        end
    end
    # - Then the weight times the derivatives of the basis functions
    for j in 1:length(basis)
        for (k,i) in enumerate(sys.free_idxs)
            J[j,l+k] = w[i] * BasisFunctions.unsafe_eval_element_derivative(basis, j, x[i], 1)
        end
    end
    J
end


# # Ice Flow over a bed with variable slipperiness and viscosity.

# In this example, we consider flow in two horizontal dimensions, with variable ice viscosity and basal slipperiness.
# This example is similar in spirit to the MISMIP 3D Intercomparison exercise (http://homepages.ulb.ac.be/~fpattyn/mismip3d/)
# This example demonstrates how to apply spatially heterogenous basal slipperiness and ice viscosity fields.

# ## Install dependencies
#
# First let's make sure we have all required packages installed.

#using Pkg
#Pkg.add("https://github.com/RJArthern/WAVI.jl"), Pkg.add(Plots)
using WAVI, Plots

# ## Model Setup
# The grid has length 800 km in the x-direction and 50km in the y-direction. The grid spacing is 2.5km
dx = 2500., dy = 2500.,
nx = round(Int, 800*1e3 / dx)
ny = round(Int, 50*1e3 / dy)
grid = Grid(nx=nx,ny=ny,dx=2000.,dy=2000.);

# Since we're running to steady state, we'll only do one velocity solve iteration per timestep:
solver_params = SolverParams(maxiter_picard = 1);

# and we'll take a uniform accumulation rate
params = Params( accumulation_rate = 0.3);

# ## Basal slipperiness
# We set the basal slipperiness via the model's initial conditions. In the MISMIP 3D experiment, the sliding coefficient is a Gaussian bump:
# $C = C_0 \left[1 - a  \exp (-(x- x_b)^2 / 2x_c^2  - (y - y_b)^2 / 2y_c^2) \right] where a = 0.75, x_b = 400km, ...


using Flux, DiffEqFlux, DelayDiffEq, Test

## Setup DDE to optimize
function delay_lotka_volterra(du,u,h,p,t)
  x, y = u
  α, β, δ, γ = p
  du[1] = dx = (α - β*y)*h(p,t-0.1)[1]
  du[2] = dy = (δ*x - γ)*y
end
h(p,t) = ones(eltype(p),2)
prob = DDEProblem(delay_lotka_volterra,[1.0,1.0],h,(0.0,10.0),constant_lags=[0.1])
p = param([2.2, 1.0, 2.0, 0.4])
function predict_fd_dde()
  diffeq_fd(p,sol->sol[1,:],101,prob,MethodOfSteps(Tsit5()),saveat=0.0:0.1:10.0)
end
loss_fd_dde() = sum(abs2,x-1 for x in predict_fd_dde())
@test_broken loss_fd_dde()
@test_broken Flux.back!(loss_fd_dde())

function predict_rd_dde()
  diffeq_rd(p,prob,MethodOfSteps(Tsit5()),saveat=0.1)[1,:]
end
loss_rd_dde() = sum(abs2,x-1 for x in predict_rd_dde())
loss_rd_dde()
Flux.back!(loss_rd_dde())


#############################################################################
# lovasz_extension.jl
# Handles the Lovasz extensions of set functions.
#############################################################################

export lovasz, LovaszExtAtom
export sign, monotonicity, curvature, evaluate
export ConvexLovasz

mutable struct LovaszExtAtom <: AbstractExpr
  head::Symbol
  id_hash::UInt64
  children::Tuple{AbstractExpr, Variable}
  size::Tuple{Int, Int}
  variable::Variable
  func::SubmodFunc

  function LovaszExtAtom(F::SubmodFunc, x::Variable)
    if length(F.setvariables) == 1
      if evaluate(F, [])[1] != 0
        error("A combinatorial function should be 0 at the empty set to derive its Lovasz extension.")
      else
        if x.size[1] == F.setvariables[1].cardinality
          children = (F, x)
          return new(:lovasz, hash(children), children, (1, 1), x, F)
        else
          error("The size of the continuous variable should be the same as the baseset of the combinatorial variable of the combinatorial function.")
        end
      end
    else
      error("A combinatorial function should be sigle-variant to obtain its Lovasz extension.")
    end
  end
end

lovasz(F::SubmodFunc, x::Variable) = LovaszExtAtom(F, x)

function sign(F::LovaszExtAtom)
  return sign(F.children[1])
end

function monotonicity(F::LovaszExtAtom)
  return monotonicity(F.children[1])
end

function curvature(F::LovaszExtAtom)
  modd = modularity(F.children[1])
  if modd == SubModularity()
    return ConvexVexity()
  elseif modd == SuperModularity()
    return ConcaveVexity()
  elseif modd == Modularity()
    return AffineVexity()
  elseif modd == ConstModularity()
    return ConstVexity()
  end
end

vexity(F::LovaszExtAtom) = curvature(F)

function evaluate(F::LovaszExtAtom)
  n = F.children[2].size[1]
  y = F.children[2].value[:, 1]
  i = sortperm(y, rev = true)
  V = sort(F.children[1].setvariables[1].baseset)
  storage = zeros(n + 1)
  x = zeros(n)
  for ii = 2:n+1
    storage[ii] = evaluate(F.children[1], V[i[1:ii-1]])[1]
    x[i[ii - 1]] = storage[ii] - storage[ii - 1]
  end
  # Compatibility with Convex.jl
  val = zeros(1, 1)
  val[1] = dot(x, y)
  return val
end

function get_cv(F::LovaszExtAtom)
  variable = []
  return push!(variable, F.variable)
end

function get_sv(F::LovaszExtAtom)
  return []
end

function get_v(F::LovaszExtAtom)
  variable = []
  return push!(variable, F.variable)
end


function binary_search(array, value, low=1, high=length(array))
    if isempty(array)
        return 0
    end

    if low >= high
        if low > high || array[low] != value
            return 0
        end

        return low
    end

    mid = (low + high) ÷ 2

    if array[mid] > value
        return binary_search(array, value, low, mid-1)
    elseif array[mid] < value
        return binary_search(array, value, mid+1, high)
    end

    return mid
end


@testset "Label components" begin
    A = [true  true  false true;
         true  false true  true]
    lbltarget = [1 1 0 2;
                 1 0 2 2]
    lbltarget1 = [1 2 0 4;
                  1 0 3 4]
    @test label_components(A) == lbltarget
    @test label_components(A, [1]) == lbltarget1
    connectivity = [false true  false;
                    true  false true;
                    false true  false]
    @test label_components(A, connectivity) == lbltarget
    connectivity = trues(3,3)
    lbltarget2 = [1 1 0 1;
                  1 0 1 1]
    @test label_components(A, connectivity) == lbltarget2
    @test component_boxes(lbltarget) == Vector{Tuple}[[(1,2),(2,3)],[(1,1),(2,2)],[(1,3),(2,4)]]
    @test component_lengths(lbltarget) == [2,3,3]
    @test component_indices(lbltarget) == Array{Int64}[[4,5],[1,2,3],[6,7,8]]
    @test component_subscripts(lbltarget) == Array{Tuple}[[(2,2),(1,3)],[(1,1),(2,1),(1,2)],[(2,3),(1,4),(2,4)]]
    @test @inferred(component_centroids(lbltarget)) == Tuple[(1.5,2.5),(4/3,4/3),(5/3,11/3)]
end


using ApproxFun, Random, InfiniteArrays, Test
import ApproxFunBase: testbandedblockbandedoperator, blocklengths

@time include("ReadmeTest.jl")
@time include("ExtrasTest.jl")
@time include("NumberTypeTest.jl")
# @time include("FractionalTest.jl")

@testset "Chebyshev and Fourier" begin
    @test norm(Fun(x->Fun(cos,Fourier(-π .. π),20)(x),20)-Fun(cos,20)) <100eps()
    @test norm(Fun(x->Fun(cos,Fourier(-π .. π))(x))-Fun(cos)) <100eps()
    @test norm(Fun(x->Fun(cos,Laurent)(x))-Fun(cos)) <100eps()
end

@testset "Piecewise space definite integral" begin
    Γ=Segment(-im,1.0-im) ∪ Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6))) ∪ Circle(2.0,0.2)
        z=Fun(Γ)

    S=PiecewiseSpace(map(d->isa(d,Circle) ? Fourier(d) : JacobiWeight(0.5,0.5,Ultraspherical(1,d)),components(Γ)))

    B=DefiniteLineIntegral(S)

    Random.seed!(0)
    f=Fun(S,rand(20))
    @test B*f ≈ linesum(component(f,1)) + linesum(component(f,2)) + linesum(component(f,3))
end

@testset "Extending function" begin
    Γ = Segment(-im,1.0-im) ∪ Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6))) ∪ Circle(2.0,0.2)

    @test isempty(component(Γ,1)\component(Γ,1))
    @test Γ \ component(Γ,1) == component(Γ,2) ∪ component(Γ,3)

    @test norm(Fun(ones(component(Γ,1)),Γ) - Fun(x->x ∈ component(Γ,1) ? 1.0 : 0.0,Γ)) == 0
end

@testset "periodic x interval" begin
    dθ=PeriodicSegment(-2.,2.);dt=Interval(0,1.)
    d=dθ×dt
    Dθ=Derivative(d,[1,0]);Dt=Derivative(d,[0,1])
    u0=Fun(θ->exp(-20θ^2),dθ)

    ε = 0.1
    @time u=\([I⊗ldirichlet(dt);Dt-ε*Dθ^2-Dθ],[u0;0.];tolerance=1E-4)
    @test ≈(u(0.1,0.2),0.3103472600253807;atol=1E-2)

    A=Dt+Dθ
    testbandedblockbandedoperator(A)

    @time u=\([I⊗ldirichlet(dt);Dt+Dθ],[u0;0.0];tolerance=1E-6)
    @test ≈(u(0.2,0.1),u0(0.1);atol=1E-6)
end

@testset "Laplace in a strip" begin
    d=PeriodicSegment() × ChebyshevInterval()
    g=Fun((x,y)->real(cos(x+im*y)),∂(d))  # boundary data

    @test g(0.1,1.0) ≈ real(cos(0.1+im))
    @test g(0.1,-1.0) ≈ real(cos(0.1-im))
    v=[g;0]
    @test v(0.1,-1) ≈ [real(cos(0.1-im));0]

    A=[Dirichlet(d);Laplacian(d)]
    a = space(v)
    b = rangespace(A)

    @test Fun(component(v[1],1), component(b[1],1))(0.1,-1.0) ≈ v(0.1,-1.0)[1]
    @test Fun(component(v[1],2), component(b[1],2))(0.1,-1.0) ≈ v(0.1,-1.0)[1]
    @test ApproxFun.default_Fun(v[1] , b[1])(0.1,1.0) ≈ v(0.1,1.0)[1]
end

@testset "Bug in Multiplication" begin
    dom = Interval(0.001, 1) × PeriodicSegment(-pi, pi)

    @test blocklengths(Space(dom)) == 2:2:∞

    r,r2 = Fun((r,t) -> [r;r^2], dom)

    @test r(0.1,0.2) ≈ 0.1
    @test r2(0.1,0.2) ≈ 0.1^2

    sp = Space(dom)
    Dr = Derivative(sp, [1,0])
    @test ApproxFun.blockbandwidths(Dr) == (-1,1)
    @test ApproxFun.subblockbandwidths(Dr)  == (1,3)

    Dθ = Derivative(sp, [0,1])
    Mr = Multiplication(Fun( (r, θ) -> r, sp ), sp)
    rDr = Mr * Dr

    testbandedblockbandedoperator(rDr)
end

@testset "Periodic x Interval" begin
    d=PeriodicSegment() × ChebyshevInterval()

    u_ex=Fun((x,y)->real(cos(x+im*y)),d)
    @test u_ex(1.0,0.1) ≈ real(cos(1.0+im*0.1)) atol=10eps()

    B=Dirichlet(Space(d))

    @test B.order == 0  # tests stupid bug
    g=Fun((x,y)->real(cos(x+im*y)),rangespace(B))  # boundary data

    @test norm((B*u_ex-g).coefficients) < 100eps()

    testbandedblockbandedoperator(Laplacian(d))

    @time u=[B;Laplacian(d)]\[g;0.]

    @test u(.1,.2) ≈ real(cos(.1+.2im))
end

@testset "Mix Fourier-Chebyshev (#602)" begin
    s = Chebyshev(-π..π)
    a = Fun(t-> 1+sin(cos(2t)), s)
    L = Derivative() + a
    f = Fun(t->exp(sin(10t)), s)
    B = periodic(s,0)
    @time uChebyshev = [B;L] \ [0.;f]

    s = Fourier(-π..π)
    a = Fun(t-> 1+sin(cos(2t)), s)
    L = Derivative() + a
    f = Fun(t->exp(sin(10t)), s)
    @time uFourier = L\f

    @test norm(uFourier-uChebyshev) ≤ 100eps()
end

@testset "Conversion" begin
    f=Fun(t->[cos(t) 0;sin(t) 1],-π..π)
    g=Fun(f,Space(PeriodicSegment(-π,π)))
    @test g(.1) ≈ f(.1)
end

@testset "definite integral" begin
    Σ = DefiniteIntegral()

    f1 = Fun(t->cos(cos(t)),-π..π)
    f = Fun(t->cos(cos(t)),Laurent(-π..π))

    @test sum(f1) ≈ Σ*f
end

@testset "Sampling" begin
    ff=(x,y)->(x-y)^2*exp(-x^2/2-y^2/2)

    f=Fun(ff,Domain(-4..4)^2)
    r=ApproxFun.sample(f,5000)

    #We can compare the histogram to the 1-point correlation
    g=sum(f,1)/sum(f)
    @test  g(0.1) ≈ 0.2004758624973169

    # check bisection inv
    f = Fun(x -> exp(-x^2/2),-5..5)
    g = cumsum(f)
    @test g(ApproxFun.bisectioninv(g,0.5)) ≈ 0.5
end

@testset "piecewise sample (#635)" begin
    f = abs(Fun(sin, -5..5))
    @test integrate(f)(-4.0) ≈ -(cos(-4.0) - cos(-5.0))
    @test -(cos(-π) - cos(-5.0)) + cos(-3.0) - cos(-π) ≈ integrate(f)(-3.0)
    r = ApproxFun.sample(f,10)
    @test maximum(r) ≤ 5
    @test minimum(r) ≥ -5
end

f=Fun(exp)
x=sample(f,100000)
x=sample(f,100000)
@time x=sample(f,100000)
println("Sample: Time should be ~0.13")

@testset "Null space" begin
    d=ChebyshevInterval()
    D=Derivative(d)
    A=D^2-I
    @time κ=nullspace(A)
    @test length(κ) == 2

    c=[κ(0.);κ'(0.)]\[exp(0.),exp(0.)]
    u=(κ*c)[1]

    @test u(1.0) ≈ ℯ

    d=(-50..5.)
    x=Fun(identity,d)
    D=Derivative(d)
    @time u=nullspace(D^2-x)
    c=[u(leftendpoint(d)); u(rightendpoint(d))]\[airyai.(endpoints(d))...]
    @test norm((u*c)[1]-Fun(airyai,d))<10000eps()
end


#=
Created on 31/01/2021 17:57:19
Last update: -

@author: Michiel Stock
michielfmstock@gmail.com

Illustration of performance types on a simple sum.
=#

using BenchmarkTools

function mysum(A)
    total = 0.0
    n, m = size(A)
    for i in 1:n
        for j in 1:m
            total += A[i,j]
        end
    end
    return total
end

function mysum_rowwise(A)
    total = 0.0
    n, m = size(A)
    for j in 1:m
        for i in 1:n
            total += A[i,j]
        end
    end
    return total
end

function mysum_inbounds(A)
    total = 0.0
    n, m = size(A)
    for i in 1:n
        for j in 1:m
            @inbounds total += A[i,j]
        end
    end
    return total
end

function mysum_simd(A)
    total = 0.0
    n, m = size(A)
    @simd for i in 1:n
        @simd for j in 1:m
            total += A[i,j]
        end
    end
    return total
end

A = randn(10000, 10000)

@btime sum($A);  # reference

@btime mysum($A);  # standard implementation

@btime mysum_rowwise($A);  # changing the order of crossing the matrix

@btime mysum_inbounds($A);  # not performing checks

@btime mysum_simd($A);  # making loops independent