function DiffEqBase.__init(
              prob::AbstractDDEProblem{uType,tupType,lType,iip},
              alg::algType,
              timeseries_init=uType[], ts_init=eltype(tupType)[], ks_init=[];
              d_discontinuities=Discontinuity{eltype(tupType)}[],
              dtmax = (prob.constant_lags === nothing || isempty(prob.constant_lags)) ?
              prob.tspan[2]-prob.tspan[1] : eltype(tupType)(7*minimum(prob.constant_lags)),
              dt=zero(eltype(tupType)), saveat=eltype(tupType)[],
              tstops = eltype(tupType)[],
              save_idxs=nothing, save_everystep=isempty(saveat),
              save_start = save_everystep || isempty(saveat) || typeof(saveat) <: Number ? true : prob.tspan[1] in saveat,
              save_end = save_everystep || isempty(saveat) || typeof(saveat) <: Number ? true : prob.tspan[2] in saveat,
              save_on = true,
              dense = save_everystep && !(typeof(alg) <: FunctionMap) && isempty(saveat),
              minimal_solution=true, discontinuity_interp_points::Int=10,
              discontinuity_abstol=eltype(tupType)(1//Int64(10)^12), discontinuity_reltol=0,
              initial_order=agrees(prob.h, prob.u0, prob.p, prob.tspan[1]) ? 1 : 0,
              initialize_integrator = true, initialize_save = true,
              callback=nothing, kwargs... ) where
    {uType,tupType,lType,iip,algType<:AbstractMethodOfStepsAlgorithm}

    tType = eltype(tupType)

    neutral = prob.neutral
    constant_lags = prob.constant_lags
    dependent_lags = prob.dependent_lags
    p = prob.p

    # no fixed-point iterations for constrained algorithms,
    # and thus `dtmax` should match minimal lag
    if isconstrained(alg) && constant_lags !== nothing && !isempty(constant_lags)
        dtmax = min(dtmax, constant_lags...)
    end

    # bootstrap the integrator using an ODE problem, but do not initialize it since
    # ODE solvers only accept functions f(du,u,p,t) or f(u,p,t) without history function
    ode_prob = ODEProblem{iip}(prob.f, prob.u0, prob.tspan, p)
    integrator = init(ode_prob, alg.alg; initialize_integrator=false,
                      dt=one(tType), dtmax=dtmax, kwargs...)

    # check that constant lags match the given time direction
    if constant_lags !== nothing
        for lag in constant_lags
            integrator.tdir * lag > zero(tType) ||
                error("Constant lags and time direction do not match. Exiting.")
        end
    end

    # ensure that ODE integrator satisfies tprev + dt == t
    integrator.dt = zero(integrator.dt)
    integrator.dtcache = zero(integrator.dt)

    # create new solution based on this integrator with an interpolation function of the
    # expected form f(du,u,p,t) or f(u,p,t) which already includes information about the
    # history function of the DDE problem, the current solution of the integrator, and
    # the extrapolation of the integrator for the future
    interp_h = HistoryFunction(prob.h, integrator.sol, integrator)
    if iip
        interp_f = (du,u,p,t) -> prob.f(du,u,interp_h,p,t)
    else
        interp_f = (u,p,t) -> prob.f(u,interp_h,p,t)
    end

    if typeof(alg.alg) <: OrdinaryDiffEq.OrdinaryDiffEqCompositeAlgorithm
        interp_data = OrdinaryDiffEq.CompositeInterpolationData(integrator.sol.interp,
                                                                interp_f)
    else
        interp_data = OrdinaryDiffEq.InterpolationData(integrator.sol.interp,
                                                       interp_f)
    end

    if typeof(alg.alg) <: OrdinaryDiffEq.OrdinaryDiffEqCompositeAlgorithm
        sol = DiffEqBase.build_solution(prob, integrator.sol.alg, integrator.sol.t, integrator.sol.u,
                             dense=integrator.sol.dense, k=integrator.sol.k,
                             interp=interp_data, alg_choice=integrator.sol.alg_choice,
                             calculate_error = false)
    else
        sol = DiffEqBase.build_solution(prob, integrator.sol.alg, integrator.sol.t, integrator.sol.u,
                             dense=integrator.sol.dense, k=integrator.sol.k,
                             interp=interp_data, calculate_error = false)
    end

    # use this improved solution together with the given history function and the integrator
    # to create a problem function of the DDE with all available history information that is
    # of the form f(du,u,p,t) or f(u,p,t) such that ODE algorithms can be applied
    dde_h = HistoryFunction(prob.h, sol, integrator)
    if iip
        dde_f = ODEFunction((du,u,p,t) -> prob.f(du,u,dde_h,p,t),
                            mass_matrix = prob.f.mass_matrix)
    else
        dde_f = ODEFunction((u,p,t) -> prob.f(u,dde_h,p,t),
                            mass_matrix = prob.f.mass_matrix)
    end

    # define absolute tolerance for fixed-point iterations
    if alg.fixedpoint_abstol === nothing
        fixedpoint_abstol_internal = recursivecopy(integrator.opts.abstol)
    else
        fixedpoint_abstol_internal = real.(alg.fixedpoint_abstol)
    end

    # use norm of ODE integrator if no norm for fixed-point iterations is specified
    if alg.fixedpoint_norm === nothing
        fixedpoint_norm = integrator.opts.internalnorm
    end

    # define relative tolerance for fixed-point iterations
    if alg.fixedpoint_reltol === nothing
        fixedpoint_reltol_internal = recursivecopy(integrator.opts.reltol)
    else
        fixedpoint_reltol_internal = real.(alg.fixedpoint_reltol)
    end

    # create separate copies u and uprev, not pointing to integrator.u or integrator.uprev,
    # to cache uprev with correct dimensions and types
    u = recursivecopy(integrator.u)
    uprev = recursivecopy(integrator.uprev)

    # create container for residuals (has to be unitless)
    uEltypeNoUnits = recursive_unitless_eltype(u)
    if typeof(integrator.u) <: AbstractArray
        resid = similar(integrator.u, uEltypeNoUnits)
    else
        resid = one(uEltypeNoUnits)
    end

    # create uprev2 in same way as in OrdinaryDiffEq
    if integrator.uprev === integrator.uprev2
        uprev2 = uprev
    else
        uprev2 = recursivecopy(uprev)
    end

    # check if all indices should be returned
    if save_idxs !== nothing && collect(save_idxs) == collect(1:length(integrator.u))
        save_idxs = nothing # prevents indexing of ODE solution and saves memory
    end

    # new cache with updated u, uprev, uprev2, and function f
    if typeof(alg.alg) <: OrdinaryDiffEq.OrdinaryDiffEqCompositeAlgorithm
        caches = map((x,y) -> build_linked_cache(x, y, u, uprev, uprev2, dde_f,
                                                 prob.tspan[1], dt, p),
                     integrator.cache.caches, alg.alg.algs)
        dde_cache = OrdinaryDiffEq.CompositeCache(caches, alg.alg.choice_function, 1)
    else
        dde_cache = build_linked_cache(integrator.cache, alg.alg, u, uprev, uprev2, dde_f,
                                       prob.tspan[1], dt, p)
    end

    # filter provided discontinuities
    filter!(x -> x.order ≤ alg_maximum_order(alg) + 1, d_discontinuities)

    # retrieve time stops, time points at which solutions is saved, and discontinuities
    tstops_internal, saveat_internal, d_discontinuities_internal =
        tstop_saveat_disc_handling(tstops, saveat, d_discontinuities, integrator.tdir,
                                   prob.tspan, initial_order, alg_maximum_order(alg), constant_lags, tType)

    # create array of tracked discontinuities
    # used to find propagated discontinuities with callbacks and to keep track of all
    # passed discontinuities
    if initial_order ≤ alg_maximum_order(alg)
        tracked_discontinuities = [Discontinuity(prob.tspan[1], initial_order)]
    else
        tracked_discontinuities = Discontinuity{tType}[]
    end

    # create additional callback to track dependent delays
    if dependent_lags !== nothing && !isempty(dependent_lags)
        discontinuity_callback = DiscontinuityCallback(dependent_lags,
                                                       tracked_discontinuities,
                                                       discontinuity_interp_points,
                                                       discontinuity_abstol,
                                                       discontinuity_reltol,
                                                       initialize!, nothing)
        callbacks = CallbackSet(callback, prob.callback, discontinuity_callback)
    else
        callbacks = CallbackSet(callback, prob.callback)
    end

    # separate options of integrator and ODE integrator since ODE integrator always saves
    # every step and every index (necessary for history function)
    opts = OrdinaryDiffEq.DEOptions(integrator.opts.maxiters,
                                    save_everystep,
                                    integrator.opts.adaptive, integrator.opts.abstol,
                                    integrator.opts.reltol, integrator.opts.gamma,
                                    integrator.opts.qmax, integrator.opts.qmin,
                                    integrator.opts.qsteady_max,
                                    integrator.opts.qsteady_min,
                                    integrator.opts.failfactor, integrator.opts.dtmax,
                                    integrator.opts.dtmin, integrator.opts.internalnorm,
                                    integrator.opts.internalopnorm,
                                    save_idxs, tstops_internal, saveat_internal,
                                    d_discontinuities_internal,
                                    tstops, saveat, d_discontinuities,
                                    integrator.opts.userdata, integrator.opts.progress,
                                    integrator.opts.progress_steps,
                                    integrator.opts.progress_name,
                                    integrator.opts.progress_message,
                                    integrator.opts.timeseries_errors,
                                    integrator.opts.dense_errors, integrator.opts.beta1,
                                    integrator.opts.beta2, integrator.opts.qoldinit,
                                    dense && integrator.opts.dense, save_on, save_start, save_end,
                                    callbacks, integrator.opts.isoutofdomain,
                                    integrator.opts.unstable_check,
                                    integrator.opts.verbose, integrator.opts.calck,
                                    integrator.opts.force_dtmin,
                                    integrator.opts.advance_to_tstop,
                                    integrator.opts.stop_at_next_tstop)

    # reduction of solution only possible if no dense interpolation required and only
    # selected time points saved, and all constant and no dependent lags are given
    # WARNING: can impact quality of solution if not all constant lags specified
    minimal_solution = minimal_solution && !opts.dense && !opts.save_everystep &&
        constant_lags !== nothing && !isempty(constant_lags) &&
        (dependent_lags === nothing || isempty(dependent_lags))

    # need copy of heap of additional time points (nodes will be deleted!) in order to
    # remove unneeded time points of ODE solution as soon as possible and keep track
    # of passed time points
    saveat_copy = minimal_solution ? deepcopy(opts.saveat) : nothing

    # create DDE integrator combining the new defined problem function with history
    # information, the new solution, the parameters of the ODE integrator, and
    # parameters of fixed-point iteration
    # do not initialize fsalfirst and fsallast
    tTypeNoUnits = typeof(one(tType))
    dde_int = DDEIntegrator{typeof(integrator.alg),isinplace(prob),uType,tType,typeof(p),
                            typeof(integrator.eigen_est),
                            typeof(fixedpoint_abstol_internal),
                            typeof(fixedpoint_reltol_internal),typeof(resid),tTypeNoUnits,
                            typeof(integrator.tdir),typeof(integrator.k),typeof(sol),
                            typeof(dde_f),typeof(dde_cache),
                            typeof(integrator),typeof(fixedpoint_norm),typeof(opts),
                            typeof(saveat_copy),fsal_typeof(integrator),typeof(integrator.last_event_error)}(
                                sol, u, integrator.k, integrator.t, dt, dde_f, p, uprev,
                                uprev2, integrator.tprev, 1, 1, fixedpoint_abstol_internal,
                                fixedpoint_reltol_internal, resid, fixedpoint_norm,
                                alg.max_fixedpoint_iters, saveat_copy,
                                tracked_discontinuities, integrator.alg,
                                integrator.dtcache,
                                integrator.dtchangeable, integrator.dtpropose,
                                integrator.tdir, integrator.eigen_est,
                                integrator.EEst, integrator.qold,
                                integrator.q11, integrator.erracc, integrator.dtacc,
                                integrator.success_iter, integrator.iter,
                                integrator.saveiter, integrator.saveiter_dense,
                                dde_cache, integrator.kshortsize,
                                integrator.force_stepfail, integrator.just_hit_tstop,
                                integrator.last_stepfail, integrator.event_last_time,
                                integrator.last_event_error, integrator.accept_step,
                                integrator.isout, integrator.reeval_fsal,
                                integrator.u_modified, opts, integrator)

    # initialize DDE integrator and callbacks
    if initialize_integrator
        initialize_callbacks!(dde_int, initialize_save)
        initialize!(dde_int)
        typeof(alg.alg) <: OrdinaryDiffEq.CompositeAlgorithm &&
            copyat_or_push!(dde_int.sol.alg_choice, 1, dde_int.cache.current)
    end

    # take care of time step dt = 0 and dt with incorrect sign
    OrdinaryDiffEq.handle_dt!(dde_int)

    dde_int
end

function solve!(integrator::DDEIntegrator)
    # step over all stopping time points, similar to solving with ODE integrators
    @inbounds while !isempty(integrator.opts.tstops)
        while integrator.tdir * integrator.t < integrator.tdir * top(integrator.opts.tstops)
            # apply step or adapt step size
            loopheader!(integrator)

            # abort integration following same criteria as for ODEs:
            # maxiters exceeded, dt <= dtmin, integration unstable
            if check_error!(integrator) != :Success
              return integrator.sol
            end

            # calculate next step
            perform_step!(integrator)

            # calculate proposed next step size, handle callbacks, and update solution
            loopfooter!(integrator)

            if isempty(integrator.opts.tstops)
                break
            end
        end

        # remove hit or passed stopping time points
        handle_tstop!(integrator)
    end

    # clean up solution
    postamble!(integrator)

    # create array of time points and values that form solution
    sol_array = build_solution_array(integrator)

    # create interpolation data of solution
    interp = build_solution_interpolation(integrator, sol_array)

    # obtain DDE problem
    prob = integrator.sol.prob

    # calculate analytical solutions to problem if existent
    if DiffEqBase.has_analytic(prob.f)
        if integrator.opts.save_idxs === nothing
            u_analytic = [prob.f(Val{:analytic}, integrator.sol[1], integrator.p, t) for t in sol_array.t]
        else
            u_analytic = [@view(prob.f(
                Val{:analytic}, integrator.sol[1], integrator.p, t)[integrator.opts.save_idxs])
                          for t in sol_array.t]
        end
        errors = Dict{Symbol,eltype(integrator.u)}()
    else
        u_analytic = nothing
        errors = nothing
    end

    # combine arrays of time points and values, interpolation data, and analytical solution
    # to solution
    if typeof(prob.u0) <: Tuple
      N = length((size(ArrayPartition(prob.u0))..., length(sol_array.u)))
    else
      N = length((size(prob.u0)..., length(sol_array.u)))
    end

    if typeof(integrator.alg) <: OrdinaryDiffEq.OrdinaryDiffEqCompositeAlgorithm
        sol = OrdinaryDiffEq.ODECompositeSolution{
            eltype(sol_array),N,typeof(sol_array.u),typeof(u_analytic),
            typeof(errors),typeof(sol_array.t),typeof(interp.ks),
            typeof(prob),typeof(integrator.sol.alg),typeof(interp)}(
                sol_array.u, u_analytic, errors, sol_array.t, interp.ks,
                prob, integrator.sol.alg, interp, interp.alg_choice,
                interp.dense, integrator.sol.tslocation, :Success)
    else
        sol = ODESolution{
            eltype(sol_array),N,typeof(sol_array.u),typeof(u_analytic),
            typeof(errors),typeof(sol_array.t),typeof(interp.ks),
            typeof(prob),typeof(integrator.sol.alg),typeof(interp)}(
                sol_array.u, u_analytic, errors, sol_array.t, interp.ks,
                prob, integrator.sol.alg, interp, interp.dense,
                integrator.sol.tslocation, :Success)
    end

    # calculate errors of solution
    if sol.u_analytic !== nothing
        DiffEqBase.calculate_solution_errors!(sol; fill_uanalytic=false,
                                   timeseries_errors=integrator.opts.timeseries_errors,
                                   dense_errors=integrator.opts.dense_errors)
    end

    return sol
end

function DiffEqBase.__solve(prob::DiffEqBase.AbstractDDEProblem{uType,tupType,lType,iip},
               alg::algType,
               timeseries_init=uType[], ts_init=eltype(tupType)[], ks_init=[]; kwargs...) where
    {uType,tupType,iip,algType<:AbstractMethodOfStepsAlgorithm,lType}

    integrator = DiffEqBase.__init(prob, alg, timeseries_init, ts_init, ks_init; kwargs...)
    solve!(integrator)
end

function initialize_callbacks!(dde_int::DDEIntegrator, initialize_save = true)
    u = dde_int.u
    integrator = dde_int.integrator
    # set up additional initial values of newly created DDE integrator
    # (such as fsalfirst) and its callbacks

    dde_int.u_modified = true

    u_modified = initialize!(integrator.opts.callback,dde_int.t,u,dde_int)

    # if the user modifies u, we need to fix previous values before initializing
    # FSAL in order for the starting derivatives to be correct
    if u_modified

        if iip
            recursivecopy!(dde_int.uprev,dde_int.u)
        else
            dde_int.uprev = dde_int.u
        end

        if OrdinaryDiffEq.alg_extrapolates(dde_int.alg)
            if iip
                recursivecopy!(dde_int.uprev2,dde_int.uprev)
            else
                dde_int.uprev2 = dde_int.uprev
            end
        end

        # update heap of discontinuities
        # discontinuity is assumed to be of order 0, i.e. solution x is discontinuous
        push!(dde_int.opts.d_discontinuities, Discontinuity(dde_int.t, 0))

        # reset this as it is now handled so the integrators should proceed as normal
        reeval_internals_due_to_modification!(dde_int,Val{false})

        if initialize_save &&
          (any((c)->c.save_positions[2],integrator.opts.callback.discrete_callbacks) ||
          any((c)->c.save_positions[2],integrator.opts.callback.continuous_callbacks))
          savevalues!(dde_int,true)
        end

        # recompute initial time step
        auto_dt_reset!(dde_int)
    end

    # reset this as it is now handled so the integrators should proceed as normal
    dde_int.u_modified = false
end

function tstop_saveat_disc_handling(tstops, saveat, d_discontinuities, tdir, tspan, initial_order, alg_maximum_order, constant_lags, tType)
    # add discontinuities propagated from initial discontinuity
    if initial_order ≤ alg_maximum_order && constant_lags !== nothing && !isempty(constant_lags)
        maxlag = abs(tspan[end] - tspan[1])
        d_discontinuities_internal = unique(
            Discontinuity{tType}[d_discontinuities;
                                 (Discontinuity(tspan[1] + lag, initial_order + 1)
                                  for lag in constant_lags if abs(lag) < maxlag)...])
    else
        d_discontinuities_internal = unique(d_discontinuities)
    end

    return OrdinaryDiffEq.tstop_saveat_disc_handling(tstops, saveat, d_discontinuities_internal, tdir, tspan, tType)
end


export learn, learn!
export arc_requirements_do_not_conflict
export DSL_BayesianSearch,          LearnParams_BayesianSearch
export DSL_GreedyThickThinning,     LearnParams_GreedyThickThinning
export DSL_NaiveBayes,              LearnParams_NaiveBayes
export DSL_PC,                      LearnParams_PC
export DSL_TreeAugmentedNaiveBayes, LearnParams_TreeAugmentedNaiveBayes

# -------

const DSL_K2   = unsafe_load(cglobal((:EDSL_K2,   LIB_SMILE), Cint))
const DSL_BDeu = unsafe_load(cglobal((:EDSL_BDeu, LIB_SMILE), Cint))

# -------

abstract DSL_LearningAlgorithm
type DSL_BayesianSearch          <: DSL_LearningAlgorithm end
type DSL_GreedyThickThinning     <: DSL_LearningAlgorithm end
type DSL_NaiveBayes              <: DSL_LearningAlgorithm end
type DSL_PC                      <: DSL_LearningAlgorithm end
type DSL_TreeAugmentedNaiveBayes <: DSL_LearningAlgorithm end

# -------

abstract DSL_LearnParams
type LearnParams_BayesianSearch <: DSL_LearnParams
		maxparents           :: Int            # limits the maximum number of parents a node can have
		maxsearchtime        :: Int            # maximum runtime of the algorithm [seconds?] (0 = infinite)
		niterations          :: Int            # number of searches (and indirectly number of random restarts)
		linkprobability      :: Float64        # defines the probabilty of having an arc between two nodes
		priorlinkprobability :: Float64        # defines a prior existence of an arc between two nodes
		priorsamplesize      :: Int            # influences the "strength" of prior link probability.
		seed                 :: Int            # random seed (0=none)
		forced_arcs          :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forced to be in the network
		forbidden_arcs       :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forbidden in the network
		tiers                :: Set{Tuple{Int,Int}} # a list of (i->tier) associating nodes with a particular tier (tier starts at 1, variable starts at 0)

		LearnParams_BayesianSearch() = new(5, 0, 20, 0.1, 0.001, 50, 0, Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}())
