const SAC_WORD_SIZE = 4

"""
    decodesacbytes(T::Type, bs::Vector{UInt8}, needswap=false)

Decode the bytes `bs` into the type `T` swapping the byte order if
`needswap=true`. Returns an array of decoded values in the order they were
decoded.

    decodesacbytes(T::Type{String}, bs::Vector{UInt8}, stringsize=8)

Decode the bytes `bs` into `Strings` with each string being `stringsize`
bytes long. Returns an array of strings in the order they were decoded.
"""
decodesacbytes(T::Type{HeaderEnum}, bs::Vector{UInt8}, needswap=false) =
    reinterpret(HeaderEnum, decodesacbytes(Int32, bs, needswap))
decodesacbytes(T::Type{Bool}, bs::Vector{UInt8}, needswap=false) =
    decodesacbytes(Int32, bs, needswap) .!= 0 # SAC treats anything that isn't 0 as true
function decodesacbytes(T::Type, bs::Vector{UInt8}, needswap=false)
    @assert(length(bs) % 4 == 0, "Length of byte array is not a multiple of 4")
    decdata = reinterpret(T, bs)
    return needswap ? map(bswap, decdata) : decdata
end
function decodesacbytes(T::Type{String}, bs::Vector{UInt8}, stringsize::Integer=8)
    @assert(length(bs) % stringsize == 0,
            "Length of byte array is not a multiple of $stringsize")
    nstr = div(length(bs), stringsize)
    return [ascii(String(bs[(stringsize * n) + 1 : (stringsize * n) + stringsize]))
            for n in 0:1:nstr-1]
end

"Determine if a SAC file is in non-native endianness by checking the header
version number is between 1 and `SAC_HDR_VERSION`. Returns `true` if the file is
alien."
function isalienend(f::IOStream)
    p = position(f)
    seek(f, 76 * SAC_WORD_SIZE)
    nvhdr = reinterpret(Int32, read(f, 4))[1]
    seek(f,p)

    return !(1 <= nvhdr <= SAC_HDR_VERSION)
end

"""
    parsetext(T::Type, text::String, colwidth::Integer)

Parse values of type `T` from `text` where each value is `colwidth` columns wide
in the text. Returns an array of `T`s. Newline characters are stripped from
`text` before parsing.

Raises an error if the length of `text` is not a multiple of `colwidth` after
stripping newlines.
"""
parsetext(T::Type{Int32}, text::String) = parsetext(T, text, 10)
parsetext(T::Type{Float32}, text::String) = parsetext(T, text, 15)
parsetext(T::Type{HeaderEnum}, text::String) = reinterpret(HeaderEnum, parsetext(Int32, text))
parsetext(T::Type{Bool}, text::String) = parsetext(Int32, text) .!= 0
parsetext(T::Type{String}, text::String) = parsetext(T, text, 8)
function parsetext(T::Type, text::String, colwidth::Integer)
    cleantext = replace(text, '\n', "")
    @assert(length(cleantext) % colwidth == 0,
            "Text is not a multiple of $colwidth characters wide after stripping newline characters.")
    readvals = Array(T, div(length(cleantext), colwidth))

    for i in eachindex(readvals)
        field = cleantext[(i-1) * colwidth + 1 : (i-1) * colwidth + colwidth]
        readvals[i] = T <: Number ? parse(T, field) : strip(field)
    end

    return readvals
end

################################################################################
#                                  HEADER IO
################################################################################

"""
    readsachdr(fname::AbstractString; ascii=false)
    readsachdr(f::IOStream; ascii=false)

Read the header data (first 158 words/632 bytes) from the file named `fname` or
the stream `f`. Returns a `Header` instance constructed from the data.

Pass `ascii=true` to read the header of a SAC alpha file.
"""
readsachdr(fname::AbstractString; kwargs...) = open((f) -> readsachdr(f; kwargs...), fname)
function readsachdr(f::IOStream; ascii=false)
    ascii ? _readsachdr_ascii(f) : _readsachdr_binary(f)
end

function _readsachdr_ascii(f)
    seekstart(f)
    fhdrcontents = ascii(String(read(f, SAC_HDR_ASCII_END)))
    # For the alphanumeric section of the header, we have to offset from the
    # start of this section to account for the different length of the kevnm
    # field.
    hdrarr = [parsetext(Float32, fhdrcontents[SAC_HDR_ASCII_FLOAT_START:SAC_HDR_ASCII_INT_START-1]);
              parsetext(Int32, fhdrcontents[SAC_HDR_ASCII_INT_START:SAC_HDR_ASCII_ENUM_START-1]);
              parsetext(HeaderEnum, fhdrcontents[SAC_HDR_ASCII_ENUM_START:SAC_HDR_ASCII_BOOL_START-1]);
              parsetext(Bool, fhdrcontents[SAC_HDR_ASCII_BOOL_START:SAC_HDR_ASCII_ALPHA_START-1]);
              parsetext(String, fhdrcontents[SAC_HDR_ASCII_ALPHA_START:SAC_HDR_ASCII_ALPHA_START+7]);
              parsetext(String, fhdrcontents[SAC_HDR_ASCII_ALPHA_START+8:SAC_HDR_ASCII_ALPHA_START+23], 16);
              parsetext(String, fhdrcontents[SAC_HDR_ASCII_ALPHA_START+25:SAC_HDR_ASCII_END])]

    hdr = Header()
    for (val, field) in zip(hdrarr, fieldnames(Header))
        setfield!(hdr, field, val)
    end

    return hdr
end

function _readsachdr_binary(f::IOStream)
    seekstart(f)
    needswap = isalienend(f)
    bs = read(f, SAC_WORD_SIZE * SAC_HDR_NWORDS)
    hdr = Header()

    hdrvals = vcat(decodesacbytes(Float32, bs[1:SAC_WORD_SIZE * 70], needswap),
                   decodesacbytes(Int32, bs[(SAC_WORD_SIZE * 70) + 1 : SAC_WORD_SIZE * 85], needswap),
                   decodesacbytes(HeaderEnum, bs[(SAC_WORD_SIZE * 85) + 1 : SAC_WORD_SIZE * 105], needswap),
                   decodesacbytes(Bool, bs[(SAC_WORD_SIZE * 105) + 1 : SAC_WORD_SIZE * 110], needswap),
                   decodesacbytes(String, bs[(SAC_WORD_SIZE * 110) + 1 : SAC_WORD_SIZE * 112]),
                   decodesacbytes(String, bs[(SAC_WORD_SIZE * 112) + 1 : SAC_WORD_SIZE * 116], 16),
                   decodesacbytes(String, bs[(SAC_WORD_SIZE * 116) + 1 : SAC_WORD_SIZE * 158]))

    for (field, val) in zip(fieldnames(hdr), hdrvals)
        setfield!(hdr, field, val)
    end

    cleanhdr!(hdr)
    return hdr
end

################################################################################
#                                   DATA IO
################################################################################

"""
    readsac(fname::AbstractString; ascii=false)
    readsac(f::IOStream; ascii=false)

Read the SAC file at path `fname` or from the stream `f`. The return type is
determined using the file type declared in the file's header. *Note* this isn't
type stable.

    readsac{S<:AbstractSACData}(T::Type{S}, f::IOStream; ascii=false)

Read the SAC file of type `T` from the stream `f`. Raises an error if the type
`T` does not match the file type declared in the file's header. The returned
type is always of type `T`.

Pass `ascii=true` to read a SAC alpha file. *Note* this uses *significantly*
more memory than reading a binary SAC file.
"""
readsac(fname::AbstractString; kwargs...) = open((f) -> readsac(f; kwargs...), fname)
function readsac(f::IOStream; kwargs...)
    hdr = readsachdr(f; kwargs...)

    if hdr.iftype == itime && hdr.leven
        readsac(EvenTimeSeries, f, hdr; kwargs...)
    elseif hdr.iftype == itime && !hdr.leven
        readsac(UnevenTimeSeries, f, hdr; kwargs...)
    elseif hdr.iftype == irlim
        readsac(ComplexSpectrum, f, hdr; kwargs...)
    elseif hdr.iftype == iamph
        readsac(AmplitudeSpectrum, f, hdr; kwargs...)
    elseif hdr.iftype == ixy
        readsac(GeneralXY, f, hdr; kwargs...)
    end
end

readsac{S<:AbstractSACData}(T::Type{S}, f::IOStream; kwargs...) = readsac(T, f, readsachdr(f; kwargs...); kwargs...)
function readsac{S<:AbstractSACData}(T::Type{S}, f::IOStream, hdr::Header; kwargs...)
    if hdr.iftype != FILE_TYPE_ENUMS[T]
        error("File's header indicates it is not a $(T)")
    end
    if T == EvenTimeSeries
        T(hdr, readsac_data(f, hdr.npts; kwargs...)[1])
    elseif T == ComplexSpectrum
        T(hdr, complex(readsac_data(f, hdr.npts; kwargs...)...))
    else
        T(hdr, readsac_data(f, hdr.npts; kwargs...)...)
    end
end

"""
    readsac_data(f::IOStream, npts::Int32; ascii=false)

Read `npts` of the data section from the stream `f`. Will automatically swap the
byte order of the data if it isn't the host's byte order. Returns a 2-tuple
containing the first data section and, if present, the second data section.
"""
function readsac_data(f::IOStream, npts::Int32; ascii=false)
    ascii ? _readsac_data_ascii(f, npts) : _readsac_data_binary(f, npts)
end

function _readsac_data_binary(f::IOStream, npts::Int32)
    needswap = isalienend(f)
    seek(f, DATA_START)

    data1 = decodesacbytes(Float32, read(f, SAC_WORD_SIZE * npts), needswap)
    if eof(f)
        return (data1, Float32[])
    end

    data2 = decodesacbytes(Float32, read(f, SAC_WORD_SIZE * npts), needswap)
    return (data1, data2)
end

