#=== Absorbing Boundary ===#
function ModExpand(para::AbstractArray, ext::Int64, iflag::Union{Void,Int64})
    if iflag == 1 #free surface
        temp_vertical2 = repmat(para[end:end,:],ext,1)
        para = vcat(para, temp_vertical2)
        temp_horizon1 = repmat(para[:,1],1,ext)
        temp_horizon2 = repmat(para[:,end],1,ext)
        para = hcat(temp_horizon2,para,temp_horizon1)
    elseif iflag == 2 # unlimited medium
        temp_vertical1 = repmat(para[1:1,:],ext,1)
        temp_vertical2 = repmat(para[end:end,:],ext,1)
        para = vcat(temp_vertical1,para,temp_vertical2)
        temp_horizon1 = repmat(para[:,1],1,ext)
        temp_horizon2 = repmat(para[:,end],1,ext)
        para = hcat(temp_horizon1,para,temp_horizon2)
    elseif iflag == nothing
        temp_horizon1 = repmat(para[:,1],1,ext)
        temp_horizon2 = repmat(para[:,end],1,ext)
        para = hcat(temp_horizon1,para,temp_horizon2)
    end
    return para
end

############ elastic medium 2d ############
function model{T1,T2,T3<:Real}(
    pvel::Union{String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64}}},
    svel::Union{Void,String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64}}},
    rho::Union{<:Real,String},
    DZ::Union{<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    HX::T1,
    pkf::T2,
    T::T3,
    ext::Int64,
    iflag::Int64,
    dx::Union{Void,<:Real},
    dz::Union{Void,<:Real},
    dt::Union{Void,<:Real},
    nT::Union{Void,Int64})

    if typeof(pvel) == String
        if !(typeof(DZ) == Int64 && typeof(HX) == Int64)
            error("Depth and Horzion have to be Integer grid number")
        elseif dx == nothing && dz == nothing
            error("Set up grid size dx and dz")
        elseif !(typeof(DZ) == Int64 && typeof(HX) == Int64)&&dx == nothing && dz == nothing
            error("1. Depth and Horzion have to be Integer grid number. 2.Set up grid size dx and dz.")
        end
        if pvel[end-2:end] == "bin"
            fid = open(pvel,"r")
            pvel = read(fid,Float64, DZ*HX,1)
            close(fid)
        elseif pvel[end-2:end] == "dat"
            fid = open(pvel,"r")
            pvel = readdlm(fid)
            close(fid)
        else error("P velocity file is not valid")
        end
        if svel == nothing
            svel = pvel./sqrt(3);
        elseif svel[end-2:end] == "bin"
            fid = open(svel,"r")
            svel = read(fid,Float64, DZ*HX,1)
            close(fid)
        elseif svel[end-2:end] == "dat"
            fid = open(svel,"r")
            svel = readdlm(fid)
            close(fid)
        else error("S velocity file is not valid")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        max_svel = maximum(svel)
        min_svel = minimum(svel)

        if !(pkf < 2*min_svel/3/dx && pkf < 2*min_svel/3/dz)
            error("Peak frequency is too high for selected grid size")
        end
        if dt == nothing
            dt = 0.406 * (min_svel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_svel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(pvel)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        pvel = Float64.(reshape(pvel, DZ,HX))
        svel = Float64.(reshape(svel, DZ,HX))
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, DZ*HX,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, DZ,HX))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(DZ,HX) .+ rho)
        else error("Give a density value or file path")
        end
        nDZ = DZ
        nHX = HX
        DZ = DZ*dz
        HX = HX*dx

    elseif typeof(pvel) != String
        if svel == nothing
            svel = pvel./sqrt(3);
        end
        if !(length(pvel)==length(svel)==length(DZ))
            error("Layer numbers of pvel, svel and depth should be the same. Please give a depth for each layer.")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        max_svel = maximum(svel)
        min_svel = minimum(svel)
        # Spatial interval and time interval
        if !(max_svel > 0 && max_pvel > 0 && min_svel > 0 && max_pvel > 0)
            error("Medium velocities have to be larger than 0")
        else
            dz = min_svel / 10 / pkf
            dx = min_svel / 10 / pkf
        end
        if dt == nothing
            dt = 0.406 * (min_svel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_svel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(P velocity)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        nDZ = 0
        nlayerdep = zeros(Int64,length(DZ))
        if typeof(DZ) <: Array{<:Real,1} || typeof(DZ) <: Real
            for i = 1:length(DZ)
                nlayerdep[i] = Int64(round(DZ[i]/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = sum(DZ)
        else
            nlayerdep[1] = Int64(round(DZ[1]/dz))
            nDZ = Int64(round(DZ[1]/dz))
            for i = 2:length(DZ)
                nlayerdep[i] = Int64(round((DZ[i]-DZ[i-1])/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = DZ[end]
        end

        nHX = Int64(round(HX/dx))
        Pvel = zeros(Float64,nDZ,nHX)
        Svel = zeros(Float64,nDZ,nHX)
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, nDZ*nHX,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, nDZ,nHX))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(nDZ,nHX) .+ rho)
        else error("Give a density value or file path")
        end

        # velocity interval and depth interval

        Pvel[1:nlayerdep[1],:] = Float64.(pvel[1])
        Svel[1:nlayerdep[1],:] = Float64.(svel[1])
        SumDep = 0
        for iL = 2 : length(pvel)
            SumDep = SumDep + nlayerdep[iL-1]
            Pvel[SumDep+1:SumDep+nlayerdep[iL],:] = Float64.(pvel[iL])
            Svel[SumDep+1:SumDep+nlayerdep[iL],:] = Float64.(svel[iL])
        end
        pvel = Float64.(Pvel)
        svel = Float64.(Svel)
    end

    pvel = ModExpand(pvel, ext, iflag) # 1 : free surface; 2: unlimited surface
    svel = ModExpand(svel, ext, iflag)
    rho = ModExpand(Rho, ext, iflag)
    BDnDZ,BDnHX = size(pvel)
    lambda = Array{Float64,2}(BDnDZ,BDnHX)
    mu = Array{Float64,2}(BDnDZ,BDnHX)
    Rho = Array{Float64,2}(BDnDZ,BDnHX)
    for i in 1 : BDnDZ
        for j in 1 : BDnHX
            lambda[i,j] = pvel[i,j]^2*rho[i,j]  - 2*svel[i,j]^2*rho[i,j]
            mu[i,j] = svel[i,j]^2*rho[i,j]
            Rho[i,j] = rho[i,j]
        end
    end

    return elastic2d(pvel,svel,Rho,lambda,mu,dx,dz,dt,DZ,HX,nDZ,nHX,BDnDZ,BDnHX,T,nT,pkf,ext,iflag)
end

############ acoustic medium 2d ############
  function model{T1,T2,T3<:Real}(
    pvel::Union{String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    rho::Union{<:Real,String},
    DZ::Union{<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    HX::T1,
    pkf::T2,
    T::T3,
    ext::Int64,
    iflag::Int64,
    dx::Union{Void,<:Real},
    dz::Union{Void,<:Real},
    dt::Union{Void,<:Real},
    nT::Union{Void,Int64})

    if typeof(pvel) == String
        if !(typeof(DZ) == Int64 && typeof(HX) == Int64)
            error("Depth and Horzion have to be Integer grid number")
        elseif dx == nothing && dz == nothing
            error("Set up grid size dx and dz")
        elseif !(typeof(DZ) == Int64 && typeof(HX) == Int64)&&dx == nothing && dz == nothing
            error("1. Depth and Horzion have to be Integer grid number. 2.Set up grid size dx and dz.")
        end
        if pvel[end-2:end] == "bin"
            fid = open(pvel,"r")
            pvel = read(fid,Float64, DZ*HX,1)
            close(fid)
        elseif pvel[end-2:end] == "dat"
            fid = open(pvel,"r")
            pvel = readdlm(fid)
            close(fid)
        else error("P velocity file is not valid")
        end

        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)

        if !(pkf < 2*min_pvel/3/dx && pkf < 2*min_pvel/3/dz)
            error("Peak frequency is too high for selected grid size")
        end
        if dt == nothing
            dt = 0.406 * (min_pvel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_pvel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(pvel)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        pvel = Float64.(reshape(pvel, DZ,HX))
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, DZ*HX,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, DZ,HX))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(DZ,HX) .+ rho)
        else error("Give a density value or file path")
        end

        nDZ = DZ
        nHX = HX
        DZ = DZ*dz
        HX = HX*dx

    elseif typeof(pvel) != String

        if !(length(pvel)==length(DZ))
            error("Layer numbers of pvel, svel and depth should be the same. Please give a depth for each layer.")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        # Spatial interval and time interval
        if !(max_pvel > 0 && max_pvel > 0)
            error("Medium velocities have to be larger than 0")
        else
            dz = min_pvel / 10 / pkf
            dx = min_pvel / 10 / pkf
        end
        if dt == nothing
            dt = 0.406 * (min_pvel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_svel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(P velocity)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        nDZ = 0
        nlayerdep = zeros(Int64,length(DZ))
        if typeof(DZ) <: Array{<:Real,1} || typeof(DZ) <: Real
            for i = 1:length(DZ)
                nlayerdep[i] = Int64(round(DZ[i]/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = sum(DZ)
        else
            nlayerdep[1] = Int64(round(DZ[1]/dz))
            nDZ = Int64(round(DZ[1]/dz))
            for i = 2:length(DZ)
                nlayerdep[i] = Int64(round((DZ[i]-DZ[i-1])/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = DZ[end]
        end

        nHX = Int64(round(HX/dx))
        Pvel = zeros(Float64,nDZ,nHX)

        # velocity interval and depth interval

        Pvel[1:nlayerdep[1],:] = Float64.(pvel[1])
        SumDep = 0
        for iL = 2 : length(pvel)
            SumDep = SumDep + nlayerdep[iL-1]
            Pvel[SumDep+1:SumDep+nlayerdep[iL],:] = Float64.(pvel[iL])
        end
        pvel = Float64.(Pvel)
    end

    if typeof(rho) == String
        if rho[end-2:end] == "bin"
            fid = open(rho,"r")
            rho = read(fid,Float64, nDZ*nHX,1)
            close(fid)
        elseif rho[end-2:end] == "dat"
            fid = open(rho,"r")
            rho = readdlm(rho)
            close(fid)
        else error("Density file is not valid")
        end
        Rho = Float64.(reshape(rho, nDZ,nHX))
    elseif typeof(rho)<:Real
        Rho = Float64.(zeros(nDZ,nHX) .+ rho)
    else error("Give a density value or file path")
    end
    pvel = ModExpand(pvel, ext, iflag) # 1 : free surface; 2: unlimited surface
    rho = ModExpand(Rho, ext, iflag)

    BDnDZ, BDnHX = size(pvel)
    lambda = Array{Float64,2}(BDnDZ,BDnHX)
    Rho = Array{Float64,2}(BDnDZ,BDnHX)
    for i in 1 : BDnDZ
        for j in 1 : BDnHX
            lambda[i,j] = pvel[i,j]^2*rho[i,j]
            Rho[i,j] = rho[i,j]
        end
    end
    return acoustic2d(pvel,Rho,lambda,dx,dz,dt,DZ,HX,nDZ,nHX,BDnDZ,BDnHX,T,nT,pkf,ext,iflag)
end

############ elastic medium 3d ############
 function model{T1,T2,T3,T4<:Real}(
    pvel::Union{String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    svel::Union{Void,String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    rho::Union{<:Real,String},
    DZ::Union{<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    HX::T1,
    HY::T2,
    pkf::T3,
    T::T4,
    ext::Int64,
    iflag::Int64,
    dx::Union{Void,<:Real},
    dy::Union{Void,<:Real},
    dz::Union{Void,<:Real},
    dt::Union{Void,<:Real},
    nT::Union{Void,Int64})

    if typeof(pvel) == String
        if !(typeof(DZ) == Int64 && typeof(HX) == Int64 && typeof(HY) == Int64)
            error("Depth and Horzion have to be Integer grid number")
        elseif dx == nothing && dz == nothing && dy == nothing
            error("Set up grid size dx, dy and dz")
        elseif !(typeof(DZ) == Int64 && typeof(HX) == Int64 && typeof(HY) == Int64) && dx == nothing && dz == nothing && dy == nothing
            error("1. Depth and Horzion have to be Integer grid number. 2.Set up grid size dx, dy and dz.")
        end
        if pvel[end-2:end] == "bin"
            fid = open(pvel,"r")
            pvel = read(fid,Float64, DZ*HX*HY,1)
            close(fid)
        elseif pvel[end-2:end] == "dat"
            fid = open(pvel,"r")
            pvel = readdlm(fid)
            close(fid)
        else error("P velocity file is not valid")
        end
        if svel == nothing
            svel = pvel./sqrt(3);
        elseif svel[end-2:end] == "bin"
            fid = open(svel,"r")
            svel = read(fid,Float64, DZ*HX*HY,1)
            close(fid)
        elseif svel[end-2:end] == "dat"
            fid = open(svel,"r")
            svel = readdlm(fid)
            close(fid)
        else error("S velocity file is not valid")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        max_svel = maximum(svel)
        min_svel = minimum(svel)

        if !(pkf < 2*min_svel/3/dx && pkf < 2*min_svel/3/dz && pkf < 2*min_svel/3/dy)
            error("Peak frequency is too high for selected grid size")
        end
        if dt == nothing
            dt = 0.406 * (min_svel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt != nothing
            if dt > 0.6 * (min_svel / 10 / pkf) / max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(pvel)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        Pvel = reshape(pvel, DZ,HX,HY)
        Svel = reshape(svel, DZ,HX,HY)
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, DZ*HX*HY,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, DZ,HX,HY))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(DZ,HX,HY) .+ rho)
        else error("Give a density value or file path")
        end
        nDZ = DZ
        nHX = HX
        nHY = HY
        DZ = DZ*dz
        HX = HX*dx
        HY = HY*dy

    elseif typeof(pvel) != String
        if svel == nothing
            svel = pvel/sqrt(3);
        end
        if !(length(pvel)==length(svel)==length(DZ))
            error("Layer numbers of pvel, svel and depth should be the same. Please give a depth for each layer.")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        max_svel = maximum(svel)
        min_svel = minimum(svel)
        # Spatial interval and time interval
        if !(max_svel > 0 && max_pvel > 0 && min_svel > 0 && max_pvel > 0)
            error("Medium velocities have to be larger than 0")
        else
            dz = min_svel / 10 / pkf
            dx = min_svel / 10 / pkf
            dy = min_svel / 10 / pkf
        end
        if dt == nothing
            dt = 0.406 * (min_svel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt != nothing
            if dt > 0.6*(min_svel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(P velocity)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        nDZ = 0
        nlayerdep = zeros(Int64,length(DZ))
        if typeof(DZ) <: Array{<:Real,1} || typeof(DZ) <: Real
            for i = 1:length(DZ)
                nlayerdep[i] = Int64(round(DZ[i]/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = sum(DZ)
        else
            nlayerdep[1] = Int64(round(DZ[1]/dz))
            nDZ = Int64(round(DZ[1]/dz))
            for i = 2:length(DZ)
                nlayerdep[i] = Int64(round((DZ[i]-DZ[i-1])/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = DZ[end]
        end

        nHX = Int64(round(HX/dx))
        nHY = Int64(round(HY/dy))
        Pvel = zeros(nDZ,nHX,nHY)
        Svel = zeros(nDZ,nHX,nHY)
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, nDZ*nHX*nHY,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, nDZ,nHX,nHY))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(nDZ,nHX,nHY) .+ rho)
        else error("Give a density value or file path")
        end
        # velocity interval and depth interval

        Pvel[1:nlayerdep[1],:,:] = pvel[1]
        Svel[1:nlayerdep[1],:,:] = svel[1]
        SumDep = 0
        for iL = 2 : length(pvel)
            SumDep = SumDep + nlayerdep[iL-1]
            Pvel[SumDep+1:SumDep+nlayerdep[iL],:,:] = pvel[iL]
            Svel[SumDep+1:SumDep+nlayerdep[iL],:,:] = svel[iL]
        end
    end
    if iflag == 1 # free surface
        BDnDZ = nDZ + ext
    elseif iflag == 2
        BDnDZ = nDZ + 2*ext
    end
    BDnHX = nHX + 2*ext
    BDnHY = nHY + 2*ext
    pvel = zeros(BDnDZ,BDnHX,BDnHY)
    svel = zeros(BDnDZ,BDnHX,BDnHY)
     rho = zeros(BDnDZ,BDnHX,BDnHY)
    # 1 : free surface; 2: unlimited surface
    pvel[:,:,1] = ModExpand(Pvel[:,:,1], ext, iflag)
    svel[:,:,1] = ModExpand(Svel[:,:,1], ext, iflag)
    rho[:,:,1] = ModExpand( Rho[:,:,1], ext, iflag)
    for i in 2:BDnHY
        pvel[:,:,i] = pvel[:,:,1]
        svel[:,:,i] = svel[:,:,1]
         rho[:,:,i] = rho[:,:,1]
    end
    for i in 1:BDnDZ
        pvel[i,:,:] = ModExpand(pvel[i,:,ext+1:ext+nHY], ext, nothing)
        svel[i,:,:] = ModExpand(svel[i,:,ext+1:ext+nHY], ext, nothing)
         rho[i,:,:] = ModExpand( rho[i,:,ext+1:ext+nHY], ext, nothing)
    end
    # dz= Float64(dz)
    # dx= Float64(dx)
    # dy= Float64(dy)
    # dt= Float64(dt)
    lambda = Array{Float64,3}(BDnDZ,BDnHX,BDnHY)
    mu = Array{Float64,3}(BDnDZ,BDnHX,BDnHY)
    Rho = Array{Float64,3}(BDnDZ,BDnHX,BDnHY)
    for i in 1 : BDnDZ
        for j in 1 : BDnHX
            for k in 1 : BDnHY
                lambda[i,j,k] = pvel[i,j,k]^2*rho[i,j,k] - 2*svel[i,j,k]^2*rho[i,j,k]
                mu[i,j,k] = svel[i,j,k]^2*rho[i,j,k]
                Rho[i,j,k] = rho[i,j,k]
            end
        end
    end
    return elastic3d(pvel,svel,Rho,lambda,mu,dx,dy,dz,dt,
    DZ,HX,HY,nDZ,nHX,nHY,BDnDZ,BDnHX,BDnHY,T,nT,pkf,ext,iflag)
end

############ acoustic medium 3d ############
 function model{T1,T2,T3,T4<:Real}(
    pvel::Union{String,<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    rho::Union{<:Real,String},
    DZ::Union{<:Real,Array{<:Real,1},StepRange{Int64,Int64},UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}},
    HX::T1,
    HY::T2,
    pkf::T3,
    T::T4,
    ext::Int64,
    iflag::Int64,
    dx::Union{Void,<:Real},
    dy::Union{Void,<:Real},
    dz::Union{Void,<:Real},
    dt::Union{Void,<:Real},
    nT::Union{Void,Int64})

    if typeof(pvel) == String
        if !(typeof(DZ) == Int64 && typeof(HX) == Int64 && typeof(HY) == Int64)
            error("Depth and Horzion have to be Integer grid number")
        elseif dx == nothing && dz == nothing && dy == nothing
            error("Set up grid size dx and dz")
        elseif !(typeof(DZ) == Int64 && typeof(HX) == Int64 && typeof(HY) == Int64) && dx == nothing && dz == nothing && dx == nothing
            error("1. Depth and Horzion have to be Integer grid number. 2.Set up grid size dx and dz.")
        end
        if pvel[end-2:end] == "bin"
            fid = open(pvel,"r")
            pvel = read(fid,Float64, DZ*HX*HY,1)
            close(fid)
        elseif pvel[end-2:end] == "dat"
            fid = open(pvel,"r")
            pvel = readdlm(fid)
            close(fid)
        else error("P velocity file is not valid")
        end

        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)

        if !(pkf < 2*min_pvel/3/dx && pkf < 2*min_pvel/3/dz && pkf < 2*min_pvel/3/dy)
            error("Peak frequency is too high for selected grid size")
        end
        if dt == nothing
            dt = 0.406 * (min_pvel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_pvel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(pvel)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        Pvel = reshape(pvel, DZ,HX,HY)
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, DZ*HX*HY,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, DZ,HX,HY))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(DZ,HX,HY) .+ rho)
        else error("Give a density value or file path")
        end
        nDZ = DZ
        nHX = HX
        nHY = HY
        DZ = DZ*dz
        HX = HX*dx
        HY = HY*dy

    elseif typeof(pvel) != String

        if !(length(pvel)==length(DZ))
            error("Layer numbers of pvel, svel and depth should be the same. Please give a depth for each layer.")
        end
        max_pvel = maximum(pvel)
        min_pvel = minimum(pvel)
        # Spatial interval and time interval
        if !(max_pvel > 0 && max_pvel > 0)
            error("Medium velocities have to be larger than 0")
        else
            dz = min_pvel / 10 / pkf
            dx = min_pvel / 10 / pkf
            dy = min_pvel / 10 / pkf
        end
        if dt == nothing
            dt = 0.406 * (min_pvel / 10 / pkf) / max_pvel
            nT = Int64(round(T/dt))
        elseif dt !=nothing
            if dt > 0.6*(min_pvel / 10 / pkf)/max_pvel
                error("Simulation does not satisfy stablity criterion:
                maximum(P velocity)*dt/dz < 0.6.")
            end
            if nT != nothing
                T= nT*dt
            elseif nT == nothing
                nT = Int64(round(T/dt))
            end
        end
        nDZ = 0
        nlayerdep = zeros(Int64,length(DZ))
        if typeof(DZ) <: Array{<:Real,1} || typeof(DZ) <: Real
            for i = 1:length(DZ)
                nlayerdep[i] = Int64(round(DZ[i]/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = sum(DZ)
        else
            nlayerdep[1] = Int64(round(DZ[1]/dz))
            nDZ = Int64(round(DZ[1]/dz))
            for i = 2:length(DZ)
                nlayerdep[i] = Int64(round((DZ[i]-DZ[i-1])/dz))
                nDZ = nDZ + nlayerdep[i]
            end
            DZ = DZ[end]
        end

        nHX = Int64(round(HX/dx))
        nHY = Int64(round(HY/dy))
        Pvel = zeros(nDZ,nHX,nHY)
        if typeof(rho) == String
            if rho[end-2:end] == "bin"
                fid = open(rho,"r")
                rho = read(fid,Float64, nDZ*nHX*nHY,1)
                close(fid)
            elseif rho[end-2:end] == "dat"
                fid = open(rho,"r")
                rho = readdlm(rho)
                close(fid)
            else error("Density file is not valid")
            end
            Rho = Float64.(reshape(rho, nDZ,nHX,nHY))
        elseif typeof(rho)<:Real
            Rho = Float64.(zeros(nDZ,nHX,nHY) .+ rho)
        else error("Give a density value or file path")
        end
        # velocity interval and depth interval
        for i in 1:nlayerdep[1]
            Pvel[i,:,:] = Pvel[i,:,:] .+ pvel[1]
        end
        SumDep = 0
        for iL = 2 : length(pvel)
            SumDep = SumDep + nlayerdep[iL-1]
            Pvel[SumDep+1:SumDep+nlayerdep[iL],:,:] = Pvel[SumDep+1:SumDep+nlayerdep[iL],:,:] .+ pvel[iL]
        end
    end

    # 1 : free surface; 2: unlimited surface
    if iflag == 1
        BDnDZ = nDZ + ext
    elseif iflag == 2
        BDnDZ = nDZ + 2*ext
    end
    BDnHX = nHX + 2*ext
    BDnHY = nHY + 2*ext
    pvel = zeros(BDnDZ,BDnHX,BDnHY)
     rho = zeros(BDnDZ,BDnHX,BDnHY)
    # 1 : free surface; 2: unlimited surface
    pvel[:,:,1] = ModExpand(Pvel[:,:,1], ext, iflag)
    rho[:,:,1] = ModExpand( Rho[:,:,1], ext, iflag)
    for i in 2:BDnHY
        pvel[:,:,i] = pvel[:,:,1]
         rho[:,:,i] = rho[:,:,1]
    end
    for i in 1:BDnDZ
        pvel[i,:,:] = ModExpand(pvel[i,:,ext+1:ext+nHY], ext, nothing)
         rho[i,:,:] = ModExpand( rho[i,:,ext+1:ext+nHY], ext, nothing)
    end

    lambda = Array{Float64,3}(BDnDZ,BDnHX,BDnHY)
    Rho = Array{Float64,3}(BDnDZ,BDnHX,BDnHY)
    for i in 1 : BDnDZ
        for j in 1 : BDnHX
            for k in 1 : BDnHY
                lambda[i,j,k] = pvel[i,j,k]^2*rho[i,j,k]
                Rho[i,j,k] = rho[i,j,k]
            end
        end
    end
    return acoustic3d(pvel,Rho,lambda,dx,dy,dz,dt,
    DZ,HX,HY,nDZ,nHX,nHY,BDnDZ,BDnHX,BDnHY,T,nT,pkf,ext,iflag)
end


using Test

using HalfEdges.BoundingVolumeTrees
using Base.Iterators
using AbstractTrees
using StaticArrays

const Vector3{T} = SVector{3,T}
using HalfEdges.BoundingVolumeTrees: contains

@testset "traverse" begin
  t = Node(1, Node(-2, Node(-3),Node(3,Node(-4),nothing)), Node(2,nothing,Node(4)))
  @test length(t) == 7
  @test [data(i) for i in t] == [1,-2,-3,3,-4,2,4]
  @test [i for i in t] == reduce(vcat,DepthFirst(t))
  @test drop(DepthFirst(t),2) |> first |> data == -3
  @test drop(BreadthFirst(t),2) |> first |> data == 2
  @test data.(reduce(vcat,BreadthFirst(t))) == [1, -2, 2, -3, 3, 4, -4]
  @test search(iseven∘data,t) |> data == -2

  animaltree = reduce(insert,["dog","dawg","kawt","awnt"]; init = tree("cat"))
  @test map(data, leaves(animaltree)) == ["awnt","dawg","kawt"]
  @test tree([key(n) for n in animaltree]) == animaltree

  t = tree(["cat", "awnt", "aardvark", "ant", "dog", "dag", "zat", "doog"])
  @test leaves(t, x->length(key(x)) < 4) |> first |> key == "dag"

  it = itree(["cat", "awnt", "aardvark", "ant", "dog", "dag", "zat", "doog"])
  @test map(data, leaves(it)) == map(data, leaves(t))
  @test map(data, it) == map(data, t)

  build_rosetta( (n, l, r ) ) = Node(n, build_rosetta(l), build_rosetta(r))
  build_rosetta( n::Nothing ) = nothing
  build_rosetta( n::Int ) = Node(n)
  rosetta = build_rosetta((1, (2, (4, 7, nothing), 5), (3, (6, 8, 9), nothing)))

  @test reduce( *, Traverse(rosetta, x->string(" ", data(x)), nothing, nothing)) ==
        " 1 2 4 7 5 3 6 8 9"
  @test reduce( *, Traverse(rosetta, nothing, nothing, x->string(" ", data(x)))) ==
        " 7 4 2 5 1 8 6 9 3"
  @test reduce( *, Traverse(rosetta, nothing, x->string(" ", data(x)), nothing)) ==
        " 7 4 5 2 8 9 6 3 1"

end

@testset "delete" begin
  t = tree(["cat", "dog","dawg","kawt","awnt", "doog", "zat", "aardvark"])
  @test map(key, leaves(t)) == ["aardvark", "dawg", "doog", "zat"]
  @test map(key, leaves(delete(t,"awnt"))) == ["aardvark", "dawg", "doog", "zat"]
  @test map(key, leaves(delete(t,"aardvark"))) == ["awnt", "dawg", "doog", "zat"]
  @test map(key, leaves(delete(t,"dog"))) == ["aardvark", "dawg", "zat"]
  @test map(key, leaves(delete(t,"cat"))) == ["aardvark", "doog", "zat"]
  @test map(key, leaves(delete(t,"diamond"))) == map(key, leaves(t))

  animals = ["cat", "ant", "emu", "dog", "zat", "aawk", "bat"]
  @test length(delete(delete(tree(animals),"cat"), "dog")) == length(tree(animals))-2
end

@testset "balanced" begin
  animals = ["cat", "dog","dawg","kawt","awnt", "doog", "zat", "aardvark"]
  @test tree((x->AVLData{String}(x,0)).(animals)) |> height == 3

  linear255 = tree(1:255|>collect)
  balanced255 = avltree(1:255|>collect)
  ibalanced255 = avlitree(1:255|>collect)
  @test height(linear255) == 255
  @test height(balanced255) == 7
  @test height(ibalanced255) == 7

  b15 = avltree(1:15|>collect)
  sb15 = reduce((t,i)->delete(t,i),[6];init=b15)|>left|>right
  @test sb15|>height > sb15|>left|>height

  bd = reduce((t,i)->delete(t,i),[12,6,2,13,4];init=b15)
  @test height(bd) == 3

  b31 = avltree(1:31|>collect)
  #seq = [16, 17, 15, 18, 5, 29, 20, 22, 17, 8, 29, 26, 11, 4]
  #@test length(bd) >= (31-14)
  #@test height(bd) == 4

  for i in 1:2000
    bd = reduce((acc,i)->delete(acc,rand(1:31)), 1:14; init = b31)
    @test length(bd) >= (31-14)
    @test height(bd) == 4
  end

  @test height(reduce((acc,_)->delete(acc,rand(1:255)), 1:127; init = balanced255)) == 7
  @test height(reduce((acc,i)->delete(acc,i), 1:129; init = balanced255)) == 6
end

@testset "traverse" begin
  t = Node(1, Node(-2, Node(-3),Node(3,Node(-4),nothing)), Node(2,nothing,Node(4)))
  @test length(t) == 7
  @test [data(i) for i in t] == [1,-2,-3,3,-4,2,4]
  @test [i for i in t] == reduce(vcat,DepthFirst(t))
  @test drop(DepthFirst(t),2) |> first |> data == -3
  @test drop(BreadthFirst(t),2) |> first |> data == 2
  @test data.(reduce(vcat,BreadthFirst(t))) == [1, -2, 2, -3, 3, 4, -4]
  @test search(iseven∘data,t) |> data == -2

  animaltree = reduce(insert,["dog","dawg","kawt","awnt"]; init = tree("cat"))
  @test map(data, Leaves(animaltree)) == ["awnt","dawg","kawt"]
end

@testset "AABB" begin
  a = Vector3(1.0,1,1)
  o = Vector3(0.0,0,0)
  @test overlaps(AABB(o,a),AABB(0.5*a,2.0*a)) == true
  @test overlaps(AABB(o,a),AABB(10.0*a,20.0*a)) == false
  @test volume(AABB(o,a)) == 1.0
  @test volume(inflate(AABB(o,a),1.0)) == 3.0^3
  @test contains(AABB(o,a),AABB(a*0.5,a*0.75)) == true
  @test contains(AABB(o,a),AABB(a,2.0*a)) == false
  @test contains(AABB(o,a),AABB(o,a)) == true
end

function aabbtree(extents, treebuilder)
  extents = map(x->(Vector3{Float64}(x[1]),Vector3{Float64}(x[2])), extents)
  treebuilder(map( ((n,x),i)->AABBNodeData(AABB(n,x),i), extents, 1:length(extents)))
end

boxes = [([-4,-1,-1],[-1,1,1]),([1,-1,-1],[4,1,1]),
         ([2,-1,-1],[3,1,1]), ([-10,-1,-1],[10,1,1]),
         ([11,-1,-1],[12,1,1]),([7,-1,-1],[8,1,1]),
         ([7.5,-1,-1],[7.75,1,1])]

@testset "AABBTree" begin
  abba = aabbtree(boxes, tree)
  babba = aabbtree(boxes, avltree)
  iabba = aabbtree(boxes, itree)
  biabba = aabbtree(boxes, avlitree)
  @test contains((abba |> data |> x->x.aabb), (abba |> left |> data |> x->x.aabb))
  @test contains((abba |> data |> x->x.aabb), (abba |> right |> data |> x->x.aabb))
  @test Leaves(iabba) |> collect |> x->map(data,x) == Leaves(abba) |> collect |> x->map(data,x)
  @test Leaves(biabba) |> collect |> x->map(data,x) == Leaves(babba) |> collect |> x->map(data,x)
  @test contains((babba |> key ), (abba |> left |> key ))
  @test contains((babba |> key ), (abba |> right |> key ))
  hits = query(abba, AABB(Vector3(9.0,-10.0,-10.0),Vector3(20.0,10.0,10.0)))
  @test setdiff(hits, [4,5]) |> isempty

  t = tree(AABBNodeData(AABB(Vector3(-1.0,-1.0,-1.0), Vector3(1.0,1.0,1.0)),1))
  t = insert(t,AABBNodeData(randAABB(),2))
  t = insert(t,AABBNodeData(randAABB(),3))
  t = insert(t,AABBNodeData(randAABB(),4))
  t = insert(t,AABBNodeData(randAABB(),5))
  t = insert(t,AABBNodeData(AABB(Vector3(-1.0,-1.0,-1.0), Vector3(-0.9, -0.9, -0.9)),6))
  t = insert(t,AABBNodeData(AABB(Vector3(0.9,0.9,0.9), Vector3(1.0,1.0,1.0)),7))
  getaabb(x) = x.aabb
  getaabb(x::Nothing) = x
  @test contains((t |> data |> getaabb), (t |> left |> data |> getaabb))
  @test contains((t |> data |> getaabb), (t |> left |> right |> data |> getaabb))
  @test contains((t |> left |> data |> getaabb), (t |> left |> right |> data |> getaabb))

end

@testset "leaky" begin
  @test Traverse(itree([1,3,2,4]), index) |> collect |> sort == [1,2,3,4]
  alli = Traverse(aabbtree(boxes, itree), index) |> collect |> sort
  @test alli == collect(1:length(alli))
end


"""
    dictionary(ds)

A dictionary of the variables `name` and `long_name` attributes.

Useful for development, `dictionary(ds)` allows me to provide shortcuts — and a
friendlier interface — for accessing GEOTRACES variables.
"""
function dictionary(ds::Dataset)
    Dict((k, longname(var)) for (k,var) in ds)
end

# Not all variables have a `long_name` attribute, so replace by ? if it's not there.
longname(var) = haskey(var.attrib,"long_name") ? var.attrib["long_name"] : "?"

# TODO check these are still useful
isconcentration(var) = occursin("CONC", longname(var))
isdissolvedconcentration(var) = occursin("D_CONC", longname(var))
islowleveldissolvedconcentration(var) = occursin("LL_D_CONC", longname(var))
isstandarddeviation(var) = occursin("Standard deviation", longname(var))

"""
    matchingvariables(ds::Dataset, str::String)

Lists names of variables that have `str` in their `long_name` attribute.
"""
function matchingvariables(ds::Dataset, str::String)
    return [p for p in dictionary(ds) if occursin(Regex(str, "i"), p[2])]
end

variable(ds::Dataset, tracer::String) = println(ds[varname(tracer)])

# Special treatment for metadata

metadatakeyvaluepair(v, idx) = @match name(v) begin
    "metavar1"  => (:cruise => reduce.(string, filter.(!=('\0'), eachcol(v.var[:,:])))[[i.I[2] for i in idx]])
    "metavar2"  => (:station => string.(reduce.(string, filter.(!=('\0'), eachcol(v.var[:,:])))[[i.I[2] for i in idx]]))
    "longitude" => (:lon => float.(v.var[[i.I[2] for i in idx]]))
    "latitude"  => (:lat => float.(v.var[[i.I[2] for i in idx]]))
    "var2"      => (:depth => unitfunction(v.attrib["units"]).(float.(v.var[idx])))
    "var1"      => (:pressure => unitfunction(v.attrib["units"]).(float.(v.var[idx])))
    "date_time" => (:date => DateTime.(v[[i.I[2] for i in idx]]))
    _           => (Symbol(name(v)) => float.(v.var[idx]))
end

"""
    varname(tracer)

Returns the GEOTRACES variable name that "matches" `tracer`.
"""
varname(tracer::String) = @match lowercase(tracer) begin
    "cruise"                                                              => "metavar1"
    "station"                                                             => "metavar2"
    "lat" || "latitude"                                                   => "latitude"
    "lon" || "longitude"                                                  => "longitude"
    "pressure"                                                            => "var1"
    "depth" || "depths"                                                   => "var2"
    "date" || "datetime" || "date/time" || "date and time" || "date time" => "date_time"
    "he" || "helium"                                                      => "var22"
    "o2" || "o₂" || "oxygen" || "dioxygen"                                => "var35"
    "p" || "po₄" || "po4" || "phosphate"                                  => "var37"
    "no₃" || "no3" || "nitrate"                                           => "var40"
    "si" || "si(oh)₄" || "silicate"                                       => "var39"
    "cd" || "cadmium"                                                     => "var85"
    "δcd" || "δ¹¹⁰cd" || "δ110cd" || "δcadmium"                           => "var158"
    "fe" || "iron" || "dissolved iron" || "dfe"                           => "var88"
    "δfe" || "δ⁵⁴fe" || "δ54fe" || "δiron"                                => "var160"
    "ni" || "nickel"                                                      => "var104"
    "nd" || "neodymium"                                                   => "var259"
    "end" || "εnd" || "eneodymium" || "εneodymium"                        => "var251"
    "v" || "vanadium"                                                     => "var109"
    _ => tracer
end

"""
    isotope(tracer)

Returns the GEOTRACES variable name of the isotope of tracer `str`.
"""
isotope(tracer::String) = @match lowercase(tracer) begin
    "cd" || "cadmium"       => "δcd"
    "fe" || "iron" || "dfe" => "δfe"
    _ => str
end
export isotope

"""
    stdvarname(tracer)

Returns the GEOTRACES variable name of the standard deviation of tracer `str`.
"""
stdvarname(tracer::String) = string(varname(tracer), "_STD")

"""
    qcvarname(tracer)

Returns the GEOTRACES variable name of the quality control of tracer `str`.
"""
qcvarname(tracer::String) = string(varname(tracer), "_qc")

# TODO add units
# - cell
# - TU
unitfunction(str::String) = @match str begin
    "degrees_east"                       => identity
    "degrees_north"                      => identity
    "days since 0006-01-01 00:00:00 UTC" => x -> Day(x) + DateTime(6,1,1,0,0,0)
    "dbar"                               => x -> x * u"dbar"
    "m"                                  => x -> x * u"m"
    "deg C"                              => x -> x * u"°C"
    "pmol/kg"                            => x -> x * u"pmol/kg"
    "fmol/kg"                            => x -> x * u"fmol/kg"
    "nmol/kg"                            => x -> x * u"nmol/kg"
    "umol/kg"                            => x -> x * u"μmol/kg"
    "per mil" || "per 10^3"              => x -> x * u"permille"
    "TU"                                 => x -> x * 0.118u"Bq/L"
    "per 10000" || "per 10^4"            => x -> x * u"pertenthousand"
    "uBq/kg"                             => x -> x * u"μBq/kg"
    "mBq/kg"                             => x -> x * u"mBq/kg"
    "atoms/kg"                           => x -> x * u"1/kg"
    "l"                                  => x -> x * u"L"
    "nmol P/kg"                          => x -> x * u"nmol/kg"
    "umol C/kg"                          => x -> x * u"μmol/kg"
    "nmol N/kg"                          => x -> x * u"nmol/kg"
    "nmol Si/kg"                         => x -> x * u"nmol/kg"
    "ug/kg"                              => x -> x * u"μg/kg"
    "ng/liter"                           => x -> x * u"μg/L"
    "um^3"                               => x -> x * u"μm^3"
    "amol/cell"                          => x -> x * u"amol"
    "fmol/cell"                          => x -> x * u"fmol"
    "fmol/liter"                         => x -> x * u"fmol/L"
end


module SetRounding
# Extracted from Julia base. License is MIT: https://julialang.org/license

# This file is a part of Julia. License is MIT: https://julialang.org/license

import Base.Rounding:
    setrounding

const Float32_64 = Union{Float32, Float64}

let fenv_consts = Vector{Cint}(undef, 9)
    ccall(:jl_get_fenv_consts, Nothing, (Ptr{Cint},), fenv_consts)
    global const JL_FE_INEXACT = fenv_consts[1]
    global const JL_FE_UNDERFLOW = fenv_consts[2]
    global const JL_FE_OVERFLOW = fenv_consts[3]
    global const JL_FE_DIVBYZERO = fenv_consts[4]
    global const JL_FE_INVALID = fenv_consts[5]

    global const JL_FE_TONEAREST = fenv_consts[6]
    global const JL_FE_UPWARD = fenv_consts[7]
    global const JL_FE_DOWNWARD = fenv_consts[8]
    global const JL_FE_TOWARDZERO = fenv_consts[9]
end

to_fenv(::RoundingMode{:Nearest}) = JL_FE_TONEAREST
to_fenv(::RoundingMode{:ToZero}) = JL_FE_TOWARDZERO
to_fenv(::RoundingMode{:Up}) = JL_FE_UPWARD
to_fenv(::RoundingMode{:Down}) = JL_FE_DOWNWARD

function from_fenv(r::Integer)
    if r == JL_FE_TONEAREST
        return RoundNearest
    elseif r == JL_FE_DOWNWARD
        return RoundDown
    elseif r == JL_FE_UPWARD
        return RoundUp
    elseif r == JL_FE_TOWARDZERO
        return RoundToZero
    else
        throw(ArgumentError("invalid rounding mode code: $r"))
    end
end

setrounding_raw(::Type{<:Float32_64}, i::Integer) = ccall(:fesetround, Int32, (Int32,), i)

@noinline setrounding(::Type{T}, r::RoundingMode) where {T<:Float32_64} = setrounding_raw(T, to_fenv(r))

end #module


@testset "GridTools" begin
    nt = length(T_all)
    n = nt * nb
    @unpack xgeo = p
    x = xgeo * ones(n)
    @testset "convert to 3D array" for xᵢ in state_to_tracers(x, nb, nt)
        xᵢ3D = rearrange_into_3Darray(xᵢ, grd)
        @test size(xᵢ3D) == size(grd)
    end
    @testset "convert to 3D array" for xᵢ in unpack_tracers(x, grd)
        xᵢ3D = rearrange_into_3Darray(xᵢ, grd)
        @test size(xᵢ3D) == size(grd)
    end
end


module SetIntersectionProjection

#packages that used to be in base in Julia 0.6
using Distributed
using LinearAlgebra
using SparseArrays
using Printf
using Statistics

#other packages
using Parameters
using Interpolations
using DistributedArrays
using JOLI
using JOLI.FFTW, JOLI.Wavelets
using SortingAlgorithms
using TimerOutputs
# using Flux
# using NNlib
# using CUDA

export log_type_PARSDMM, set_properties, PARSDMM_options, set_definitions

#main scripts
include("PARSDMM.jl")
include("PARSDMM_multi_level.jl")
include("PARSDMM_precompute_distribute.jl")

#algorithms and solver scripts
include("argmin_x.jl");
include("adapt_rho_gamma.jl");
include("update_y_l.jl");
include("stop_PARSDMM.jl");
include("PARSDMM_initialize.jl")
include("Q_update!.jl")
include("rhs_compose.jl")

#linear algebra functions
include("cg.jl");
include("mat2CDS.jl")
include("CDS_MVp.jl")
include("CDS_MVp_MT.jl")
include("CDS_MVp_MT_subfunc.jl")
include("CDS_scaled_add!.jl")
#include("CDS2stencil.jl")

#scripts for parallelism
include("update_y_l_parallel.jl")
include("adapt_rho_gamma_parallel.jl")
include("compute_relative_feasibility.jl")

#multi-threaded scripts
include("a_is_b_min_c_MT!.jl")

#multi level related scripts
include("setup_multi_level_PARSDMM.jl")
include("constraint2coarse.jl")
include("interpolate_y_l.jl")

#scripts for setting up constraints, projectors, linear operators
include("default_PARSDMM_options.jl")
include("convert_options!.jl")
include("get_discrete_Grad.jl");
include("get_TD_operator.jl");
include("get_projector.jl");
include("get_bound_constraints.jl");
include("setup_constraints.jl");

#projectors
include("projectors/project_bounds!.jl")
include("projectors/project_cardinality!.jl");
include("projectors/project_rank!.jl");
include("projectors/project_nuclear!.jl");
include("projectors/project_l1_Duchi!.jl");
include("projectors/project_l2!.jl");
include("projectors/project_annulus!.jl");
include("projectors/project_subspace!.jl");
include("projectors/project_histogram_relaxed.jl");

#other proximal maps
include("prox_l2s!.jl")
include("prox_l1!.jl")

#scripts that are required to run examples
include("constraint_learning_by_observation.jl")

#Minkowski sum related scripts
include("PARSDMM_precompute_distribute_Minkowski.jl")

#define types
mutable struct log_type_PARSDMM
      set_feasibility   :: Array{Real,2}
      r_dual            :: Array{Real,2}
      r_pri             :: Array{Real,2}
      r_dual_total      :: Vector{Real}
      r_pri_total       :: Vector{Real}
      obj               :: Vector{Real}
      evol_x            :: Vector{Real}
      rho               :: Array{Real,2}
      gamma             :: Array{Real,2}
      cg_it             :: Vector{Integer}
      cg_relres         :: Vector{Real}
      timing            :: TimerOutput
end

@with_kw mutable struct PARSDMM_options
  x_min_solver          :: String       = "CG_normal" #what algorithm to use for the x-minimization (CG applied to normal equations)
  maxit                 :: Integer      = 200         #max number of PARSDMM iterations
  evol_rel_tol          :: Real         = 1e-3        #stop PARSDMM if ||x^k - X^{k-1}||_2 / || x^k || < options.evol_rel_tol AND options.feas_tol is reached
  feas_tol              :: Real         = 5e-2        #stop PARSDMM if the transform-domain relative feasibility error is < options.feas_tol AND options.evol_rel_tol is reached
  obj_tol               :: Real         = 1e-3        #optional stopping criterion for change in distance from point that we want to project
  rho_ini               :: Vector{Real} = [10.0]      #initial values for the augmented-Lagrangian penalty parameters. One value in array or one value per constraint set in array
  rho_update_frequency  :: Integer      = 2           #update augmented-Lagrangian penalty parameters and relaxation parameters every X number of PARSDMM iterations
  gamma_ini             :: Real         = 1.0         #initial value for all relaxation parameters (scalar)
  adjust_rho            :: Bool         = true        #adapt augmented-Lagrangian penalty parameters or not
  adjust_gamma          :: Bool         = true        #adapt relaxation parameters in PARSDMM
  adjust_feasibility_rho:: Bool         = true        #adapt augmented-Lagrangian penalty parameters based on constraint set feasibility errors (can be used in combination with options.adjust_rho)
  Blas_active           :: Bool         = true        #use direct BLAS calls, otherwise the code will use Julia loop-fusion where possible
  feasibility_only      :: Bool         = false       #drop distance term and solve a feasibility problem
  FL                    :: DataType     = Float32     #type of Float: Float32 or Float64
  parallel              :: Bool         = false       #compute proximal mappings, multiplier updates, rho and gamma updates in parallel
  zero_ini_guess        :: Bool         = true        #zero initial guess for primal, auxiliary, and multipliers
  Minkowski             :: Bool         = false       #the intersection of sets includes a Minkowski set
end

#save properties of the constraint set and its linear operator (not all are currently used in the code)
#each entry in the following vectors contain the information corresponding to one set
mutable struct set_properties
           ncvx       ::Vector{Bool}                                          #is the set non-convex? (true/false)
           AtA_diag   ::Vector{Bool}                                          #is A^T A a diagonal matrix?
           dense      ::Vector{Bool}                                          #is A a dense matrix?
           TD_n       ::Vector{Tuple}                                         #the grid dimensions in the transform-domain.
           tag        ::Vector{Tuple{String,String,String,String}}            #(constraint type, linear operator description, application mode,application direction if applicable)
           banded     ::Vector{Bool}                                          #is A a banded matrix?
           AtA_offsets::Union{Vector{Vector{Int32}},Vector{Vector{Int64}}}    #only required if A is banded. A vector of indices of the non-zero diagonals, where the main diagonal is index 0
end

mutable struct set_definitions
  set_type          ::String #"bounds","l1","l2","annulus","subspace","nuclear","rank","histogram","cardinality"
  TD_OP             ::String
  min               ::Union{Vector,Real}
  max               ::Union{Vector,Real}
  app_mode          ::Tuple{String,String} #("tensor/"matrix"/"slice"/"fiber" , "x","y","z") , x,y,z apply only to slice and fibers of a tensor or matrix
  custom_TD_OP      ::Tuple{Union{Array{Any},SparseMatrixCSC{Float32,Int64},SparseMatrixCSC{Float64,Int64},Array{Float64,2},Array{Float32,2}, joAbstractLinearOperator},Bool} #custom matrix for subspace, and boolean to indicate orthogonality
end

end # module


abstract type DDMPreconditioner{T} <: DDMMatrix{T} end


# # Grouping

"A special type that will break up incoming data into groups, and allow for easier creation of grouped plots"
mutable struct GroupBy
    group_labels::Vector                # length == numGroups
    group_indices::Vector{Vector{Int}}  # list of indices for each group
end

# this is when given a vector-type of values to group by
function _extract_group_attributes(v::AVec, args...; legend_entry = string)
    group_labels = collect(unique(sort(v)))
    n = length(group_labels)
    if n > 100
        @warn("You created n=$n groups... Is that intended?")
    end
    group_indices = Vector{Int}[filter(i -> v[i] == glab, eachindex(v)) for glab in group_labels]
    GroupBy(map(legend_entry, group_labels), group_indices)
end

legend_entry_from_tuple(ns::Tuple) = join(ns, ' ')

# this is when given a tuple of vectors of values to group by
function _extract_group_attributes(vs::Tuple, args...)
    isempty(vs) && return GroupBy([""], [axes(args[1],1)])
    v = map(tuple, vs...)
    _extract_group_attributes(v, args...; legend_entry = legend_entry_from_tuple)
end

# allow passing NamedTuples for a named legend entry
legend_entry_from_tuple(ns::NamedTuple) =
    join(["$k = $v" for (k, v) in pairs(ns)], ", ")

function _extract_group_attributes(vs::NamedTuple, args...)
    isempty(vs) && return GroupBy([""], [axes(args[1],1)])
    v = map(NamedTuple{keys(vs)}∘tuple, values(vs)...)
    _extract_group_attributes(v, args...; legend_entry = legend_entry_from_tuple)
end

# expecting a mapping of "group label" to "group indices"
function _extract_group_attributes(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
    group_labels = sortedkeys(idxmap)
    group_indices = Vector{Int}[collect(idxmap[k]) for k in group_labels]
    GroupBy(group_labels, group_indices)
end

filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
filter_data(v, idxfilter) = v

function filter_data!(plotattributes::AKW, idxfilter)
    for s in (:x, :y, :z)
        plotattributes[s] = filter_data(get(plotattributes, s, nothing), idxfilter)
    end
end

function _filter_input_data!(plotattributes::AKW)
    idxfilter = pop!(plotattributes, :idxfilter, nothing)
    if idxfilter !== nothing
        filter_data!(plotattributes, idxfilter)
    end
end

function groupedvec2mat(x_ind, x, y::AbstractArray, groupby, def_val = y[1])
    y_mat = Array{promote_type(eltype(y), typeof(def_val))}(
        undef,
        length(keys(x_ind)),
        length(groupby.group_labels),
    )
    fill!(y_mat, def_val)
    for i in eachindex(groupby.group_labels)
        xi = x[groupby.group_indices[i]]
        yi = y[groupby.group_indices[i]]
        y_mat[getindex.(Ref(x_ind), xi), i] = yi
    end
    return y_mat
end

groupedvec2mat(x_ind, x, y::Tuple, groupby) =
    Tuple(groupedvec2mat(x_ind, x, v, groupby) for v in y)

group_as_matrix(t) = false

# split the group into 1 series per group, and set the label and idxfilter for each
@recipe function f(groupby::GroupBy, args...)
    plt = plotattributes[:plot_object]
    group_length = maximum(union(groupby.group_indices...))
    if !(group_as_matrix(args[1]))
        for (i, glab) in enumerate(groupby.group_labels)
            @series begin
                label --> string(glab)
                idxfilter --> groupby.group_indices[i]
                for (key, val) in plotattributes
                    if splittable_attribute(plt, key, val, group_length)
                        :($key) := split_attribute(plt, key, val, groupby.group_indices[i])
                    end
                end
                args
            end
        end
    else
        g = args[1]
        if length(g.args) == 1
            x = zeros(Int, group_length)
            for indexes in groupby.group_indices
                x[indexes] = eachindex(indexes)
            end
            last_args = g.args
        else
            x = g.args[1]
            last_args = g.args[2:end]
        end
        x_u = unique(sort(x))
        x_ind = Dict(zip(x_u, eachindex(x_u)))
        for (key, val) in plotattributes
            if splittable_attribute(plt, key, val, group_length)
                :($key) := groupedvec2mat(x_ind, x, val, groupby)
            end
        end
        label --> reshape(groupby.group_labels, 1, :)
        typeof(g)((
            x_u,
            (groupedvec2mat(x_ind, x, arg, groupby, NaN) for arg in last_args)...,
        ))
    end
end


module TestConstraints

using Base.Test
using DataArrays
using DataFrames
using Dates

using TimeData
## using TimeData

println("Running constraints tests")

############
## chkIdx ##
############

## test Dates
validIdx = Date[Date(2010,02,ii) for ii=1:10]
TimeData.chkIdx(validIdx)

## test times
validIdx = DateTime[DateTime(2010,02,ii,00,00,00)
                                 for ii=1:10]
TimeData.chkIdx(validIdx)

## test integers
validIdx = [1:10]
TimeData.chkIdx(validIdx)

## test invalid idx
invalidIdx = @data([1. NA 3])
@test_throws TypeError TimeData.chkIdx(invalidIdx)

## idx must be array!
invalidIdx = Dates.today()
@test_throws TypeError TimeData.chkIdx(invalidIdx)

#########################
## chkNum and chkNumDf ##
#########################

## numeric DataFrame with NAs
naDf = DataFrame(a = [1, 2, 3], b = [4, 5, 6])
naDf[1, 2] = NA
naDf[3, 1] = NA
TimeData.chkNumDf(naDf)
@test_throws ArgumentError TimeData.chkNum(naDf)

numDf = DataFrame(a = [1, 2, 3], b = [4, 5, 6])
TimeData.chkNumDf(numDf)
TimeData.chkNum(numDf)

## invalid DataFrame with strings

invalidDf = DataFrame(a = [1, 2, 3], b = ["hello", "world", "hello"])
@test_throws ArgumentError TimeData.chkNumDf(invalidDf)
@test_throws ArgumentError TimeData.chkNum(invalidDf)

invalidDf = DataFrame(a = [true, false, true])
@test_throws ArgumentError TimeData.chkNumDf(invalidDf)

#############
## chkUnit ##
#############

validDf = convert(DataFrame, rand(8, 3))
TimeData.chkUnit(validDf)

invalidDf = convert(DataFrame, rand(8, 3))
invalidDf[2, 2] = -0.2
@test_throws ArgumentError TimeData.chkUnit(invalidDf)
end


####
#### Numerical methods for simulating Hamiltonian trajectory.
####

# TODO: The type `<:Tuple{Integer,Bool}` is introduced to address
# https://github.com/TuringLang/Turing.jl/pull/941#issuecomment-549191813
# We might want to simplify it to `Tuple{Int,Bool}` when we figured out
# why the it behaves unexpected on Windos 32.

abstract type AbstractIntegrator end

NesterovDualAveraging(δ::AbstractFloat, i::AbstractIntegrator) = NesterovDualAveraging(δ, nom_step_size(i))

stat(::AbstractIntegrator) = NamedTuple()

"""
    nom_step_size(::AbstractIntegrator)

Get the nominal integration step size. The current integration step size may
differ from this, for example if the step size is jittered. Nominal step size is
usually used in adaptation.
"""
nom_step_size(i::AbstractIntegrator) = step_size(i)

"""
    step_size(::AbstractIntegrator)

Get the current integration step size.
"""
function step_size end

abstract type AbstractLeapfrog{T} <: AbstractIntegrator end

step_size(lf::AbstractLeapfrog) = lf.ϵ
jitter(::Union{AbstractRNG,AbstractVector{<:AbstractRNG}}, lf::AbstractLeapfrog) = lf
temper(lf::AbstractLeapfrog, r, ::NamedTuple{(:i, :is_half),<:Tuple{Integer,Bool}}, ::Int) = r
stat(lf::AbstractLeapfrog) = (step_size=step_size(lf), nom_step_size=nom_step_size(lf))

function step(
    lf::AbstractLeapfrog{T},
    h::Hamiltonian,
    z::P,
    n_steps::Int=1;
    fwd::Bool=n_steps > 0,  # simulate hamiltonian backward when n_steps < 0
    res::Union{Vector{P}, P}=z
) where {T<:AbstractScalarOrVec{<:AbstractFloat}, P<:PhasePoint}
    n_steps = abs(n_steps)  # to support `n_steps < 0` cases

    ϵ = fwd ? step_size(lf) : -step_size(lf)
    ϵ = ϵ'

    @unpack θ, r = z
    @unpack value, gradient = ∂H∂θ(h, θ)
    for i = 1:n_steps
        # Tempering
        r = temper(lf, r, (i=i, is_half=true), n_steps)
        # Take a half leapfrog step for momentum variable
        r = r - ϵ / 2 .* gradient
        # Take a full leapfrog step for position variable
        ∇r = ∂H∂r(h, r)
        θ = θ + ϵ .* ∇r
        # Take a half leapfrog step for momentum variable
        @unpack value, gradient = ∂H∂θ(h, θ)
        r = r - ϵ / 2 .* gradient
        # Tempering
        r = temper(lf, r, (i=i, is_half=false), n_steps)
        # Create a new phase point by caching the logdensity and gradient
        z = phasepoint(h, θ, r; ℓπ=DualValue(value, gradient))
        !isfinite(z) && break
        if res isa Vector
            res[i] = z
        else
            res = z
        end
    end
    res
end

struct Leapfrog{T<:AbstractScalarOrVec{<:AbstractFloat}} <: AbstractLeapfrog{T}
    ϵ       ::  T
end
Base.show(io::IO, l::Leapfrog) = print(io, "Leapfrog(ϵ=$(round(l.ϵ; sigdigits=3)))")

### Jittering

struct JitteredLeapfrog{FT<:AbstractFloat,T<:AbstractScalarOrVec{FT}} <: AbstractLeapfrog{T}
    ϵ0      ::  T  # nominal (unjittered) step size
    jitter  ::  FT
    ϵ       ::  T  # current (jittered) step size
end

JitteredLeapfrog(ϵ0, jitter) = JitteredLeapfrog(ϵ0, jitter, ϵ0)

function Base.show(io::IO, l::JitteredLeapfrog)
    print(io, "JitteredLeapfrog(ϵ0=$(round(l.ϵ0; sigdigits=3)), jitter=$(round(l.jitter; sigdigits=3)), ϵ=$(round(l.ϵ; sigdigits=3)))")
end

nom_step_size(lf::JitteredLeapfrog) = lf.ϵ0

# Jitter step size; ref: https://github.com/stan-dev/stan/blob/1bb054027b01326e66ec610e95ef9b2a60aa6bec/src/stan/mcmc/hmc/base_hmc.hpp#L177-L178
function _jitter(
    rng::Union{AbstractRNG,AbstractVector{<:AbstractRNG}},
    lf::JitteredLeapfrog{FT,T}
) where {FT<:AbstractFloat,T<:AbstractScalarOrVec{FT}}
    ϵ = lf.ϵ0 .* (1 .+ lf.jitter .* (2 .* rand(rng) .- 1))
    return reconstruct(lf, ϵ=ϵ)
end

jitter(rng::AbstractRNG, lf::JitteredLeapfrog) = _jitter(rng, lf)

jitter(
    rng::AbstractVector{<:AbstractRNG},
    lf::JitteredLeapfrog{FT,T}
) where {FT<:AbstractFloat,T<:AbstractScalarOrVec{FT}} = _jitter(rng, lf)

### Tempering

struct TemperedLeapfrog{FT<:AbstractFloat,T<:AbstractScalarOrVec{FT}} <: AbstractLeapfrog{T}
    ϵ       ::  T
    α       ::  FT
end

function Base.show(io::IO, l::TemperedLeapfrog)
    print(io, "TemperedLeapfrog(ϵ=$(round(l.ϵ; sigdigits=3)), α=$(round(l.α; sigdigits=3)))")
end

"""
    temper(lf::TemperedLeapfrog, r, step::NamedTuple{(:i, :is_half),<:Tuple{Integer,Bool}}, n_steps::Int)

Tempering step. `step` is a named tuple with
- `i` being the current leapfrog iteration and
- `is_half` indicating whether or not it's (the first) half momentum/tempering step
"""
function temper(lf::TemperedLeapfrog, r, step::NamedTuple{(:i, :is_half),<:Tuple{Integer,Bool}}, n_steps::Int)
    if step.i > n_steps
        throw(BoundsError("Current leapfrog iteration exceeds the total number of steps."))
    end
    i_temper = 2(step.i - 1) + 1 + !step.is_half    # counter for half temper steps
    return i_temper <= n_steps ? r * sqrt(lf.α) : r / sqrt(lf.α)
end


module BioLizard

include("l0.jl")
export ZeroNorm

include("ordinalregr.jl")
export OrdinalRegr, fitordinalsvm, score, predict

end # module


push!(LOAD_PATH, "../src/")
push!(LOAD_PATH, joinpath(Pkg.dir("PumiInterface"), "src"))
push!(LOAD_PATH, joinpath(Pkg.dir("SummationByParts"), "src"))
push!(LOAD_PATH, joinpath(Pkg.dir("MeshMovement"), "src"))
push!(LOAD_PATH, joinpath(Pkg.dir("PDESolver"), "src/Utils"))

using PdePumiInterface
using SummationByParts
using ODLCommonTools
using ArrayViews
using Utils
using MPI
# using MeshMovement
using FFD
using Base.Test

# Include functions that will be used for performing tests
include("./test_parallel_FFD_2D_mesh.jl")
include("./test_parallel_FFD_3D_mesh.jl")

#MPI.Finalize()


### A Pluto.jl notebook ###
# v0.11.2

using Markdown
using InteractiveUtils

# ╔═╡ 6630175e-d5aa-11ea-3171-a1dd3b56dfda
using DrWatson

# ╔═╡ aa1da76a-d5aa-11ea-366d-efb23acf302a
begin
	@quickactivate "GCN_HM_GRN-Integration"
	using Plots, DataFrames
end

# ╔═╡ 1a72945c-d5ac-11ea-0c5d-53dd35d75bc3
begin
	theme(:default)
	gr(fmt = :png, dpi = 200)
end

# ╔═╡ b21e917c-d5aa-11ea-35e0-ff99bdaa8e2e
df = collect_results(datadir("inner_batch"));

# ╔═╡ dc048b40-d5aa-11ea-1ec2-75f9ac6c2592
begin
	plts = []
	for i ∈ 1:size(df, 1)
		plt = plot([df[i, :losses_train], df[i, :losses_test], df[i, :auc_list]], label=["train" "test" "auc"], legend = :outerright)
		title!(plt, "$(df[i, :layer]), $(df[i, :batch]), $(DrWatson.roundval(df[i, :auc]; digits = 4, scientific = 4))")
		pushfirst!(plts, plt)
	end
	p = plot(plts..., layout = (Int32(length(plts) / 2),2))
end

# ╔═╡ Cell order:
# ╠═6630175e-d5aa-11ea-3171-a1dd3b56dfda
# ╠═aa1da76a-d5aa-11ea-366d-efb23acf302a
# ╠═1a72945c-d5ac-11ea-0c5d-53dd35d75bc3
# ╠═b21e917c-d5aa-11ea-35e0-ff99bdaa8e2e
# ╠═dc048b40-d5aa-11ea-1ec2-75f9ac6c2592


struct IndexToLocation{T}
    offsets::Vector{T}
end

function Base.parse(io::IO, ::Type{IndexToLocation{T}}, maxp::MaximumProfile) where {T}
    offsets = map(0:maxp.nglyphs) do i
        read(io, T)
    end
    IndexToLocation{T}(offsets)
end

function Base.parse(io::IO, ::Type{IndexToLocation}, maxp::MaximumProfile, head::FontHeader)
    head.index_to_loc_format in (0, 1) || error("Index to location format must be either 0 or 1.")
    T = head.index_to_loc_format == 0 ? UInt16 : UInt32
    parse(io, IndexToLocation{T}, maxp)
end

function glyph_ranges(loca::IndexToLocation)
    [start:finish for (start, finish) in zip(loca.offsets[1:end-1], loca.offsets[2:end])]
end


module Walk1DMCTSVisualize

using Plots, Walk1DMDP, POMDPToolbox, MCTS

export Walk1DMCTSVis

mutable struct Walk1DMCTSVis
    mdp::Walk1D
    s::Walk1DState
    actions::Vector{Vector{Float64}}
    next_states::Vector{Vector{Float64}}
    ns::Vector{Int}
    radii::Vector{Vector{Float64}}
end
Walk1DMCTSVis(mdp::Walk1D, s::Walk1DState) = Walk1DMCTSVis(mdp, s, Vector{Float64}[], Int[], Vector{Float64}[], Float64[])

function circle(x,y,r)
    c = Shape(Plots.partialcircle(0,2π,20,r))
    translate!(c, x, y)
    c
end

@recipe function f(vis::Walk1DMCTSVis, index::Int, h::SimHistory)
    m = vis.mdp
    xlim := (-1, (m.p.threshx+1))
    ylim := (-m.p.threshx-1, m.p.threshx+1)
	aspect_ratio := :equal
    @series begin
        label := "actions"
        alpha := 0.25
        x = state_hist(h)[end-1].t+1
        y = vis.next_states[index]
        [circle(x,y,r) for (y,r) in zip(y, vis.radii[index])]
    end
    @series begin
        seriestype := :line
        color := :black
        label := "thresh+"
        x = [-2.0, m.p.t_max]
        y = [m.p.threshx, m.p.threshx]
        x, y
    end
    @series begin
        seriestype := :line
        color := :black
        label := "thresh-"
        x = [-2.0, m.p.t_max]
        y = [-m.p.threshx, -m.p.threshx]
        x, y
    end
    @series begin
        label := "path"
        color := :blue
        x = [s.t for s in state_hist(h)[1:end-1]]
        y = [s.x for s in state_hist(h)[1:end-1]]
        x, y
    end
    @series begin
        seriestype := :scatter
        label := "current position"
        t = state_hist(h)[end-1].t
        x = state_hist(h)[end-1].x
        if abs(x) < abs(m.p.threshx)
            color := :blue
        else
            color := :red
        end
        [t], [x]
    end
end

function MCTS.notify_listener(vis::Walk1DMCTSVis,dsb::DSBPlanner,s,a,sp,r,snode,sanode,spnode)
    if s == vis.s
        tree = get(dsb.tree)
        sol = dsb.solver
        actions = [tree.a_labels[c] for c in tree.children[snode]]
        if isempty(vis.actions) || (actions != vis.actions[end])  #only push the deltas
            push!(vis.actions, actions)
            push!(vis.next_states, [tree.s_labels[tree.transitions[c][1][1]].x for c in tree.children[snode]])  #deterministic transition
            push!(vis.ns, tree.total_n[snode])
            push!(vis.radii, [sol.r0_action/tree.total_n[snode]^sol.lambda_action for action in actions])
        end
    end
end
function MCTS.notify_listener(vis::Walk1DMCTSVis,asb::ASBPlanner,s,a,sp,r,snode,sanode,spnode)
    if s == vis.s
        tree = get(asb.tree)
        sol = asb.solver
        actions = [tree.a_labels[c] for c in tree.children[snode]]
        if isempty(vis.actions) || (actions != vis.actions[end])  #only push the deltas
            push!(vis.actions, actions)
            push!(vis.next_states, [tree.s_labels[tree.transitions[c][1][1]].x for c in tree.children[snode]])  #deterministic transition
            push!(vis.ns, tree.total_n[snode])
            push!(vis.radii, [tree.a_radius[x] for x in tree.children[snode]])
        end
    end
end

end # module


include("../../AF.jl")

module MNIST

using AF

include("load.jl")

end


println("Loading required packages...")
using Plots, JSON

function plot_results()
    println("Generating plots...")
    for filename in readdir()
        endswith(filename, ".json") || continue
        plot_results(replace(filename, ".json" => ""))
    end
end

function plot_results(filename)
    results = JSON.parsefile(filename*".json")
    if get(results, "type", nothing) == nothing
        @warn "Badly formatted file: `$filename.json'"
        return
    end

    println("-> $filename")
    plot_results(Val(Symbol(results["type"])), filename, results)
end

function plot_results(::Val{:accuracy}, filename, results)
    title = results["title"]
    labels = results["labels"]
    data = results["data"]

    scatter(title=title,
            xscale=:log10, yscale=:log10,
            xlabel="Condition number",
            ylabel="Relative error")

    markers = Symbol[:circle, :+, :rect, :x, :utriangle]

    for i in 1:length(labels)
        scatter!(Float64.(data[1]), Float64.(data[i+1]), label=labels[i], markershape=markers[i])
    end

    savefig(filename*".pdf")
    savefig(filename*".svg")
end

function plot_results(::Val{:ushift}, filename, results)
    title  = results["title"]
    labels = results["labels"]
    data   = results["data"]

    p = plot(title=title,
             xlabel="log2(U)",
             ylabel="Time [ns/elem]")

    for i in 1:length(labels)
        plot!(Float64.(data[1]), Float64.(data[i+1]), label="2^$(Int(round(log2(labels[i])))) elems")
    end

    savefig(filename*".pdf")
    savefig(filename*".svg")
end

function plot_results(::Val{:performance}, filename, results)
    title  = results["title"]
    labels = results["labels"]
    data   = results["data"]

    p = plot(title=title,
             xscale=:log10,
             xlabel="Vector size",
             ylabel="Time [ns/elem]")

    for i in 1:length(labels)
        plot!(Float64.(data[1]), Float64.(data[i+1]), label=labels[i])
    end

    savefig(filename*".pdf")
    savefig(filename*".svg")
end


using ..SymEngine
using ..SymEngine: @vars, Basic
export @vars, Basic, subs

include("register.jl")
include("instruct.jl")
include("blocks.jl")
include("patch.jl")


#
module PccfMain

export
    Pccf,           # Done
    Adwin,          # In progress
    Adwin3,         # In progress
    BayesChp,       # Done
    BayesianBlocks, # Done
    BasicCounting   # In progress

include("./lib/pccf.jl")

end


function isone(a::AlgAssAbsOrdFracIdl)
  a = simplify!(a)
  return isone(denominator(a, copy = false)) && isone(numerator(a, copy = false))
end

function one(S::AlgAssAbsOrdFracIdlSet)
  return frac_ideal(order(S), ideal(order(S), one(order(S))), fmpz(1))
end

function one(I::AlgAssAbsOrdFracIdl)
  return frac_ideal(order(I), ideal(order(I), one(order(I))), fmpz(1))
end

function Base.copy(I::AlgAssAbsOrdFracIdl)
  return I
end

###############################################################################
#
#  String I/O
#
###############################################################################

function show(io::IO, a::AlgAssAbsOrdFracIdl)
  print(io, "Fractional Ideal of ")
  show(IOContext(io, :compact => true), order(a))
  println(io, " with basis matrix ")
  print(io, basis_matrix(a, copy = false))
end

###############################################################################
#
#  Deepcopy
#
###############################################################################

function Base.deepcopy_internal(a::AlgAssAbsOrdFracIdl, dict::IdDict)
  b = typeof(a)(order(a), numerator(a), denominator(a))
  for i in fieldnames(typeof(a))
    if isdefined(a, i)
      if i != :order && i != :num && i != :den
        setfield!(b, i, Base.deepcopy_internal(getfield(a, i), dict))
      end
    end
  end
  return b
end

###############################################################################
#
#  Field access
#
###############################################################################

order(a::AlgAssAbsOrdFracIdl) = a.order

function numerator(a::AlgAssAbsOrdFracIdl; copy::Bool = true)
  if !isdefined(a, :num)
    if !isdefined(a, :basis_matrix)
      error("No basis matrix defined")
    end
    a.num = ideal(order(a), numerator(basis_matrix(a)))
  end
  if copy
    return deepcopy(a.num)
  else
    return a.num
  end
end

function denominator(a::AlgAssAbsOrdFracIdl; copy::Bool = true)
  if !isdefined(a, :den)
    if !isdefined(a, :basis_matrix)
      error("No basis matrix defined")
    end
    a.den = denominator(basis_matrix(a))
  end
  if copy
    return deepcopy(a.den)
  else
    return a.den
  end
end

function basis_matrix(a::AlgAssAbsOrdFracIdl; copy::Bool = true)
  if !isdefined(a, :basis_matrix)
    if !isdefined(a, :num) || !isdefined(a, :den)
      error("Numerator and/or denominator not defined")
    end
    a.basis_matrix = FakeFmpqMat(basis_matrix(a.num, copy = false), a.den)
  end
  if copy
    return deepcopy(a.basis_matrix)
  else
    return a.basis_matrix
  end
end

function basis_mat_inv(a::AlgAssAbsOrdFracIdl; copy::Bool = true)
  if !isdefined(a, :basis_mat_inv)
    a.basis_mat_inv = inv(basis_matrix(a, copy = false))
  end
  if copy
    return deepcopy(a.basis_mat_inv)
  else
    return a.basis_mat_inv
  end
end

################################################################################
#
#  Basis
#
################################################################################

function basis(a::AlgAssAbsOrdFracIdl)
  B = basis_matrix(a, copy = false)
  O = order(a)
  A = algebra(O)
  d = dim(A)
  Oba = basis(O, copy = false)
  res = Array{elem_type(A)}(undef, d)

  for i in 1:d
    z = A()
    for j in 1:d
      z = z + B.num[i, j]*elem_in_algebra(Oba[j], copy = false)
    end
    z = divexact(z, A(B.den))
    res[i] = z
  end

  return res
end

################################################################################
#
#  Construction
#
################################################################################

function frac_ideal(O::AlgAssAbsOrd{S, T}, M::FakeFmpqMat, M_in_hnf::Bool = false) where {S, T}
  !M_in_hnf ? M = hnf(M, :lowerleft) : nothing

  return AlgAssAbsOrdFracIdl{S, T}(O, M)
end

function frac_ideal(O::AlgAssAbsOrd{S, T}, a::AlgAssAbsOrdIdl{S, T}, d::fmpz) where {S, T}
  return AlgAssAbsOrdFracIdl{S, T}(O, a, d)
end

function frac_ideal(O::AlgAssAbsOrd{S, T}, a::T) where {S, T}
  d = denominator(a, O)
  return frac_ideal(O, ideal(O, O(d*a)), d)
end

*(O::AlgAssAbsOrd{S, T}, a::T) where {S, T} = frac_ideal(O, a)
*(a::T, O::AlgAssAbsOrd{S, T}) where {S, T} = frac_ideal(O, a)

function frac_ideal_from_z_gens(O::AlgAssAbsOrd{S, T}, b::Vector{T}) where {S, T}
  d = degree(O)
  den = lcm([ denominator(bb, O) for bb in b ])
  num = ideal_from_z_gens(O, [ O(den*bb) for bb in b ])
  return frac_ideal(O, num, den)
end

################################################################################
#
#  Arithmetic
#
################################################################################

function +(a::AlgAssAbsOrdFracIdl{S, T}, b::AlgAssAbsOrdFracIdl{S, T}) where {S, T}
  d = lcm(denominator(a, copy = false), denominator(b, copy = false))
  ma = div(d, denominator(a, copy = false))
  mb = div(d, denominator(b, copy = false))
  return frac_ideal(order(a), numerator(a, copy = false)*ma + numerator(b, copy = false)*mb, d)
end

function *(a::AlgAssAbsOrdFracIdl{S, T}, b::AlgAssAbsOrdFracIdl{S, T}) where {S, T}
  d = denominator(a, copy = false)*denominator(b, copy = false)
  return frac_ideal(order(a), numerator(a, copy = false)*numerator(b, copy = false), d)
end

^(A::AlgAssAbsOrdFracIdl, e::Int) = Base.power_by_squaring(A, e)
^(A::AlgAssAbsOrdFracIdl, e::fmpz) = Base.power_by_squaring(A, BigInt(e))

################################################################################
#
#  Ad hoc binary operations
#
################################################################################

function +(a::AlgAssAbsOrdIdl{S, T}, b::AlgAssAbsOrdFracIdl{S, T}) where {S, T}
  c = a*denominator(b, copy = false) + numerator(b, copy = false)
  return frac_ideal(order(a), c, denominator(b))
end

+(a::AlgAssAbsOrdFracIdl{S, T}, b::AlgAssAbsOrdIdl{S, T}) where {S, T} = b + a

function *(a::AlgAssAbsOrdIdl{S, T}, b::AlgAssAbsOrdFracIdl{S, T}) where {S, T}
  return typeof(b)(order(a), a*numerator(b, copy = false), denominator(b))
end

function *(a::AlgAssAbsOrdFracIdl{S, T}, b::AlgAssAbsOrdIdl{S, T}) where {S, T}
  return typeof(a)(order(a), numerator(a, copy = false)*b, denominator(a))
end

function *(x::T, a::AlgAssAbsOrdFracIdl{S, T}) where {S, T}
  O = order(a)
  return frac_ideal(O, x)*a
end

function *(a::AlgAssAbsOrdFracIdl{S, T}, x::T) where {S, T}
  O = order(a)
  return a*frac_ideal(O, x)
end

function *(x::T, a::AlgAssAbsOrdIdl{S, T}) where {S, T}
  O = order(a)
  return frac_ideal(O, x)*a
end

function *(a::AlgAssAbsOrdIdl{S, T}, x::T) where {S, T}
  O = order(a)
  return a*frac_ideal(O, x)
end

################################################################################
#
#  Equality
#
################################################################################

function ==(A::AlgAssAbsOrdFracIdl, B::AlgAssAbsOrdFracIdl)
  order(A) !== order(B) && return false
  return basis_matrix(A, copy = false) == basis_matrix(B, copy = false)
end

################################################################################
#
#  Simplification
#
################################################################################

function simplify!(a::AlgAssAbsOrdFracIdl)
  b = basis_matrix(numerator(a, copy = false), copy = false)
  g = gcd(denominator(a, copy = false), content(b))

  if g != 1
    a.num = divexact(a.num, g)
    a.den = divexact(a.den, g)
  end

  return a
end

################################################################################
#
#  Random elements
#
################################################################################

function rand(a::AlgAssAbsOrdFracIdl, B::Int)
  z = rand(numerator(a, copy = false), B)
  return fmpq(1, denominator(a, copy = false))*elem_in_algebra(z, copy = false)
end

################################################################################
#
#  Inclusion of elements
#
################################################################################

function in(x::AbsAlgAssElem, a::AlgAssAbsOrdFracIdl)
  O = order(a)
  M = zero_matrix(FlintZZ, 1, degree(O))
  t = FakeFmpqMat(M)
  elem_to_mat_row!(t.num, 1, t.den, x)
  v = t*basis_mat_inv(O, copy = false)
  v = v*basis_mat_inv(a, copy = false)

  return v.den == 1
end

################################################################################
#
#  Colon
#
################################################################################

function colon(I::AlgAssAbsOrdFracIdl, J::AlgAssAbsOrdFracIdl)
  # Let I = a/c and J = b/d, a and b integral ideals, c, d \in Z, then
  # \{ x \in K | xJ \subseteq I \} = \{ x \in K | xcb \subseteq da \}

  II = numerator(I, copy = false)*denominator(J, copy = false)
  JJ = numerator(J, copy = false)*denominator(I, copy = false)
  return colon(II, JJ)
end

################################################################################
#
#  Move ideals to another order
#
################################################################################

function extend(I::AlgAssAbsOrdFracIdl, O::AlgAssAbsOrd)
  J = extend(numerator(I, copy = false), O)
  return frac_ideal(O, J, denominator(I, copy = false))
end

*(I::AlgAssAbsOrdFracIdl, O::AlgAssAbsOrd) = extend(I, O)
*(O::AlgAssAbsOrd, I::AlgAssAbsOrdFracIdl) = extend(I, O)

function _as_frac_ideal_of_smaller_order(O::AlgAssAbsOrd, I::AlgAssAbsOrdIdl)
  M = basis_matrix(I, copy = false)
  M = M*basis_matrix(order(I), copy = false)*basis_mat_inv(O, copy = false)
  return frac_ideal(O, M)
end

function _as_frac_ideal_of_smaller_order(O::AlgAssAbsOrd, I::AlgAssAbsOrdFracIdl)
  J = _as_frac_ideal_of_smaller_order(O, numerator(I, copy = false))
  return algebra(O)(fmpq(1, denominator(I, copy = false)))*J
end

################################################################################
#
#  Ideal Set
#
################################################################################

function show(io::IO, a::AlgAssAbsOrdFracIdlSet)
  print(io, "Set of fractional ideals of $(order(a))")
end

order(a::AlgAssAbsOrdFracIdlSet) = a.order

parent(I::AlgAssAbsOrdFracIdl) = AlgAssAbsOrdFracIdlSet(order(I))

function FracIdealSet(O::AlgAssAbsOrd)
   return AlgAssAbsOrdFracIdlSet(O)
end

elem_type(::Type{AlgAssAbsOrdFracIdlSet{S, T}}) where {S, T} = AlgAssAbsOrdFracIdl{S, T}

elem_type(::AlgAssAbsOrdFracIdlSet{S, T}) where {S, T} = AlgAssAbsOrdFracIdl{S, T}

parent_type(::Type{AlgAssAbsOrdFracIdl{S, T}}) where {S, T} = AlgAssAbsOrdFracIdlSet{S, T}


"""
Running scenarios one after the other.
"""

using DataFrames, CSV, FileIO, Dates
using BenchmarkTools
using Agtor

import Agtor: run_model
import OrderedCollections: LittleDict

# Start month/day of growing season
# Represents when water allocations are announced.
const gs_start = (5, 1)

function setup_zone(data_dir::String="test/data/")
    zone_spec_dir::String = "$(data_dir)zones"
    zone_params::Dict{Symbol, Dict} = load_spec(zone_spec_dir)

    return create(zone_params[:Zone_3])
end

"""
Allocation callback for example.
"""
function allocation_callback!(zone, dt_i)

    # Resetting allocations for example run
    if monthday(dt_i) == gs_start
        # Example annual water allocations
        allocs = (surface_water=150.0, groundwater=40.0)

        update_available_water!(zone, allocs)
    end
end

"""
Run scenarios in sequence, and save results as they complete.

Compared to storing all results and saving in one go, this approach
is slower overall, but does not use as much memory.
"""
function example_scenario_run(data_dir::String="test/data/")::Nothing
    z1 = setup_zone(data_dir)
    z1.manager = BaseManager("test")

    scen_data = joinpath(data_dir, "scenarios", "sampled_params.csv")
    samples = DataFrame!(CSV.File(scen_data))

    @sync for (row_id, r) in enumerate(eachrow(samples))
        tmp_z = deepcopy(z1)
        update_model!(tmp_z, r)

        results = run_model(tmp_z, run_timestep!; post=allocation_callback!)

        # Save results as they complete
        @async save_results!("async_save.jld2", row_id, results)
    end
end

"""
Same example as above, but collect all results before saving.

Should be faster than the first example (for the small number of scenarios run) as all
results are kept in memory until the end.
"""
function example_batch_save(data_dir::String="test/data/")::Nothing
    z1 = setup_zone(data_dir)
    z1.manager = BaseManager("test")

    scen_data = joinpath(data_dir, "scenarios", "sampled_params.csv")
    samples = DataFrame!(CSV.File(scen_data))

    all_results = Dict()
    for (row_id, r) in enumerate(eachrow(samples))
        tmp_z = deepcopy(z1)
        update_model!(tmp_z, r)

        all_results[row_id] = run_model(tmp_z, run_timestep!; post=allocation_callback!)
    end

    save_results!("batch_run.jld2", all_results)
end

# @btime example_scenario_run()
# @btime example_batch_save()

example_batch_save()

# Loading and displaying results
saved_results = load("batch_run.jld2")
# @info saved_results

# @info saved_results["1/zone_results"]

# df = collate_results("batch_run.jld2", "zone_results", "irrigated_yield_sum")
df = collate_results("batch_run.jld2", "zone_results", "dryland_yield_sum")

using Gadfly

col_names = map(Symbol, names(df))
# p = plot(df, x=Row, y=Col.value(col_names...), color=Col.index(col_names...), Geom.line)
p = plot(df, y=Col.value(col_names...), color=Col.index(col_names...), Geom.line)


include("1.jl")
include("5.jl")
function smallsquare(r::Robot)
    angleofsquare(r)
    i = mod(snake(r,West),2)
    if (i == 1)
        i = 1
    else i = 3
    end

    for side in (HorizonSide(mod(i, 4)),Nord,HorizonSide(mod(i+2, 4)),Sud)
        putmarker!(r)
        putmarkhole1(r,side,mod(i+2,4))
        move!(r,HorizonSide(mod(Int(side) + (i+2), 4)))
    end

end
function snake(r::Robot,side1::HorizonSide)
    i = 0
    while !isborder(r,Nord)
        if isborder(r,Ost)
            movehole1(r,side1)
        else
            movehole1(r,inverse(side1))
        end
        if isborder(r,Ost) || isborder(r,West)
            move!(r,Nord)
        end
        i+=1
    end
    return i
end
function movehole1(r::Robot,side::HorizonSide)
    while (!isborder(r,side))
        move!(r,side)
        if (isborder(r,Nord))
            break
        end
    end

end
function putmarkhole1(r::Robot,side::HorizonSide,i::Int64)

    while isborder(r,HorizonSide(mod(Int(side) + i, 4)))
        move!(r,side)
        putmarker!(r)
    end
end


module NeuralNetworkAnalysis

include("init.jl")
include("problem.jl")
include("nnops.jl")
include("setops.jl")
include("split.jl")
include("utils.jl")
include("simulate.jl")
include("solve.jl")

# problem types
export ControlledPlant,
       BlackBoxController

# splitters
export BoxSplitter, ZonotopeSplitter,
       IndexedSplitter,
       SignSplitter

# solvers
export solve, forward, simulate,
       SampledApprox, VertexSolver, BoxSolver, SplitSolver, BlackBoxSolver

# utility functions
export @modelpath, read_nnet_mat, read_nnet_yaml, read_nnet_sherlock,
       write_nnet_sherlock,
       print_timed

end


# module Noosphere

using StatsBase
using PlotlyJS
using DataFrames
using HTTP
using StringEncodings
using CSV
using DelimitedFiles
using Missings
using Dates
using TranscodingStreams
using CodecZlib

include("types.jl")
include("util.jl")
include("cache.jl")
include("parse.jl")
include("plot.jl")
include("request.jl")

function test()
    start_day = Date(2021, 7, 1)
    end_day = Date(2021, 7, 2)

    dur = Duration(start_day, end_day)

    params = Params(duration=dur)
    res = get(params)

    saveplotmultiday(res)

    # params = Params(year=2021, month=8, day=1, stime="00:00:00", etime="23:59:59")
    # res = get(params)
    # saveplot(res)
end

test()

# end # module


using CSV
using DataFrames
using DelimitedFiles
using Glob
using Plots; pgfplotsx()

# Create plots
function plot_gmres_orth()

	# Plot labels
	labels = Dict(
		# kernels
		"ser" => "SparseArrays", "par" =>  "MtSpMV.jl", "mkl" => "",
		# formats
		"csr" => "", "csc" => "",
		# orth
		"mgs" => ", MGS", "cgs" => ", CGS", "dgks" => ", DGKS",
		# precon
		"none" => "", "jacobi" => "", "ilu0" => "",
		"parjacobi" => "", "parilu0" => "",
	)

   	# Plot linestyles
   	linestyles = [:solid, :dash, :dot]

   	# Benchmarks
   	benchs = [
   		"bench_gmres_orth_par_ser",
   		"bench_gmres_orth_ser_par",
   		"bench_gmres_orth_par_par"
   	]

   	# Folders
   	folders = [
   		joinpath("bench_par_ser", readdir("bench_par_ser")[1]),
   		joinpath("bench_ser_par", readdir("bench_ser_par")[1]),
		joinpath("bench_par_par", readdir("bench_par_par")[1])
	]

   	# Get result for serial spmv and serial dense (orth=mgs)
   	file = joinpath(folders[2], "csv", "runtimes_ser_csr_mgs_none.csv")
   	df = DataFrame!(CSV.File(file, header=true))
   	y = convert(Vector, df[!, 5])
   	y_ser_ser = similar(y)
   	y_ser_ser .= y[1]

   	# Iterate through folders
   	for (ifolder, folder) in enumerate(folders)

   		# Create plots
   		plt1 = plot(ylabel="Runtime in seconds", xlabel="Number of threads",
   					legend=:topright, framestyle=:box)
   		plt2 = plot(ylabel="Parallel speedup", xlabel="Number of threads",
   					legend=:none, framestyle=:box)

		# Get all csv files with runtimes in sorted order
		files = glob(string("runtimes*"), joinpath(folder, "csv"))
		if length(files) == 3
			files = [files[3], files[1], files[2]]
		end

		# Add results from all files to plots
		for (ifile, file) in enumerate(files)

			# Get benchmark results
		    df = DataFrame!(CSV.File(file, header=true))
		    x = convert(Vector, df[!, 1])
		    y = convert(Vector, df[!, 5])

		    # Create label from filename
		    infos = file[findlast('/', file) + 1 : findlast('.', file) - 1]
		    infos = infos[findfirst('_', infos) + 1 : length(infos)]
		    infos = split(infos, "_")
		    label = ""
		    for info in infos
				label = string(label, labels[info])
			end

			# Add runtimes to first plot
			plot!(plt1, x, y, label=label, linestyles=linestyles[ifile])

			# Add speedups to second plot
			y_speedups = y_ser_ser ./ y
			plot!(plt2, x, y_speedups, linestyles=linestyles[ifile])

		end # for

		# Plot side by side and save
		width = 530
		plot(plt1, plt2, layout=(1,2), size=(width, width/2))
		savefig(string(benchs[ifolder], ".png"))
		savefig(string(benchs[ifolder], ".tex"))

	end # for

end # function


abstract type Mutability end
struct Immutable <: Mutability end
struct Mutable <: Mutability end

Mutability(::Any) = Mutable()
Mutability(::Union{Tuple, NamedTuple}) = Immutable()

setindices(obj, batch) = setindices(Mutability(obj), obj, batch)
setkeys(obj, batch) = setkeys(Mutability(obj), obj, batch)

_eachindex(x) = eachindex(x)
_eachindex(x::NamedTuple) = Base.OneTo(length(x))

function setindices(::Mutable, obj, batch)
    out = copy(obj)
    for i in _eachindex(batch)
        out[i] = batch[i]
    end
    return out
end

setindices(::Immutable, obj, batch) =
    _mapfoldl(i -> (i, batch[i]),
              (obj′, (i, v)) -> set(obj′, (@lens _[i]), v),
              Tuple(_eachindex(batch)),
              obj)

function setkeys(::Mutable, obj, batch)
    out = copy(obj)
    for (k, v) in pairs(batch)
        out[k] = v
    end
    return out
end

setkeys(::Immutable, obj, batch) =
    _foldl(Tuple(pairs(batch)), obj) do obj, (k, v)
        set(obj, (@lens _[k]), v)
    end


@testset "ranking" begin
    e = Set([1, 2, 4])
    q = Set([1])
    S = [1 0 0.5; 0 1 0.5; 0.5 0.5 1]
    rl = [3, 2]
    rs = [3.0, 2]
    r = Ranking(e, q, S, rl, rs)

    @testset "constructors" begin
        @test isa(r, Ranking)
        @test r.e == e && r.q == q && r.S == S && r.rl == rl && r.rs == rs
    end
end


struct MaximumProfile
    version::VersionNumber
    nglyphs::UInt16
    max_points::UInt16
    max_contours::UInt16
    max_composite_points::UInt16
    max_composite_contours::UInt16
    max_zones::UInt16
    max_twilight_points::UInt16
    max_storage::UInt16
    max_function_defs::UInt16
    max_instruction_defs::UInt16
    max_stack_elements::UInt16
    max_size_of_instructions::UInt16
    max_component_elements::UInt16
    max_component_depth::UInt16
end

function Base.parse(io::IO, ::Type{MaximumProfile})
    MaximumProfile(
        version_16_dot_16(read(io, UInt32)),
        (read(io, T) for T in fieldtypes(MaximumProfile)[2:end])...
    )
end


module Test12

import Automa
import Automa.RegExp: @re_str
const re = Automa.RegExp
using Test

@testset "Test12" begin
    a = re"a*"
    a.actions[:all] = [:a]
    machine = Automa.compile(a)

    ctx = Automa.CodeGenContext()
    @eval function validate(data)
        logger = Symbol[]
        $(Automa.generate_init_code(ctx, machine))
        p_end = p_eof = sizeof(data)
        $(Automa.generate_exec_code(ctx, machine, :debug))
        return logger, cs == 0 ? :ok : cs < 0 ? :error : :incomplete
    end
    @test validate(b"") == ([], :ok)
    @test validate(b"a") == ([:a], :ok)
    @test validate(b"aa") == ([:a, :a], :ok)
    @test validate(b"aaa") == ([:a, :a, :a], :ok)
    @test validate(b"aaab") == ([:a, :a, :a], :error)
end

end


function tron_dcopy(n,dx,incx,dy,incy)
    # Copies a vector, x, to a vector, y.
    # It uses unrolled loops for increments equal to one.
    # Jack Dongarra, LINPACK, 3/11/78.
    # Modified 12/3/93, array(1) declarations changed to array(*)

    if n <= 0
        return
    end
    if incx != 1 || incy != 1

        # Code for unequal increments or equal increments
        # not equal to 1

        ix = 1
        iy = 1
        if incx < 0
            ix = (-n+1)*incx + 1
        end
        if incy < 0
            iy = (-n+1)*incy + 1
        end
        for i=1:n
            dy[iy] = dx[ix]
            ix = ix + incx
            iy = iy + incy
        end

        return
    else

        # Code for both increments equal to 1
        # Clean-up loops

        m = mod(n,7)
        if m != 0
            for i=1:m
                dy[i] = dx[i]
            end
            if n < 7
                return
            end
        end
        mp1 = m + 1
        for i=mp1:7:n
            dy[i] = dx[i]
            dy[i + 1] = dx[i + 1]
            dy[i + 2] = dx[i + 2]
            dy[i + 3] = dx[i + 3]
            dy[i + 4] = dx[i + 4]
            dy[i + 5] = dx[i + 5]
            dy[i + 6] = dx[i + 6]
        end

        return
    end
end

if isequal(BLAS_LIBRARY, :OpenBlas)
    dcopy(n,dx,incx,dy,incy) = copyto!(dy, 1, dx, 1, n)
else
    dcopy(n,dx,incx,dy,incy) = tron_dcopy(n,dx,incx,dy,incy)
end

@inline function dcopy(n::Int,dx::CuDeviceArray{Float64,1},incx::Int,
                       dy::CuDeviceArray{Float64,1},incy::Int)
    tx = threadIdx().x
    ty = threadIdx().y

    # Ignore incx and incy for now.
    if tx <= n && ty == 1
        @inbounds dy[tx] = dx[tx]
    end
    CUDA.sync_threads()

    return
end


__precompile__()

module DataStructures

    import Base: <, <=, ==, length, isempty, start, next, done, delete!,
                 show, dump, empty!, getindex, setindex!, get, get!,
                 in, haskey, keys, merge, copy, cat,
                 push!, pop!, shift!, unshift!, insert!,
                 union!, delete!, similar, sizehint!,
                 isequal, hash,
                 map, reverse,
                 first, last, eltype, getkey, values, sum,
                 merge, merge!, lt, Ordering, ForwardOrdering, Forward,
                 ReverseOrdering, Reverse, Lt,
                 isless,
                 union, intersect, symdiff, setdiff, issubset,
                 find, searchsortedfirst, searchsortedlast, endof, in

    using Compat: uninitialized, Nothing, Cvoid, AbstractDict

    export Deque, Stack, Queue, CircularDeque
    export deque, enqueue!, dequeue!, dequeue_pair!, update!, reverse_iter
    export capacity, num_blocks, front, back, top, top_with_handle, sizehint!

    export Accumulator, counter, reset!, inc!, dec!

    export ClassifiedCollections
    export classified_lists, classified_sets, classified_counters

    export IntDisjointSets, DisjointSets, num_groups, find_root, in_same_set, root_union!

    export AbstractHeap, compare, extract_all!
    export BinaryHeap, binary_minheap, binary_maxheap, nlargest, nsmallest
    export MutableBinaryHeap, mutable_binary_minheap, mutable_binary_maxheap
    export heapify!, heapify, heappop!, heappush!, isheap

    export OrderedDict, OrderedSet
    export DefaultDict, DefaultOrderedDict
    export Trie, subtrie, keys_with_prefix, path

    export LinkedList, Nil, Cons, nil, cons, head, tail, list, filter, cat,
           reverse
    export SortedDict, SortedMultiDict, SortedSet
    export SDToken, SDSemiToken, SMDToken, SMDSemiToken
    export SetToken, SetSemiToken
    export startof
    export pastendsemitoken, beforestartsemitoken
    export searchsortedafter, searchequalrange
    export packcopy, packdeepcopy
    export exclusive, inclusive, semitokens
    export orderobject, ordtype, Lt, compare, onlysemitokens

    export MultiDict, enumerateall

    import Base: eachindex, keytype, valtype

    include("delegate.jl")

    include("deque.jl")
    include("circ_deque.jl")
    include("stack.jl")
    include("queue.jl")
    include("accumulator.jl")
    include("classified_collections.jl")
    include("disjoint_set.jl")
    include("heaps.jl")

    include("dict_support.jl")
    include("ordered_dict.jl")
    include("ordered_set.jl")
    include("default_dict.jl")
    include("trie.jl")

    include("int_set.jl")

    include("list.jl")
    include("balanced_tree.jl")
    include("tokens.jl")

    import .Tokens: IntSemiToken

    include("multi_dict.jl")
    include("sorted_dict.jl")
    include("sorted_multi_dict.jl")
    include("sorted_set.jl")
    include("tokens2.jl")
    include("container_loops.jl")

    include("dict_sorting.jl")

    export
        CircularBuffer,
        capacity,
        isfull
    include("circular_buffer.jl")

    export status
    export deref_key, deref_value, deref, advance, regress

    export PriorityQueue, peek

    include("priorityqueue.jl")

    # Deprecations

    # Remove when Julia 0.7 (or whatever version is after v0.6) is released
    @deprecate DefaultDictBase(default, ks::AbstractArray, vs::AbstractArray) DefaultDictBase(default, zip(ks, vs))
    @deprecate DefaultDictBase(default, ks, vs) DefaultDictBase(default, zip(ks, vs))
    @deprecate DefaultDictBase(::Type{K}, ::Type{V}, default) where {K,V} DefaultDictBase{K,V}(default)

    @deprecate DefaultDict(default, ks, vs) DefaultDict(default, zip(ks, vs))
    @deprecate DefaultDict(::Type{K}, ::Type{V}, default) where {K,V} DefaultDict{K,V}(default)

    @deprecate DefaultOrderedDict(default, ks, vs) DefaultOrderedDict(default, zip(ks, vs))
    @deprecate DefaultOrderedDict(::Type{K}, ::Type{V}, default) where {K,V} DefaultOrderedDict{K,V}(default)

    function SortedMultiDict(ks::AbstractVector{K},
                             vs::AbstractVector{V},
                             o::Ordering=Forward) where {K,V}
        Base.depwarn("SortedMultiDict(ks, vs, o::Ordering=Forward) is deprecated.\n" * "Use SortedMultiDict(o, zip(ks,vs)) or SortedMultiDict(zip(ks, vs))", :SortedMultiDict)
        if length(ks) != length(vs)
            throw(ArgumentError("SortedMultiDict(ks,vs,o): ks and vs arrays must be the same length"))
        end
        SortedMultiDict(o, zip(ks,vs))
    end

    @deprecate PriorityQueue(::Type{K}, ::Type{V}) where {K,V} PriorityQueue{K,V}()
    @deprecate PriorityQueue(::Type{K}, ::Type{V}, o::Ordering) where {K,V} PriorityQueue{K,V,typeof(o)}(o)
    @deprecate (PriorityQueue{K,V,ForwardOrdering}() where {K,V}) PriorityQueue{K,V}()

    function PriorityQueue(ks::AbstractVector{K},
                           vs::AbstractVector{V},
                           o::Ordering=Forward) where {K,V}
        Base.depwarn("PriorityQueue(ks, vs, o::Ordering=Forward) is deprecated.\n" * "Use PriorityQueue(o, zip(ks,vs)) or PriorityQueue(zip(ks, vs))", :PriorityQueue)
        if length(ks) != length(vs)
            throw(ArgumentError("PriorityQueue(ks,vs,o): ks and vs arrays must be the same length"))
        end
        PriorityQueue(o, zip(ks,vs))
    end

end