end
type LearnParams_GreedyThickThinning <: DSL_LearnParams
		maxparents           :: Int # limits the maximum number of parents a node can have
		priors               :: Int # either K2 or BDeu
		netWeight            :: Float64
		forced_arcs          :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forced to be in the network
		forbidden_arcs       :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forbidden in the network
		tiers                :: Set{Tuple{Int,Int}} # a list of (i->tier) associating nodes with a particular tier
		LearnParams_GreedyThickThinning() = new(5, DSL_K2, 1.0, Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}())
end
type LearnParams_NaiveBayes <: DSL_LearnParams
	classVariableId :: AbstractString # the variable ID of the column that is our class

	LearnParams_NaiveBayes() = new("class")
	LearnParams_NaiveBayes(var::AbstractString) = new(var)
end
type LearnParams_PC <: DSL_LearnParams
	maxcache      :: UInt64
	maxAdjacency  :: Cint
	maxSearchTime :: Cint
	significance  :: Float64
	forced_arcs     :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forced to be in the network
		forbidden_arcs  :: Set{Tuple{Int,Int}} # a list of (i->j) arcs which are forbidden in the network
		tiers           :: Set{Tuple{Int,Int}} # a list of (i->tier) associating nodes with a particular tier

	LearnParams_PC() = new(2048, 8, 0, 0.05, Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}(), Set{Tuple{Int,Int}}())