function _readsac_data_ascii(f::IOStream, npts::Int32)
    seek(f, ASCII_DATA_START)

    data = readstring(f)
    data = parsetext(Float32, data)

    if length(data) == 2 * npts
        return (data[1:npts], data[npts+1:end])
    elseif length(data) == npts
        return (data, Float32[])
    else
        error("Number of data points in file ($(length(data)) does not match
       the"*"number of points declared in the header ($(hdr.npts)).")
    end
end


# Profiling
# Activate the profiling environment before calling this.
# `using UtilityFunctionsLH` must be issued while that package is active.

# using UtilityFunctionsLH
using Profile, StatProfilerHTML, BenchmarkTools

uf = UtilityFunctionsLH;

function profile_lifetime_utility()
    b = @benchmarkable  uf.lifetime_utility(CRRA(2.0, false), 0.96, 1.05, 30, 75.0)
    @show run(b)

    Profile.clear();
    @profile begin
    	for i1 = 1 : 1e7
	    	uf.lifetime_utility(CRRA(2.0, false), 0.96, 1.05, 30, 75.0);
		end
	end
    Profile.print(maxdepth = 8, mincount = 50)

    statprofilehtml()

end

# ----------


function eg1()
    x = 2
    y = eg1a(x)
    x = eg1b()
    return x
end
eg1a(x) = x
eg1b() = eg1c()
eg1c() = 7

function eg2()
    x = 2
    y = eg2()
    return x
end
eg3() = 5

function badcake1()
    y=0
    f = ()-> y+=1
    f()
    return y
end

function badcake2()
    y=sum([1 for ii in 1:3])
    return y
end

GLOBAL_VAR=0
function badcake3()
    global GLOBAL_VAR
    GLOBAL_VAR+=1
end

badbread1() = ()->99

string_demo1() = "outer" * string_demo1a()
string_demo1a() = "inner"

fma_demo1(a) = 8*a+2
fma_demo2(a) = (8*a)*a+(sqrt.(a))


using Markdown # hide

# ## Parallel construction and parallel search
# By Eric S. Téllez
#

ENV["GKSwstype"] = "nul" # hide

using SimilaritySearch
# Similarity search on very large datasets and high-dimensional datasets require high computational
# resources. In this example we show how to parallelize both the construction and search to be able to
# handle this kind of databases.

const dim = 16

db = MatrixDatabase(randn(Float32, dim, 10^5))
Q = MatrixDatabase(randn(Float32, dim, 30))
dist = SqL2Distance()
G = SearchGraph(; dist, db)

# The `SearchGraph` construction algorithm is incremental:
# - If the index is empty, an element is inserted just inserting it into the index
# - If the index is not empty, the element is inserted and connected to its nearest neighbors (looking into the current index)
#
# the parallel construction is made with `index!` or `append!`, for this matter these functions accept
# a `parallel_block` argument, that controls how many elements are inserted at once, i.e., looking for its nearest neighbors
# in parallel and connected also in parallel.
#
# As in the sequential version, a minimum number of elements must exists to work, and therefore, the `parallel_minimum_first_block`
# argument can also be specified. By default, it is equal to `parallel_block`.
# The `parallel_block` argument should be set to at least the number of available threads, and perhaps multiplying it by
# a small constant is also a good approach.
#
# This example didn't run in parallel due to the document generation pipeline but all demonstrations actually do it.
# Nonetheless, they ran with the parallel API in single thread mode.
index!(G; parallel_block=512)
nothing # hide

# Note that you can't call `push!`, `append!`, or `index!` from several threads, instead you must use the `parallel_block` argument
# to let the algorithm take advantage of the multiple threads.
#
# ## Searching
# Once the index is constructed, you can solve batches in parallel and also single queries.
# In contrast with append, these functions can be called in multithreading algorithms.
# However, you must pause the searching requests while perform insertions (parallel or sequential).

I, D = searchbatch(G, Q, 10; parallel=true)

Threads.@threads for i in eachindex(Q)
    res, cost = search(G, Q[i], KnnResult(10))
    println(res.id) # do something with `res`
end

# ## About searching pools
# Searching and also insertion methods (since they also perform searches) make use of several objects that are cached
# and reused to reduce memory allocations. These caches are named pools and are of type `SearchGraphPools`, their
# default values should be enough for multithreading applications, but special usage will require to review them.
# Among the special usages that require an explicit handling of pools we can think on objects indexing objects
# that are also indexes, or distance functions that can perform context switches as tasks (?).
#
#- Using BeutifulMakie style
# ## Dependencies
using Pkg # hide
Pkg.status(["SimilaritySearch"]) # hide


using Localize

@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using Test
end

replace!(Base.Math, Docs.@var(sin), Docs.docstr("正弦函数"), Tuple{Number})


using SeaPearl
using SeaPearlExtras
using ReinforcementLearning
const RL = ReinforcementLearning
using Flux
using GeometricFlux
using Random

include("rewards.jl")
include("features.jl")

# -------------------
# Generator
# -------------------
latin_generator = SeaPearl.LatinGenerator(11,0.6)
# -------------------
# Internal variables
# -------------------

SR = SeaPearl.DefaultStateRepresentation{LatinFeaturization, SeaPearl.DefaultTrajectoryState}
numInFeatures = SeaPearl.feature_length(SR)
numGlobalFeature = SeaPearl.global_feature_length(SR)
# -------------------
# Experience variables
# -------------------
nbEpisodes = 500
evalFreq = 30
nbInstances = 1
nbRandomHeuristics = 0

# -------------------
# Agent definition
# -------------------

include("agents.jl")

# -------------------
# Value Heuristic definition
# -------------------
learnedHeuristic=SeaPearl.LearnedHeuristic{SR, InspectReward, SeaPearl.FixedOutput}(agent)
# Basic value-selection heuristic
selectMin(x::SeaPearl.IntVar; cpmodel=nothing) = SeaPearl.minimum(x.domain)
heuristic_min = SeaPearl.BasicHeuristic(selectMin)

function select_random_value(x::SeaPearl.IntVar; cpmodel=nothing)
    selected_number = rand(1:length(x.domain))
    i = 1
    for value in x.domain
        if i == selected_number
            return value
        end
        i += 1
    end
    @assert false "This should not happen"
end

randomHeuristics = []
for i in 1:nbRandomHeuristics
    push!(randomHeuristics, SeaPearl.BasicHeuristic(select_random_value))
end

valueSelectionArray = [learnedHeuristic,heuristic_min]
append!(valueSelectionArray, randomHeuristics)
# -------------------
# Variable Heuristic definition
# -------------------
variableSelection = SeaPearl.MinDomainVariableSelection{false}()

# -------------------
# -------------------
# Core function
# -------------------
# -------------------

function trytrain(nbEpisodes::Int)

    metricsArray, eval_metricsArray = SeaPearl.train!(
        valueSelectionArray=valueSelectionArray,
        generator=latin_generator,
        nbEpisodes=nbEpisodes,
        strategy=SeaPearl.DFSearch(),
        variableHeuristic=variableSelection,
        out_solver=false,
        verbose = false,
        evaluator=SeaPearl.SameInstancesEvaluator(valueSelectionArray,latin_generator; evalFreq = evalFreq, nbInstances = nbInstances),
        restartPerInstances = 1
    )

    return metricsArray, eval_metricsArray
end

# -------------------
# -------------------

metricsArray, eval_metricsArray = trytrain(nbEpisodes)
nothing


using BangBang

struct UndefArray{N, T <: NTuple{N, Int}} <: AbstractArray{Union{}, N}
    size::T
end

Base.size(arr::UndefArray) = arr.size
Base.isassigned(::UndefArray, ::Integer...) = false

function BangBang.NoBang._setindex(x::UndefArray{N}, v, I...) where N
    ys = Array{typeof(v), N}(undef, x.size...)
    ys[I...] = v
    return ys
end

mapbb(f, xs::Array, inbounds::Val = Val(true)) =
    __map!!(f, UndefArray(size(xs)), xs, 1, inbounds)

@inline function __map!!(f, ys, xs, n, inbounds)
    @simd for i in n:length(ys)
        if inbounds === Val(true)
            @inbounds begin
                ys′ = setindex!!(ys, f(xs[i]), i)
            end
        else
            ys′ = setindex!!(ys, f(xs[i]), i)
        end
        if !(ys′ isa typeof(ys))
            return __map!!(f, ys′, xs, i + 1, inbounds)
        end
    end
    return ys
end


using MuZero
using Test

include("state.jl")
include("game.jl")

@testset "TicTacToeState" begin
    s = TicTacToeState()
    @test legal_actions(s)  |> (x) -> issetequal(x, collect(1:9))
    @test dynamics(s, 1)    |> (x) -> isa(x, Tuple{Float32, TicTacToeState})
    @test is_terminal(s)    |> typeof == Bool
    @test to_image(s)       |> typeof == Array{Float32, 3}
    @test svg(s)            |> typeof == TicTacToeStateSVG
    #@test png(s)            |> typeof == TicTacToeStatePNG
end


module TestExamples1DAdvection

using Test
using Trixi

include("test_trixi.jl")

# pathof(Trixi) returns /path/to/Trixi/src/Trixi.jl, dirname gives the parent directory
EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "1d")

@testset "Linear scalar advection" begin
  @testset "elixir_advection_basic.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"),
      l2   = [6.0388296447998465e-6],
      linf = [3.217887726258972e-5])
  end

  @testset "elixir_advection_amr.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"),
      l2   = [0.3540206249507417],
      linf = [0.9999896603382347])
  end

  @testset "elixir_advection_amr_nonperiodic.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr_nonperiodic.jl"),
      l2   = [4.283508859843524e-6],
      linf = [3.235356127918171e-5])
  end
end

end # module


"""
    increment_bins!(h, data)

Increments the bin counts for a 1D histogram `h` using the `data`. Elements of `data` that are outside the range of
the histogram's bins will NOT be filtered out, they will be considered as members of the closest bin.
"""
increment_bins!(h, data) = increment_bins!(BinSearchAlgorithm(h), HistogramParallelization(h), h, data)

"""
    increment_bins!(h, data1, data2)

Increments the bin counts for a 2D histogram `h` using the data `data1` and `data2`. Elements of `data` that are
outside the range of the histogram's bins will NOT be filtered out, they will be considered as members of the closest
bin.
"""
increment_bins!(h, data1, data2) = increment_bins!(BinSearchAlgorithm(h), HistogramParallelization(h), h, data1, data2)

"""
    get_weights(h)

Returns the weights array. All histograms must implement this.
"""
get_weights

"""
    get_subweights(h)

Returns the subweights array. All histograms implementing SIMD and PrivateThreads parallelization must implement this.
"""
get_subweights

"""
    counts(h)

Returns the bin counts of the histogram `h`. All histograms must implement this.
"""
counts(h) = get_weights(h)

"""
    zero!(h)

Sets all bin counts of the histogram `h` to zero. All histograms must implement this.
"""
function zero!(h)
    get_weights(h) .= 0
    get_subweights(h) .= 0
    nothing
end

function increment_bins!(::BinSearchAlgorithm, ::NoParallelization, h, data)
    for d in data
        @inbounds i = bin_search(h, 1, d)
        @inbounds get_weights(h)[i] += 1
    end

    nothing
end

function increment_bins!(
    ::BinSearchAlgorithm,
    ::NoParallelization,
    h,
    data1::Union{AbstractVector,AbstractMatrix},
    data2::Union{AbstractVector,AbstractMatrix},
)
    for c = 1:size(data1, 2)
        for r = 1:size(data1, 1)
            @inbounds tx = data1[r, c]
            @inbounds ty = data2[r, c]
            @inbounds ix = bin_search(h, 1, tx)
            @inbounds iy = bin_search(h, 2, ty)
            @inbounds get_weights(h)[ix, iy] += 1
        end
    end

    nothing
end

# This implementation is slower than NoParallelization in my benchmarks.
# TODO: Match performance with https://github.com/pcjung/AHTL
function increment_bins!(::Arithmetic, ::SIMD, h, data::Union{AbstractVector,AbstractMatrix})
    rows = size(data, 1)
    align_rows = floor(Int, rows / 3)

    w = get_subweights(h)
    for c = 1:size(data, 2)
        r = 1

        while r < align_rows
            @turbo for i = 0:2
                @inbounds tx = data[r+i, c]
                @inbounds w[bin_search(h, 1, tx), i+1] += 1
            end
            r += 3
        end

        for r2 = r:rows
            @inbounds tx = data[r2, c]
            @inbounds w[bin_search(h, 1, tx), 1] += 1
        end
    end

    sum_subweights!(h)

    nothing
end

# This implementation is slower than NoParallelization in my benchmarks.
# TODO: Match performance with https://github.com/pcjung/AHTL
function increment_bins!(
    ::Arithmetic,
    ::SIMD,
    h,
    img1::Union{AbstractVector,AbstractMatrix},
    img2::Union{AbstractVector,AbstractMatrix},
)
    rows = size(img1, 1)
    align_rows = floor(Int, rows / 4)

    w = get_subweights(h)
    for c = 1:size(img1, 2)
        r = 1

        while r < align_rows
            @turbo for i = 0:3
                @inbounds tx = img1[r+i, c]
                @inbounds ty = img2[r+i, c]
                ix = bin_search(h, 1, tx)
                iy = bin_search(h, 2, ty)
                @inbounds w[ix, iy, i+1] += 1
            end
            r += 4
        end

        for r2 = r:rows
            @inbounds tx = img1[r2, c]
            @inbounds ty = img2[r2, c]
            ix = bin_search(h, 1, tx)
            iy = bin_search(h, 2, ty)
            @inbounds w[ix, iy, 1] += 1
        end
    end

    sum_subweights!(h)

    nothing
end

function increment_bins!(::BinSearchAlgorithm, ::PrivateThreads, h, data::Union{AbstractVector,AbstractMatrix})
    nthreads = size(get_subweights(h), 2)
    Threads.@threads for thread_idx = 1:nthreads
        if length(data) <= nthreads
            if thread_idx == 1
                start_idx = 1
                end_idx = length(data)
            else
                # Make an empty range
                start_idx = 1
                end_idx = 0
            end
        else
            step = length(data) ÷ nthreads
            start_idx = (thread_idx - 1) * step + 1
            end_idx = step * thread_idx
            if end_idx + step > length(data) - (length(data) % nthreads)
                end_idx = length(data)
            end
        end

        for i = start_idx:end_idx
            @inbounds i = bin_search(h, 1, data[i])
            @inbounds get_subweights(h)[i, thread_idx] += 1
        end
    end

    sum!(h.weights, get_subweights(h))

    nothing
end

function increment_bins!(
    ::BinSearchAlgorithm,
    ::PrivateThreads,
    h,
    data1::Union{AbstractVector,AbstractMatrix},
    data2::Union{AbstractVector,AbstractMatrix},
)
    nthreads = size(get_subweights(h), 3)
    Threads.@threads for thread_idx = 1:nthreads
        if length(data1) <= nthreads
            if thread_idx == 1
                start_idx = 1
                end_idx = length(data1)
            else
                # Make an empty range
                start_idx = 1
                end_idx = 0
            end
        else
            step = length(data1) ÷ nthreads
            start_idx = (thread_idx - 1) * step + 1
            end_idx = step * thread_idx
            if end_idx + step > length(data1) - (length(data1) % nthreads)
                end_idx = length(data1)
            end
        end

        for i = start_idx:end_idx
            @inbounds ix = bin_search(h, 1, data1[i])
            @inbounds iy = bin_search(h, 2, data2[i])
            @inbounds get_subweights(h)[ix, iy, thread_idx] += 1
        end
    end

    sum!(h.weights, get_subweights(h))

    nothing
end

function increment_bins!(::HashFunction, ::NoParallelization, h, data::AbstractVector{<:AbstractString})
    t = get_table(h)
    for d in data
        t[d] = get!(t, d, 0) + 1
    end

    nothing
end