end
type LearnParams_TreeAugmentedNaiveBayes <: DSL_LearnParams
	classvar      :: AbstractString # Used to pick the class variable for the network. Is case-sensitive.
	maxSearchTime :: Cint   # maximum runtime for the algorithm
	seed          :: UInt32 # random seed (0 for none)
	maxcache      :: UInt64 # maximum cache size (units?)

	LearnParams_TreeAugmentedNaiveBayes() = new("class", 0, 0, 2048)
	LearnParams_TreeAugmentedNaiveBayes(class) = new(class, 0, 0, 2048)
end

# ------

function learn!( net::Network, dset::Dataset, ::Type{DSL_BayesianSearch},
	params::LearnParams_BayesianSearch = LearnParams_BayesianSearch()
	)

	nforcedarcs,    forcedarcs_arr    = _get_arcs_arr(params.forced_arcs)
	nforbiddenarcs, forbiddenarcs_arr = _get_arcs_arr(params.forbidden_arcs)
	lentiers,       tiers_arr         = _get_arcs_arr(params.tiers)

	retval = ccall( (:dataset_learnBayesianSearch, LIB_SMILE), Bool,
		(Ptr{Void},Ptr{Void},Int32,Int32,Int32,Float64,Float64,Int32,Int32,
			Ptr{Int32},Int32,Ptr{Int32},Int32,Ptr{Int32},Int32),
		dset.ptr, net.ptr, params.maxparents, params.maxsearchtime, params.niterations,
		params.linkprobability, params.priorlinkprobability, params.priorsamplesize,
		params.seed, forcedarcs_arr, nforcedarcs,
		forbiddenarcs_arr, nforbiddenarcs, tiers_arr, lentiers )
	if ( !retval )
		warn("Dataset::learn_bayesian_search: did not work")
	end
	return retval # true on success
end
function learn!( net::Network, dset::Dataset, ::Type{DSL_GreedyThickThinning},
	params::LearnParams_GreedyThickThinning = LearnParams_GreedyThickThinning()
	)

	nforcedarcs,    forcedarcs_arr    = _get_arcs_arr(params.forced_arcs)
	nforbiddenarcs, forbiddenarcs_arr = _get_arcs_arr(params.forbidden_arcs)
	lentiers,       tiers_arr         = _get_arcs_arr(params.tiers)

	retval = ccall( (:dataset_learnGreedyThickThinning, LIB_SMILE), Bool,
		(Ptr{Void},Ptr{Void},Int32,Int32,Float64,
			Ptr{Int32},Int32,Ptr{Int32},Int32,Ptr{Int32},Int32),
		dset.ptr, net.ptr, params.priors, params.maxparents,
		params.netWeight, forcedarcs_arr, nforcedarcs,
		forbiddenarcs_arr, nforbiddenarcs, tiers_arr, lentiers )
	if ( !retval )
		warn("Dataset::learn_greedy_thick_thinning: did not work")
	end
	return retval # true on success
end
function learn!( net::Network, dset::Dataset, ::Type{DSL_NaiveBayes},
	params::LearnParams_NaiveBayes = LearnParams_NaiveBayes()
	)

	if find_variable(dset, params.classVariableId) == -1
		warn("class variable $(params.classVariableId) could not be found")
	end

	retval = ccall( (:dataset_learnNaiveBayes, LIB_SMILE), Bool,
		(Ptr{Void},Ptr{Void},Ptr{UInt8}),
		dset.ptr, net.ptr, bytestring(params.classVariableId) )
	if ( !retval )
		warn("Dataset::learn_naive_bayes: did not work")
	end
	return retval # true on success
end
function learn!( net::Network, dset::Dataset, ::Type{DSL_TreeAugmentedNaiveBayes},
	params::LearnParams_TreeAugmentedNaiveBayes = LearnParams_TreeAugmentedNaiveBayes()
	)

	if find_variable(dset, params.classvar) == -1
		warn("class variable $(params.classvar) could not be found")
	end

	retval = ccall( (:dataset_learnTAN, LIB_SMILE), Bool,
		(Ptr{Void},Ptr{Void},Ptr{UInt8}, Cint, UInt32, UInt64),
		dset.ptr, net.ptr, bytestring(params.classvar), params.maxSearchTime, params.seed, params.maxcache )
	if ( !retval )
		warn("Dataset::learn_TAN: did not work")
	end
	return retval # true on success
end
function learn!( net::Network, dset::Dataset, ::Type{DSL_PC},
	params::LearnParams_PC = LearnParams_PC()
	)

	pat = Pattern()
	worked = learn!( pat, dset, DSL_PC, params )
	to_network(pat, dset, net)
	return worked
end
function learn!( pat::Pattern, dset::Dataset, ::Type{DSL_PC},
	params::LearnParams_PC = LearnParams_PC()
	)

	nforcedarcs,    forcedarcs_arr    = _get_arcs_arr(params.forced_arcs)
	nforbiddenarcs, forbiddenarcs_arr = _get_arcs_arr(params.forbidden_arcs)
	lentiers,       tiers_arr         = _get_arcs_arr(params.tiers)

	retval = ccall( (:dataset_learnPC, LIB_SMILE), Bool,
		(Ptr{Void},Ptr{Void},UInt64,Cint,Cint,Float64,
			Ptr{Int32},Int32,Ptr{Int32},Int32,Ptr{Int32},Int32),
		dset.ptr, pat.ptr, params.maxcache, params.maxAdjacency,
		params.maxSearchTime, params.significance,
		forcedarcs_arr, nforcedarcs, forbiddenarcs_arr,
		nforbiddenarcs, tiers_arr, lentiers)
	if ( !retval )
		warn("Dataset::dataset_learnPC: did not work")
	end
	return retval # true on success
end

learn!( net::Network, dset::Dataset, params::LearnParams_BayesianSearch ) =
	learn!(net, dset, DSL_BayesianSearch, params)
learn!( net::Network, dset::Dataset, params::LearnParams_GreedyThickThinning ) =
	learn!(net, dset, DSL_GreedyThickThinning, params)
learn!( net::Network, dset::Dataset, params::LearnParams_NaiveBayes ) =
	learn!(net, dset, DSL_NaiveBayes, params)
learn!( net::Network, dset::Dataset, params::LearnParams_TreeAugmentedNaiveBayes ) =
	learn!(net, dset, DSL_TreeAugmentedNaiveBayes, params)
learn!( net::Network, dset::Dataset, params::LearnParams_PC ) =
	learn!(net, dset, DSL_PC, params)
learn!( pat::Pattern, dset::Dataset, params::LearnParams_PC ) =
	learn!(pat, dset, DSL_PC, params)

function learn{S<:DSL_LearningAlgorithm}( dset::Dataset, alg::Type{S} )
	net = Network()
	worked = learn!(net, dset, alg)
	(net, worked)
end
function learn{S<:DSL_LearnParams}( dset::Dataset, params::S )
	net = Network()
	worked = learn!(net, dset, params)
	(net, worked)
end

# -------

function arc_requirements_do_not_conflict(
	forced_arcs    :: Set{Tuple{Int,Int}},
	forbidden_arcs :: Set{Tuple{Int,Int}}
	)
	# returns true if a forced arc is also a forbidden arc
	for tup in params.forbidden_arcs
		if in(tup, params.forced_arcs)
			return false
		end
	end
	true
end
function arc_requirements_do_not_conflict(
	forced_arcs    :: Set{Tuple{Int,Int}},
	forbidden_arcs :: Set{Tuple{Int,Int}},
	tiers          :: Set{Tuple{Int,Int}},
	)
	# converts tiers to arc requirements before checking conflict
	forced, forbidden = _tiers_to_arc_requirements(tiers)
	union!(forced, forced_arcs)
	union!(forbidden, forbidden_arcs)
	arc_requirements_do_not_conflict(forced, forbidden)
end
arc_requirements_do_not_conflict( params::DSL_BayesianSearch ) =
	arc_requirements_do_not_conflict(params.forced_arcs, params.forbidden_arcs, params.tiers)
arc_requirements_do_not_conflict( params::DSL_GreedyThickThinning ) =
	arc_requirements_do_not_conflict(params.forced_arcs, params.forbidden_arcs, params.tiers)
arc_requirements_do_not_conflict( params::DSL_BayesianSearch ) =
	arc_requirements_do_not_conflict(params.forced_arcs, params.forbidden_arcs, params.tiers)

# -------

function _get_arcs_arr(arcs::Set{Tuple{Int,Int}})
	narcs = length(arcs)
	arr = zeros(Int32, 2*narcs)
	for (i,tup) in enumerate(arcs)
		arr[2*i-1] = tup[1]
		arr[2*i] = tup[2]
	end
	(narcs, arr)
end
function _tiers_to_arc_requirements(
	tiers :: Set{Tuple{Int,Int}} # a list of (i->tier) associating nodes with a particular tier
	)

	# the node starts counting at 0
	# the tier starts counting at 1

	forced_arcs    :: Set{Tuple{Int,Int}}()
	forbidden_arcs :: Set{Tuple{Int,Int}}()

	for (i,tier_i) in tiers
		for (j,tier_j) in tiers
			if tier_i > tier_j
				push!(forbidden_arcs, (i,j))
			elseif tier_j > tier_i
				push!(forbidden_arcs, (j,i))
			end
		end
	end

	(forced_arcs, forbidden_arcs)
end


abstract type Sim end

struct POMDPSim <: Sim
    simulator::Simulator
    pomdp::POMDP
    policy::Policy
    updater::Updater
    initial_belief::Any
    initial_state::Any
    metadata::Dict{Symbol}
end

struct MDPSim <: Sim
    simulator::Simulator
    mdp::MDP
    policy::Policy
    initial_state::Any
    metadata::Dict{Symbol}
end

"""
    Sim(p::POMDP, policy::Policy, metadata=Dict(:note=>"a note"))
    Sim(p::POMDP, policy::Policy[, updater[, initial_belief[, initial_state]]]; kwargs...)

Create a `Sim` object that represents a POMDP simulation.
"""
function Sim(pomdp::POMDP,
                    policy::Policy,
                    up=updater(policy),
                    initial_belief=initial_state_distribution(pomdp),
                    initial_state=nothing;
                    rng::AbstractRNG=Base.GLOBAL_RNG,
                    max_steps::Int=typemax(Int),
                    simulator::Simulator=HistoryRecorder(rng=rng, max_steps=max_steps),
                    metadata::Dict{Symbol}=Dict{Symbol, Any}()
                   )

    if initial_state == nothing && state_type(pomdp) != Void
        is = rand(rng, initial_belief)
    else
        is = initial_state
    end
    return POMDPSim(simulator, pomdp, policy, up, initial_belief, is, metadata)
end

"""
    Sim(p::MDP, policy::Policy, metadata=Dict(:note=>"a note"))
    Sim(p::MDP, policy::Policy[, initial_state]; kwargs...)

Create a `Sim` object that represents a MDP simulation.

A vector of `Sim` objects can be executed with `run` or `run_parallel`.

## Keyword Arguments
- `rng::AbstractRNG=Base.GLOBAL_RNG`
- `max_steps::Int=typemax(Int)`
- `simulator::Simulator=HistoryRecorder(rng=rng, max_steps=max_steps)`
- `metadata::Dict{Symbol}=Dict{Symbol, Any}()` a dictionary of metadata for the sim that will be recorded, e.g. `Dict(:solver_iterations=>500)`.
"""
function Sim(mdp::MDP,
             policy::Policy,
             initial_state=nothing;
             rng::AbstractRNG=Base.GLOBAL_RNG,
             max_steps::Int=typemax(Int),
             simulator::Simulator=HistoryRecorder(rng=rng, max_steps=max_steps),
             metadata::Dict{Symbol}=Dict{Symbol, Any}()
            )

    if initial_state == nothing && state_type(pomdp) != Void
        is = initial_state(mdp, rng)
    else
        is = initial_state
    end
    return MDPSim(simulator, mdp, policy, is, metadata)
end

POMDPs.simulate(s::POMDPSim) = simulate(s.simulator, s.pomdp, s.policy, s.updater, s.initial_belief, s.initial_state)
POMDPs.simulate(s::MDPSim) = simulate(s.simulator, s.mdp, s.policy, s.initial_state)

function default_process(s::Sim, r::Float64)
    stuff = metadata_as_pairs(s)
    return push!(stuff, :reward=>r)
end
default_process(s::Sim, hist::SimHistory) = default_process(s, discounted_reward(hist))

metadata_as_pairs(s::Sim) = convert(Array{Any}, collect(s.metadata))

run_parallel(queue::AbstractVector) = run_parallel(default_process, queue)

function run_parallel(process::Function, queue::AbstractVector;
                      progress=Progress(length(queue), desc="Simulating..."))

    #=
    frame_lines = pmap(progress, queue) do sim
        result = simulate(sim)
        return process(sim, result)
    end
    =#

    # based on the simple implementation of pmap here: https://docs.julialang.org/en/latest/manual/parallel-computing
    np = nprocs()
    n = length(queue)
    i = 1
    prog = 0
    frame_lines = Vector{Any}(n)
    nextidx() = (idx=i; i+=1; idx)
    prog_lock = ReentrantLock()
    @sync begin
        for p in 1:np
            if np == 1 || p != myid()
                @async begin
                    while true
                        idx = nextidx()
                        if idx > n
                            break
                        end
                        frame_lines[idx] = remotecall_fetch(p, queue[idx]) do sim
                            result = simulate(sim)
                            return process(sim, result)
                        end
                        if progress isa Progress
                            lock(prog_lock)
                            update!(progress, prog+=1)
                            unlock(prog_lock)
                        end
                    end
                end
            end
        end
    end

    return create_dataframe(frame_lines)
end

Base.run(queue::AbstractVector) = run(default_process, queue)

function Base.run(process::Function, queue::AbstractVector; show_progress=true)
    lines = []
    if show_progress
        @showprogress for sim in queue
            result = simulate(sim)
            push!(lines, process(sim, result))
        end
    else
        for sim in queue
            result = simulate(sim)
            push!(lines, process(sim, result))
        end
    end
    return create_dataframe(lines)
end

function create_dataframe(lines::Vector)
    master = Dict{Symbol, DataArray}()
    for line in lines
        push_line!(master, line)
    end
    return DataFrame(master)
end

function _push_line!(d::Dict{Symbol, DataArray}, line)
    if isempty(d)
        len = 0
    else
        len = length(first(values(d)))
    end
    for (key, val) in line
        if !haskey(d, key)
            d[key] = DataArray(typeof(val), len)
        end
        data = d[key]
        if !(typeof(val) <: eltype(data))
            d[key] = convert(DataArray{Any,1}, data)
        end
        push!(d[key], val)
    end
    for da in values(d)
        if length(da) < len + 1
            push!(da, NA)
        end
    end
    return d
end
push_line!(d::Dict{Symbol, DataArray}, line::Dict) = _push_line!(d, line)
push_line!(d::Dict{Symbol, DataArray}, line::DataFrame) = _push_line!(d, n=>first(line[n]) for n in names(line))
push_line!(d::Dict{Symbol, DataArray}, line::Vector{Any}) = _push_line!(d, line)
push_line!(d::Dict{Symbol, DataArray}, line::Tuple) = _push_line!(d, line)


"""
"""
struct SingleFieldFEFunction{T<:CellField} <: CellField
  cell_field::T
  cell_vals
  free_values
  dirichlet_values
  fe_space
  @doc """
  """
  function SingleFieldFEFunction(
    cell_field::CellField,
    cell_vals::AbstractArray{<:AbstractArray},
    free_values::AbstractVector,
    dirichlet_values::AbstractVector,
    fe_space::SingleFieldFESpace)

    T = typeof(cell_field)
    new{T}(cell_field,cell_vals,free_values,dirichlet_values,fe_space)
  end
end

FEFunctionStyle(::Type{<:SingleFieldFEFunction}) = Val{true}()

get_array(f::SingleFieldFEFunction) = get_array(f.cell_field)

get_free_values(f::SingleFieldFEFunction) = f.free_values

get_dirichlet_values(f::SingleFieldFEFunction) = f.dirichlet_values

get_fe_space(f::SingleFieldFEFunction) = f.fe_space

get_cell_map(f::SingleFieldFEFunction) = get_cell_map(f.cell_field)

get_cell_values(f::SingleFieldFEFunction) = f.cell_vals

RefStyle(::Type{SingleFieldFEFunction{T}}) where T = RefStyle(T)

CellData.MetaSizeStyle(::Type{SingleFieldFEFunction{T}}) where T = MetaSizeStyle(T)

CellData.get_cell_axes(a::SingleFieldFEFunction) = get_cell_axes(a.cell_field)

CellData.get_memo(a::SingleFieldFEFunction) = get_memo(a.cell_field)


NIScope_Horizontal_Dict = Dict()

# Nr of acquisition points
function set_nr_pts(obj::T;npts=1e6) where {T<:NIScope}
	#NISCOPE_ATTR_HORZ_MIN_NUM_PTS=1250009; # ((1000000 + 250000) + 9)
	status = niScope_SetAttributeViInt32(obj, "", NISCOPE_ATTR_HORZ_MIN_NUM_PTS, Int32(npts))
end
# Sampling rate
function set_srate(obj::T;sr=1e6) where {T<:NIScope}
	#NISCOPE_ATTR_MIN_SAMPLE_RATE=1150009; # ((1000000 + 150000) + 9)
	status = niScope_SetAttributeViReal64(obj, "", NISCOPE_ATTR_MIN_SAMPLE_RATE, Float64(sr))
end
# Acquisition duration
function set_hduration(obj::T;d=1e-3) where {T<:NIScope}
	error("not available yet")
end
# Horizontal offset origin
function set_hoffs_div(obj::T;href::Int=1) where {T<:NIScope}
	#NISCOPE_ATTR_HORZ_RECORD_REF_POSITION=1250011; # ((1000000 + 250000) + 11)
	status = niScope_SetAttributeViReal64(obj, "", NISCOPE_ATTR_HORZ_RECORD_REF_POSITION, Float64(href*10))
end


# ### Image viewer using MiniFB
# This example displays the PNG files in a directory, rotating through the available images on each mouse click.

# This code needs Images.jl and ImageTransformations.jl. Add those packages to your environment before proceeding
using Images
using ImageTransformations
using MiniFB

# Set the size of the window
global const WIDTH = 600
global const HEIGHT = 400

# Global state
global ni = 1
global num_images = 1

# A function that will be called when a mouse button is clicked. It simply rotates the current image by storing a global integer.
function onclick(window, button, mod, isPressed)::Cvoid
    global ni, num_images
    if Bool(isPressed)
        if ni < num_images
            ni = ni+1
        else
            ni = 1
        end
    end
    return nothing
end

# The callback needs to be passed to the C library. The @cfunction macro creates a
onclick_c = @cfunction(onclick, Cvoid, (Ptr{Cvoid}, mfb_mouse_button, mfb_key_mod, UInt8) )

# Populate a MiniFB buffer from the image data.
# First, resize the image to the window dimensions. Calculate it's position so that it is centered in the window.
# Finally, inside the loop, convert each pixel to the 32 bit MiniFB buffer format.
function populate_buffer!(buffer, img)
    global WIDTH, HEIGHT
    buffer[:] = zeros(UInt32, HEIGHT*WIDTH)
    h, w = size(img)
    ratio = min(1, HEIGHT/h, WIDTH/w)
    nimg = imresize(img, ratio=ratio)
    nh, nw = size(nimg)
    oh = (HEIGHT-nh) ÷ 2
    ow = (WIDTH-nw) ÷ 2
    for i in 1:nh
        for j in 1:nw
            buffer[(oh+i-1)*WIDTH + ow+j] = mfb_rgb(nimg[i,j])
        end
    end
end

# This is the main function. It takes a directory, calls `loadfiles` to get the image data.
# Creates the windows, and sets up the callback. Then, inside the `while` loop, renders the buffer into the window.
# The `mfb_update` not only renders the buffer, but also flushes the input event queue.
# The buffer is changed only when the state changes. The `mfb_wait_sync` method enforces the required frame rate.
function imageview(dir::String=".")
    global ni, num_images
    images = loadfiles(dir)
    num_images = length(images)
    buffer = zeros(UInt32, HEIGHT*WIDTH)
    mfb_set_target_fps(10)
    window = mfb_open_ex("Image Viewer", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE);
    mfb_set_mouse_button_callback(window, onclick_c);
    old_ni=0
    while mfb_wait_sync(window)
        if ni != old_ni
            populate_buffer!(buffer, images[ni])
            old_ni = ni
        end
        state = mfb_update(window, buffer);
        if state != MiniFB.STATE_OK
            break;
        end
    end
    mfb_close(window)
end

# Load the files from disk. Select all png files, and then use ImageIO to load them into memory.
function loadfiles(dir::String=".")
    files = readdir(dir)
    filter!(x->occursin(r"\.png"i, x), files)
    [load(joinpath(dir,x)) for x in files]
end

# Finally, call the main method to display the application
imageview(joinpath(dirname(pathof(MiniFB)),"..", "example"))

# ![](../assets/imageviewer.png)


#!/usr/bin/env julia

module QJuliaCUDAFields

#load path to qjulia home directory
push!(LOAD_PATH, joinpath(@__DIR__, "../..", "core"))
push!(LOAD_PATH, @__DIR__)

import QJuliaRegisters
import QJuliaEnums
import QJuliaGrid
import QJuliaFields

using CUDAnative
using CuArrays

const complex_length = 2