function increment_bins!(::HashFunction, ::PrivateThreads, h, data::AbstractVector{<:AbstractString})
    nthreads = length(get_subtable(h))
    Threads.@threads for thread_idx = 1:nthreads
        if length(data) <= nthreads
            if thread_idx == 1
                start_idx = 1
                end_idx = length(data)
            else
                # Make an empty range
                start_idx = 1
                end_idx = 0
            end
        else
            step = length(data) ÷ nthreads
            start_idx = (thread_idx - 1) * step + 1
            end_idx = step * thread_idx
            if end_idx + step > length(data) - (length(data) % nthreads)
                end_idx = length(data)
            end
        end

        st = get_subtable(h)[thread_idx]
        for i = start_idx:end_idx
            @inbounds d = data[i]
            st[d] = get!(st, d, 0) + 1
        end
    end

    t = get_table(h)
    for thread_idx = 1:nthreads
        st = get_subtable(h)[thread_idx]
        for (k, v) in pairs(st)
            t[k] = get!(t, k, 0) + v
        end
    end

    nothing
end

sum_subweights!(h) = sum!(get_weights(h), get_subweights(h))


struct ScoreParams
    word_groups::Vector{Vector{String}}
    group_scores::Vector{Float64}
    required_groups::Vector{Int}
    gap_penalty::Float64
    normalize::Bool
    word_to_group::Dict{String, Int}

    function ScoreParams(word_groups, group_scores, required_groups, gap_penalty; normalize=false)
        word_to_group = Dict(word => i for (i, words) in enumerate(word_groups) for word in words)
        return new(word_groups, group_scores, required_groups, gap_penalty, normalize, word_to_group)
    end
end

function ScoreParams(pairs::AbstractVector{<:Pair}, gap_penalty::Real; normalize=false)
    word_groups = Vector{String}[]
    group_scores = Float64[]
    required_groups = Int[]

    for (i, (words, score)) in enumerate(pairs)
        push!(word_groups, words)
        if score == Inf
            push!(group_scores, 0.)
            push!(required_groups, i)
        else
            push!(group_scores, score)
        end
    end

    return ScoreParams(word_groups, group_scores, required_groups, gap_penalty; normalize=normalize)
end

function score(components, params::ScoreParams)
    group_seen = falses(length(params.word_groups))
    s = 0.

    for word in components
        g = get(params.word_to_group, word, 0)
        if g > 0
            if !group_seen[g]
                s += params.group_scores[g]
                group_seen[g] = true
            end
        else
            s -= params.gap_penalty
        end
    end

    params.normalize && (s /= length(components))

    return all(group_seen[i] for i in params.required_groups) ? s : -Inf
end

score(a::Acronym, params::ScoreParams) = score(a.components, params)

function score_word(word::String, params::ScoreParams)
    g = params.word_to_group[word]
    return g == 0 ? -params.gap_penalty : params.group_scores[g]
end

struct ScoredAcronymFamily
    family::AcronymFamily
    params::ScoreParams
    best::Acronym
    best_score::Float64

    ScoredAcronymFamily(family, params, best) = new(family, params, best, score(best, params))
    ScoredAcronymFamily(family, params) = new(family, params, best_acronym(family, params)...)
end


abstract type Observable{T} end

"""
    DateObs() <: Observable{Date}

A singleton type representing the "free" date observable.
"""
struct DateObs <: Observable{Date}
end

"""
    AcquisitionDateObs() <: Observable{Date}

The acquisition date of the contract.
"""
struct AcquisitionDateObs <: Observable{Date}
end

"""
    ConstObs(x)

A constant observable
"""
struct ConstObs{T} <: Observable{T}
    val::T
end

obstype(::Observable{T}) where {T} = T
obstype(::Tuple{Observable{T1}}) where {T1} = Tuple{T1}
obstype(::Tuple{Observable{T1},Observable{T2}}) where {T1,T2} = Tuple{T1,T2}

struct LiftObs{F,A,R} <: Observable{R}
    f::F
    a::A
    function LiftObs{F,A,R}(f,a...) where {F,A,R}
        new{F,A,R}(f,a)
    end
end

function LiftObs(f::Function, a::Observable...)
    RR = Base.return_types(f,obstype(a))
    R = length(RR) > 1 ? Any : RR[1]
    LiftObs{typeof(f),typeof(a),R}(f,a...)
end

### Derived observables

# This is a bit of a hack
#  - we could make `at` a function
#  - we could create a separate type which is equivalent, e.g
#
#     immutable AtObs <: Observable{Bool}
#         d::Date
#     end
#

"""
    At(t::Date) <: Observable{Bool}

An observable that is `true` when the date is `t`.
"""
const At = LiftObs{typeof(==),Tuple{DateObs,ConstObs{Date}},Bool}
At(t::Date) = LiftObs(==,DateObs(),ConstObs(t))

"""
    Before(t::Date) <: Observable{Bool}

An observable that is `true` when the date is before or equal to `t`.
"""
const Before = LiftObs{typeof(<=),Tuple{DateObs,ConstObs{Date}},Bool}
Before(t::Date) = LiftObs(<=,DateObs(),ConstObs(t))

@deprecate AtObs At
@deprecate BeforeObs Before


module RepeatedSolvers

abstract Solver
include("direct_solver.jl")
include("jacobi_solver.jl")

end


type PSRacos
    rc::RacosCommon
    evaluation_server_num
    sample_set
    result_set
    asyn_result
    history
    is_finish

    function PSRacos(ncomputer)
        new(RacosCommon(), ncomputer, RemoteChannel(()->Channel(ncomputer)),
        RemoteChannel(()->Channel(ncomputer)), RemoteChannel(()->Channel(1)),
        RemoteChannel(()->Channel(1)), false)
    end
end

function psracos_init_sample_set!(psracos::PSRacos, ub)
    rc = psracos.rc
    classifier = RacosClassification(rc.objective.dim, rc.positive_data,
        rc.negative_data, ub=ub)
    mixed_classification(classifier)
    for i = 1:(psracos.evaluation_server_num)
        if rand(rng, Float64) < rc.parameter.probability
            solution, distinct_flag = distinct_sample_classifier(rc, classifier, data_num=rc.parameter.train_size)
        else
            solution, distinct_flag = distinct_sample(rc, rc.objective.dim)
        end
        # sol_print(solution)
        put!(psracos.sample_set, solution)
    end
end


#eliminates missing values
function cleanpanel(panel::DataFrame, ξs::Vector{Characteristic})
  essentialfields =  [ξ.Fξ for ξ ∈ ξs]

  pushmaybe(s::Symbol) = s ∈ essentialfields ? essentialfields : push!(essentialfields, s)
  pushmaybe(::Nothing) = essentialfields
  for ξ ∈ ξs
    if ξ.ws.specname === PARAM[:caraname]
      pushmaybe(PARAM[:iivol])
    elseif ξ.ws.specname === PARAM[:sortedname]
      pushmaybe(PARAM[:sorteddefaultconditioning])
    elseif ξ.ws.specname === PARAM[:doublesortedname]
      pushmaybe(PARAM[:doublesorteddefaultconditioning])
      pushmaybe(PARAM[:doublesorteddefaultsecondary])
    elseif ξ.ws.specname === PARAM[:zname]
      continue
    elseif ξ.ws.specname === PARAM[:fixedname]
      continue
    else
      @assert false
    end
  end

  for f ∈ essentialfields
    panel[!, f] .= finiteormissing.(panel[!, f])
  end

  panel = panel[completecases(panel, essentialfields), :]

  return panel
end

function basiccharacteristics!(panel::DataFrame;
  strategies=error("strategies is required"),
  strategyprefix::Symbol = error("strategyprefix is required"),
  weightspec::Symbol=error("weightspec is required"),
  preconditionertype::Symbol=error("preconditioner is required"))

  #form the weight spec
  local preconditioner
  if preconditionertype === :quantiledemean
    preconditioner = demeanξ ∘ quantiles
  elseif preconditionertype === :quantile
    preconditioner = quantiles
  elseif preconditionertype === :identity
    preconditioner = (x)->x
  else
    error("unrecognized preconditionertype $preconditionertype")
  end

  local ws::WeightSpec
  if weightspec === :z
    ws = zweightspec(; preconditioner)
  elseif weightspec === :cara
    ws = caraweightspec(; preconditioner)
  elseif weightspec === :sorted
    ws = sortedweightspec(; preconditioner)
  elseif weightspec === :sorted2
    ws = doublesortedweightspec(; preconditioner)
  elseif weightspec === :fixed
    ws = fixedweightspec(; preconditioner)
  else
    error("unrecognized weightspec $weightspec")
  end

  Fstrategies = Symbol.(strategyprefix, strategies)

  ξs = (s->Characteristic(ws, s)).(Fstrategies)

  return ξs
end
basiccharacteristics!(panel::DataFrame, ::Val{:momentum};
  strategies=PARAM[:momentumstrategiesused],
  strategyprefix::Symbol = PARAM[:momentumstrategyprefix],
  preconditionertype::Symbol = PARAM[:momentumpreconditionertype],
  weightspec::Symbol=PARAM[:momentumweightspec]) = basiccharacteristics!(panel;
    strategies, strategyprefix, preconditionertype, weightspec)

basiccharacteristics!(panel::DataFrame ,::Val{:placebo};
  strategies=PARAM[:placebostrategiesused],
  strategyprefix::Symbol = Symbol(),
  preconditionertype::Symbol = PARAM[:placebopreconditionertype],
  weightspec::Symbol=PARAM[:placeboweightspec]) = basiccharacteristics!(panel;
    strategies, strategyprefix, preconditionertype, weightspec)

basiccharacteristics!(panel::DataFrame, ::Val{:fundamental};
  strategies=PARAM[:fundamentalstrategiesused],
  strategyprefix::Symbol = PARAM[:fundamentalstrategyprefix],
  preconditionertype::Symbol = PARAM[:fundamentalpreconditionertype],
  weightspec::Symbol=PARAM[:fundamentalweightspec]) = basiccharacteristics!(panel;
    strategies, strategyprefix, preconditionertype, weightspec)

basiccharacteristics!(panel::DataFrame, ::Val{:fixed};
  strategies=PARAM[:fixedstrategiesused],
  strategyprefix::Symbol = PARAM[:fixedstrategyprefix],
  preconditionertype::Symbol = :identity,
  weightspec::Symbol=:fixed) = basiccharacteristics!(panel;
    strategies, strategyprefix, preconditionertype, weightspec)

#####################

#forms a measure from a combination of different characteristic categories
#this is the main function for forming the weights and measure spec,
#though we can make others as needed with the measuretype parameter
function measure!(panel::DataFrame, ::Val{:combinedbasic};
  strategycategories::Vector{Symbol} = PARAM[:combinedstrategycategories])

  ξs = reduce(vcat, (C->basiccharacteristics!(panel, Val(C))).(strategycategories))

  panel = cleanpanel(panel, ξs)

  #assign weights
  (ξ->assignweights!(panel, ξ)).(ξs)
  #error("Got through it! make sure all the testing code is reverted")

  #form the measure
  ms::MeasureSpec = MeasureSpec(ξs...)

  return (panel, ms)
end

#a simple version for grabbing the MeasureSpec
function MeasureSpec(::Val{:combinedbasic};
  strategycategories::Vector{Symbol} = PARAM[:combinedstrategycategories])

  ξs = reduce(vcat, (C->basiccharacteristics!(panel, Val(C))).(strategycategories))
  return MeasureSpec(ξs...)
end

##########################################
#Simulated data from excel
#replaces actual data with simulated data
function measure!(::Any, ::Val{:testsimulated};
  Fxis = [:xi1,:xi2],
  testpath::String = PARAM[:testpath],
  testdataname::String = PARAM[:testdataname],
  testdateformat=PARAM[:wrdsdateformat])

  #read in the test dataset
  panel = CSV.File("$testpath\\$testdataname.csv") |> DataFrame
  panel.date = (s->Dates.Date("$s", testdateformat)).(panel.date)
  sort!(panel, [:permno,:date])

  panel.Wdvol = panel.dvol #do not winsorize since we are trying to match excel
  #differencewithin2!(panel, [:Wdvol], :permno, date=:date)

  wsz::WeightSpec = zweightspec(preconditioner=demeanξ)
  #ξs = (Fxi->Characteristic(wsz, Symbol(:x_,Fxi))).(Fxis)
  ξs = (Fxi->Characteristic(wsz, Fxi)).(Fxis)
  (ξ->assignweights!(panel, ξ)).(ξs)
  ms::MeasureSpec = MeasureSpec(ξs...)

  return (panel, ms)
end


struct DPM_MCMC_Chain
    gamma::Vector{Vector{Bool}}
    f::Vector{Vector{Float64}}
end


function dir_to_idx(dir::Symbol)
    if dir === :x
        1
    elseif dir === :y
        2
    elseif dir === :z
        3
    else
        0
    end
end

dir_to_idx(i::Int) = i