function CreateGenericField(fdesc::QJuliaGrid.QJuliaLatticeFieldDescr_qj)

  generic_field = QJuliaFields.QJuliaLatticeField_qj(fdesc)

  if fdesc.grid.location == QJuliaEnums.QJULIA_CUDA_FIELD_LOCATION
    # Get reference to field parameters:
    fdesc = generic_field.field_desc
    # Set the field array total elements:
    tot_elems = complex_length*fdesc.siteSubset*(fdesc.geom == QJuliaEnums.QJULIA_SCALAR_GEOMETRY ? fdesc.nSpin : fdesc.nColor)*fdesc.nColor*fdesc.grid.grid_volumeCB

    generic_field.v = CuArray{fdesc.grid.register_type, 2}(undef, tot_elems, Int(fdesc.geom)*fdesc.nBlock)

    #address_space = AS.Global or AS.Generic etc.
    #dev_ptr = CUDAnative.DevicePtr{fdesc.grid.register_type,address_space}(tot_elems*Int(fdesc.geom)*fdesc.nBlock)
    #generic_field.v = CuDeviceArray{fdesc.grid.register_type,2,address_space}((tot_elems, Int(fdesc.geom)*fdesc.nBlock), dev_ptr)
  end

  return generic_field
end

end # module QJuliaCUDAFields


@testset "Test Reserves from Thermal Dispatch" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_uc = build_system("c_sys5_uc"; add_reserves = true)
    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_uc; use_parameters = p)
        moi_tests(op_problem, p, 648, 0, 120, 216, 72, false)
    end
end

@testset "Test Reserves from Thermal Standard UC" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalBasicUnitCommitment),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :UpReserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_uc = build_system("c_sys5_uc"; add_reserves = true)
    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_uc; use_parameters = p)
        moi_tests(op_problem, p, 1008, 0, 240, 216, 192, true)
    end
end

@testset "Test Upwards Reserves from Renewable Dispatch" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(RenewableDispatch, RenewableFullDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_re = build_system("c_sys5_re"; add_reserves = true)
    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_re; use_parameters = p)
        moi_tests(op_problem, p, 360, 0, 72, 48, 72, false)
    end
end

@testset "Test Reserves from Storage" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
        :Storage => DeviceModel(GenericBattery, BookKeeping),
        # Added here to test it doesn't add reserve variables
        :Ren => DeviceModel(RenewableDispatch, FixedOutput),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_bat = build_system("c_sys5_bat"; add_reserves = true)
    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_bat; use_parameters = p)
        moi_tests(op_problem, p, 408, 0, 192, 264, 96, false)
    end
end

@testset "Test Reserves from Hydro" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(HydroEnergyReservoir, HydroDispatchRunOfRiver),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_hyd = build_system("c_sys5_hyd"; add_reserves = true)
    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_hyd; use_parameters = p)
        moi_tests(op_problem, p, 240, 0, 24, 96, 72, false)
    end
end

@testset "Test Reserves from with slack variables" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_uc = build_system("c_sys5_uc"; add_reserves = true)
    for p in [true, false]
        op_problem = OperationsProblem(
            TestOpProblem,
            model_template,
            c_sys5_uc;
            use_parameters = p,
            services_slack_variables = true,
            balance_slack_variables = true,
        )
        moi_tests(op_problem, p, 504, 0, 120, 192, 24, false)
    end
end

@testset "Test AGC" begin
    c_sys5_reg = build_system("c_sys5_reg")
    # End of the system creation code.
    devices = Dict(
        :Loads => DeviceModel(PowerLoad, StaticPowerLoad),
        :Regulation_thermal =>
            DeviceModel(RegulationDevice{ThermalStandard}, DeviceLimitedRegulation),
    )
    services = Dict(:AGC => ServiceModel(AGC, PIDSmoothACE))

    @test_throws ArgumentError template_agc_reserve_deployment(devices = devices)

    template_agc = template_agc_reserve_deployment()
    agc_problem = OperationsProblem(AGCReserveDeployment, template_agc, c_sys5_reg)
    # These values might change as the AGC model is refined
    moi_tests(agc_problem, false, 720, 0, 480, 0, 384, false)
end

@testset "Test GroupReserve from Thermal Dispatch" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
        :GroupReserve => ServiceModel(StaticReserveGroup{ReserveDown}, GroupReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_uc = build_system("c_sys5_uc"; add_reserves = true)
    services = get_components(Service, c_sys5_uc)
    contributing_services = Vector{Service}()
    for service in services
        push!(contributing_services, service)
    end
    groupservice = StaticReserveGroup{ReserveDown}(;
        name = "init",
        available = true,
        requirement = 0.0,
        ext = Dict{String, Any}(),
    )
    add_service!(c_sys5_uc, groupservice, contributing_services)

    for p in [true, false]
        op_problem =
            OperationsProblem(TestOpProblem, model_template, c_sys5_uc; use_parameters = p)
        moi_tests(op_problem, p, 648, 0, 120, 240, 72, false)
    end
end

@testset "Test GroupReserve Errors" begin
    devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalDispatch),
        :Loads => DeviceModel(PowerLoad, PSI.StaticPowerLoad),
    )
    branches = Dict{Symbol, DeviceModel}()
    services_template = Dict{Symbol, PSI.ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
        :DownReserve => ServiceModel(VariableReserve{ReserveDown}, RangeReserve),
        :ORDC => ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve),
        :GroupReserve => ServiceModel(StaticReserveGroup{ReserveDown}, GroupReserve),
    )
    model_template = OperationsProblemTemplate(
        CopperPlatePowerModel,
        devices,
        branches,
        services_template,
    )
    c_sys5_uc = build_system("c_sys5_uc"; add_reserves = true)
    services = get_components(Service, c_sys5_uc)
    contributing_services = Vector{Service}()
    for service in services
        push!(contributing_services, service)
    end
    groupservice = StaticReserveGroup{ReserveDown}(;
        name = "init",
        available = true,
        requirement = 0.0,
        ext = Dict{String, Any}(),
    )
    add_service!(c_sys5_uc, groupservice, contributing_services)

    off_service = VariableReserve{ReserveUp}("Reserveoff", true, 0.6, 10)
    push!(groupservice.contributing_services, off_service)

    @test_logs(
        (:error, r"is not stored"),
        match_mode = :any,
        @test_throws InfrastructureSystems.InvalidValue op_problem = OperationsProblem(
            TestOpProblem,
            model_template,
            c_sys5_uc;
            use_parameters = false,
        )
    )

end


# All numerical values should be here. No hard-coded values
# elsewhere in the Sesi module

# The design_ functions create the parameter dictionary. This contains
# design parameters that do not change in time.

function design_base!()
    p = Dict()
    p[:utc_offset] = -7
    # These will be toggled on as they are added to the model
    p[:cbp] = false
    p[:carbon] = false
    p[:peak] = false
    p[:sw] = false
    p[:cap_thstorage] = false
    p[:cap_machines] = false
    p[:ev] = false
    p[:umLoadsL2] = true # set this to false to use L1 norm instead for unmet loads

    return p
end

function design_water!(p::Dict)
    # chilled water storage capacity
    p[:coldTankMax] = 90.e3 # ton-hr # 90
    p[:coldTankSafety] = 0.05 # safety zone (%)
    p[:cwTankInit] = 0.1*p[:coldTankMax] # ton-hr
    p[:S_c_dot_max] = 18.e3 # ton

    # hot water storage capacity
    p[:hotTankMax] = 600. # mmbtu
    p[:hotTankSafety] = 0.05 # safety zone (%)
    p[:hwTankInit] = 0.1*p[:hotTankMax] # mmbtu
    p[:S_h_dot_max] = 120. # mmbtu/hr

    # machines
    # there are 3 HRCs, 4 chillers and 3 heaters. For now we assume one of each and scale accordingly
    # When data does not vary, we use a scalar instead of a vector
    p[:HRCCAP] = 2500.*3# data[:HRC1CAP]*3 # tons
    p[:chillerCAP] = 3000.*4 # data[:C6CAP]*4 # tons
    p[:heaterCAP] = 60.55*3 # data[:H1CAP]*3 # mmbtu
    p[:HRCPE] = 1.32703695050335 #data[:HRC1PE] # kWh/ton-hr
    p[:heaterPE] = 0.85 # data[:H1PE] # %HHV
    p[:heaterEPE] = 2.0 # data[:H1ePE] # kWh/mmbtu
    p[:chillerPE] = 0.45566 # kW/ton - mean value in 2016 - will be used if efficiency is not supplied in the timeseries data
    p[:HRCHotToColdRatio] = 41. / 2500. # mmbtu/ton-hr - my assumption based on CEPOM

    p[:swPenalty] = 1e-8 # $/kW

    # capital costs
    # 7.4e6 for the three tanks - assume equal split -> 2.46e6 per tank
    # Assume a lifetime of 20 years
    p[:capCostCold] = 2.46666666e6 / p[:coldTankMax] / (20*365*24) # $/ton/hr
    p[:capCostHot] = 2 * 2.46666666e6 / p[:hotTankMax] / (20*365*24) # $/mmbtu/hr

    # unmet load cost
    p[:umCold] = 1.32 * 10 # kWh/ton-hr * $/kWh
    p[:umHot] = 1.32 * 10 * 2500. / 41. # kWh/ton-hr * $/kWh * ton-hr/mmbtu

    return p
end

function design_storage!(p::Dict)
    # battery
    p[:capitalCostBattery] = 400 # $/kWh # 400
    p[:elecStorageMax] = 10.e3 # kWh
    p[:elecTransferMax] = p[:elecStorageMax]/2
    p[:elecTransferEta] = 1-0.064500
    p[:elecStorageInit] = p[:elecStorageMax] # kWh
    p[:lifetimeBattery] = 3000 #[number of cycles]
    # this is intentionnally left very small and is needed numerically to avoid overusing the battery
    p[:usageCostBattery] = p[:capitalCostBattery] / p[:lifetimeBattery] # $/kWh
    return p
end

function design_prices!(p::Dict)
    p[:gasPrice] = 5.40 # $/mmbtu
    #p[:demandCharge] = 4.56# $/kW

    # data from SESI Performance Review (recieved 2017-09-15)
    prices = [3.38,3.38,3.76,3.76,3.76,3.76,3.76,3.76,3.55,3.55,3.55,3.55,2.63,
	      2.63,3.18,3.18,3.18,3.18,3.18,3.18,3.18,3.18,2.98,2.98,2.71,2.71,
	      3.03,3.03,3.03,3.03,3.03,3.03,3.03,3.03,3.03,3.03,3.25,3.25,3.67,
	      3.67,3.67,3.67,3.67,3.67,3.67,3.67,3.67,3.67,3.44,3.44,4.28,4.28,
	      4.28,3.97,3.97,3.97,3.97,3.97,3.97,3.97,4.22,4.22,4.79,4.79,4.79,
	      4.79,4.79,4.79,4.79,4.79,4.79,4.79,4.06,4.06,4.06,4.06,4.06,4.06,
	      4.06,4.06,4.06,4.06,4.06,4.06,4.05,4.05,4.05,4.05,4.75,4.75,4.75,
	      4.75,4.75,4.64,4.64,4.64,4.61,4.61,4.61,4.61,4.77,4.77,4.77,4.77,
	      4.77,4.66,4.66,4.66,4.64,4.64,6.08,6.08,6.08,6.08,6.08,6.08,5.37,
	      5.37,5.37,5.37,5.95,5.95,7.4,7.4,7.4,7.4,7.4,7.39,7.39,6.59,6.59,
	      6.59,6.54,6.54,8.31,8.31,8.31,8.31,8.31,8.31,8.31, 8.31, 8.31, 8.31,
          8.32, 8.32, 8.79, 8.79, 8.79, 8.79, 8.79, 8.79, 8.79, 8.31, 8.31, 8.31,
          8.31]
    dates = [Dates.DateTime("2006-01-01") + Dates.Month(ii) for ii in 0:157]
    p[:demandCharge] = Dict(zip(dates, prices))
    return p