getdomain(g::AbstractGrid) = getfield(g, :grid)
getdomain(f::AbstractPICDataStructure) = getfield(f, :grid)
unwrapdata(f::AbstractPICDataStructure) = getfield(f, :data)

function newstruct(f::AbstractPICDataStructure, data, grid; name=:same)
    name = name == :same ? nameof(f) : name
    parameterless_type(f)(data, grid, name)
end

Base.nameof(f::AbstractPICDataStructure) = getfield(f, :name)

"""
    axisnames(f::AbstractPICDataStructure; include_units=true)

Get the names of the axis of the grid corresponding to the input data structure `f`.
In the case of Unitful quantities, they can be excluded by setting `include_units` to `false`.
"""
axisnames(f::AbstractPICDataStructure; include_units=true) = axisnames(getdomain(f); include_units)

function unwrap(f)
    _f = hasunit(f) ? ustrip(f) : f

    grid = getdomain(_f)
    data = unwrapdata(_f)

    return grid, data
end

expandgrid(f::Observable{T}) where T = expandgrid(domain_discretization(T), f)

function expandgrid(::LatticeGrid, f)
    dirs = propertynames(getdomain(f[]))
    map(dirs) do dir
        lift(f) do val_f
            g = getdomain(val_f)
            getproperty(g, dir)
        end
    end
end

function expandgrid(::ParticleGrid, f)
    @lift getdomain($f)
end

@inline function apply_reinterpret(f, data)
    T = recursive_unitless_bottom_eltype(data)
    data_units = recursive_bottom_unit(data)
    raw_data = reinterpret(T, data)
    resized_data = f(raw_data)
    U = typeof(one(T) * data_units)
    reinterpret(U, resized_data)
end

function unwrap(f::Observable)
    _f = @lift hasunit($f) ? ustrip($f) : $f

    grid = expandgrid(_f)
    data = @lift unwrapdata($_f)

    return grid, data
end

dimensionality(::AbstractGrid{N}) where N = N
dimensionality(::Type{<:AbstractGrid{N}}) where N = N
dimensionality(::AbstractPICDataStructure{T,N,G}) where {T,N,G} = dimensionality(G)
dimensionality(::Type{<:AbstractPICDataStructure{T,N,G}}) where {T,N,G} = dimensionality(G)
# use Tuple{T, Vararg{T,N}} since NTuple{N,T} can have N==0
dimensionality(::Type{<:NamedTuple{Names, Tuple{T, Vararg{T,N}}}}) where {Names, N, T} = N + 1

function mapgrid(f, grid::AbstractAxisGrid)
    map(f, Iterators.product(grid...))
end

function mapgrid(f, grid::ParticlePositions)
    map(f, zip(grid...))
end

mapgrid(f, field::AbstractPICDataStructure) = mapgrid(f, getdomain(field))

function scalarfield(f, grid; name="")
    data = mapgrid(f, grid)
    ScalarField(data, grid, name)
end

function scalarvariable(f, grid; name="")
    data = mapgrid(f, grid)
    ScalarVariable(data, grid, name)
end

function vectorfield(f, grid; name="")
    data = mapgrid(f, grid)
    VectorField(data, grid; name)
end


#  git	c1ed2e+	abb2cfe	e53b6d4
#  with	awsgpu	cpu	gpu	cpu	af	kn	kn+gc1	kn+gc2	kn+gc3
#1 mul	0.67	0.95	0.56    0.94	0.56	0.56	0.56	0.56	0.56
#2 bias	0.71	1.05	0.59    1.05	0.56	0.59	0.59	0.59	0.59
#3 max	0.75	1.34	0.62    1.34	0.56	0.63	0.62	0.62	0.62
#4 mul	0.81	1.43	0.75    1.44	0.74	0.75	0.75	0.75	0.75
#5 bias	0.85	1.48	0.78    1.48	0.75	0.79	0.78	0.78	0.78
#6 sub	0.89	1.49	0.82    1.49	0.81	0.82	0.81	0.81	0.82
#7 sq	0.92	1.62	0.85    1.62	0.93	0.85	0.84	0.84	0.85
#8 sum	1.21	1.63	1.06±01 1.62	1.22	1.19	1.07	1.08	1.07
#9 forw	1.51	1.96±04	1.18±02 2.47	2.60	2.25	1.67	1.46	1.68
#A grad	2.89	4.40±12	2.10±22	5.52	6.53	5.86	3.52	3.62	3.30
#
# (*) timeall(weights(), weights(64), data(), 10)
# (*) af results with gc_enable=false and sync()
# (*) kn uses `similar`, +gc1 runs tmpfree every epoch, +gc2 runs tmpfree every iteration (minibatch), +gc3 uses KnetArray.
# AF: The forw records arrays preventing their reuse?
# AF: They are merging consecutive ops in one kernel, which breaks down with forw?

using AutoGrad, GZip, Compat
using AutoGrad: forward_pass

fun = []

push!(fun,(w,x,y)->w[1]*x)
push!(fun,(w,x,y)->w[1]*x.+w[2])
push!(fun,(w,x,y)->max(0,w[1]*x.+w[2]))
push!(fun,(w,x,y)->w[3]*max(0,w[1]*x.+w[2]))
push!(fun,(w,x,y)->w[3]*max(0,w[1]*x.+w[2]).+w[4])
push!(fun,(w,x,y)->((w[3]*max(0,w[1]*x.+w[2]).+w[4])-y))
push!(fun,(w,x,y)->(((w[3]*max(0,w[1]*x.+w[2]).+w[4])-y).^2))
fun1 = (w,x,y)->sum(((w[3]*max(0,w[1]*x.+w[2]).+w[4])-y).^2)
push!(fun, fun1)
push!(fun,(w,x,y)->forward_pass(fun1,(w,x,y),(),1))
push!(fun,grad(fun1))

function timeall(w=w2,d=d0,t=10)
    for i=1:length(fun)
        printfun(fun[i])
        for j=1:3
            sleep(2)
            @time loop(fun[i],w,d,t)
        end
    end
end

function loop(f,w,d,t)
    for i in 1:t
        for (x,y) in d
            f(w,x,y)
        end
    end
end

function weights(h...; seed=nothing)
    seed==nothing || srand(seed)
    w = Array{Float32}[]
    x = 28*28
    for y in [h..., 10]
        push!(w, convert(Array{Float32}, 0.1*randn(y,x)))
        push!(w, zeros(Float32,y))
        x = y
    end
    return w
end

function data()
    info("Loading data...")
    xshape(a)=reshape(a./255f0,784,div(length(a),784))
    yshape(a)=(a[a.==0]=10; full(sparse(convert(Vector{Int},a),1:length(a),1f0)))
    xtrn = xshape(gzload("train-images-idx3-ubyte.gz")[17:end])
    ytrn = yshape(gzload("train-labels-idx1-ubyte.gz")[9:end])
    #xtst = xshape(gzload("t10k-images-idx3-ubyte.gz")[17:end])
    #ytst = yshape(gzload("t10k-labels-idx1-ubyte.gz")[9:end])
    batch(xtrn,ytrn,100)
end

function gzload(file; path=joinpath(AutoGrad.datapath,file), url="http://yann.lecun.com/exdb/mnist/$file")
    isfile(path) || download(url, path)
    f = gzopen(path)
    a = @compat read(f)
    close(f)
    return(a)
end

function batch(x, y, batchsize)
    data = Any[]
    nx = size(x,2)
    for i=1:batchsize:nx
        j=min(i+batchsize-1,nx)
        push!(data, (x[:,i:j], y[:,i:j]))
    end
    return data
end

function printfun(x)
    if isdefined(x,:code)
        println(Base.uncompressed_ast(x.code).args[3].args[2].args[1])
    else
        println(x)
    end
end

if !isdefined(:d0)
    d0 = data()
    w1 = weights(seed=1)
    w2 = weights(64;seed=1)
end

:ok

# julia> timeall()
# (Main.getindex)(w,1) * x
#   0.956369 seconds (30.00 k allocations: 147.400 MB, 0.97% gc time)
#   0.947161 seconds (30.00 k allocations: 147.400 MB, 0.78% gc time)
#   0.947129 seconds (30.00 k allocations: 147.400 MB, 0.66% gc time)
# (Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)
#   1.055720 seconds (144.00 k allocations: 297.913 MB, 1.41% gc time)
#   1.054730 seconds (144.00 k allocations: 297.913 MB, 1.41% gc time)
#   1.054276 seconds (144.00 k allocations: 297.913 MB, 1.31% gc time)
# (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2))
#   1.356736 seconds (168.03 k allocations: 445.315 MB, 1.63% gc time)
#   1.353720 seconds (168.00 k allocations: 445.313 MB, 1.55% gc time)
#   1.353312 seconds (168.00 k allocations: 445.313 MB, 1.56% gc time)
# (Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2))
#   1.443865 seconds (180.04 k allocations: 468.844 MB, 1.62% gc time)
#   1.440977 seconds (180.00 k allocations: 468.842 MB, 1.56% gc time)
#   1.441619 seconds (180.00 k allocations: 468.842 MB, 1.63% gc time)
# (Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)
#   1.486593 seconds (288.04 k allocations: 495.852 MB, 1.63% gc time)
#   1.487364 seconds (288.00 k allocations: 495.850 MB, 1.69% gc time)
#   1.485600 seconds (288.00 k allocations: 495.850 MB, 1.69% gc time)
# ((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y
#   1.498100 seconds (300.04 k allocations: 519.382 MB, 1.70% gc time)
#   1.498842 seconds (300.00 k allocations: 519.379 MB, 1.78% gc time)
#   1.497004 seconds (300.00 k allocations: 519.379 MB, 1.78% gc time)
# (((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y) .^ 2
#   1.634301 seconds (318.04 k allocations: 543.277 MB, 1.73% gc time)
#   1.631012 seconds (318.00 k allocations: 543.274 MB, 1.66% gc time)
#   1.632553 seconds (318.00 k allocations: 543.274 MB, 1.96% gc time)
# (Main.sum)((((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y) .^ 2)
#   1.636876 seconds (324.00 k allocations: 543.365 MB, 1.74% gc time)
#   1.635672 seconds (324.00 k allocations: 543.365 MB, 1.74% gc time)
#   1.636505 seconds (324.00 k allocations: 543.365 MB, 1.74% gc time)
# (Main.forward_pass)(Main.fun1,(top(tuple))(w,x,y),(top(tuple))(),1)
#   2.109984 seconds (1.45 M allocations: 601.044 MB, 1.89% gc time)
#   2.110840 seconds (1.45 M allocations: 601.044 MB, 1.91% gc time)
#   2.109753 seconds (1.45 M allocations: 601.044 MB, 1.83% gc time)
# gradfun
#   4.794647 seconds (3.39 M allocations: 2.200 GB, 2.58% gc time)
#   4.788467 seconds (3.39 M allocations: 2.200 GB, 2.54% gc time)
#   4.790467 seconds (3.39 M allocations: 2.200 GB, 2.57% gc time)

# julia> include(Pkg.dir("Knet/test/profile_kn.jl"))
# before d0kn,w2kn (4934356992,:cuda_ptrs,0)
# after d0kn,w2kn (4720431104,(4000,600,0),(200704,1,0),(2560,1,0),(40,1,0),(256,1,0),(313600,600,0),:cuda_ptrs,0)
# 1(Main.getindex)(w,1) * x
#   0.615343 seconds (278.52 k allocations: 10.871 MB)
#   0.559348 seconds (222.00 k allocations: 8.331 MB)
#   0.559312 seconds (222.00 k allocations: 8.331 MB)
# 2(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)
#   1.278285 seconds (374.67 k allocations: 14.424 MB)
#   0.593602 seconds (330.00 k allocations: 12.268 MB)
#   0.592977 seconds (312.00 k allocations: 11.627 MB)
# 3(Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2))
#   0.632009 seconds (371.78 k allocations: 13.691 MB)
#   0.623921 seconds (348.00 k allocations: 12.817 MB)
#   0.624452 seconds (366.00 k allocations: 13.458 MB)
# 4(Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2))
#   0.755348 seconds (528.04 k allocations: 20.327 MB)
#   0.752531 seconds (510.00 k allocations: 19.684 MB)
#   0.752733 seconds (528.00 k allocations: 20.325 MB)
# 5(Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)
#   0.787278 seconds (618.04 k allocations: 23.623 MB)
#   0.784750 seconds (618.00 k allocations: 23.621 MB)
#   0.785054 seconds (618.00 k allocations: 23.621 MB)
# 6((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y
#   0.852492 seconds (705.88 k allocations: 27.000 MB)
#   0.815621 seconds (654.00 k allocations: 24.811 MB)
#   0.815684 seconds (654.00 k allocations: 24.811 MB)
# 7(((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y) .^ 2
#   0.853973 seconds (713.80 k allocations: 26.875 MB)
#   0.845969 seconds (690.00 k allocations: 26.001 MB)
#   0.846093 seconds (690.00 k allocations: 26.001 MB)
# 8(Main.sum)((((Main.getindex)(w,3) * (Main.max)(0,(Main.getindex)(w,1) * x .+ (Main.getindex)(w,2)) .+ (Main.getindex)(w,4)) - y) .^ 2)
#   1.065069 seconds (697.55 k allocations: 26.163 MB)
#   1.059029 seconds (696.00 k allocations: 26.093 MB)
#   1.058951 seconds (696.00 k allocations: 26.093 MB)
# 9(Main.forward_pass)(Main.fun1,(top(tuple))(w,x,y),(top(tuple))(),1)
#   1.606405 seconds (2.22 M allocations: 101.994 MB)
#   1.270358 seconds (1.90 M allocations: 87.799 MB)
#   1.270771 seconds (1.90 M allocations: 87.799 MB)
# 10gradfun
#   4.155291 seconds (4.51 M allocations: 188.382 MB)
#   2.494650 seconds (4.09 M allocations: 171.112 MB)
#   2.528842 seconds (4.09 M allocations: 171.112 MB)


# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
    _UnsafeVectorView(offset, len, ptr)

Lightweight unsafe view for vectors.

## Motivation

`_UnsafeVectorView` is needed as an allocation-free equivalent of `view`. Other
alternatives, like `view(x, 1:len)` or a struct like
```
struct _SafeView{T}
    x::Vector{T}
    len::Int
end
```
will allocate so that `x` can be tracked by Julia's GC.

`_UnsafeVectorView` relies on the fact that the use-cases of `_UnsafeVectorView`
only temporarily wrap a long-lived vector like `d.jac_storage` so that we don't
have to worry about the GC removing `d.jac_storage` while `_UnsafeVectorView`
exists. This lets us use a `Ptr{T}` and create a struct that is `isbitstype` and
therefore does not allocate.

## Example

Instead of `view(x, 1:10)`, use `_UnsafeVectorView(0, 10, pointer(x))`.

## Unsafe behavior

`_UnsafeVectorView` is unsafe because it assumes that the vector `x` that the
pointer `ptr` refers to remains valid during the usage of `_UnsafeVectorView`.
"""
struct _UnsafeVectorView{T} <: DenseVector{T}
    offset::Int
    len::Int
    ptr::Ptr{T}
end

Base.getindex(x::_UnsafeVectorView, i) = unsafe_load(x.ptr, i + x.offset)

function Base.setindex!(x::_UnsafeVectorView, value, i)
    unsafe_store!(x.ptr, value, i + x.offset)
    return value
end

Base.length(v::_UnsafeVectorView) = v.len

Base.size(v::_UnsafeVectorView) = (v.len,)

"""
    _UnsafeVectorView(x::Vector, N::Int)

Create a new [`_UnsafeVectorView`](@ref) from `x`, and resize `x` if needed to
ensure it has a length of at least `N`.

## Unsafe behavior

In addition to the usafe behavior of `_UnsafeVectorView`, this constructor is
additionally unsafe because it may resize `x`. Only call it if you are sure that
the usage of `_UnsafeVectorView(x, N)` is short-lived, and that there are no
other views to `x` while the returned value is within scope.
"""
function _UnsafeVectorView(x::Vector, N::Int)
    if length(x) < N
        resize!(x, N)
    end
    return _UnsafeVectorView(0, N, pointer(x))
end

"""
    _UnsafeLowerTriangularMatrixView(x, N)

Lightweight unsafe view that converts a vector `x` into the lower-triangular
component of a symmetric `N`-by-`N` matrix.

## Motivation

`_UnsafeLowerTriangularMatrixView` is needed as an allocation-free equivalent of
`view`. Other alternatives, like `reshape(view(x, 1:N^2), N, N)` or a struct
like
```julia
struct _SafeView{T}
    x::Vector{T}
    len::Int
end
```
will allocate so that `x` can be tracked by Julia's GC.
`_UnsafeLowerTriangularMatrixView` relies on the fact that the use-cases of
`_UnsafeLowerTriangularMatrixView` only temporarily wrap a long-lived vector
like `d.jac_storage` so that we don't have to worry about the GC removing
`d.jac_storage` while `_UnsafeLowerTriangularMatrixView` exists. This lets us
use a `Ptr{T}` and create a struct that is `isbitstype` and therefore does not
allocate.

## Unsafe behavior

`_UnsafeLowerTriangularMatrixView` is unsafe because it assumes that the vector
`x` remains valid during the usage of `_UnsafeLowerTriangularMatrixView`.
"""
struct _UnsafeLowerTriangularMatrixView <: AbstractMatrix{Float64}
    N::Int
    ptr::Ptr{Float64}
end

Base.size(x::_UnsafeLowerTriangularMatrixView) = (x.N, x.N)

function _linear_index(row, col)
    if row < col
        error("Unable to access upper-triangular component: ($row, $col)")
    end
    return div((row - 1) * row, 2) + col
end

function Base.getindex(x::_UnsafeLowerTriangularMatrixView, i, j)
    return unsafe_load(x.ptr, _linear_index(i, j))
end

function Base.setindex!(x::_UnsafeLowerTriangularMatrixView, value, i, j)
    unsafe_store!(x.ptr, value, _linear_index(i, j))
    return value
end

"""
    _UnsafeLowerTriangularMatrixView(x::Vector{Float64}, N::Int)

Create a new [`_UnsafeLowerTriangularMatrixView`](@ref) from `x`, zero the
elements in `x`, and resize `x` if needed to ensure it has a length of at least
`N * (N + 1) / 2`.

## Unsafe behavior

In addition to the usafe behavior of `_UnsafeLowerTriangularMatrixView`, this
constructor is additionally unsafe because it may resize `x`. Only call it if
you are sure that the usage of `_UnsafeLowerTriangularMatrixView(x, N)` is
short-lived, and that there are no other views to `x` while the returned value
is within scope.
"""
function _UnsafeLowerTriangularMatrixView(x::Vector{Float64}, N::Int)
    z = div(N * (N + 1), 2)
    if length(x) < z
        resize!(x, z)
    end
    for i in 1:z
        x[i] = 0.0
    end
    return _UnsafeLowerTriangularMatrixView(N, pointer(x))
end

function _reinterpret_unsafe(::Type{T}, x::Vector{R}) where {T,R}
    # how many T's fit into x?
    @assert isbitstype(T) && isbitstype(R)
    len = length(x) * sizeof(R)
    p = reinterpret(Ptr{T}, pointer(x))
    return _UnsafeVectorView(0, div(len, sizeof(T)), p)
end


"""
    getparent(model::AbstractModel)

Returns the parent model of `model`.
"""
function getparent end

"""
    getobjectiveconstraints(model::AbstractModel)

Returns an instance of `AbstractFunction` that when called with input `x` returns the objective and constraint values of `model` at `x` in a single vector.
"""
function getobjectiveconstraints end

"""
    getobjective(model::AbstractModel)

Returns a function that when called with input `x` returns the objective value of `model` at `x`.
"""
function getobjective end

"""
    getineqconstraint(model::AbstractModel, i::Integer)

Returns a function that when called with input `x` returns the constraint violation value of the `i`^th inequality constraint of `model` at `x`.
"""
function getineqconstraint end

"""
    geteqconstraint(model::AbstractModel, i::Integer)

Returns a function that when called with input `x` returns the constraint violation value of the `i`^th equality constraint of `model` at `x`.
"""
function geteqconstraint end

"""
    getineqconstraints(model::AbstractModel)

Returns a function that when called with input `x` returns the constraint violation values of all the inequality constraints of `model` at `x`.
"""
function getineqconstraints end

"""
    geteqconstraints(model::AbstractModel)

Returns a function that when called with input `x` returns the constraint violation values of all the equality constraints of `model` at `x`.
"""
function geteqconstraints end

"""
    get_objective_multiple(model::AbstractModel)

Returns the factor by which the objective of `model` was scaled. This is 1 by default.
"""
function get_objective_multiple end

"""
    set_objective_multiple!(model::AbstractModel, multiple::Number)

Set the objective's scaling factor of `model` to `multiple`.
"""
function set_objective_multiple! end

"""
    getnconstraints(model::AbstractModel)

Returns the total number of constraints in `model`.
"""
function getnconstraints end

"""
    getnineqconstraints(model::AbstractModel)

Returns the number of inequality constraints in `model`.
"""
function getnineqconstraints end

"""
    getneqconstraints(model::AbstractModel)

Returns the number of equality constraints in `model`.
"""
function getneqconstraints end

"""
    getnvars(model::AbstractModel)

Returns the number of variables in `model`.
"""
function getnvars end

"""
    getmin(model::AbstractModel)

Returns the variables' lower bounds of `model`.
"""
function getmin end

"""
    getmax(model::AbstractModel)

Returns the variables' upper bounds of `model`.
"""
function getmax end

"""
    setmin!(model::AbstractModel, min::Union{Number, AbstractVector})
    setmin!(model::AbstractModel, i::Integer, min::Number)

Sets the variables' lower bounds in `model` to `min`. Or if `i` is given, it sets the lower bound of the `i`^th variable in `model` to `min`.
"""
function setmin! end

"""
    setmax!(model::AbstractModel, max::Union{Number, AbstractVector})
    setmax!(model::AbstractModel, i::Integer, max::Number)

Sets the variables' upper bound in `model` to `max`. Or if `i` is given, it sets the upper bound of the `i`^th variable in `model` to `max`.
"""
function setmax! end

"""
    setbox!(model::AbstractModel, min::Union{Number, AbstractVector}, max::Union{Number, AbstractVector})
    setbox!(model::AbstractModel, i::Integer, min::Number, max::Number)

Sets the variables' lower and upper bounds in `model` to `min` and `max` respectively. Or if `i` is given, it sets the lower and upper bounds of the `i`^th variable in `model` to `min` and `max` respectively.
"""
function setbox! end

"""
    addvar!(model::AbstractModel, lb::Number, ub::Number)

Adds a new variable to `model` with lower and upper bounds `lb` and `ub`.
"""
function addvar! end

"""
    add_ineq_constraint!(model::AbstractModel, f::IneqConstraint)
    add_ineq_constraint!(model::AbstractModel, fs::Vector{<:IneqConstraint})

Adds a new constraint `f` or multiple new constraints `fs` to `model`.
"""
function add_ineq_constraint! end

"""
    init!(model::AbstractModel)