end

function design_carbon!(p::Dict)
    # kg/MW -> ton/kW
    p[:carbon_units] = 1e-6
    # ton/mmbtu
    p[:carbonIntensityGas] = 14.46*44/12*1e-3;
    # carbon intenstiy gas - according to this
    # https://www.epa.gov/energy/greenhouse-gases-equivalencies-calculator-calculations-and-references

     # $/ton
    p[:carbonPrice] = 100;
end


#!/usr/bin/env julia
# del2z <delta.z@aliyun.com>

module Embedding

using ..Ant: Polar, Model
export nothing

struct WordRep{T <: Union{Polar, AbstractFloat}} <: Model
    size::Integer
    dim::Integer
    id2word::Vector{String}
    word2id::Dict{String, Integer}
    embedding::Matrix{T}
end

function WordRep(fname::String)
end

function WordRep(wordlist::Vector{String})
end

include("word2vec.jl")
include("fasttext.jl")
include("glove.jl")

end # module


@enum PlatformID::UInt16 begin
    UNICODE   = 0
    MACINTOSH = 1
    ISO       = 2
    WINDOWS   = 3
    CUSTOM    = 4
end

@enum EncodingUnicode::UInt16 begin
    UNICODE_1_0                 = 0
    UNICODE_1_1                 = 1
    ISO_IEC_10646               = 2
    UNICODE_2_0_BMP             = 3
    UNICODE_2_0_FULL            = 4
    UNICODE_VARIATION_SEQUENCES = 5
    UNICODE_FULL                = 6
end

abstract type CmapSubtable end

struct SequentialMapGroup
    char_range::UnitRange{UInt32}
    start_glyph_id::UInt32
end

const ManyToOneRangeMappings = SequentialMapGroup

struct SegmentedCoverage <: CmapSubtable
    groups::Vector{SequentialMapGroup}
end

struct ByteEncodingTable <: CmapSubtable
    glyph_id_array::Vector{UInt8}
end

struct EncodingRecord
    platform_id::PlatformID
    encoding_id::UInt16
    subtable_offset::UInt32
end

struct CharToGlyph
    encoding_records::Vector{EncodingRecord}
    tables::Dict{Int,CmapSubtable}
end

function Base.parse(io::IO, table_mappings, ::Type{CharToGlyph})
    skip(io, 2)
    ntables = read(io, UInt16)
    records = map(1:ntables) do _
        EncodingRecord(
            PlatformID(read(io, UInt16)),
            [read(io, T) for T in fieldtypes(EncodingRecord)[2:3]]...
        )
    end
    d = Dict()
    foreach(records) do rec
        seek(io, table_mappings["cmap"].offset + rec.subtable_offset)
        format = read(io, UInt16)
        table = if format == 0
            skip(io, 4)
            ByteEncodingTable([read(io, UInt8) for _ in 1:256])
        elseif format == 12
            skip(io, 10)
            ngroups = read(io, UInt32)
            groups = map(1:ngroups) do i
                SequentialMapGroup(
                    read(io, UInt32):read(io, UInt32),
                    read(io, UInt32),
                )
            end
            SegmentedCoverage(groups)
        else
            return
        end
        d[format] = table
    end
    CharToGlyph(records, d)
end


# Tudor Berariu, 2015

module Miners

using Scenarios;

export Gatherers;

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

#= Global constants =#

const HEIGHT           = 12;
const WIDTH            = 12;
const AGENTS_NO        = 8;
const WAREHOUSES_NO    = 1;
const MINES_NO         = 1;
const DISTANCE         = 6;
const RANGE            = 15;
const AGENT_RANGE      = 2.5;

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

#= Type aliases =#

typealias CellInfo    Uint8;
typealias Signal      Uint8;
typealias AgentState  Array{Uint8, 1};
typealias Action      Uint8;

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

#= CellInfo =#

#=
    +---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |N/S|
    +---+---+---+---+---+---+---+---+
=#
const NOTHING   = convert(CellInfo, 0);
const SOMETHING = convert(CellInfo, 1);

isNothing(cell::CellInfo)   = cell == 0;
isSomething(cell::CellInfo) = cell > 0;

#=
    Entity info
    +---+---+---+---+---+---+---+---+
    |   |   |   |   | W | B | A | 1 |
    +---+---+---+---+---+---+---+---+
=#
const AGENT    = SOMETHING | convert(CellInfo, 1<<1);
const BUILDING = SOMETHING | convert(CellInfo, 1<<2);
const WALL     = SOMETHING | convert(CellInfo, 1<<3);

isAgent(cell::CellInfo)    = (cell & AGENT) == AGENT;
isBuilding(cell::CellInfo) = (cell & BUILDING) == BUILDING;
isWall(cell::CellInfo)     = (cell & WALL) == WALL;

#=
    Agent info
    +---+---+---+---+---+---+---+---+
    |   |   | E | L | 0 | 0 | 1 | 1 |
    +---+---+---+---+---+---+---+---+
=#

const LOADED = SOMETHING | AGENT | convert(CellInfo, 1<<4)
const EMPTY  = SOMETHING | AGENT | convert(CellInfo, 1<<5)

isLoaded(agent::CellInfo) = (agent & LOADED) == LOADED;
isEmpty(agent::CellInfo)  = (agent & EMPTY) == EMPTY;

#=
    Building info
    +---+---+---+---+---+---+---+---+
    |   |   | M | W | 0 | 1 | 0 | 1 |
    +---+---+---+---+---+---+---+---+
=#

const WAREHOUSE = SOMETHING | BUILDING | convert(CellInfo, 1<<4);
const MINE      = SOMETHING | BUILDING | convert(CellInfo, 1<<5);

isWarehouse(building::CellInfo) = (building & WAREHOUSE) == WAREHOUSE;
isMine(building::CellInfo)      = (building & MINE     ) == MINE;

const CellInfoToChar = Dict{CellInfo, Char}(EMPTY     => char('\u26c4'),
                                            LOADED    => char('\u26c7'),
                                            WAREHOUSE => char('\u26ea'),
                                            MINE      => char('\u233e'),
                                            NOTHING   => ' ',
                                            WALL      => char('\u2588'));

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

#=
    Actions
=#

const DO_NOTHING     = convert(Action, 0) ;              # 00000000

const DO_MOVE        = convert(Action, 1<<0);            # ----0001
const DO_LOAD        = convert(Action, 1<<1);            # 00000010
const DO_UNLOAD      = convert(Action, 1<<2);            # 00000100
const DO_RANDOM_MOVE = convert(Action, 1<<3);            # 00001000

const UP             = DO_MOVE | convert(Action, 1<<4);  # 00010001
const DOWN           = DO_MOVE | convert(Action, 1<<5);  # 00100001
const LEFT           = DO_MOVE | convert(Action, 1<<6);  # 01000001
const RIGHT          = DO_MOVE | convert(Action, 1<<7);  # 10000001

const UP_LEFT        = UP   | LEFT ;                     # 01010001
const UP_RIGHT       = UP   | RIGHT;                     # 10010001
const DOWN_LEFT      = DOWN | LEFT ;                     # 01100001
const DOWN_RIGHT     = DOWN | RIGHT;                     # 10100001

const moveActions = [UP, DOWN, LEFT, RIGHT,
                     UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT];

const validActions = [DO_NOTHING,
                      DO_LOAD, DO_UNLOAD, #DO_RANDOM_MOVE,
                      UP, DOWN, LEFT, RIGHT,
                      UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT];

isMoveAction(a::Action) = (a & DO_MOVE) == DO_MOVE;

δy(a::Action) = (a & UP)   > DO_MOVE ? -1 : (a & DOWN)  > DO_MOVE ? 1 : 0;
δx(a::Action) = (a & LEFT) > DO_MOVE ? -1 : (a & RIGHT) > DO_MOVE ? 1 : 0;

nextCell(row::Integer, column::Integer, action::Action) =
    min(HEIGHT, max(1, row    + δy(action))),
    min(WIDTH,  max(1, column + δx(action)));

const ActionToChar = Dict{Action, Char}(DO_NOTHING     => char('\u2716'),
                                        DO_LOAD        => char('\u27f0'),
                                        DO_UNLOAD      => char('\u27f1'),
                                        DO_RANDOM_MOVE => char('?'),
                                        UP             => char('\u21d1'),
                                        DOWN           => char('\u21d3'),
                                        LEFT           => char('\u21d0'),
                                        RIGHT          => char('\u21d2'),
                                        UP_LEFT        => char('\u21d6'),
                                        UP_RIGHT       => char('\u21d7'),
                                        DOWN_LEFT      => char('\u21d9'),
                                        DOWN_RIGHT     => char('\u21d8'))

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

#= Signal =#

#=
    +---+---+---+---+---+---+---+---+
    | MINE SIGNAL   | DEPOSIT SIGNAL|
    +---+---+---+---+---+---+---+---+
=#

const NO_SIGNAL = convert(Signal, 0);
const NORTH     = convert(Signal, 1<<0);
const SOUTH     = convert(Signal, 1<<1);
const EAST      = convert(Signal, 1<<2);
const WEST      = convert(Signal, 1<<3);

const R4      = convert(Signal, 1 | 1<<1 | 1<<2 | 1<<3);

signalInfo(s::Signal, oldS::Signal) =
    ( s== NO_SIGNAL) ? (oldS & R4) : ((s<<4) | s);

const SignalToChar = Dict{Signal, Char}(NO_SIGNAL    => char(' '),
                                        WEST         => char('\u2190'),
                                        NORTH        => char('\u2191'),
                                        EAST         => char('\u2192'),
                                        SOUTH        => char('\u2193'),
                                        NORTH | WEST => char('\u2196'),
                                        NORTH | EAST => char('\u2197'),
                                        SOUTH | WEST => char('\u2199'),
                                        SOUTH | EAST => char('\u2198'));

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

#= Cartesian stuff =#

const ROW    = 1;
const COLUMN = 2;
const RC = [ROW, COLUMN];

sqEuclid(r1, c1, r2, c2) = (r1 - r2) * (r1 - r2) + (c1 - c2) * (c1 - c2);

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

type GlobalState
    board::Array{CellInfo, 2}
    warehouses::Array{Int64, 2}
    mines::Array{Int64, 2}
    agents::Array{Int64, 2}
    wSignal::Array{Signal, 2}
    mSignal::Array{Signal, 2}
    agentsMap::Array{CellInfo, 2}
end

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

function placeBuildings()
    assert(WAREHOUSES_NO == MINES_NO);

    warehouses = zeros(Int64, 2, WAREHOUSES_NO);
    mines = zeros(Int64, 2, MINES_NO);

    notOver = true;
    idx = 0;

    const MAX_ATTEMPTS = 10;

    while idx < WAREHOUSES_NO
        attemptNo = 0;
        found = false
        while attemptNo < MAX_ATTEMPTS && found == false
            attemptNo += 1;
            rw = rand(2:HEIGHT-1);
            cw = rand(2:WIDTH-1);
            isGood = true;
            for i = 1:idx
                if maximum(abs(warehouses[RC,i] - [rw, cw])) <=  DISTANCE ||
                    maximum(abs(mines[RC,i] - [rw, cw])) <= DISTANCE
                    isGood = false;
                    break;
                end
            end
            if isGood == false
                continue
            end
            p = filter(crd -> (crd[1] > 1) && (crd[1] < HEIGHT) &&
                       (crd[2] > 1) && (crd[2] < WIDTH) &&
                       (max(abs(crd[1]-rw), abs(crd[2]-cw)) == DISTANCE),
                       [(r, c) for r in rw-DISTANCE:rw+DISTANCE,
                        c in cw-DISTANCE:cw+DISTANCE]);
            if length(p) == 0
                continue
            end
            crd = rand(p);
            rm = crd[1];
            cm = crd[2];
            for i = 1:idx
                if maximum(abs(warehouses[RC,i] - [rm, cm])) <=  DISTANCE ||
                    maximum(abs(mines[RC,i] - [rm, cm])) <= DISTANCE
                    isGood = false;
                    break;
                end
            end
            if isGood == false
                continue;
            end
            found = true
            idx = idx + 1;
            warehouses[RC, idx] = [rw, cw];
            mines[RC, idx] = [rm, cm];
        end
        if found == false
            warehouses = zeros(Int64, 2, WAREHOUSES_NO);
            mines = zeros(Int64, 2, MINES_NO);
            idx = 0;
        end
    end

    return warehouses, mines;
end

function computeSignal(board::Array{CellInfo,2},
                       warehouses::Array{Int64,2}, mines::Array{Int64,2})
    mSignal = fill(NO_SIGNAL, size(board));
    wSignal = fill(NO_SIGNAL, size(board));
    for r in 1:HEIGHT
        for c in 1:WIDTH
            #-- Warehouses
            bestDistance = Inf
            wIdx = 0
            for w in 1:WAREHOUSES_NO
                d = sqEuclid(warehouses[ROW, w], warehouses[COLUMN, w], r, c);
                if d < bestDistance
                    bestDistance = d;
                    wIdx = w;
                end
            end
            if bestDistance <= RANGE * RANGE
                if r < warehouses[ROW, wIdx]
                    wSignal[r,c] = wSignal[r,c] | SOUTH;
                elseif r > warehouses[ROW, wIdx]
                    wSignal[r,c] = wSignal[r,c] | NORTH;
                end
                if c < warehouses[COLUMN, wIdx]
                    wSignal[r,c] = wSignal[r,c] | EAST;
                elseif c > warehouses[COLUMN, wIdx]
                    wSignal[r,c] = wSignal[r,c] | WEST;
                end
            end
            #-- Mines
            bestDistance = Inf;
            mIdx = 0;
            for m in 1:MINES_NO
                d = sqEuclid(mines[ROW, m], mines[COLUMN, m], r, c);
                if d < bestDistance
                    bestDistance = d;
                    mIdx = m;
                end
            end
            if bestDistance <= RANGE * RANGE
                if r < mines[ROW, mIdx]
                    mSignal[r,c] = mSignal[r,c] | SOUTH;
                elseif r > mines[ROW, mIdx]
                    mSignal[r,c] = mSignal[r,c] | NORTH;
                end
                if c < mines[COLUMN, mIdx]
                    mSignal[r,c] = mSignal[r,c] | EAST;
                elseif c > mines[COLUMN, mIdx]
                    mSignal[r,c] = mSignal[r,c] | WEST;
                end
            end
        end
    end
    return wSignal, mSignal;
end

function initialState()
    board = fill(NOTHING, HEIGHT, WIDTH);
    # Put warehouses and mines
    warehouses, mines = placeBuildings();
    for w in 1:WAREHOUSES_NO
        board[warehouses[ROW, w], warehouses[COLUMN, w]] = WAREHOUSE;
    end
    for m in 1:MINES_NO
        board[mines[ROW, m], mines[COLUMN, m]] = MINE;
    end
    # Put agents
    agents = zeros(Int64, 2, AGENTS_NO);
    agentsMap = zeros(Int64, HEIGHT, WIDTH);
    for a in 1:AGENTS_NO
        agents[ROW, a]    = rand(1:HEIGHT)
        agents[COLUMN, a] = rand(1:WIDTH)
        while (board[agents[ROW, a], agents[COLUMN, a]] != NOTHING)
            agents[ROW, a]    = rand(1:HEIGHT)
            agents[COLUMN, a] = rand(1:WIDTH)
        end
        board[agents[ROW, a], agents[COLUMN, a]] = EMPTY;
        agentsMap[agents[ROW, a], agents[COLUMN, a]] = a;
    end
    # Compute signals
    wSignal, mSignal = computeSignal(board, warehouses, mines);
    # Pack everything
    return GlobalState(board, warehouses, mines, agents, wSignal, mSignal,
                       agentsMap);
end

function perceiveMap(gs::GlobalState,
                     a::Int64,
                     oldState::AgentState = [NO_SIGNAL, NO_SIGNAL])
    const diameter = trunc(Int64, AGENT_RANGE) * 2 + 1;
    const mid = trunc(Int64, AGENT_RANGE) + 1;
    const row = gs.agents[ROW,a];
    const col = gs.agents[COLUMN,a];

    state = fill(NOTHING, diameter, diameter);

    for c in 1:diameter, r in 1:diameter
        if sqEuclid(r, c, mid, mid) <= AGENT_RANGE * AGENT_RANGE
            state[r, c] =
                ((1 <= col-mid+c <= WIDTH) && (1 <= row-mid+r <= HEIGHT)) ?
            gs.board[row-mid+r,col-mid+c] : WALL
        end
    end

    const wSignal = signalInfo(gs.wSignal[gs.agents[ROW,a],gs.agents[COLUMN,a]],
                               oldState[1]);
    const mSignal = signalInfo(gs.mSignal[gs.agents[ROW,a],gs.agents[COLUMN,a]],
                               oldState[2]);
    return vcat(wSignal, mSignal, state[:]);
end

function neighbours(gs::GlobalState, ag::Int64)
    const row = gs.agents[ROW,ag];
    const col = gs.agents[COLUMN,ag];
    const mid = trunc(Int64, AGENT_RANGE) + 1;
    const diameter = trunc(Int64, AGENT_RANGE) * 2 + 1;

    nbs = Array(Int64, 0);

    for c in 1:diameter, r in 1:diameter
        if sqEuclid(r, c, mid, mid) <= AGENT_RANGE * AGENT_RANGE
            if ((1 <= col-mid+c <= WIDTH) && (1 <= row-mid+r <= HEIGHT))
                cellInfo = gs.agentsMap[row-mid+r,col-mid+c];
                if cellInfo > 0 && cellInfo != ag
                    push!(nbs, gs.agentsMap[row-mid+r, col-mid+c]);
                end
            end
        end
    end
    return nbs;
end

function sortAgents(gs::GlobalState, actions::Array{Action, 1})
    sortedAgents = zeros(Int64, AGENTS_NO);
    crtIdx = 1;

    isBlocked = fill(true, AGENTS_NO);
    waitsOn = zeros(Int64, AGENTS_NO);

    # First put in the finall list all agents that do not wait on others to move
    for i in 1:AGENTS_NO
        if isMoveAction(actions[i])
            const crtRow = gs.agents[ROW,i];
            const crtColumn = gs.agents[COLUMN,i];
            nextRow, nextColumn = nextCell(crtRow, crtColumn, actions[i]);
            if (((nextRow != crtRow) || (nextColumn != crtColumn)) &&
                isAgent(gs.board[nextRow, nextColumn]) &&
                isBlocked[gs.agentsMap[nextRow, nextColumn]])
                waitsOn[i] = gs.agentsMap[nextRow, nextColumn];
                continue;
            end
        end
        sortedAgents[crtIdx] = i;
        crtIdx = crtIdx + 1;
        isBlocked[i] = false;
    end

    # Put agents that wait for agents already put in the final list
    foundOne = true;
    while foundOne && crtIdx <= AGENTS_NO
        foundOne = false;
        for i in find(isBlocked)
            if isBlocked[waitsOn[i]] == false
                foundOne = true
                sortedAgents[crtIdx] = i;
                crtIdx = crtIdx + 1;
                isBlocked[i] = false;
            end
        end
    end
    return vcat(sortedAgents[1:(crtIdx-1)], find(isBlocked));
end

function doLoad!(gs::GlobalState, ag::Int64)
    row = gs.agents[ROW, ag];
    col = gs.agents[COLUMN, ag];
    if isEmpty(gs.board[row, col])
        for m in 1:MINES_NO
            if sqEuclid(gs.mines[ROW,m], gs.mines[COLUMN,m], row, col) <= 2
                gs.board[row,col] = LOADED;
                return
            end
        end
    end
    nothing
end

function doUnload!(gs::GlobalState, ag::Int64)
    row = gs.agents[ROW, ag];
    col = gs.agents[COLUMN, ag];
    if isLoaded(gs.board[row, col])
        for w in 1:WAREHOUSES_NO
            if sqEuclid(gs.warehouses[ROW,w], gs.warehouses[COLUMN,w],
                        row, col) <= 2
                gs.board[row, col] = EMPTY;
                return w;
            end
        end
    end
    return 0;
end

function doRandomMove!(gs::GlobalState, ag::Int64)
    doMove!(gs, ag, moveActions[rand(1:end)])
    nothing
end

function doMove!(gs::GlobalState, ag::Int64, action::Action)
    const row = gs.agents[ROW, ag];
    const column = gs.agents[COLUMN, ag];
    const newRow, newColumn = nextCell(row, column, action);
    if gs.board[newRow, newColumn] == NOTHING
        gs.board[newRow, newColumn] = gs.board[row, column];
        gs.board[row, column] = NOTHING;
        gs.agents[ROW, ag] = newRow;
        gs.agents[COLUMN, ag] = newColumn;
        gs.agentsMap[row, column] = NOTHING;
        gs.agentsMap[newRow, newColumn] = ag;
    end
    nothing
end

reward(dropCount::Int64) = 2.5 ^ dropCount - 1;