Initializes the model.
"""
function init! end


using Test
using StatsBase
using LearnBase
using MLLabelUtils
using MLDataPattern
using ReferenceTests
using Random
using SparseArrays

# --------------------------------------------------------------------
# create some test data

showcompact(io, x) = show(IOContext(io, :compact => true), x)

Random.seed!(1335)
X = rand(4, 150)
y = repeat(["setosa","versicolor","virginica"], inner = 50)
Y = permutedims(hcat(y,y), [2,1])
Yt = hcat(y,y)
yt = Y[1:1,:]
Xv = view(X,:,:)
yv = view(y,:)
XX = rand(20,30,150)
XXX = rand(3,20,30,150)
vars = (X, Xv, yv, XX, XXX, y)
tuples = ((X,y), (X,Y), (XX,X,y), (XXX,XX,X,y))
Xs = sprand(10,150,.5)
ys = sprand(150,.5)
# to compare if obs match
X1 = hcat((1:150 for i = 1:10)...)'
Y1 = collect(1:150)

struct EmptyType end

struct CustomType end
LearnBase.nobs(::CustomType) = 100
LearnBase.getobs(::CustomType, i::Int) = i
LearnBase.getobs(::CustomType, i::AbstractVector) = collect(i)
LearnBase.gettargets(::CustomType, i::Int) = "obs $i"
LearnBase.gettargets(::CustomType, i::AbstractVector) = "batch $i"

struct CustomStorage end
struct CustomObs{T}; data::T end
LearnBase.nobs(::CustomStorage) = 2
LearnBase.getobs(::CustomStorage, i) = CustomObs(i)
LearnBase.gettarget(str::String, obs::CustomObs) = "$str - obs $(obs.data)"
LearnBase.gettarget(obs::CustomObs) = "obs $(obs.data)"

struct ObsDimTriggeredException <: Exception end
struct MetaDataStorage end
LearnBase.nobs(::MetaDataStorage) = 3
LearnBase.getobs(::MetaDataStorage, i) = throw(ObsDimTriggeredException())
LearnBase.gettargets(::MetaDataStorage) = "full"
LearnBase.gettargets(::MetaDataStorage, i::Int) = "obs $i"
LearnBase.gettargets(::MetaDataStorage, i::AbstractVector) = "batch $i"

# --------------------------------------------------------------------

tests = [
    "tst_container.jl"
    "tst_datasubset.jl"
    "tst_randobs.jl"
    "tst_shuffleobs.jl"
    "tst_splitobs.jl"
    "tst_dataview.jl"
    "tst_slidingwindow.jl"
    "tst_targets.jl"
    "tst_stratifiedobs.jl"
    "tst_resample.jl"
    "tst_folds.jl"
    "tst_dataiterator.jl"
]

for t in tests
    @testset "$t" begin
        include(t)
    end
end


function rsu7(in_ch=3, mid_ch=12, out_ch=3)
    rebnconvin = Chain(rebnconv(in_ch,out_ch,1)...)

    rebnconv1 = rebnconv_pool(out_ch, mid_ch, 1)
    rebnconv2 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv3 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv4 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv5 = rebnconv_pool(mid_ch, mid_ch, 1)

    rebnconv6 = Chain(rebnconv(mid_ch,mid_ch,1)...)
    rebnconv7 = Chain(rebnconv(mid_ch,mid_ch,2)...)

    rebnconv6d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv5d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv4d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv3d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv2d = rebnconv_upsample(mid_ch*2,mid_ch,1)

    rebnconv1d = Chain(rebnconv(mid_ch*2,out_ch,1)...)

    layer = x -> begin
        hxin = rebnconvin(x)

        hx1, hx = rebnconv1(hxin)
        hx2, hx = rebnconv2(hx)
        hx3, hx = rebnconv3(hx)
        hx4, hx = rebnconv4(hx)
        hx5, hx = rebnconv5(hx)

        hx6 = rebnconv6(hx)
        hx7 = rebnconv7(hx6)

        hx6dup = rebnconv6d(hx5, hx6, hx7)
        hx5dup = rebnconv5d(hx4, hx5, hx6dup)
        hx4dup = rebnconv4d(hx3, hx4, hx5dup)
        hx3dup = rebnconv3d(hx2, hx3, hx4dup)
        hx2dup = rebnconv6d(hx1, hx2, hx3dup)

        hx1d = rebnconv1d(cat(hx2dup, hx1; dims=3))

        return hx1d + hxin
    end
end

function rsu6(in_ch=3, mid_ch=12, out_ch=3)
    rebnconvin = Chain(rebnconv(in_ch,out_ch,1)...)

    rebnconv1 = rebnconv_pool(out_ch, mid_ch, 1)
    rebnconv2 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv3 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv4 = rebnconv_pool(mid_ch, mid_ch, 1)

    rebnconv5 = Chain(rebnconv(mid_ch,mid_ch,1)...)
    rebnconv6 = Chain(rebnconv(mid_ch,mid_ch,2)...)

    rebnconv5d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv4d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv3d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv2d = rebnconv_upsample(mid_ch*2,mid_ch,1)

    rebnconv1d = Chain(rebnconv(mid_ch*2,out_ch,1)...)

    layer = x -> begin
        hxin = rebnconvin(x)

        hx1, hx = rebnconv1(hxin)
        hx2, hx = rebnconv2(hx)
        hx3, hx = rebnconv3(hx)
        hx4, hx = rebnconv4(hx)

        hx5 = rebnconv5(hx)
        hx6 = rebnconv6(hx5)

        hx5dup = rebnconv5d(hx4, hx5, hx6)
        hx4dup = rebnconv4d(hx3, hx4, hx5dup)
        hx3dup = rebnconv3d(hx2, hx3, hx4dup)
        hx2dup = rebnconv2d(hx1, hx2, hx3dup)

        hx1d = rebnconv1d(cat(hx2dup, hx1; dims=3))

        return hx1d + hxin
    end
end

function rsu5(in_ch=3, mid_ch=12, out_ch=3)
    rebnconvin = Chain(rebnconv(in_ch,out_ch,1)...)

    rebnconv1 = rebnconv_pool(out_ch, mid_ch, 1)
    rebnconv2 = rebnconv_pool(mid_ch, mid_ch, 1)
    rebnconv3 = rebnconv_pool(mid_ch, mid_ch, 1)

    rebnconv4 = Chain(rebnconv(mid_ch,mid_ch,1)...)
    rebnconv5 = Chain(rebnconv(mid_ch,mid_ch,2)...)

    rebnconv4d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv3d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv2d = rebnconv_upsample(mid_ch*2,mid_ch,1)

    rebnconv1d = Chain(rebnconv(mid_ch*2,out_ch,1)...)

    layer = x -> begin
        hxin = rebnconvin(x)

        hx1, hx = rebnconv1(hxin)
        hx2, hx = rebnconv2(hx)
        hx3, hx = rebnconv3(hx)

        hx4 = rebnconv4(hx)
        hx5 = rebnconv5(hx4)

        hx4dup = rebnconv4d(hx3, hx4, hx5)
        hx3dup = rebnconv3d(hx2, hx3, hx4dup)
        hx2dup = rebnconv2d(hx1, hx2, hx3dup)

        hx1d = rebnconv1d(cat(hx2dup, hx1; dims=3))

        return hx1d + hxin
    end
end

function rsu4(in_ch=3, mid_ch=12, out_ch=3)
    rebnconvin = Chain(rebnconv(in_ch,out_ch,1)...)

    rebnconv1 = rebnconv_pool(out_ch, mid_ch, 1)
    rebnconv2 = rebnconv_pool(mid_ch, mid_ch, 1)

    rebnconv3 = Chain(rebnconv(mid_ch,mid_ch,1)...)
    rebnconv4 = Chain(rebnconv(mid_ch,mid_ch,2)...)

    rebnconv3d = rebnconv_upsample(mid_ch*2,mid_ch,1)
    rebnconv2d = rebnconv_upsample(mid_ch*2,mid_ch,1)

    rebnconv1d = Chain(rebnconv(mid_ch*2,out_ch,1)...)

    layer = x -> begin
        hxin = rebnconvin(x)

        hx1, hx = rebnconv1(hxin)
        hx2, hx = rebnconv2(hx)

        hx3 = rebnconv3(hx)
        hx4 = rebnconv4(hx3)

        hx3dup = rebnconv3d(hx2, hx3, hx4)
        hx2dup = rebnconv2d(hx1, hx2, hx3dup)

        hx1d = rebnconv1d(cat(hx2dup, hx1; dims=3))

        return hx1d + hxin
    end
end

function rsu4f(in_ch=3, mid_ch=12, out_ch=3)
    rebnconvin = Chain(rebnconv(in_ch,out_ch,1)...)

    rebnconv1 = Chain(rebnconv(out_ch,mid_ch,1)...)
    rebnconv2 = Chain(rebnconv(mid_ch,mid_ch,2)...)
    rebnconv3 = Chain(rebnconv(mid_ch,mid_ch,4)...)

    rebnconv4 = Chain(rebnconv(mid_ch,mid_ch,8)...)

    rebnconv3d = Chain(rebnconv(mid_ch*2,mid_ch,4)...)
    rebnconv2d = Chain(rebnconv(mid_ch*2,mid_ch,2)...)

    rebnconv1d = Chain(rebnconv(mid_ch*2,out_ch,1)...)

    layer = x -> begin
        hxin = rebnconvin(x)

        hx1 = rebnconv1(hxin)
        hx2 = rebnconv2(hx1)
        hx3 = rebnconv3(hx2)

        hx4 = rebnconv4(hx3)

        hx3d = rebnconv3d(cat(hx4, hx3; dims=3))
        hx2d = rebnconv2d(cat(hx3d, hx2; dims=3))
        hx1d = rebnconv1d(cat(hx2d, hx1; dims=3))

        return hx1d + hxin
    end
end


using Escher
using DataFrames
import eve
function makeFrameCol(col :: DataArray)
  vbox(map(x -> pad(0.5em, string(x)), col)) |> borderwidth(1px) |> borderstyle([left, right, top, bottom], solid)
end
function makeFrameRow(row :: DataFrameRow)
  hbox(map(x -> pad(0.5em, string(x[2])), row))
end
function makeFrameTable(df :: DataFrame)
  #cols = map(x -> pad([left, right], 1em, makeFrameCol(x)), eachcol(df))
  #for k in eachindex(cols)
  #  if iseven(k)
  #    cols[k] = fillcolor("#892", v[k])
  #  else
  #    cols[k] = fillcolor("#859", v[k])
  #  end
  #end
  vbox(map(makeFrameRow, eachrow(df))) |> packlines(center)
  #hbox(cols) |> packitems(spacebetween)
end
key_input = Signal(Key, nokey)

selection = Signal(leftbutton)
function main(window)
  t = eve.importOrders()
  name = names(t)
  name = map(string, name)
  #name = map(plaintext, name)
  #t = vbox("a", "b", "c", "d")
  #v = vbox("f", string(1.0))
  #makeFrameTable(t)
  #tbl.body = map(pad(3em), eachrow(tbl.body))
  #tbl

  tbl = table(t) |> borderwidth(1px) |> borderstyle(solid)
  cont = container(40em, 40em) |> fillcolor("#f1f3f1")

  sub = clickable(cont) >>> selection
  vbox(tbl, cont, selection, sub)
end


module TestUtils

using FoldsCUDA: ithtype
using Test

function test_ithtype()
    for n in 2:5
        T = Union{(Val{i} for i in 1:n)...}
        @test Union{(ithtype(T, Val(i)) for i in 1:n)...} == T
    end
end

end  # module


int main()
{
  printInt(+32 % -10);
  printInt(-32 % -10);
  printInt(+32 % +10);
  printInt(-32 % +10);
  printString("");
  printInt(+38 % -10);
  printInt(-38 % -10);
  printInt(+38 % +10);
  printInt(-38 % +10);
  printString("");
  printDouble(+32.0 % -10.0);
  printDouble(-32.0 % -10.0);
  printDouble(+32.0 % +10.0);
  printDouble(-32.0 % +10.0);
  printString("");
  printDouble(+38.0 % -10.0);
  printDouble(-38.0 % -10.0);
  printDouble(+38.0 % +10.0);
  printDouble(-38.0 % +10.0);
  return 0;
}

/* vim:set ts=2 sts=2 sw=2 et ft=c: */


module PlasmoPlots

using Statistics
using Plasmo
using Plots
using NetworkLayout
using GeometryBasics:Point2f0,Point
using Colors
using LightGraphs

export plot, spy, layout_plot, matrix_plot

include("layout_plot.jl")

include("matrix_plot.jl")

end


module Logs

using Statistics, DelimitedFiles, BeastUtils.MatrixUtils, JLD, CSV, DataFrames

export get_log,
       get_log_match,
       import_log,
    list_cols,
    make_meanmatrix,
    make_meanvector,
    make_diagonal_vector,
    make_vector,
    make_correlationMatrix,
    confidence_intervals,
    HDP_intervals,
    log2corr,
    find_cols,
    get_cols,
    combine_logs,
    condense_logs,
    condense_trees,
    make_log,
    compress_log,
    compress_log!,
    read_time

const DELIM_CHAR = '\t'

function compress_log(log_path::String, compressed_path::String)
    cols, data = get_log(log_path, burnin = 0.0)
    save(compressed_path, "cols", cols, "data", data)
end

function compress_log!(log_path::String, compressed_path::String)
    compress_log(log_path, compressed_path)
    rm(log_path)
end

function uncompress_log(compressed_path::String, log_path::String)
    d = load(compressed_path)
    cols = d["cols"]
    data = d["data"]
    make_log(log_path, data, cols, includes_states = true)
end

function uncompress_log!(compressed_path::String, log_path::String)
    uncompress_log(compressed_path, log_path)
    rm(compressed_path)
end

function uncompress_log!(path::String)
    split_path = split(path, '.')
    ext = last(split_path)
    if ext != "jld"
        error("Compressed file must have `.jld` file extension.")
    end

    split_path[end] = "log"
    uncompress_log!(path, join(split_path, '.'))
end

function make_log(path::String, data::AbstractMatrix{Float64},
            col_labels::Vector{String}; includes_states::Bool = false)
    n, p = size(data)
    if length(col_labels) != p
        error("The number of column labels must be the same as the number of columns.")
    end
    open(path, "w") do f
        if !includes_states
            write(f, "state$DELIM_CHAR")
        end
        write(f, join(col_labels, DELIM_CHAR))
        write(f, '\n')
        for i = 1:n
            if !includes_states
                write(f, "$(i - 1)$DELIM_CHAR")
            end
            write(f, join(data[i, :], DELIM_CHAR))
            write(f, '\n')
        end
    end
end

# function below should eventually deprecate get_log()
function import_log(path::String; burnin::Float64 = 0.0)
    header_row = find_header_row(path)
    all_lines = countlines(path)
    n = all_lines - header_row
    skipto = header_row + Int(round(n * burnin)) + 1

    df = CSV.read(path,
                  DataFrame,
                  header=header_row,
                  skipto = skipto,
                  delim='\t')

    return df
end

function get_log(inpath::String; burnin::Float64 = 0.1)
    if endswith(inpath, ".jld")
        d = load(inpath)
        cols = d["cols"]
        data = d["data"]
        n = size(data, 1)
        n_burn = Int(round(burnin * n))

        return cols, data[(n_burn + 1):end, :]
    else

        col_labels, labels_ind = get_cols_and_ind(inpath)
        p = length(col_labels)
        f = open(inpath)
        m = countlines(f) - labels_ind
        close(f)
        n_burn = Int(round(burnin * m))
        n = m - n_burn
        data = readdlm(inpath, skipstart = labels_ind + n_burn, dims = (n, p))
        return string.(col_labels), data
    end
end

function read_last_line(inpath::String)
    col_labels, labels_ind = get_cols_and_ind(inpath)
    p = length(col_labels)
    f = open(inpath)
    m = countlines(f)

    data = readdlm(inpath, skipstart = m-1, dims = (1, p))
    return string.(col_labels), vec(data)
end

function get_log(inpath::String, cols::Array{Int}; burnin::Float64 = 0.1)
    col_labels, data = get_log(inpath, burnin = burnin)
    return col_labels[cols], data[:, cols]
end

function get_log(inpath::String, header::Union{Regex, String};
                    burnin::Float64 = 0.1)
    Base.depwarn("get_log(path::String, header::$(typeof(header))) is deprecated. " *
                "Use get_log_match(path, header) instead.", :get_log)
    all_cols = get_cols(inpath)
    f, l = find_cols(all_cols, header)
    cols, data = get_log(inpath, collect(f:l), burnin = burnin)
end

function get_log_match(inpath::String, header::Union{Regex, String};
                        burnin::Float64 = 0.1)
    all_cols = get_cols(inpath)
    inds = findall(x -> matches(x, header), all_cols)
    cols, data = get_log(inpath, inds, burnin = burnin)
end

function get_cols(inpath::String)
    if endswith(inpath, ".jld")
        cols = load(inpath, "cols")
        return cols
    else
        cols, ind = get_cols_and_ind(inpath)
        return cols
    end
end

function get_cols_and_ind(inpath::String)
    f = open(inpath)
    col_labels = ""
    ind = 0
    for line in eachline(f)
        ind += 1
        if startswith(line, "state")
            col_labels = line
            break
        end
    end
    close(f)
    cols = split(col_labels)
    return cols, ind
end

function find_header_row(inpath::String)
    counter = 0
    open(inpath, "r") do f
        for line in eachline(inpath)
            counter += 1
            if startswith(line, "state")
                break
            end
        end
    end
    return counter
end

function list_cols(cols::Vector{String})
    p = length(cols)
    for i = 1:p
        println("$i: $(cols[i])")
    end
end

function find_cols(cols::Vector{S}, col::Union{String, Regex}) where S <: AbstractString
    first = 0
    last = 0
    p = length(cols)
    for i = 1:p
        if matches(cols[i], col)
            first = i
            break
        end
    end
    if first == 0
        error("$col not found in column names.")
    end
    for i = p:-1:first
        if matches(cols[i], col)
            last = i
            break
        end
    end
    return first, last
end

function matches(col::S, header::String) where S <: AbstractString
    if startswith(col, header)
        return true
    end
    return false
end

function matches(col::S, header::Regex) where S <: AbstractString
    if occursin(header, col)
        return true
    end
    return false
end

function make_meanmatrix(data::Matrix{T}, cols::Vector{String},
        col::String) where T <: Real

    first, last = find_cols(cols, col)
    return make_meanmatrix(data, first, last)
end

function make_meanmatrix(data::Matrix{T}, first::Int, last::Int) where T <: Real
    t_data = data[:, (first:last)]
    means = mean(t_data, dims = 1)
    q = length(means)
    q = Int(sqrt(q))
    mat = reshape(means, q, q)
    return mat
end

function make_meanmatrix(data::Matrix{T}) where T <: Real
    means = mean(data, dims = 1)
    q = length(means)
    q = Int(sqrt(q))
    mat = reshape(means, q, q)
    return mat
end

function make_meanvector(data::Matrix{T}, first::Int, last::Int) where T <: Real

    means = [mean(data[:, i]) for i in first:last]

    return means
end

function make_diagonal_vector(data::Matrix{T}, first::Int, last::Int) where T <: Real
    q = last - first + 1
    p = Int(sqrt(q))
    n = size(data, 1)
    new_mat = zeros(n, p)
    for i = 1:p
        new_mat[:, i] .= data[:, first + (i - 1) * (p + 1)]
    end
    return new_mat
end

function make_vector(data::Matrix{T}, first::Int, last::Int) where T <: Real
    t_data = data[:, (first:last)]
    return t_data
end

function make_correlationMatrix(X::Matrix{T}) where T <: Real
    n, p = size(X)
    @assert n == p
    C = zeros(n, n)
    for i = 1:n
        for j = 1:n
            C[i, j] = X[i, j] / sqrt(X[i, i] * X[j, j])
        end
    end
    return C
end

function confidence_intervals(d::Matrix{Float64}, p::Int)
    x = d[:, p]
    n = length(x)
    sort!(x)
    bottom = Int(floor(0.025 * n))
    top = Int(ceil(0.975 * n))
    return x[bottom], x[top]
end

function HPD_intervals(d::Matrix{Float64}, p::Int; conf::Float64 = 0.95)
    x = d[:, p]
    return HDP_intervals(x, conf = conf)
end

function HPD_intervals(x::Vector{Float64}; conf::Float64 = 0.95)
    n = length(x)
    sort!(x)
    s = Int(ceil(conf * n))
    smallest = Inf
    smallest_inds = [0, 0]
    for i = 1:(n - s + 1)
        diff = x[i + s - 1] - x[i]
        if diff < smallest
            smallest = diff
            smallest_inds[1] = i
            smallest_inds[2] = i + s - 1
        end
    end
    return x[smallest_inds]
end

function HPD_intervals(d::Matrix{Float64}, ps::Vector{Int}; conf::Float64 = 0.95)
    hdps = zeros(length(ps), 2)
    for i = 1:length(ps)
        hdpi = HDP_intervals(d, ps[i], conf = conf)
        hdps[i, 1] = hdpi[1]
        hdps[i, 2] = hdpi[2]
    end
    return hdps
end

function log2corr(X::Matrix{Float64}; invert::Bool = false)
    n, q = size(X)
    p = Int(sqrt(q))
    M = zeros(p, p)
    new_X = zeros(n, q)
    for i = 1:n
        for j = 1:q
            M[j] = X[i, j]
        end
        if invert
            M = inv(M)
        end
        corrs = cov2corr(M)
        for j = 1:q
            new_X[i, j] = corrs[j]
        end
    end
    return new_X
end

function get_header(path::String)
    f = open(path)
    lines = Vector{String}(undef, 0)
    for line in eachline(f)
        if startswith(line, "state")
            break
        end
        push!(lines, line)
    end
    close(f)
    return lines
end

function combine_logs(paths::Vector{String}, outpath::String;
        force::Bool = false)
    n = length(paths)
    cols = get_cols(paths[1])
    for i = 2:n
        test_cols = get_cols(paths[i])
        @assert test_cols == cols
    end
    lines = get_header(paths[1])
    push!(lines, join(cols, "\t"))
    state = 0
    all_data = Matrix{Float64}(undef, 0, length(cols))
    for i = 1:n
        cols, data = get_log(paths[i], burnin = 0.0)
        m = size(data, 1)
        for i = 1:m
            data[i, 1] += state
        end
        if i != 1
            data = data[2:end, :]
        end
        all_data = [all_data; data]
        state = data[end, 1]
    end
    m = size(all_data, 1)
    string_data = Vector{String}(undef, m)
    for i = 1:m
        string_data[i] = join(all_data[i, :], "\t")
    end
    lines = [lines; string_data]
    lines = join(lines, "\n")
    write(outpath, lines)
end

function writeline(io::IOStream, s::AbstractString)
    write(io, s)
    write(io, "\n")
end

function condense_logfiles(in_path::String, out_path::String;
                       every::Int = 10,
                       is_valid = is_valid_logfile
    )

    counter = 0

    open(in_path, "r") do input
        open(out_path, "w") do output

            for line in eachline(input)
                if is_valid(line)
                    if rem(counter, every) == 0
                        writeline(output, line)
                    end
                    counter += 1
                else
                    writeline(output, line)
                end
            end
        end
    end
end

function is_valid_logfile(line::AbstractString)::Bool
    first_tab = findfirst('\t', line)
    if isnothing(first_tab)
        return false
    end

    sub_line = @view line[1:(first_tab - 1)]

    try parse(Int, sub_line)
        return true
    catch
        return false
    end
end

function is_valid_treefile(line::AbstractString)::Bool
    return startswith(line, "tree STATE_")
end

function condense_logs(in_path::String, out_path::String; every::Int = 10)
    return condense_logfiles(in_path, out_path, every = every, is_valid = is_valid_logfile)
end

function condense_trees(in_path::String, out_path::String; every::Int = 10)
    return condense_logfiles(in_path, out_path, every = every, is_valid = is_valid_treefile)
end

const TIME_DICT = Dict("seconds" => 1.0,
                       "minutes" => 60.0,
                       "hours" => 3600.0,
                       "days" => 86400.0)

function read_time(path::String; unit::String = "seconds")
    t, u = split(read(path, String))
    t = parse(Float64, t)
    return t * TIME_DICT[u] / TIME_DICT[unit]
end

end


module Pedigrees

    export Pedigree, inbreeding, laporder, Ltrans, Tinvt

    include("types.jl")
    include("Tmat.jl")

end #module


module test_lupin_compose

using Test
using Lupin

@test (identity ∘ ≈)(10, 11; atol = 2)

end # module test_lupin_compose


mutable struct StepDate{T,F} <: AbstractStep
    selections::T
    params::Union{Nothing,Vector{Symbol}}
    features::F
    trained::Bool
    skip::Bool
    keep::Bool
    roles::Union{Nothing,Symbol,Vector{Symbol}}
end
function fit!(step::StepDate, df)
    step.params = getselectionkeys(df, step.selections)
    step.trained = true
end

function transform!(s::StepDate, df)
    for k in s.params
        for f in s.features
            df[Symbol(string(k) * "_" * funname(f))] = f.(df[k])
        end
        if !s.keep
            deletecols!(df, k)
        end
    end
    df
end
function transform(s::StepDate, df)
    transform!(s, copy(df))
end

function update!(roles::Dict, step::StepDate)
    for k in step.params
        kroles = whichkeys(roles, k)
        newroles = step.roles === nothing ? kroles : asarray(step.roles)
        if newroles !== nothing
            for newrole in newroles
                !(newrole in keys(roles)) && get!(roles, newrole, Symbol[])
                for f in step.features
                    push!(roles[newrole], Symbol(string(k) * "_" * funname(f)))
                end
            end
        end
        if !step.keep
            if kroles !== nothing
                for role in kroles
                    roles[role] = filter(x -> !(k in x), roles[role])
                end
            end
        end
    end
    roles
end

function step_date!(r::Recipe, s...; features=[year, month, dayofweek], keep::Bool=false, roles=nothing, skip::Bool=false)
    push!(r.steps,
          StepDate([s...],
                   nothing,
                   asarray(features),
                   false,
                   skip,
                   keep,
                   roles))
end
function step_date!(r::Recipe; features=[year, month, dayofweek], keep::Bool=false, roles=nothing, skip=false)
    push!(r.steps,
          StepDate([if_eltype(Date)],
                   nothing,
                   asarray(features),
                   false,
                   skip,
                   keep,
                   roles))
end

function step_datetime!(r::Recipe, s...; features=[year, month, dayofweek, hour, minute, second], keep::Bool=false, roles=nothing, skip::Bool=false)
    push!(r.steps,
          StepDate([s...],
                   nothing,
                   asarray(features),
                   false,
                   skip,
                   keep,
                   roles))
end
function step_datetime!(r::Recipe; features=[year, month, dayofweek, hour, minute, second], keep::Bool=false, roles=nothing, skip::Bool=false)
    push!(r.steps,
          StepDate([if_eltype(DateTime)],
                   nothing,
                   asarray(features),
                   false,
                   skip,
                   keep,
                   roles))
end


module LearningStrategies

import LearnBase: learn!, update!
using LinearAlgebra

export
    LearningStrategy, MetaStrategy, strategy, Offline, InfiniteNothing,
    # LearningStrategies
    Verbose, MaxIter, TimeLimit, Converged, ConvergedTo, IterFunction, Tracer, Breaker,
    # functions
    setup!, update!, hook, finished, cleanup!, learn!

#---------------------------------------------------------------------# LearningStrategy interface
"""
A LearningStrategy should implement at least one of the following methods:

- `setup!(strat, model, data)`
- `cleanup!(strat, model)`
- `hook(strat, model, i)`
- `finished(strat, model, data, i)`
- `update!(model, strat, item)`
"""
abstract type LearningStrategy end

setup!(s::LearningStrategy, model) = nothing
setup!(s::LearningStrategy, model, data) = setup!(s, model)

update!(model, s::LearningStrategy) = nothing
update!(model, s::LearningStrategy, item) = update!(model, s)
update!(model, s::LearningStrategy, i, item) = update!(model, s, item)

hook(s::LearningStrategy, model) = nothing
hook(s::LearningStrategy, model, i) = hook(s, model)
hook(s::LearningStrategy, model, data, i) = hook(s, model, i)

finished(s::LearningStrategy, model) = false
finished(s::LearningStrategy, model, i) = finished(s, model)
finished(s::LearningStrategy, model, data, i) = finished(s, model, i)

cleanup!(s::LearningStrategy, model) = nothing

#-----------------------------------------------------------------------# MetaStrategy
"""
    MetaStrategy(strats::LearningStrategy...)

A collection of learning strategies in a type-stable way.
"""
struct MetaStrategy{T <: Tuple} <: LearningStrategy
    strategies::T
end

MetaStrategy(s::LearningStrategy...) = MetaStrategy(s)

function Base.show(io::IO, s::MetaStrategy)
    print(io, "MetaStrategy")
    for s in s.strategies
        print(io, "\n  > ", s)
    end
end

setup!(ms::MetaStrategy, model, data) = foreach(s -> setup!(s, model, data), ms.strategies)
update!(model, s::MetaStrategy, i, item) = foreach(x -> update!(model, x, i, item), s.strategies)
hook(s::MetaStrategy, model, data, i) = foreach(x -> hook(x, model, data, i), s.strategies)
finished(s::MetaStrategy, model, data, i) = any(x -> finished(x, model, data, i), s.strategies)
cleanup!(s::MetaStrategy, model) = foreach(x -> cleanup!(x, model), s.strategies)

"""
    strategy(s::LearningStrategy...)
    strategy(ms::MetaStrategy, s::LearningStrategy...)

Create a MetaStrategy from LearningStrategies or add a LearningStrategy to an existing MetaStrategy.
"""
strategy(s::LearningStrategy...) = MetaStrategy(s)
strategy(ms::MetaStrategy, s::LearningStrategy...) = MetaStrategy(ms.strategies..., s...)

Base.getindex(ms::MetaStrategy, i) = ms.strategies[i]

#-----------------------------------------------------------------------# learn!
"""
    learn!(model, strategy, data) -> model

Learn a `model` from `data` using `strategy`.
New models/strategies/data types should overload at least one of the following:

- [`setup!`](@ref)
- [`update!`](@ref)
- [`hook`](@ref)
- [`finished`](@ref)
- [`cleanup!`](@ref)

# `learn!` Implementation:

    function learn!(model, s::LearningStrategy, data)
        setup!(s, model[, data])
        for (i, item) in enumerate(data)
            update!(model, s[, i], item)
            hook(s, model[, data], i)
            finished(s, model[, data], i) && break
        end
        cleanup!(s, model)
    end
"""
function learn!(model, s::LearningStrategy, data)
    setup!(s, model, data)
    for (i, item) in enumerate(data)
        update!(model, s, i, item)
        hook(s, model, data, i)
        finished(s, model, data, i) && break
    end
    cleanup!(s, model)
    model
end

#-----------------------------------------------------------------------# InfiniteNothing
# learn without input data... good for minimizing functions
struct InfiniteNothing end
Base.iterate(o::InfiniteNothing) = (nothing, 1)
Base.iterate(o::InfiniteNothing, state) = (state, 1)
learn!(model, s::LearningStrategy) = learn!(model, s, InfiniteNothing())

################################################################################## Strategies

#-----------------------------------------------------------------------# Verbose
"""
    Verbose(s::LearningStrategy)
    Verbose(s::LearningStrategy, io::IO)

Allow the LearningStrategy `s` to print output.

- Will automatically print when `finished(s, args...) == true`.
- Other methods should be overloaded to add printout.
    - For example: `update!(model, v::Verbose{MyStrategy}, item) = ...`
"""
struct Verbose{S <: LearningStrategy} <: LearningStrategy
    strategy::S
end
Base.show(io::IO, v::Verbose) = print(io, "Verbose ", v.strategy)

setup!(v::Verbose, model, data) = setup!(v.strategy, model, data)

update!(model, v::Verbose, i, item) = update!(model, v.strategy, i, item)

hook(v::Verbose, model, data, i) = hook(v.strategy, model, data, i)

function finished(v::Verbose, model, data, i)
    done = finished(v.strategy, model, data, i)
    done && @info("$(v.strategy) finished")
    done
end
function finished(v::Verbose{<:MetaStrategy}, model, data, i)
    done = finished(v.strategy, model, data, i)
    if done
        for s in v.strategy.strategies
            finished(Verbose(s), model, data, i)
        end
    end
    done
end
cleanup!(v::Verbose, model) = cleanup!(v.strategy, model)

#-----------------------------------------------------------------------# MaxIter
"""
    MaxIter(n)

Stop learning after `n` iterations.

Wrapping `MaxIter` with [`Verbose`](@ref):

```jldoctest
julia> learn!(nothing, Verbose(MaxIter(5)), 1:100)
INFO: MaxIter: 1/5
INFO: MaxIter: 2/5
INFO: MaxIter: 3/5
INFO: MaxIter: 4/5
INFO: MaxIter: 5/5
INFO: MaxIter(5) finished
```
"""
struct MaxIter <: LearningStrategy
    n::Int
end
MaxIter() = MaxIter(100)

Base.show(io::IO, s::MaxIter) = print(io, "MaxIter($(s.n))")

function update!(model, v::Verbose{MaxIter}, i, item)
  n = v.strategy.n
  l = length(string(n))
  @info("MaxIter: $(lpad(string(i), l, string(0)))/$n")
end

finished(s::MaxIter, model, data, i) = i >= s.n

#-----------------------------------------------------------------------# TimeLimit
"""
    TimeLimit(s)

Stop learning after `s` seconds.
"""
mutable struct TimeLimit <: LearningStrategy
    secs::Float64
    secs_end::Float64
    TimeLimit(secs::Number) = new(secs)
end
Base.show(io::IO, s::TimeLimit) = print(io, "TimeLimit($(s.secs))")
setup!(strat::TimeLimit, model, data) = (strat.secs_end = time() + strat.secs)
finished(strat::TimeLimit, model, data, i) = time() >= strat.secs_end

#-----------------------------------------------------------------------# Converged
"""
    Converged(f; tol = 1e-6, every = 1)

Stop learning when `norm(f(model) - lastf) ≦ tol`.  `f` must return a `Vector{Float64}`.
"""
mutable struct Converged{F <: Function} <: LearningStrategy
    f::F          # f(model)
    tol::Float64  # normdiff tolerance
    every::Int    # only check every ith iteration
    lastval::Vector{Float64}
end
Converged(f::Function; tol::Number = 1e-6, every::Int = 1) = Converged(f, tol, every, [0.0])

Base.show(io::IO, s::Converged) = print(io, "Converged($(s.f), $(s.tol), $(s.every))")

function setup!(s::Converged, model, item)
    val = s.f(model)
    s.lastval = val isa Number ? [val] : fill(0.0, length(val))
end

function finished(s::Converged, model, data, i)
    val = s.f(model)
    if i > 1 && norm(val .- s.lastval) <= s.tol
        true
    else
        copyto!(s.lastval, val)
        false
    end
end
function finished(v::Verbose{<:Converged}, model, data, i)
    done = finished(v.strategy, model, data, i)
    done && @info("Converged after $i iterations: $(v.strategy.f(model))")
    done
end

#-----------------------------------------------------------------------# ConvergedTo
"""
    ConvergedTo(f, goal; tol=1e-6, every=1)

Stop learning when `‖f(model) - goal‖ ≦ tol`.
"""
struct ConvergedTo{V} <: LearningStrategy
    f::Function   # f(model)
    tol::Float64  # normdiff tolerance
    goal::V       # goal value
    every::Int    # only check every ith iteration
end
function ConvergedTo(f::Function, goal; tol::Number = 1e-6, every::Int = 1)
    ConvergedTo(f, tol, goal, every)
end
Base.show(io::IO, s::ConvergedTo) = print(io, "ConvergedTo($(s.f), $(s.tol), $(s.goal), $(s.every))")
function finished(strat::ConvergedTo, model, data, i)
    val = strat.f(model)
    if norm(val - strat.goal) <= strat.tol
        @info("Converged after $i iterations: $val")
        true
    else
        false
    end
end

#-----------------------------------------------------------------------# IterFunction
"""
    IterFunction(f)
    IterFunction(f, b)
    IterFunction(b, f)

Call `f(model, i)` every `b` iterations at `hook` call.
Default value of `b` is 1.
"""
struct IterFunction{F<:Function} <: LearningStrategy
    f::F
    b::Int
end
IterFunction(f::Function) = IterFunction(f, 1)
IterFunction(b::Int, f::Function) = IterFunction(f, b)

Base.show(io::IO, o::IterFunction) = print(io, "IterFunction($(o.f), $(o.b))")

function hook(s::IterFunction, model, i)
    if mod1(i, s.b) == s.b
        s.f(model, i)
    end
    return
end

#-----------------------------------------------------------------------# Tracer
"""
    Tracer{T}(::Type{T}, f, b=1)

Store `f(model, i)` every `b` iterations.

To extract the data `Tracer` collected, `collect(tracer)`.
Note that this operation will copy.

```jldoctest
julia> t = Tracer(Int, (model, i) -> @show(i))
Tracer(1, #5, 0-element Array{Int64,1})

julia> learn!(nothing, strategy(MaxIter(3), t))
i = 1
i = 2
i = 3

julia> collect(t)
3-element Array{Int64,1}:
 1
 2
 3
```
"""
struct Tracer{S} <: LearningStrategy
    every::Int
    f::Function
    storage::Vector{S}
end

Tracer(::Type{S}, f::Function, every::Int = 1) where {S} = Tracer(every, f, S[])

Base.show(io::IO, s::Tracer) = print(io, "Tracer($(s.every), $(s.f), $(summary(s.storage)))")

function hook(strat::Tracer, model, i)
    if mod1(i, strat.every) == strat.every
        push!(strat.storage, strat.f(model, i))
    end
    return
end

Base.collect(t::Tracer) = collect(t.storage)

#-----------------------------------------------------------------------# Breaker
"""
    Breaker(f)

Stop learning when `f(model, i)` returns true.
"""
struct Breaker{F<:Function} <: LearningStrategy
    f::F
end
Base.show(io::IO, s::Breaker) = print(io, "Breaker($(s.f))")
finished(strat::Breaker, model, data, i)::Bool = strat.f(model, i)

end # module


module MEngProject
include("./LaminartFunc.jl")
include("./LaminartInitFunc.jl")
include("./LaminartEqImfilter.jl")
include("./LaminartEqImfilterGPU_FFT.jl")
include("./LaminartEqImfilterGPU_FIR.jl")
include("./LaminartEqImfilterGPU_IIR.jl")
include("./LaminartEqConv.jl")
include("./LaminartKernels.jl")
include("./Utils.jl")
include("./Parameters.jl")
export LaminartFunc,
    LaminartInitFunc,
    LaminartEqImfilter,
    LaminartEqConv,
    LaminartKernels,
    Utils,
    Parameters,
    LaminartEqImfilterGPU_FFT,
    LaminartEqImfilterGPU_FIR,
    LaminartEqImfilterGPU_IIR
end


# This file is a part of DensityInterface.jl, licensed under the MIT License (MIT).

"""
    DensityInterface.test_density_interface(d, x, ref_logd_at_x; kwargs...)

Test if `d` is compatible with `DensityInterface`.

Tests that [`logdensityof(d, x)`](@ref) equals `ref_logd_at_x` and
that the behavior of [`logdensityof(d)`](@ref),
[`densityof(d, x)`](@ref) and [`densityof(d)`](@ref) is consistent.

Also tests if `logfuncdensity(logdensityof(d))` returns
a density equivalent to `d` in respect to the functions above.

The results of `logdensityof(d, x)` and `densityof(d, x)` are compared to
`ref_logd_at_x` and `exp(ref_logd_at_x)` using `isapprox`. `kwargs...` are
forwarded to `isapprox`.
"""
function test_density_interface(d, x, ref_logd_at_x; kwargs...)
    @testset "test_density_interface: $d with input $x" begin
        ref_d_at_x = exp(ref_logd_at_x)

        @test hasdensity(d) == true
        @test isapprox(logdensityof(d, x), ref_logd_at_x; kwargs...)
        log_f = logdensityof(d)
        @test isapprox(log_f(x), ref_logd_at_x; kwargs...)
        @test isapprox(densityof(d,x), ref_d_at_x; kwargs...)
        @test isapprox(densityof(d)(x), ref_d_at_x; kwargs...)

        d2 = logfuncdensity(log_f)
        @test hasdensity(d2) == true
        @test isapprox(logdensityof(d2, x), ref_logd_at_x; kwargs...)
        log_f2 = logdensityof(d2)
        @test isapprox(log_f2(x), ref_logd_at_x; kwargs...)
        @test isapprox(densityof(d2,x), ref_d_at_x; kwargs...)
        @test isapprox(densityof(d2)(x), ref_d_at_x; kwargs...)
    end
end