function sizeHint()
    x = zero(Int64)
    const mid = trunc(Int64, AGENT_RANGE) + 1;
    const diameter = trunc(Int64, AGENT_RANGE) * 2 + 1;
    const cells = [(sqEuclid(r,c,mid,mid) <= AGENT_RANGE * AGENT_RANGE) ? 1 : 0
                   for r in 1:diameter, c in 1:diameter];
    const cellsNo = sum(cells)-1;
    for i = 1:AGENTS_NO-1
        l = one(Int64);
        for j = 0:i
            l *= 2 * (cellsNo - j);
        end
        x += l;
    end
    x *= 16 * 4 * (cellsNo - AGENTS_NO + 1);
    x += cellsNo * 16 * 4;
    println(x);
    return x;
end

function doActions!(gs::GlobalState, actions::Array{Action, 1})
    rewards        = zeros(Float64, AGENTS_NO);
    rewarders      = zeros(Int64, AGENTS_NO);
    warehouseDrops = zeros(Int64,   WAREHOUSES_NO);

    for ag in sortAgents(gs, actions)
        if actions[ag] != DO_NOTHING
            if actions[ag] == DO_LOAD
                doLoad!(gs, ag);
            elseif actions[ag] == DO_UNLOAD
                wIdx = doUnload!(gs, ag);
                if wIdx > 0
                    warehouseDrops[wIdx] = warehouseDrops[wIdx] + 1;
                    rewarders[ag] = wIdx;
                end
            elseif actions[ag] == DO_RANDOM_MOVE
                doRandomMove!(gs, ag);
            else
                doMove!(gs, ag, actions[ag]);
            end
        end
    end

    for ag in 1:AGENTS_NO
        if rewarders[ag] > 0
            if warehouseDrops[rewarders[ag]] > 0
                rewards[ag] = reward(warehouseDrops[rewarders[ag]]);
            end
        end
    end

    return rewards, warehouseDrops;
end

function printState(state::AgentState)
    wSignal = state[1];
    crtWSignal = wSignal>>4;
    oldWSignal = (wSignal<<4)>>4;
    mSignal = state[2];
    crtMSignal = mSignal>>4;
    oldWSignal = (mSignal<<4)>>4;
    println("W: $(SignalToChar[crtWSignal]) - $(SignalToChar[crtWSignal])");
    println("M: $(SignalToChar[crtMSignal]) - $(SignalToChar[crtMSignal])");
    l = int(sqrt(length(state[3:end])));
    printBoard(reshape(state[3:end], l, l));
end

function printSignalMap(signalMap::Array{Signal, 2})
    for col in 1:(size(signalMap,2)+2)
        print(char('\u2588'));
    end
    println();
    for row in 1:size(signalMap,1)
        print(char('\u2588'));
        for col in 1:size(signalMap,2)
            print(SignalToChar[signalMap[row, col]]);
        end
        println(char('\u2588'));
    end
    for col in 1:(size(signalMap,2)+2)
        print(char('\u2588'));
    end
    println();
end

function printBoard(board::Array{CellInfo, 2})
    for col in 1:(size(board,2)+2)
        print(char('\u2588'));
    end
    println();
    for row in 1:size(board,1)
        print(char('\u2588'));
        for col in 1:size(board,2)
            print(CellInfoToChar[board[row, col]]);
        end
        println(char('\u2588'));
    end
    for col in 1:(size(board,2)+2)
        print(char('\u2588'));
    end
    println();
end

Gatherers = Scenario{AgentState, Action}(AGENTS_NO, WAREHOUSES_NO,
                                         initialState, perceiveMap,
                                         validActions, doActions!,
                                         neighbours);

end


# Copyright 2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

""" The semantic enrichment algorithm for dataflow graphs.
"""
module SemanticEnrichment
export to_semantic_graph

using Base.Iterators: product
using Nullables

using LightGraphs, MetaGraphs

using Catlab.WiringDiagrams
using ..Doctrine
using ..Ontology
using ..RawFlowGraphs

""" Convert a raw flow graph into a semantic flow graph.
"""
function to_semantic_graph(db::OntologyDB, raw::WiringDiagram)::WiringDiagram
  sem = WiringDiagram(to_semantic_ports(db, input_ports(raw)),
                      to_semantic_ports(db, output_ports(raw)))

  # Add boxes.
  to_substitute = Int[]
  for v in box_ids(raw)
    raw_box = box(raw, v)
    sem_box = to_semantic_graph(db, raw_box)
    @assert add_box!(sem, sem_box) == v
    if isa(raw_box, Box) && isa(sem_box, WiringDiagram)
      # If the raw box is atomic but the semantic box is a wiring diagram,
      # we should expand it by substitution. We defer the substitution until
      # later so that the box IDs on the wires remain valid.
      push!(to_substitute, v)
    end
  end

  # Add wires.
  # FIXME: If a raw box expands to a semantic box that is missing a port with
  # incoming or outoging wires in the raw graph (e.g., when an unannotated
  # keyword argument is passed), this logic will fail. We should detect this
  # situation and either raise an informative error or discard the wire.
  add_wires!(sem, wires(raw))

  # Perform deferred substitutions (see above).
  substitute!(sem, to_substitute)

  # Simplify the diagram by collapsing adjacent unannotated boxes.
  collapse_unannotated_boxes!(sem)

  return sem
end

function to_semantic_graph(db::OntologyDB, raw_box::Box{RawNode})::AbstractBox
  if isnull(raw_box.value.annotation)
    Box(nothing,
        to_semantic_ports(db, input_ports(raw_box)),
        to_semantic_ports(db, output_ports(raw_box)))
  else
    expand_annotated_box(db, raw_box)
  end
end

function to_semantic_ports(db::OntologyDB, ports::Vector{RawPort})
  [ isnull(port.annotation) ? nothing : expand_annotated_port(db, port)
    for port in ports ]
end

""" Expand a single annotated box from a raw flow graph.
"""
function expand_annotated_box(db::OntologyDB, raw_box::Box{RawNode})::WiringDiagram
  expand_annotated_box(db, raw_box, Val{raw_box.value.annotation_kind})
end

function expand_annotated_box(db::OntologyDB, raw_box::Box{RawNode},
                              ::Type{Val{FunctionAnnotation}})
  inputs = input_ports(raw_box)
  outputs = output_ports(raw_box)
  note = load_annotation(db, get(raw_box.value.annotation))::HomAnnotation
  f = WiringDiagram(inputs, outputs)
  v = add_box!(f, to_wiring_diagram(note.definition))
  add_wires!(f, ((input_id(f), i) => (v, get(port.annotation_index))
                 for (i, port) in enumerate(inputs)
                 if !isnull(port.annotation_index)))
  add_wires!(f, ((v, get(port.annotation_index)) => (output_id(f), i)
                 for (i, port) in enumerate(outputs)
                 if !isnull(port.annotation_index)))
  substitute!(f, v)
  return f
end

function expand_annotated_box(db::OntologyDB, raw_box::Box{RawNode},
                              ::Type{Val{ConstructAnnotation}})
  note = load_annotation(db, get(raw_box.value.annotation))::ObAnnotation
  to_wiring_diagram(construct(note.definition))
end

function expand_annotated_box(db::OntologyDB, raw_box::Box{RawNode},
                              ::Type{Val{SlotAnnotation}})
  note = load_annotation(db, get(raw_box.value.annotation))::ObAnnotation
  index = get(raw_box.value.annotation_index)
  to_wiring_diagram(note.slots[index])
end

""" Expand a single annotated port from a raw flow graph.
"""
function expand_annotated_port(db::OntologyDB, raw_port::RawPort)::Monocl.Ob
  note = load_annotation(db, get(raw_port.annotation))::ObAnnotation
  note.definition
end

""" Collapse adjacent unannotated boxes into single boxes.
"""
function collapse_unannotated_boxes!(diagram::WiringDiagram)
  # Find maximal groups of unannotated boxes.
  unannotated = filter(box_ids(diagram)) do v
    isnothing(box(diagram,v).value)
  end
  groups = group_blank_vertices(graph(diagram), unannotated)

  # Encapsulate the groups, including groups of size 1 because encapsulation
  # will simplify the ports.
  encapsulate!(diagram, groups, nothing)
  return diagram
end

""" Group adjacent blank vertices of a directed graph.
"""
function group_blank_vertices(graph::SimpleDiGraph, blank::Vector{Int})::Vector{Vector{Int}}
  # Create transitive closure of graph.
  closure = transitiveclosure(graph)
  has_path(u::Int, v::Int) = has_edge(closure, u, v)
  ancestors(v::Int) = inneighbors(closure, v)
  descendants(v::Int) = outneighbors(closure, v)

  # Initialize groups as singletons.
  graph = MetaDiGraph(graph)
  for v in blank
    set_prop!(graph, v, :vertices, [v])
  end
  get_group(v::Int) = get_prop(graph, v, :vertices)
  is_blank(v::Int) = has_prop(graph, v, :vertices)
  not_blank(v::Int) = !is_blank(v)

  # Definition: Two adjacent blank vertices are mergeable if their merger
  # does not introduce any new dependencies between non-blank vertices.
  # That is, if there is no directed path from non-blank vertex `v1` to
  # non-blank vertex `v2` before merging, there will not be one afterwards.
  #
  # This criterion holds iff it's *not* the case that
  #   there exists a non-blank ancestor of the child that isn't an ancestor of
  #   the parent and a non-blank descendant of the parent isn't a descendant of
  #   the child
  # (because if there was, merging would create a new dependency) iff
  #   each non-blank ancestor of the child is also an ancestor of the parent, or
  #   each non-blank descendant of the parent is also a descendent of the child.
  function is_mergable(u::Int, v::Int)
    (is_blank(u) && is_blank(v)) || return false
    parent, child = if has_edge(graph, u, v); (u, v)
      elseif has_edge(graph, v, u); (v, u)
      else return false end
    all(has_path(v, parent) for v in filter(not_blank, ancestors(child))) ||
      all(has_path(child, v) for v in filter(not_blank, descendants(parent)))
  end
  function merge_blank!(u::Int, v::Int)
    append!(get_group(min(u,v)), get_group(max(u,v)))
    merge_vertices_directed!(graph, [u,v])
    merge_vertices_directed!(closure, [u,v])
  end

  # Merge pairs of mergable vertices until there are no more mergable pairs.
  # The loop maintains the invariant that no two vertices less than the current
  # one are mergable.
  v = 1
  while v <= nv(graph)
    merged = false
    for u in all_neighbors(graph, v)
      if u > v && is_mergable(u, v)
        merge_blank!(u, v)
        merged = true
        break
      end
    end
    v += !merged
  end

  Vector{Int}[ get_group(v) for v in vertices(graph) if is_blank(v) ]
end

""" Merge the vertices into a single vertex, preserving edges.

Note: LightGraphs.merge_vertices! only supports undirected graphs.
"""
function merge_vertices_directed!(graph::AbstractGraph, vs::Vector{Int})
  @assert is_directed(graph)
  vs = sort(vs, rev=true)
  v0 = vs[end]
  for v in vs[1:end-1]
    for u in inneighbors(graph, v)
      if !(u in vs)
        add_edge!(graph, u, v0)
      end
    end
    for u in outneighbors(graph, v)
      if !(u in vs)
        add_edge!(graph, v0, u)
      end
    end
    rem_vertex!(graph, v)
  end
  v0
end

end