### Control the ego car in the occluded_crosswalk environment ###
# Each policy should implement its own version of propagate corresponding to the returned action type
function AutomotiveDrivingModels.propagate(veh::Vehicle, action::Float64, roadway::Roadway, Δt::Float64)
    x_ = veh.state.posG.x + veh.state.v*Δt + action*Δt^2/2
    if x_ <= veh.state.posG.x
        x_ = veh.state.posG.x
    end
    v_ = veh.state.v + action*Δt
    if v_ <= 0.
        v_ = 0.
    elseif v_ >= 8.0 #TODO parameterized
        v_ = 8.0
    end
    return VehicleState(VecSE2(x_, veh.state.posG.y, veh.state.posG.θ), roadway, v_)
end

"""
    The driver model to be evaluated, contains the policy and the sensor model. The action is an
    acceleration updated by the observe! function at each time step
"""
mutable struct AVDriver{A, B, O, P, U, S} <: DriverModel{Float64}
    update_freq::Int64
    tick::Int64
    env::CrosswalkEnv
    a::A# acceleration
    b::B # belief
    o::O
    ego::Vehicle
    sensor::S
    policy::P # replace by policy type
    updater::U
end

AutomotiveDrivingModels.get_name(model::AVDriver) = "AVDriver"
Base.rand(model::AVDriver) = model.a # deterministic policy

function AutomotiveDrivingModels.observe!(model::AVDriver,
                                          scene::EntityFrame{VehicleState, VehicleDef, Int64},
                                          roadway::Roadway,
                                          egoid::Int)
    # @assert scene[1].id == egoid
    model.o = measure(scene, model.sensor, model.env)
    b_ = update(model.updater, model.b, model.a, model.o)
    # f = open("timing.csv", "a")
    # write(f,"belief, $btime, $(scene.n - 1) \n")
    # close(f)
    model.b = deepcopy(b_)
    if model.tick % model.update_freq == 0
        model.a = action(model.policy, model.b) # policy
        # f = open("timing.csv", "a")
        # write(f, "action, $atime, $(scene.n - 1) \n")
        # close(f)
    end
    model.tick += 1
end

"""
    initial_ego(env::CrosswalkEnv, rng::AbstractRNG)
generate an initial state for the ego car, returns a Vehicle object
"""
function initial_ego(env::CrosswalkEnv, rng::AbstractRNG)
    x0 = env.params.start_pos
    y0 = 0. # parameterize
    v0 = rand(rng, Distributions.Uniform(6., env.params.speed_limit))
    # v0 = 8.

    return Vehicle(VehicleState(VecSE2(x0, y0, 0.0), env.roadway.segments[1].lanes[1], env.roadway, v0),
                   VehicleDef(), 1)
end

function reset_model!(model::AVDriver, ego::Vehicle)
    model.tick = 0.
    model.a = 0.
    model.o = Dict{Int64, SingleOCState}()
    # model.b = initialstate(model.policy.pomdp, model.sensor.n_ped)
    b0 = Dict{Int64, SingleOCDistribution}()
    b0[-1] = initialstate(policy.pomdp, ego.state)
    model.b = b0
    model.ego = ego
    reset_policy!(model.policy)
end


using IonSim
using QuantumOptics
using DifferentialEquations
using Distributions
using DataStructures

export simulate_with_ion_sim

# Note: IonSim seems to have problems with timescales other than 1e-6
timescale = 1e-6

function simulate_with_ion_sim(parameters, pulses, num_ions, b_field)
    #############################################
    # This function must return a dictionary of result values. The keys
    #   must represent each possible state, and the values must
    #   represent the probability of each state.
    # By convention, "S" and "D" are used for the bright and dark states,
    #   respectively. So the keys of the dictionary should look like:
    #   --> for num_ions == 1: "S", "D"
    #           e.g., Dict("S" => 0.1, "D" => 0.9)
    #   --> for num_ions == 2: "SS", "SD", "DS", "DD"
    #           e.g., Dict("SS" => 0.1, "SD" => 0.2, "DS" => 0.3, "DD" => 0.4)
    #   --> for num_ions == 3: "SSS", "SSD", "SDS", "SDD", etc.

    #############################################
    # Create and load the ions
    S = ["S-1/2", "S+1/2"]
    D = ["D-5/2", "D-3/2", "D-1/2" ,"D+1/2", "D+3/2", "D+5/2"]
    ions = Array{Ca40}(undef, num_ions)
    for i = 1:num_ions
        ions[i] = Ca40([S; D])
    end

    axial_frequency = parameters["TrapFrequencies.axial_frequency"]
    radial_frequency_1 = parameters["TrapFrequencies.radial_frequency_1"]
    radial_frequency_2 = parameters["TrapFrequencies.radial_frequency_2"]
    chain = LinearChain(
        ions=ions,
        com_frequencies=(x=radial_frequency_1, y=radial_frequency_2, z=axial_frequency),
        vibrational_modes=(;z=[1]))

    #############################################
    # Set up the lasers and the trap
    laser_pulses = Dict()
    for pulse_index in 1:length(pulses)
        pulse = pulses[pulse_index]
        if occursin("729G", pulse["dds_name"])
            laser = Laser(
                k=(x̂ + ẑ)/√2,
                ϵ=(x̂ - ẑ)/√2,
                Δ=pulse["freq"],
            )
            laser_pulses[laser] = pulse
        end
        # TODO: Add 729L1 and 729L2
    end

    lasers = Array{Laser}(undef, length(laser_pulses))
    for (i, (laser, pulse)) in enumerate(laser_pulses)
        lasers[i] = laser
    end

    trap = Trap(configuration=chain, B=b_field*1e-4, Bhat=ẑ, δB=0, lasers=lasers)
    mode = trap.configuration.vibrational_modes.z[1]

    for laser in lasers
        global_beam!(trap, laser)
    end

    #############################################
    # Determine the simulation time when the relevant lasers are on
    simulation_start_time = Inf
    simulation_stop_time = 0
    for laser in lasers
        pulse = laser_pulses[laser]
        simulation_start_time = min(simulation_start_time, pulse["time_on"] / timescale)
        simulation_stop_time = max(simulation_stop_time, pulse["time_off"] / timescale)
    end
    if isinf(simulation_start_time)
        simulation_start_time = 0
        simulation_stop_time = 0
    end
    simulation_total_time = simulation_stop_time - simulation_start_time
    simulation_tspan = range(simulation_start_time - (1e-3*timescale), simulation_stop_time + (1e-3*timescale), length=2)

    println("Total simulation time is $(simulation_total_time*timescale*1e6) μs")
    println("(start time = $(simulation_start_time*timescale*1e6) μs, stop time = $(simulation_stop_time*timescale*1e6) μs)")

    #############################################
    # Helpful functions
    function step_interval(t, t_begin, t_end)
        t >= t_begin && t < t_end ? 1 : 0
    end

    function project(solution, states...)
        real.(expect(ionprojector(trap, states...), solution))
    end

    #############################################
    # Set up the time-dependent E-field
    intensity_factor = 10^(-0.5) # this corresponds to pi time 3 μs
    pi_min = 3e-6
    for laser in lasers
        pulse = laser_pulses[laser]
        t_pi = pi_min * sqrt(intensity_factor / (pulse["amp"] * 10^(-1.5 * pulse["att"] / 10)))
        E = Efield_from_pi_time(t_pi, trap.Bhat, laser, ions[1], ("S-1/2", "D-1/2"))
        laser.E = t -> E * step_interval(t, pulse["time_on"] / timescale, pulse["time_off"] / timescale)
    end

    #############################################
    # Set up the time-dependent phase
    for laser in lasers
        pulse = laser_pulses[laser]
        laser.ϕ = t -> 2π * pulse["phase"]
    end

    #############################################
    # Run the simulation
    initial_state = undef
    if length(ions) == 1
        initial_state = ionstate(trap, "S-1/2")
    elseif length(ions) == 2
        initial_state = ionstate(trap, "S-1/2", "S-1/2")
    elseif length(ions) == 3
        initial_state = ionstate(trap, "S-1/2", "S-1/2", "S-1/2")
    end

    simulation_tstops = SortedSet{Float64}()
    for laser in lasers
        pulse = laser_pulses[laser]
        push!(simulation_tstops, pulse["time_on"] / timescale)
        push!(simulation_tstops, pulse["time_off"] / timescale)
    end
    simulation_tstops = collect(simulation_tstops)
    println("Calculated pulse start/stop times as $simulation_tstops")

    h = hamiltonian(trap, lamb_dicke_order=1, timescale=timescale, rwa_cutoff=1e5)
    @time tout, solution = timeevolution.schroedinger_dynamic(
        simulation_tspan,
        tensor(initial_state, fockstate(mode, 0)),
        h,
        callback=PresetTimeCallback(simulation_tstops, integrator -> return));
    solution = solution[end]

    #############################################
    # Measure the expectation values
    result = undef
    if length(ions) == 1
        result = Dict(
            "S" => sum([project(solution, state) for state=S]),
            "D" => sum([project(solution, state) for state=D]),
            )
    elseif length(ions) == 2
        result = Dict(
            "SS" => sum([project(solution, state1, state2) for state1=S, state2=S]),
            "SD" => sum([project(solution, state1, state2) for state1=S, state2=D]),
            "DS" => sum([project(solution, state1, state2) for state1=D, state2=S]),
            "DD" => sum([project(solution, state1, state2) for state1=D, state2=D]),
            )
    elseif length(ions) == 3
        result = Dict(
            "SSS" => sum([project(solution, state1, state2, state3) for state1=S, state2=S, state3=S]),
            "SSD" => sum([project(solution, state1, state2, state3) for state1=S, state2=S, state3=D]),
            "SDS" => sum([project(solution, state1, state2, state3) for state1=S, state2=D, state3=S]),
            "SDD" => sum([project(solution, state1, state2, state3) for state1=S, state2=D, state3=D]),
            "DSS" => sum([project(solution, state1, state2, state3) for state1=D, state2=S, state3=S]),
            "DSD" => sum([project(solution, state1, state2, state3) for state1=D, state2=S, state3=D]),
            "DDS" => sum([project(solution, state1, state2, state3) for state1=D, state2=D, state3=S]),
            "DDD" => sum([project(solution, state1, state2, state3) for state1=D, state2=D, state3=D]),
            )
    end

    #############################################
    # Apply projection noise and renormalize
    total_probability = 0
    for (state, probability) in result
        result[state] = mean(rand(Binomial(1, max(0, min(probability, 1))), 100))
        total_probability += result[state]
    end
    for (state, probability) in result
        result[state] = probability / total_probability
    end

    #############################################
    # Return the result
    println("Simulation results: $result")
    return result

end


using RRTMGP.Gases
using RRTMGP.GasConcentrations
using RRTMGP.GasOptics
using RRTMGP.ReferenceStates
using NCDatasets

function read_char_vec(ds, var_name)
  tmp = ds[var_name][:]
  return String[strip(join(tmp[:,i])) for i = 1:size(tmp, 2)]
end

read_gases(ds, var_name) = convert.(AbstractGas, read_char_vec(ds, var_name))

"""
    load_and_init(ds, ::Type{FT}, gases_prescribed::Vector{AbstractGas}) where {FT<:AbstractFloat}

Initialize the gas optics class with data. The calls look slightly different depending
  on whether the radiation sources are internal to the atmosphere (longwave) or external (shortwave)
"""
function load_and_init(ds, ::Type{FT}, gases_prescribed::Vector{AbstractGas}) where {FT<:AbstractFloat}

  # Reading the properties from the NetCDF file

  I = Int
  kmajor                          = Array{FT}(ds["kmajor"][:])
  key_species                     = Array{I}(ds["key_species"][:])
  band2gpt                        = Array{I}(ds["bnd_limits_gpt"][:])
  band_lims_wavenum               = Array{FT}(ds["bnd_limits_wavenumber"][:])
  gases_in_database               = read_gases(ds, "gas_names")
  gas_minor                       = read_gases(ds, "gas_minor")
  identifier_minor                = read_gases(ds, "identifier_minor")

  rayl_lower = haskey(ds,"rayl_lower") ? Array{FT}(ds["rayl_lower"][:]) : nothing
  rayl_upper = haskey(ds,"rayl_upper") ? Array{FT}(ds["rayl_upper"][:]) : nothing
  @assert haskey(ds,"rayl_lower") == haskey(ds,"rayl_upper")

  lower = GasOpticsVars{FT,I}(Array{I}(ds["minor_limits_gpt_lower"][:]),
                              Array{Bool}(ds["minor_scales_with_density_lower"][:]),
                              Array{Bool}(ds["scale_by_complement_lower"][:]),
                              Array{I}(ds["kminor_start_lower"][:]),
                              Array{FT}(ds["kminor_lower"][:]),
                              read_gases(ds, "scaling_gas_lower"),
                              read_gases(ds, "minor_gases_lower"))
  upper = GasOpticsVars{FT,I}(Array{I}(ds["minor_limits_gpt_upper"][:]),
                              Array{Bool}(ds["minor_scales_with_density_upper"][:]),
                              Array{Bool}(ds["scale_by_complement_upper"][:]),
                              Array{I}(ds["kminor_start_upper"][:]),
                              Array{FT}(ds["kminor_upper"][:]),
                              read_gases(ds, "scaling_gas_upper"),
                              read_gases(ds, "minor_gases_upper"))

  ref = ReferenceState(Array{FT}(ds["press_ref"][:]),
                       Array{FT}(ds["temp_ref"][:]),
                       FT(ds["press_ref_trop"][:]),
                       Array{FT}(ds["vmr_ref"][:]),
                       gases_prescribed,
                       gases_in_database)

  optical_props = OpticalPropsBase("GasOptics optical props", band_lims_wavenum, band2gpt)

  args = (gases_prescribed,
          gases_in_database,
          key_species,
          optical_props,
          kmajor,
          gas_minor,
          identifier_minor,
          lower,
          upper)

  if haskey(ds,"totplnk")
    totplnk     = Array{FT}(ds["totplnk"][:])
    planck_frac = Array{FT}(ds["plank_fraction"][:])
    kdist = get_k_dist_lw(totplnk, planck_frac, rayl_lower, rayl_upper, ref, args...)
  else
    solar_src = ds["solar_source"][:]
    kdist = get_k_dist_sw(solar_src, rayl_lower, rayl_upper, ref, args...)
  end
  return kdist

end


export AbstractPolyhedraOptimizer

struct PolyhedraOptSet{T, RepT <: Rep{T}} <: MOI.AbstractVectorSet
    rep::RepT
end
MOI.dimension(set::PolyhedraOptSet) = fulldim(set.rep)

function JuMP.build_constraint(error_fun::Function, func, set::Rep)
    return JuMP.BridgeableConstraint(
        JuMP.build_constraint(error_fun, func, PolyhedraOptSet(set)),
        PolyhedraToLPBridge)
end

abstract type AbstractPolyhedraOptimizer{T} <: MOI.AbstractOptimizer end

function MOI.copy_to(dest::AbstractPolyhedraOptimizer, src::MOI.ModelLike; kws...)
    return MOI.Utilities.automatic_copy_to(dest, src; kws...)
end
MOI.Utilities.supports_default_copy_to(optimizer::AbstractPolyhedraOptimizer, copy_names::Bool) = true

function MOI.add_variable(optimizer::AbstractPolyhedraOptimizer)
    return MOI.add_variable(optimizer.lphrep.model)
end
function MOI.add_variables(optimizer::AbstractPolyhedraOptimizer, n)
    return MOI.add_variables(optimizer.lphrep.model, n)
end

function MOI.supports(::AbstractPolyhedraOptimizer{T},
                      ::Union{MOI.ObjectiveSense,
                              MOI.ObjectiveFunction{MOI.SingleVariable},
                              MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}}) where T
    return true
end
function MOI.set(optimizer::AbstractPolyhedraOptimizer, ::MOI.ObjectiveSense,
                 sense::MOI.OptimizationSense)
    optimizer.objective_sense = sense
end
function MOI.set(optimizer::AbstractPolyhedraOptimizer{T}, ::MOI.ObjectiveFunction,
                 func::MOI.SingleVariable) where T
    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
            convert(MOI.ScalarAffineFunction{T}, func))
end
function MOI.set(optimizer::AbstractPolyhedraOptimizer, ::MOI.ObjectiveFunction,
                 func::MOI.ScalarAffineFunction)
    indices = [term.variable_index.value for term in func.terms]
    coefs = [term.coefficient for term in func.terms]
    optimizer.objective_func = sparsevec(indices, coefs, fulldim(optimizer.lphrep))
    optimizer.objective_constant = func.constant
end

function MOI.supports_constraint(::AbstractPolyhedraOptimizer{T},
                                 ::Type{MOI.ScalarAffineFunction{T}},
                                 ::Type{<:Union{MOI.EqualTo{T}, MOI.LessThan{T}}}) where T
    return true
end
function MOI.get(optimizer::AbstractPolyhedraOptimizer{T},
                 attr::Union{MOI.ConstraintFunction,
                             MOI.ConstraintSet},
                 ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},
                                         <:Union{MOI.EqualTo{T},
                                                 MOI.LessThan{T}}}) where T
    return MOI.get(optimizer.lphrep.model, attr, ci)
end
function MOI.add_constraint(optimizer::AbstractPolyhedraOptimizer{T},
                            func::MOI.ScalarAffineFunction{T},
                            set::Union{MOI.EqualTo{T}, MOI.LessThan{T}}) where T
    optimizer.feasible_set = nothing # Invalidated by the new constraint
    return MOI.add_constraint(optimizer.lphrep, func, set)
end
function MOI.supports_constraint(::AbstractPolyhedraOptimizer,
                                 ::Type{MOI.VectorOfVariables},
                                 ::Type{<:PolyhedraOptSet})
    return true
end
function MOI.add_constraint(optimizer::AbstractPolyhedraOptimizer,
                            vov::MOI.VectorOfVariables, rep::PolyhedraOptSet)
    if vov.variables != MOI.get(optimizer.lphrep.model, MOI.ListOfVariableIndices())
        error("Cannot only add VectorOfVariables polyhedra constraints with all variables in creation order.")
    end
    if optimizer.rep !== nothing
        error("Cannot only add one polyhedra constraint.")
    end
    optimizer.feasible_set = nothing # Invalidated by the new constraint
    optimizer.rep = rep.rep
    return MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(rep)}(1)
end

coefficient_type(::MOI.ModelLike) = Float64

# Inspired by `JuMP.set_optimizer`
function layered_optimizer(factory::JuMP.OptimizerFactory)
    optimizer = factory()
    T = coefficient_type(optimizer)
    if !MOIU.supports_default_copy_to(optimizer, false)
        universal_fallback = MOIU.UniversalFallback(_MOIModel{T}())
        optimizer = MOIU.CachingOptimizer(universal_fallback, optimizer)
    end
    optimizer = MOI.Bridges.full_bridge_optimizer(optimizer, T)
    MOI.Bridges.add_bridge(optimizer, PolyhedraToLPBridge{T})
    return optimizer, T
end

"""
    isempty(p::Rep, solver::JuMP.OptimizerFactory=Polyhedra.linear_objective_solver(p))

Check whether the polyhedron `p` is empty by using the solver `solver`.
"""
function Base.isempty(p::Rep, solver::Solver=Polyhedra.linear_objective_solver(p))
    N = fulldim(p)
    if N == -1
        if p isa VRepresentation || (p isa Polyhedron && fulldim(vrep(p)) == -1)
            # Empty V-representation means empty polyhedron
            return true
        else
            # Empty H-representation means the polyhedron the full space
            return false
        end
    end
    model, T = layered_optimizer(solver)
    x, cx = MOI.add_constrained_variables(model, PolyhedraOptSet(p))
    MOI.optimize!(model)
    term = MOI.get(model, MOI.TerminationStatus())
    if term == MOI.OPTIMAL
        return false
    elseif term == MOI.INFEASIBLE || term == MOI.INFEASIBLE_OR_UNBOUNDED
        # The problem has no objective so it cannot be unbounded so
        # the `MOI.INFEASIBLE_OR_UNBOUNDED` status is accepted
        return true
    else
        error("Cannot determine whether the polyhedron is empty or not because",
              " the linear program terminated with status $term.")
    end
end


import Base: bits, show

bits{N, ES, mode}(x::Sigmoid{N, ES, mode}) = bits(reinterpret(@UInt, x))[1:N]
function bits{N, ES, mode}(x::Sigmoid{N, ES, mode}, separator::AbstractString)
  #we're going to create this as a string array, then join() it at the end.
  stringarray = Vector{String}()

  bitstring = bits(x)

  push!(stringarray, bitstring[1:1])
  seek_idx = 2
  term_idx = N - (mode == :ubit) #store the index of termination.

  #calculate how many regime bits there are
  r_length = regimebits(x)

  push!(stringarray, bitstring[seek_idx:(seek_idx + r_length - 1)])
  seek_idx += r_length

  (seek_idx > term_idx) && @goto finish

  if (ES > 0)
    if ((seek_idx + ES) < term_idx)
      push!(stringarray, bitstring[seek_idx:seek_idx + ES - 1])
      seek_idx += ES
    else
      push!(stringarray, bitstring[seek_idx:term_idx])
      @goto finish
    end
  end

  push!(stringarray, bitstring[seek_idx:term_idx])

  @label finish

  if mode == :ubit
    push!(stringarray, bitstring[end:end])
  end

  return join(stringarray, separator)
end

function show{N, ES, mode}(io::IO, x::Sigmoid{N, ES, mode})

  if mode == :ubit
    #cast to Exact or ULP, depending on what the number looks like.
    if (reinterpret((@UInt), x) & increment(Sigmoid{N, ES, mode})) != 0
      print(io, "ULP{$N,$ES}")
    else
      print(io, "Exact{$N,$ES}")
    end
  else
    show(io, typeof(x))
  end

  innerval = reinterpret((@UInt), x)

  if N == 16
    print(io, "(0x", hex(innerval,16)[1:4], ")")
  elseif N == 32
    print(io, "(0x", hex(innerval,16)[1:8], ")")
  elseif N == 64
    print(io, "(0x", hex(innerval,16)[1:16], ")")
  else
    print(io, "(0b", bits(innerval)[1:N], ")")
  end
end

@generated function show{N, ES, mode}(io::IO, ::Type{Sigmoid{N, ES, mode}})
  if (mode == :guess)
    sig = "{$N,$ES}"
    :(print(io, "Posit", $sig))
  else
    sig = "{$N,$ES,$mode}"
    :(print(io, "Sigmoid", $sig))
  end
end


"""
	loadeop(filein)
(Download &) read EOP data (C04)

**Input**
* filein: either URL to EOP C04 or file name with downloaded EOP C04 parameters

**Output**
* DataFrame with DateTime, and all columns of the input file

**Example**
```
# Download data automatically
eop = loadeop("http://hpiers.obspm.fr/iers/eop/eopc04/eopc04_IAU2000.62-now");
# Or read data from already downloaded file
eop = loadeop("/test/input/eop_parameters.c04");
```
"""
function loadeop(filein::String)
	# Download file if necessary
	downto = downfile(filein);
	if !isempty(downto)
		channels = eopchannels(downto);
		dataall = readdlm(downto,skipstart=14)
		eopfile = DataFrame(datetime=FileTools.mat2time(dataall[:,1:3]));
		for (i,v) in enumerate(channels)
			eopfile[v] = dataall[:,i+3];
		end
		# delete downloaded file
		delfile(filein);
		return eopfile;
	else
		return DataFrame(datetime=0);
	end
end

"""
	downfile(filein::String)
Auxiliary function for downloading of files on internet. The downloaded files
will be stored in a temporary file
"""
function downfile(filein::String)
	downto = "tempdownfile.down";
	if occursin("http:",filein) || occursin("ftp:",filein)
		try
			download(filein,downto)
		catch
			downto = "";
		end
		return downto;
	else
		return filein;
	end
end

"""
	delfile(filein)
Delete file IF downloaded
"""
function delfile(filein::String)
	if occursin("http:",filein) || occursin("ftp:",filein)
		if isfile("tempdownfile.down")
			rm("tempdownfile.down")
		end
	end
end

"""
	eopchannels(filein)
Get EOP header=channels
"""
function eopchannels(filein::String)
	channels = Array{Symbol,1}();
	open(filein,"r") do fid
		row = "";
		for i in 1:11
			row = readline(fid);
		end
		temp = split(row,"  ");
		for i in temp
			if (i != "") && (i != "Date")
				push!(channels,Symbol(replace(i," "=>"")));
			end
		end
	end
	return channels;
end


import Images
using Color, FixedPointNumbers, Base.Test

const writedir = joinpath(tempdir(), "Images")

if !isdir(writedir)
    mkdir(writedir)
end

a = rand(2,2)
aa = convert(Array{Ufixed8}, a)
fn = joinpath(writedir, "2by2.png")
Images.imwrite(a, fn)
b = Images.imread(fn)
@test convert(Array, b) == aa
Images.imwrite(aa, fn)
b = Images.imread(fn)
@test convert(Array, b) == aa
aaimg = Images.grayim(aa)
open(fn, "w") do file
    writemime(file, "image/png", aaimg)
end
b = Images.imread(fn)
@test b == aaimg
aa = convert(Array{Ufixed16}, a)
Images.imwrite(aa, fn)
b = Images.imread(fn)
@test convert(Array, b) == aa
aa = Ufixed12[0.6 0.2;
              1.4 0.8]
open(fn, "w") do file
    writemime(file, "image/png", Images.grayim(aa))
end
b = Images.imread(fn)
@test Images.data(b) == Ufixed8[0.6 0.2;
                                1.0 0.8]

# test writemime's use of restrict
abig = Images.grayim(rand(Uint8, 1024, 1023))
fn = joinpath(writedir, "big.png")
open(fn, "w") do file
    writemime(file, "image/png", abig)
end
b = Images.imread(fn)
@test Images.data(b) == convert(Array{Ufixed8,2}, Images.data(Images.restrict(abig, (1,2))))

# More writemime tests
a = Images.colorim(rand(Uint8, 3, 2, 2))
fn = joinpath(writedir, "2by2.png")
open(fn, "w") do file
    writemime(file, "image/png", a)
end
b = Images.imread(fn)
@test Images.data(b) == Images.data(a)

abig = Images.colorim(rand(Uint8, 3, 1021, 1026))
fn = joinpath(writedir, "big.png")
open(fn, "w") do file
    writemime(file, "image/png", abig)
end
b = Images.imread(fn)
@test Images.data(b) == convert(Array{RGB{Ufixed8},2}, Images.data(Images.restrict(abig, (1,2))))

using Color
datafloat = reshape(linspace(0.5, 1.5, 6), 2, 3)
dataint = iround(Uint8, 254*(datafloat .- 0.5) .+ 1)  # ranges from 1 to 255
# build our colormap
b = RGB(0,0,1)
w = RGB(1,1,1)
r = RGB(1,0,0)
cmaprgb = Array(RGB{Float64}, 255)
f = linspace(0,1,128)
cmaprgb[1:128] = [(1-x)*b + x*w for x in f]
cmaprgb[129:end] = [(1-x)*w + x*r for x in f[2:end]]
img = Images.ImageCmap(dataint, cmaprgb)
Images.imwrite(img,joinpath(writedir,"cmap.jpg"))


using DocStringExtensions,
      FillArrays,
      ForwardDiff,
      LinearAlgebra,
      RecipesBase,
      Reexport,
      UnPack

@reexport using MathematicalSystems

import CommonSolve: solve, init, solve!


type IC <: InfoCrit
   likelihood ::Float64
   df         ::Float64
   method     ::ASCIIString
   crterm     ::Vector{Float64}
   value      ::Float64
end


__precompile__(true)
module ChaoticMaps

#using Reexport
########################################
# Common structures used by all the maps.
########################################
include("InformationStruct.jl")
export Info

##################################################################
# Individual maps (each map module may contain several submodules)
##################################################################
include("Logistic.jl")
export Logistic

end


@testset "132.palindrome-partitioning-ii.jl" begin
    @test partition_ps132("aab") == 1
    @test partition_ps132("a") == 0
    @test partition_ps132("ab") == 1
    @test partition_ps132(
        "iquvgtinxldylufwujdosgtqogqdibxeygwnbqbcuyxydkytolhvowhishkvukqoybxmdjajqtdvttmicmlmwerqhpheeckxckzuixsmoniyasaqxdwirntgebirlvzttabegwdseluyiktjglbsxivwkqowwtqbrciowkmdvhfsoslcspnukxxnsgbvpxddqqjkdzki",
    ) == 178
end


function StructuredQueries._collect(src::AbstractTable, q::StructuredQueries.SelectNode)
    res = default(src)
    for h in q.helpers
        apply!(res, h, src)
    end
    return res
end

function apply!(res, h::SQ.SelectHelper, src)::Void
    res_field, f, arg_fields, = SQ.parts(h)
    row_itr = eachrow(src, arg_fields...)
    inner_eltypes = map(
        eltype,
        eltypes(src, arg_fields...)
    )

    # TODO: check for non-leaf types and follow slow path
    T = Core.Inference.return_type(f, (Tuple{inner_eltypes...},))
    res_column = NullableVector{T}()

    _apply!(res_column, h, row_itr)
    res[res_field] = res_column
    return
end

function _apply!(res_column, h::SQ.SelectHelper, row_itr)::Void
    T = eltype(res_column)
    f = h.f
    for row in row_itr
        if hasnulls(row)
            push!(res_column, Nullable{T}())
        else
            v = f(map(unsafe_get, row))
            push!(res_column, v)
        end
    end
    return
end


using Test
using LinearAlgebra
using Random
using Strided
using Strided: StridedView, UnsafeStridedView

Random.seed!(1234)

@testset "construction of $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T1 in (Float32, Float64, Complex{Float32}, Complex{Float64})
        A1 = randn(T1, (60,60))
        B1 = SV(A1)
        for op1 in (identity, conj, transpose, adjoint)
            if op1 == transpose || op1 == adjoint
                @test op1(A1) == op1(B1) == SV(op1(A1))
            else
                @test op1(A1) == op1(B1)
            end
            for op2 in (identity, conj, transpose, adjoint)
                @test op2(op1(A1)) == op2(op1(B1))
            end
        end

        A2 = view(A1, 1:36, 1:20)
        B2 = SV(A2)
        for op1 in (identity, conj, transpose, adjoint)
            if op1 == transpose || op1 == adjoint
                @test op1(A2) == op1(B2) == SV(op1(A2))
            else
                @test op1(A2) == op1(B2)
            end
            for op2 in (identity, conj, transpose, adjoint)
                @test op2(op1(A2)) == op2(op1(B2))
            end
        end

        A3 = reshape(A1, 360, 10)
        B3 = SV(A3)
        @test size(A3) == size(B3)
        @test strides(A3) == strides(B3)
        @test stride(A3, 1) == stride(B3, 1)
        @test stride(A3, 2) == stride(B3, 2)
        @test stride(A3, 3) == stride(B3, 3)
        for op1 in (identity, conj, transpose, adjoint)
            if op1 == transpose || op1 == adjoint
                @test op1(A3) == op1(B3) == SV(op1(A3))
            else
                @test op1(A3) == op1(B3)
            end
            for op2 in (identity, conj, transpose, adjoint)
                @test op2(op1(A3)) == op2(op1(B3))
            end
        end

        A4 = reshape(view(A1, 1:36, 1:20), (6,6,5,4))
        B4 = SV(A4)
        for op1 in (identity, conj)
            @test op1(A4) == op1(B4)
            for op2 in (identity, conj)
                @test op2(op1(A4)) == op2(op1(B4))
            end
        end

        A5 = reshape(view(A1, 1:36, 1:20), (6,120))
        @test_throws Strided.ReshapeException SV(A5)

        A6 = [randn(T1, (5,5)) for i=1:5, j=1:5]
        if SV == UnsafeStridedView
            @test_throws AssertionError SV(A6)
        else
            B6 = SV(A6)
            for op1 in (identity, conj, transpose, adjoint)
                @test op1(A6) == op1(B6) == SV(op1(A6))
                for op2 in (identity, conj, transpose, adjoint)
                    @test op2(op1(A6)) == op2(op1(B6))
                end
            end
        end

        if SV == StridedView
            C3 = UnsafeStridedView(B3)
            D3 = StridedView(B3)
            @test D3 === B3
            for op1 in (identity, conj, transpose, adjoint)
                @test op1(A3) == op1(C3)
                for op2 in (identity, conj, transpose, adjoint)
                    @test op2(op1(A3)) == op2(op1(C3))
                end
            end
        end
    end
end

@testset "elementwise conj, transpose and adjoint" begin
    @testset for T in (Float32, Float64, Complex{Float32}, Complex{Float64})
        A = [randn(T,(3,3)) for i=1:5, b=1:4, c=1:3, d=1:2]
        Ac = deepcopy(A)
        B = StridedView(A)

        @test conj(B) == conj(A)
        @test conj(B) == map(conj, B)
        @test map(transpose, B) == map(transpose, A)
        @test map(adjoint, B) == map(adjoint, A)
    end
end

@testset "reshape and permutedims with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, Complex{Float32}, Complex{Float64})
        A0 = randn(T, 10)
        GC.@preserve A0 begin
            @test permutedims(SV(A0), (1,)) == A0
        end

        @testset "in-place matrix operations" begin
            A1 = randn(T, (1000,1000))
            A2 = similar(A1)
            A1c = copy(A1)
            A2c = copy(A2)
            GC.@preserve A1c A2c begin
                B1 = SV(A1c)
                B2 = SV(A2c)

                @test conj!(A1) == conj!(B1)
                @test adjoint!(A2, A1) == adjoint!(B2, B1)
                @test transpose!(A2, A1) == transpose!(B2, B1)
                @test permutedims!(A2, A1, (2,1)) == permutedims!(B2, B1, (2,1))
            end
        end

        @testset "reshape and permutedims with $N-dimensional arrays" for N = 2:6
            dims = ntuple(n->rand(1:div(60,N)), N)
            A = rand(T, dims)
            Ac = copy(A)
            GC.@preserve Ac begin
                B = SV(Ac)
                @test conj(A) == conj(B)
                p = randperm(N)
                B2 = permutedims(B, p)
                A2 = permutedims(A, p)
                @test B2 == A2
                @test copy(B2) == A2
                @test convert(Array, B2) == A2
            end

            dims = ntuple(n->10, N)
            A = rand(T, dims)
            Ac = copy(A)
            GC.@preserve Ac begin
            B = SV(Ac)
                @test conj(A) == conj(B)
                p = randperm(N)
                B2 = permutedims(B, p)
                A2 = permutedims(A, p)
                @test B2 == A2
                @test copy(B2) == A2
                @test convert(Array, B2) == A2

                B2 = sreshape(B, (2, 5, ntuple(n->10, N-2)..., 5, 2))
                A2 = reshape(A, (2, 5, ntuple(n->10, N-2)..., 5, 2))
                A3 = reshape(copy(A), size(A2))
                @test B2 == A3
                @test B2 == A2
                p = randperm(N+2)
                @test conj(permutedims(B2, p)) == conj(permutedims(A3, p))
            end
        end

        @testset "more reshape" begin
            A = randn(4,0)
            B = SV(A)
            @test_throws DimensionMismatch sreshape(B, (4,1))
            C = sreshape(B, (2, 1, 2, 0, 1))
            @test sreshape(C, (4,0)) == A

            A = randn(4,1,2)
            B = SV(A)
            @test_throws DimensionMismatch sreshape(B, (4,4))
            C = sreshape(B, (2,1,1,4,1,1))
            @test C == reshape(A, (2,1,1,4,1,1))
            @test sreshape(C, (4,1,2)) == A
        end
    end
end

@testset "map, scale!, axpy! and axpby! with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        @testset for N = 2:6
            dims = ntuple(n->div(60,N), N)
            R1, R2, R3 = rand(T, dims), rand(T, dims), rand(T, dims)
            GC.@preserve R1 R2 R3 begin
                B1 = permutedims(SV(R1), randperm(N))
                B2 = permutedims(SV(R2), randperm(N))
                B3 = permutedims(SV(R3), randperm(N))
                A1 = convert(Array, B1)
                A2 = convert(Array{T}, B2) # test different converts
                A3 = convert(Array{T,N}, B3)
                C1 = deepcopy(B1)

                @test rmul!(B1, 1//2) ≈ rmul!(A1, 1//2)
                @test lmul!(1//3, B2) ≈ lmul!(1//3, A2)
                @test axpy!(1//3, B1, B2) ≈ axpy!(1//3, A1, A2)
                @test axpby!(1//3, B1, 1//2, B3) ≈ axpby!(1//3, A1, 1//2, A3)
                @test map((x,y,z)->sin(x)+y/exp(-abs(z)), B1, B2, B3) ≈ map((x,y,z)->sin(x)+y/exp(-abs(z)), A1, A2, A3)
                @test map((x,y,z)->sin(x)+y/exp(-abs(z)), B1, B2, B3) isa StridedView
                @test map((x,y,z)->sin(x)+y/exp(-abs(z)), B1, A2, B3) isa Array
            end
        end
    end
end

@testset "views with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        A = randn(T,(10,10,10,10))
        B = SV(A)
        @test isa(view(B,:,1:5,3,1:5), SV)
        @test isa(view(B,:,[1,2,3],3,1:5), Base.SubArray)
        @test isa(sview(B,:,1:5,3,1:5), SV)
        @test_throws MethodError sview(B,:,[1,2,3],3,1:5)

        @test view(B,:,1:5,3,1:5) == view(A,:,1:5,3,1:5)
        @test view(B,:,1:5,3,1:5) === sview(B,:,1:5,3,1:5) === B[:,1:5,3,1:5]
        @test view(B,:,1:5,3,1:5) == SV(view(A,:,1:5,3,1:5))
        @test pointer(view(B,:,1:5,3,1:5)) == pointer(SV(view(A,:,1:5,3,1:5)))
        @test Strided.offset(view(B,:,1:5,3,1:5)) == 2*stride(B,3)
    end
end

@testset "broadcast with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        R1, R2, R3 = rand(T, (10,)), rand(T, (10,10)), rand(T, (10,10,10))
        GC.@preserve R1 R2 R3 begin
            B1 = SV(R1)
            B2 = permutedims(SV(R2), randperm(2))
            B3 = permutedims(SV(R3), randperm(3))
            A1 = convert(Array, B1)
            A2 = convert(Array{T}, B2)
            A3 = convert(Array{T,3}, B3)

            @test @inferred(B1 .+ sin.(B2 .- 3)) ≈ A1 .+ sin.(A2 .- 3)
            @test @inferred(B2' .* B3 .- Ref(0.5)) ≈ A2' .* A3 .- Ref(0.5)
            @test @inferred(B2' .* B3 .- max.(abs.(B1),real.(B3))) ≈ A2' .* A3 .- max.(abs.(A1),real.(A3))

            @test (B1 .+ sin.(B2 .- 3)) isa StridedView
            @test (B2' .* B3 .- Ref(0.5)) isa StridedView
            @test (B2' .* B3 .- max.(abs.(B1),real.(B3))) isa StridedView
            @test (B2' .* A3 .- max.(abs.(B1),real.(B3))) isa Array
        end
    end
end

@testset "mapreduce with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        R1 = rand(T, (10, 10, 10, 10, 10, 10))
        @test sum(R1; dims = (1, 3, 5)) ≈ sum(SV(R1); dims = (1, 3, 5))
        @test mapreduce(sin, +, R1; dims = (1, 3, 5)) ≈ mapreduce(sin, +, SV(R1); dims = (1, 3, 5))
        R2 = rand(T, (10, 10, 10))
        R2c = copy(R2)
        @test Strided._mapreducedim!(sin, +, identity, (10,10,10,10,10,10), (sreshape(SV(R2c),(10,1,1,10,10,1)), SV(R1))) ≈
            mapreduce(sin, +, R1; dims = (2, 3, 6)) .+ reshape(R2, (10,1,1,10,10,1))
        R2c = copy(R2)
        @test Strided._mapreducedim!(sin, +, x->0, (10,10,10,10,10,10), (sreshape(SV(R2c),(10,1,1,10,10,1)), SV(R1))) ≈
            mapreduce(sin, +, R1; dims = (2, 3, 6))
        R2c = copy(R2)
        β = rand(T)
        @test Strided._mapreducedim!(sin, +, x->β*x, (10,10,10,10,10,10), (sreshape(SV(R2c),(10,1,1,10,10,1)), SV(R1))) ≈
            mapreduce(sin, +, R1; dims = (2, 3, 6)) .+ β .* reshape(R2, (10,1,1,10,10,1))
        R2c = copy(R2)
        @test Strided._mapreducedim!(sin, +, x->β, (10,10,10,10,10,10), (sreshape(SV(R2c),(10,1,1,10,10,1)), SV(R1))) ≈
            mapreduce(sin, +, R1; dims = (2, 3, 6), init = β)
        R2c = copy(R2)
        @test Strided._mapreducedim!(sin, +, conj, (10,10,10,10,10,10), (sreshape(SV(R2c),(10,1,1,10,10,1)), SV(R1))) ≈
            mapreduce(sin, +, R1; dims = (2, 3, 6)) .+ conj.(reshape(R2, (10,1,1,10,10,1)))

        R3 = rand(T, (100, 100, 2))
        @test sum(R3; dims = (1, 2)) ≈ sum(SV(R3); dims = (1, 2))
    end
end

@testset "complete reductions with $SV" for SV in (StridedView, UnsafeStridedView)
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        R1 = rand(T, (10, 10, 10, 10, 10, 10))

        @test sum(R1) ≈ sum(SV(R1))
        @test maximum(abs, R1) ≈ maximum(abs, SV(R1))
        @test minimum(real, R1) ≈ minimum(real, SV(R1))
        @test sum(x->real(x)<0, R1) == sum(x->real(x)<0, SV(R1))

        R1 = permutedims(R1, (randperm(6)...,))

        @test sum(R1) ≈ sum(SV(R1))
        @test maximum(abs, R1) ≈ maximum(abs, SV(R1))
        @test minimum(real, R1) ≈ minimum(real, SV(R1))
        @test sum(x->real(x)<0, R1) == sum(x->real(x)<0, SV(R1))

        R2 = rand(T, (5,5,5))
        @test prod(exp, SV(R2)) ≈ exp(sum(SV(R2)))
    end
end

for T1 in (Float32, Float64, Complex{Float32}, Complex{Float64})
    for T2 in (Float32, Float64, Complex{Float32}, Complex{Float64})
        @testset "multiplication with $SV: $T1 times $T2" for SV in (StridedView, UnsafeStridedView)
            d = 20
            A1 = rand(T1, (d,d))
            A1c = copy(A1)
            B1 = SV(A1c)
            for op1 in (identity, conj, transpose, adjoint)
                @test op1(A1) == op1(B1)
            end
            A2 = rand(T2, (d,d))
            T3 = promote_type(T1,T2)
            A3 = rand(T3, (d,d))
            A2c = copy(A2)
            A3c = copy(A3)
            GC.@preserve A1c A2c A3c begin
                B2 = SV(A2c)
                B3 = SV(A3c)

                for op1 in (identity, conj, transpose, adjoint)
                    for op2 in (identity, conj, transpose, adjoint)
                        @test op1(A1)*op2(A2) ≈ op1(B1)*op2(B2)
                        for op3 in (identity, conj, transpose, adjoint)
                            mul!(op3(B3), op1(B1), op2(B2))
                            @test B3 ≈ op3(op1(A1)*op2(A2)) # op3 is its own inverse
                        end
                    end
                end
            end
        end
    end
end

@testset "multiplication with $SV: Complex{Int}" for SV in (StridedView, UnsafeStridedView)
    d = 10
    A1 = map(complex, rand(-100:100, (d,d)), rand(-100:100, (d,d)))
    A2 = map(complex, rand(-100:100, (d,d)), rand(-100:100, (d,d)))
    A3 = map(complex, rand(-100:100, (d,d)), rand(-100:100, (d,d)))
    A1c = copy(A1)
    A2c = copy(A2)
    A3c = copy(A3)
    GC.@preserve A1c A2c A3c begin
        B1 = SV(A1c)
        B2 = SV(A2c)
        B3 = SV(A3c)

        for op1 in (identity, conj, transpose, adjoint)
            @test op1(A1) == op1(B1)
            for op2 in (identity, conj, transpose, adjoint)
                @test op1(A1)*op2(A2) ≈ op1(B1)*op2(B2)
                for op3 in (identity, conj, transpose, adjoint)
                    Strided.mul!(op3(B3), op1(B1), op2(B2))
                    @test B3 ≈ op3(op1(A1)*op2(A2)) # op3 is its own inverse
                end
            end
        end
    end
end

@testset "multiplication with $SV: Rational{Int}" for SV in (StridedView, UnsafeStridedView)
    d = 10
    A1 = map(//, rand(-10:10, (d,d)), rand(1:10, (d,d)))
    A2 = map(//, rand(-10:10, (d,d)), rand(1:10, (d,d)))
    A3 = map(//, rand(-10:10, (d,d)), rand(1:10, (d,d)))
    A1c = copy(A1)
    A2c = copy(A2)
    A3c = copy(A3)
    GC.@preserve A1c A2c A3c begin
        B1 = SV(A1c)
        B2 = SV(A2c)
        B3 = SV(A3c)

        for op1 in (identity, conj, transpose, adjoint)
            @test op1(A1) == op1(B1)
            for op2 in (identity, conj, transpose, adjoint)
                @test op1(A1)*op2(A2) ≈ op1(B1)*op2(B2)
                for op3 in (identity, conj, transpose, adjoint)
                    mul!(op3(B3), op1(B1), op2(B2))
                    @test B3 ≈ op3(op1(A1)*op2(A2)) # op3 is its own inverse
                end
            end
        end
    end
end

@testset "@strided macro" begin
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        A1, A2, A3 = rand(T, (10,)), rand(T, (10,10)), rand(T, (10,10,10))

        @test (@strided(@. A1 + sin(A2 - 3))) isa StridedView
        @test (@strided(A1 .+ sin.(A2 .- 3))) isa StridedView
        @test (@strided(A1 .+ sin.(A2 .- 3))) ≈ A1 .+ sin.(A2 .- 3)
        @test (@strided(A2' .* A3 .- Ref(0.5))) ≈ A2' .* A3 .- Ref(0.5)
        @test (@strided(A2' .* A3 .- max.(abs.(A1),real.(A3)))) ≈ A2' .* A3 .- max.(abs.(A1),real.(A3))

        B2 = view(A2, :, 1:2:10)
        @test (@strided(A1 .+ sin.(view(A2,:,1:2:10) .- 3))) ≈
            (@strided(A1 .+ sin.(B2 .- 3))) ≈
            A1 .+ sin.(view(A2,:,1:2:10) .- 3)

        B2 = view(A2', :, 1:6)
        B3 = view(A3,:,1:6,4)
        @test (@strided(view(A2',:,1:6) .* view(A3,:,1:6,4) .- Ref(0.5))) ≈
            (@strided(B2 .* B3 .- Ref(0.5))) ≈
            view(A2',:,1:6) .* view(A3,:,1:6,4) .- Ref(0.5)

        B2 = view(A2, :, 3)
        B3 = view(A3, 1:5, :, 2:2:10)
        B1 = view(A1, 1:5)
        B3b = view(A3, 4:4, 4:4, 2:2:10)
        @test (@strided(view(A2,:,3)' .* view(A3,1:5,:,2:2:10) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10))))) ≈
            (@strided(B2' .* B3 .- max.(abs.(B1),real.(B3b)))) ≈
            view(A2,:,3)' .* view(A3,1:5,:,2:2:10) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10)))

        B2 = reshape(A2, (10,2,5))
        @test (@strided(A1 .+ sin.(reshape(A2, (10,2,5)) .- 3))) ≈
            (@strided(A1 .+ sin.(B2 .- 3))) ≈
            A1 .+ sin.(reshape(A2, (10,2,5)) .- 3)

        B2 = reshape(A2, 1, 100)
        B3 = reshape(A3, 100, 1, 10)
        @test (@strided(reshape(A2, 1, 100)' .* reshape(A3, 100, 1, 10) .- Ref(0.5))) ≈
            (@strided(B2' .* B3 .- Ref(0.5))) ≈
            reshape(A2, 1, 100)' .* reshape(A3, 100, 1, 10) .- Ref(0.5)

        B2 = view(A2, :, 3)
        B3 = reshape(view(A3, 1:5, :, :), 5, 10, 5, 2)
        B1 = view(A1, 1:5)
        B3b = view(A3, 4:4, 4:4, 2:2:10)
        @test (@strided(view(A2,:,3)' .* reshape(view(A3,1:5,:,:), 5, 10, 5, 2) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10))))) ≈
            (@strided(B2' .* B3 .- max.(abs.(B1),real.(B3b)))) ≈
            view(A2,:,3)' .* reshape(view(A3,1:5,:,:), 5, 10, 5, 2) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10)))
    end
end

@testset "@unsafe_strided macro" begin
    @testset for T in (Float32, Float64, ComplexF32, ComplexF64)
        A1, A2, A3 = rand(T, (10,)), rand(T, (10,10)), rand(T, (10,10,10))

        @test (@unsafe_strided(A1,A2,@. A1 + sin(A2 - 3))) isa StridedView
        @test (@unsafe_strided(A1,A2,A1 .+ sin.(A2 .- 3))) isa StridedView

        @test (@unsafe_strided(A1,A2,A1 .+ sin.(A2 .- 3))) ≈ A1 .+ sin.(A2 .- 3)
        @test (@unsafe_strided(A2,A3, A2' .* A3 .- Ref(0.5))) ≈ A2' .* A3 .- Ref(0.5)
        @test (@unsafe_strided(A1,A2,A3, A2' .* A3 .- max.(abs.(A1),real.(A3)))) ≈ A2' .* A3 .- max.(abs.(A1),real.(A3))

        B2 = view(A2, :, 1:2:10)
        @test (@unsafe_strided(A1,A2,A1 .+ sin.(view(A2,:,1:2:10) .- 3))) ≈
            (@unsafe_strided(A1,B2,A1 .+ sin.(B2 .- 3))) ≈
            A1 .+ sin.(view(A2,:,1:2:10) .- 3)

        B2 = view(A2', :, 1:6)
        B3 = view(A3,:,1:6,4)
        @test (@unsafe_strided(A2,A3,view(A2',:,1:6) .* view(A3,:,1:6,4) .- Ref(0.5))) ≈
            (@unsafe_strided(B2,B3,B2 .* B3 .- Ref(0.5))) ≈
            view(A2',:,1:6) .* view(A3,:,1:6,4) .- Ref(0.5)

        B2 = view(A2, :, 3)
        B3 = view(A3, 1:5, :, 2:2:10)
        B1 = view(A1, 1:5)
        B3b = view(A3, 4:4, 4:4, 2:2:10)
        @test (@unsafe_strided(A1,A2,A3,view(A2,:,3)' .* view(A3,1:5,:,2:2:10) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10))))) ≈
            (@unsafe_strided(B1,B2,B3,B2' .* B3 .- max.(abs.(B1),real.(B3b)))) ≈
            view(A2,:,3)' .* view(A3,1:5,:,2:2:10) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10)))

        B2 = reshape(A2, (10,2,5))
        @test (@unsafe_strided(A1,A2,A1 .+ sin.(reshape(A2, (10,2,5)) .- 3))) ≈
            (@unsafe_strided(A1,B2,A1 .+ sin.(B2 .- 3))) ≈
            A1 .+ sin.(reshape(A2, (10,2,5)) .- 3)

        B2 = reshape(A2, 1, 100)
        B3 = reshape(A3, 100, 1, 10)
        @test (@unsafe_strided(A2,A3,reshape(A2, 1, 100)' .* reshape(A3, 100, 1, 10) .- Ref(0.5))) ≈
            (@unsafe_strided(B2,B3,B2' .* B3 .- Ref(0.5))) ≈
            reshape(A2, 1, 100)' .* reshape(A3, 100, 1, 10) .- Ref(0.5)

        B2 = view(A2, :, 3)
        B3 = reshape(view(A3, 1:5, :, :), 5, 10, 5, 2)
        B1 = view(A1, 1:5)
        B3b = view(A3, 4:4, 4:4, 2:2:10)
        @test (@unsafe_strided(A1,A2,A3,view(A2,:,3)' .* reshape(view(A3,1:5,:,:), 5, 10, 5, 2) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10))))) ≈
            (@unsafe_strided(B1,B2,B3,B2' .* B3 .- max.(abs.(B1),real.(B3b)))) ≈
            view(A2,:,3)' .* reshape(view(A3,1:5,:,:), 5, 10, 5, 2) .- max.(abs.(view(A1,1:5)),real.(view(A3,4:4,4:4,2:2:10)))
    end
end


module JLLUtils

include("exceptions.jl");
include("units.jl");
include("stats.jl");
include("arrays.jl");
include("datemath.jl");
include("plotting.jl");

import .JLLUnits: parse_unit_string, strip_units;
import .JLLStats: normalize_to, normalize_to!;
import .JLLPlots: plotas, plotas!;

export parse_unit_string, strip_units, normalize_to, normalize_to!, plotas, plotas!;

end # module


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/MortarContact2DAD.jl/blob/master/LICENSE

using FEMBase, MortarContact2DAD, Test, LinearAlgebra, SparseArrays, Statistics,
      DelimitedFiles

include(joinpath("..", "docs", "make.jl"))

@testset "MortarContact2DAD.jl" begin
    @testset "test_01.jl" begin include("test_01.jl") end
    @testset "test_02.jl" begin include("test_02.jl") end
    @testset "test_contact_1.jl" begin include("test_contact_1.jl") end
    @testset "test_contact_2.jl" begin include("test_contact_2.jl") end
    @testset "test_contact_projection.jl" begin include("test_contact_projection.jl") end
end

include(joinpath("..", "docs", "deploy.jl"))


# by lazarusA # HIDE
# using GLMakie # HIDE
using GLMakie
GLMakie.activate!() # HIDE
let
    t = 0:0.1:15
    fig, _ = lines(sin.(t), cos.(t), t/4, color = t/4, linewidth = 4,
        colormap = :plasma)
    fig
    save(joinpath(@__DIR__, "output", "line3D.png"), fig, px_per_unit = 2.0) # HIDE
end
using Pkg # HIDE
Pkg.status(["GLMakie"]) # HIDE


function mark_zebra!(robot, line_side, ortogonal_line_side, num_passes, num_start_passes=0)
    start_side = get_start_side(line_side,ortogonal_line_side)
    #УТВ: start_side - кортеж типа NTuple{2,HorizonSide}, например: (Sud,Ost)

    nun_steps = [get_num_movements!(robot,start_side[i]) for i in 1:2]
    #УТВ: Робот - в стартовом углу

    movements_if_posible!(robot, ortogonal_line_side, num_start_passes) || return
    line_mark!(robot,line_side)
    #ИНВАРИАНТ: линия с Роботом и все предыдущие (по ходу движения в направлении ortogonal_line_side) замаркированы
    while movements_if_posible!(robot,ortogonal_line_side, num_passes) == true
        line_side = inverse(line_side)
        line_mark!(robot,line_side)
    end
    #УТВ: все линии замаркированы

    for s in start_side
        movements!(r,s)
    end
    #УТВ: Робот - снова в стартовом углу

    back_side=inverse(start_side)
    for (i,num) in arange(num_steps)
        movements!(robot,back_side[i], num)
    end
    #УТВ: Робот - в исходном положении
end

function movements_if_posible!(robot, side, max_num_steps)
    for _ in 1:max_num_steps
        isborder(robot,side) && (return false)
        move!(robot,side)
    end
    return true
end

function line_mark!(robot,side)
    putmarker!(robot)
    putmarkers!(robot,side)
end


"""
Piecewise Legendre polynomial.
"""

import PyCall: PyObject

struct PiecewiseLegendrePoly
    o::PyObject
end

Base.convert(::Type{PiecewiseLegendrePoly}, o::PyObject) = PiecewiseLegendrePoly(o)
(p::PiecewiseLegendrePoly)(x) = p.o(x)

function Base.getindex(p::PiecewiseLegendrePoly, i::Int64)
    1 <= i <= p.o.size || error("Invalid index!")
    PiecewiseLegendrePoly(p.o.__getitem__(i-1))
end

"""
f: Function-like object
   f should takes a real-valued vector and return a real-valued D-dimensional arrays.
   axis option specifies which axis of this array correponds to the axis of the input.

axis:
   By default, the last axis is used for computing overlap.
"""
function overlap(poly::PiecewiseLegendrePoly, f)::Float64
    return poly.o.overlap(f)[1]
end

"""
PiecewiseLegendreFT
"""
struct PiecewiseLegendreFT
    o::PyObject
end

Base.convert(::Type{PiecewiseLegendreFT}, o::PyObject) = PiecewiseLegendreFT(o)
(p::PiecewiseLegendreFT)(x) = p.o(x)

function Base.getindex(p::PiecewiseLegendreFT, i::Int64)
    1 <= i <= p.o.size || error("Invalid index!")
    PiecewiseLegendreFT(p.o.__getitem__(i - 1))
end


"""
A Julia suite for chaos and nonlinear dynamics
"""
module DynamicalSystems

using StaticArrays

export SVector, SMatrix, @SVector, @SMatrix, Size

using Reexport

@reexport using DelayEmbeddings
@reexport using DynamicalSystemsBase
@reexport using Entropies
@reexport using ChaosTools
@reexport using RecurrenceAnalysis

# Update messages:
using Scratch
display_update = true
version_number = "2.0.1"
update_name = "update_v$(version_number)"

if display_update
    # Get scratch space for this package
    versions_dir = @get_scratch!("versions")
    if !isfile(joinpath(versions_dir, update_name))
        printstyled(
            stdout,
            """
            \nUpdate message: DynamicalSystems v$(version_number)\n
            Welcome to this new (and slightly breaking) release of DynamicalSystems.jl!
            You can find an announcement of the new version on the Julia discourse:

            https://discourse.julialang.org/t/nonlinear-dynamics-textbook-dynamicalsystems-jl-2-0/65665

            The *breaking* changes in this release are:
            1. The keyword `dt` of many functions has been renamed to `Δt`.
               This keyword had conflicts with the options of DifferentialEquations.jl.
               No warning can be thrown for this change, and users still using `dt` will
               have it silently propagated as keyword to the diffeq solvers.
               Functions affected: `trajectory, lyapunov, lyapunovspectrum, gali, expansionentropy, orbitdiagram`
            2. If `A` is a `Dataset` then `A[range_of_integers]` now returns a `Dataset`.
               Before it used to return `Vector{SVector}`.
            """;
            color = :light_magenta,
        )
        touch(joinpath(versions_dir, update_name))
    end
end

end # module


module Pulses

# more coming soon, once I rewrite them
include("computational_funcs.jl")
include("pulse_defs.jl")

export propagator, full_propagator, fast_propagator, full_fast_propagator,
expectation_value, track_expectation_value,
pulse, pi_pulse, pi_half_pulse, delay, optimal_pulse, PulseSequence, nmr_pulse, array_pulse

end


struct OrderedStronglyConvexError <: Exception
end

function Base.showerror(io::IO, e::OrderedStronglyConvexError)
    print(io, "Points are not ordered or do not represent a strongly convex set")
end


using Revise
using MetaGen
using Gen
using Profile
using StatProfilerHTML
using GenRFS

#Profile.init(; n = 10^4, delay = 1e-5)

#GenRFS.modify_partition_ctx!(1000)

#call it
#@profilehtml gt_trace,_ = Gen.generate(metacog, (possible_objects,))
#@profilehtml gt_trace,_ = Gen.generate(metacog, (possible_objects,))
num_frames = 1
num_videos = 2
params = Video_Params(n_possible_objects = 2)

@time gt_trace,_ = Gen.generate(main, (false, num_videos, num_frames, params));

@profilehtml Gen.generate(main, (num_videos, num_frames, params));

#Profile.init(; n = 10^7, delay = 1e-6)
@profilehtml gt_trace,_ = Gen.generate(main, (num_videos, num_frames, params));
#println(gt_trace)
#gt_choices = get_choices(gt_trace)


import Base.write

function Base.write(io::IO, waveforms::SpikeWaveforms)
    #write a header
    headersize = 64
    nbytes = 0
    npoints, nchannels, nspikes = size(waveforms.waveforms)
    nbytes += write(io, headersize)
    nbytes += write(io, npoints)
    nbytes += write(io, nchannels)
    nbytes += write(io, nspikes)
    seek(io, headersize)

    #write timestamps, followed by waveforms
    nbytes += write(io, waveforms.timestamps)
    nbytes += write(io, waveforms.waveforms)
    nbytes
end

function Base.read(io::IO, ::Type{SpikeWaveforms})
    #read the header
    headersize = read(io,Int64)
    npoints = read(io, Int64)
    nchannels = read(io, Int64)
    nspikes = read(io, Int64)
    seek(io, headersize)

    #read timestamps
    timestamps = read(io, Float64, nspikes)
    waveforms = read(io, Float64, (npoints, nchannels, nspikes))
    SpikeWaveforms(waveforms, timestamps)
end


# Add packages. This only has to be done once under the environment of Disorderness.jl
# After that, before every time running the project you just simply:
# 	Pkg.activate(rootdir)
# 	Pkg.instantiate(); # install the packages in the same state that is given by that manifest.
# and you are freely to `using` the packages in dependency.
using Pkg;
Pkg.add(["Plots","PlutoUI","DifferentialEquations","Formatting","LaTeXStrings"])


import AbstractTrees
using AbstractTrees: children

export TreePlot

"""
    TreePlot(root)

Wrap a tree-like object for plotting. Uses `AbstractTrees.children()` to recursively add children to the plot and `AbstractTrees.printnode()` to generate the labels.

# Example

```julia
using AbstractTrees, GraphRecipes
AbstractTrees.children(d::Dict) = [p for p in d]
AbstractTrees.children(p::Pair) = AbstractTrees.children(p[2])
function AbstractTrees.printnode(io::IO, p::Pair)
    str = isempty(AbstractTrees.children(p[2])) ? string(p[1], ": ", p[2]) : string(p[1], ": ")
    print(io, str)
end

d = Dict(:a => 2,:d => Dict(:b => 4,:c => "Hello"),:e => 5.0)

plot(TreePlot(d))
````
"""
struct TreePlot{T}
    root::T
end

function add_children!(nodes, source, destiny, node, parent_idx)
    for child in children(node)
        push!(nodes, child)
        child_idx = length(nodes)
        push!(source, parent_idx)
        push!(destiny, child_idx)
        add_children!(nodes, source, destiny, child, child_idx)
    end
end

function string_from_node(node)
    io = IOBuffer()
    AbstractTrees.printnode(io, node)
    String(take!(io))
end

# recursively build a graph of children of `tree_wrapper.root`
@recipe function f(tree_wrapper::TreePlot; namefunc = string_from_node )
    root = tree_wrapper.root
    # recursively add children
    nodes = Any[root]
    source, destiny = Int[], Int[]
    add_children!(nodes, source, destiny, root, 1)

    # set up the graphplot
    names := map(namefunc, nodes)
    method --> :buchheim
    root --> :top
    GraphPlot((source, destiny))
end


isdefined(Base, :__precompile__) && __precompile__()

module TexExtensions

using Compat

@compat import Base.show

@Base.Multimedia.textmime "text/mathtex+latex"

const tm = MIME("text/mathtex+latex")
const T = MIME"text/mathtex+latex"

@compat function show(io::IO, ::T, x::AbstractString)
    write(io,"\\text{")
    write(io,x)
    write(io,"}")
end

@compat function show(io::IO, ::T, x::Rational)
    write(io,"\\frac{")
    show(io,tm,x.num)
    write(io,"}{")
    show(io,tm,x.den)
    write(io,"}")
end

# This code is largely copied from grisu.jl, we really should have to copy this mutch

_writefloat(io::IO, x::AbstractFloat, mode::Int32, n::Int, t) =
    _writefloat(io, x, mode, n, t, "NaN", "Inf")
_writefloat(io::IO, x::Float32, mode::Int32, n::Int, t) =
    _writefloat(io, x, mode, n, t, "NaN32", "Inf32")
_writefloat(io::IO, x::Float16, mode::Int32, n::Int, t) =
    _writefloat(io, x, mode, n, t, "NaN16", "Inf16")

function _writefloat(io::IO, x::AbstractFloat, mode::Int32, n::Int, typed, nanstr, infstr)
    if isnan(x) return write(io, "\\text{", typed ? nanstr : "NaN","}"); end
    if isinf(x)
        if x < 0 write(io,"\{\}-") end
        write(io, "\\infty")
        return
    end
    if typed && isa(x,Float16) write(io, "\\text{float16("); end
    len, pt, neg, pdigits = grisu_fmt(x, mode, n)
    if mode == Base.Grisu.PRECISION
        while len > 1 && Base.Grisu.DIGITS[len] == '0'
            len -= 1
        end
    end
    # Make sure latex understands this is negations
    if neg write(io,"\{\}-") end
    if pt <= -4 || pt > 6 # .00001 to 100000.
        # => #.#######e###
        write(io, pdigits, 1)
        write(io, '.')
        if len > 1
            write(io, pdigits+1, len-1)
        else
            write(io, '0')
        end
        write(io, "\\times 10^{",dec(pt-1),"}")
        if typed && isa(x,Float16)
            write(io, ")\\}")
        end
        return
    elseif pt <= 0
        # => 0.00########
        write(io, "0.")
        while pt < 0
            write(io, '0')
            pt += 1
        end
        write(io, pdigits, len)
    elseif pt >= len
        # => ########00.0
        write(io, pdigits, len)
        while pt > len
            write(io, '0')
            len += 1
        end
        write(io, ".0")
    else # => ####.####
        write(io, pdigits, pt)
        write(io, '.')
        write(io, pdigits+pt, len-pt)
    end
    if typed && isa(x,Float16) write(io, ")\\}"); end
    nothing
end

if VERSION < v"0.4-dev"
    eval(quote
        function grisu_fmt(x, mode, n)
            @Base.Grisu.grisu_ccall x mode n
            pdigits = pointer(Base.Grisu.DIGITS)
            neg = Base.Grisu.NEG[1]
            len = int(Base.Grisu.LEN[1])
            pt  = int(Base.Grisu.POINT[1])
            len, pt, neg, pdigits
        end
    end)
else
    grisu_fmt(x, mode, n) = Base.Grisu.grisu(x, mode, n)
end

@compat function show(io::IO, ::T, x::BigFloat)
    e = Array(Culong,1)
    str = ccall((:mpfr_get_str,:libmpfr),Ptr{UInt8},(Ptr{UInt8},Ptr{Culong},Cint,Csize_t,Ptr{Void},Int32),C_NULL,e,
        10,0,&x,Base.MPFR.ROUNDING_MODE[end])
    ret = bytestring(str)
    ccall((:mpfr_free_str,:libmpfr),Void,(Ptr{UInt8},),str)
    if ret[1] == '-'
        radix = 2
    else
        radix = 1
    end
    write(io,ret[1:radix])
    write(io,'.')
    write(io,ret[(radix+1):end])
    write(io, "\\times 10^{",dec(e[1]),"}")
end

@compat show(io::IO, ::T, x::Float64) = _writefloat(io, x, Base.Grisu.SHORTEST, 0, true)
@compat show(io::IO, ::T, x::Float32) = _writefloat(io, x, Base.Grisu.SHORTEST_SINGLE, 0, true)
@compat show(io::IO, ::T, x::Float16) = _writefloat(io, x, Base.Grisu.PRECISION, 4, true)

@compat show(io::IO, ::T, x::Integer) = show(io,x)

end # module


## Quick test of envelope detection

pl = Gadfly.plot(y=leftwavdata[0], Geom.line);

pl |> PDF("/tmp/test.pdf",30cm,20cm)

# now abs and correlate with 2^16 size

leftenv = loadPs3WavEnvelopes(joinpath(datadir,"left_wavdata_corrected.jld2"))

pl = vstack(
Gadfly.plot(y=leftenv[0], Geom.line),
Gadfly.plot(y=leftenv[1], Geom.line),
Gadfly.plot(y=leftenv[2], Geom.line),
Gadfly.plot(y=leftenv[3], Geom.line),
);

pl |> PDF("/tmp/test.pdf",20cm,30cm)

##

@async run(`evince /tmp/test.pdf`)


#InspectDR: Rendering rectangular axes & ticks
#-------------------------------------------------------------------------------

#==Constants
===============================================================================#

#==Main types
===============================================================================#

#==Rendering base plot elements
===============================================================================#

#Render main plot annotation (titles, axis labels, ...)
#-------------------------------------------------------------------------------
function render_baseannotation(ctx::CairoContext, rplot::RPlot2D, lyt::PlotLayout, a::Annotation)
	usemarkup=false #want user to be able to make fancy labels - but can cause crashes.
	TIMESTAMP_OFFSET = 3 #WANTCONST
	bb =  rplot.bb #WANTCONST
	databb =  rplot.databb #WANTCONST

	#Title
#	xcenter = (bb.xmin+bb.xmax)/2 #Entire plot BB.
	xcenter = (databb.xmin+databb.xmax)/2 #Data-area BB only (looks less odd).
	pt = Point2D(xcenter, databb.ymin-lyt.voffset_title)
	render(ctx, a.title, pt, lyt.font_title, align=ALIGN_HCENTER|ALIGN_BOTTOM, markup=usemarkup)

	#X-axis label
	xcenter = (databb.xmin+databb.xmax)/2
	pt = Point2D(xcenter, databb.ymax+lyt.voffset_xaxislabel)
	render(ctx, a.xlabel, pt, lyt.font_axislabel, align=ALIGN_HCENTER|ALIGN_TOP, markup=usemarkup)

	#Strip annotation
	nstrips = min(length(a.ylabels), length(rplot.strips))
	for i in 1:nstrips
		rstrip = rplot.strips[i]
		sbb = rstrip.bb
		ycenter = (sbb.ymin+sbb.ymax)/2
		xcenter = (sbb.xmin+sbb.xmax)/2

		#Strip label
		if i <= length(a.ystriplabels)
			lbl = a.ystriplabels[i]
			pt = Point2D(xcenter, sbb.ymin-lyt.voffset_ystriplabel)
			render(ctx, lbl, pt, lyt.font_striplabel, align=ALIGN_HCENTER|ALIGN_BOTTOM, markup=usemarkup)
		end

		#Y-axis label
		if i <= length(a.ylabels)
			lbl = a.ylabels[i]
			pt = Point2D(sbb.xmin-lyt.hoffset_yaxislabel, ycenter)
			render(ctx, lbl, pt, lyt.font_axislabel, align=ALIGN_HCENTER|ALIGN_BOTTOM, angle=-π/2, markup=usemarkup)
		end
	end

	#Time stamp
	if lyt.enable_timestamp
		pt = Point2D(bb.xmax-TIMESTAMP_OFFSET, bb.ymax-TIMESTAMP_OFFSET)
		render(ctx, a.timestamp, pt, lyt.font_time, align=ALIGN_RIGHT|ALIGN_BOTTOM, markup=usemarkup)
	end
end

#Render frame around graph
#-------------------------------------------------------------------------------
function render_graphframe(ctx::CairoContext, rstrip::RStrip2D, aa::AreaAttributes)
Cairo.save(ctx)
	setlinestyle(ctx, LineStyle(aa.line))
	Cairo.rectangle(ctx, rstrip.bb)
	Cairo.stroke(ctx)
Cairo.restore(ctx)
	return
end

#==Render grid
===============================================================================#
render_vlines(ctx::CairoContext, graphbb::BoundingBox, xf::Transform2D, lyt::PlotLayout, xlines::AbstractGridLines) = nothing
function render_vlines(ctx::CairoContext, graphbb::BoundingBox, xf::Transform2D, lyt::PlotLayout, xlines::GridLines)
	if xlines.displaymajor
		setlinestyle(ctx, lyt.line_gridmajor)
		for xline in xlines.major
			x = apply(xf, Point2D(xline, 0)).x
			drawline(ctx, Point2D(x, graphbb.ymin), Point2D(x, graphbb.ymax))
		end
	end
	if xlines.displayminor
		setlinestyle(ctx, lyt.line_gridminor)
		for xline in xlines.minor
			x = apply(xf, Point2D(xline, 0)).x
			drawline(ctx, Point2D(x, graphbb.ymin), Point2D(x, graphbb.ymax))
		end
	end
end
render_hlines(ctx::CairoContext, graphbb::BoundingBox, xf::Transform2D, lyt::PlotLayout, ylines::AbstractGridLines) = nothing
function render_hlines(ctx::CairoContext, graphbb::BoundingBox, xf::Transform2D, lyt::PlotLayout, ylines::GridLines)
	if ylines.displaymajor
		setlinestyle(ctx, lyt.line_gridmajor)
		for yline in ylines.major
			y = apply(xf, Point2D(0, yline)).y
			drawline(ctx, Point2D(graphbb.xmin, y), Point2D(graphbb.xmax, y))
		end
	end
	if ylines.displayminor
		setlinestyle(ctx, lyt.line_gridminor)
		for yline in ylines.minor
			y = apply(xf, Point2D(0, yline)).y
			drawline(ctx, Point2D(graphbb.xmin, y), Point2D(graphbb.xmax, y))
		end
	end
end

function render_grid(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, grid::GridRect)
	Cairo.save(ctx) #-----
	render_vlines(ctx, rstrip.bb, rstrip.xf, lyt, grid.xlines)
	render_hlines(ctx, rstrip.bb, rstrip.xf, lyt, grid.ylines)
	Cairo.restore(ctx) #-----
end

#==Render ticks
===============================================================================#

#Default label:
function render_ticklabel(ctx::CairoContext, val::DReal, pt::Point2D, font::Font, align::CAlignment, fmt::TickLabelFormatting, ::AxisScale)
	tstr = ""
	if false #Old display system.
		#TODO: deprecate
		tstr = "$val"
		if length(tstr) > 7 #HACK!
			tstr = @sprintf("%0.1e", val)
		end
	else
		tstr = formatted(val, fmt.fmt, showexp=!fmt.splitexp)
	end
	render(ctx, tstr, pt, font, align=align)
end

render_ticklabel(ctx::CairoContext, val::DReal, pt::Point2D, font::Font, align::CAlignment, fmt::TickLabelFormatting, scale::LogScale{:e}) =
	render_power(ctx, "e", val, pt, font, align)
render_ticklabel(ctx::CairoContext, val::DReal, pt::Point2D, font::Font, align::CAlignment, fmt::TickLabelFormatting, scale::LogScale{2}) =
	render_power(ctx, "2", val, pt, font, align)
render_ticklabel(ctx::CairoContext, val::DReal, pt::Point2D, font::Font, align::CAlignment, fmt::TickLabelFormatting, scale::LogScale{10}) =
	render_power(ctx, "10", val, pt, font, align)

function render_axisscalelabel(ctx::CairoContext, pt::Point2D, font::Font, align::CAlignment, fmt::TickLabelFormatting, ::AxisScale)
	tstr = formatted_exp(fmt.fmt)
	render(ctx, tstr, pt, font, align=align)
end

#Render ticks: Well-defined GridLines
#-------------------------------------------------------------------------------
function render_xticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, xlines::GridLines, ticklabels::Bool)
	graphbb = rstrip.bb
	xs = rstrip.xscale

	#TODO: Fix LineAttributes to have concrete type:
	line_frame = LineStyle(lyt.frame_data.line) #Convert/do some validation
	line_major = LineStyle(line_frame.style, lyt.line_gridmajor.width, line_frame.color)
	line_minor = LineStyle(line_frame.style, lyt.line_gridminor.width, line_frame.color)
	tframe = line_frame.width #WANTCONST
	fmt = TickLabelFormatting(lyt.format_xtick, xlines.rnginfo)
	yframe = graphbb.ymax
	ylabel = graphbb.ymax + lyt.voffset_xticklabel

Cairo.save(ctx) #-----
	setlinestyle(ctx, line_major)
	for xtick in xlines.major
		x = apply(rstrip.xf, Point2D(xtick, 0)).x
		if ticklabels
			render_ticklabel(ctx, xtick, Point2D(x, ylabel), lyt.font_ticklabel, ALIGN_TOP|ALIGN_HCENTER, fmt, xs)
		end
		drawline(ctx, Point2D(x, yframe), Point2D(x, yframe-lyt.length_tickmajor))
	end
	setlinestyle(ctx, line_minor)
	for xtick in xlines.minor
		x = apply(rstrip.xf, Point2D(xtick, 0)).x
		drawline(ctx, Point2D(x, yframe), Point2D(x, yframe-lyt.length_tickminor))
	end
	if fmt.splitexp && ticklabels
		xlabel = graphbb.xmax + tframe
		render_axisscalelabel(ctx, Point2D(xlabel, yframe), lyt.font_ticklabel, ALIGN_BOTTOM|ALIGN_LEFT, fmt, xs)
	end
Cairo.restore(ctx) #-----
end
function render_yticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, ylines::GridLines)
	#TODO: Fix LineAttributes to have concrete type:
	line_frame = LineStyle(lyt.frame_data.line) #Convert/do some validation
	line_major = LineStyle(line_frame.style, lyt.line_gridmajor.width, line_frame.color)
	line_minor = LineStyle(line_frame.style, lyt.line_gridminor.width, line_frame.color)
	tframe = line_frame.width #WANTCONST
	graphbb = rstrip.bb
	ys = rstrip.yscale
	fmt = TickLabelFormatting(lyt.format_ytick, ylines.rnginfo)
	xframe = graphbb.xmin
	xlabel = graphbb.xmin - lyt.hoffset_yticklabel
Cairo.save(ctx) #-----
	setlinestyle(ctx, line_major)
	for ytick in ylines.major
		y = apply(rstrip.xf, Point2D(0, ytick)).y
		render_ticklabel(ctx, ytick, Point2D(xlabel, y), lyt.font_ticklabel, ALIGN_RIGHT|ALIGN_VCENTER, fmt, ys)
		drawline(ctx, Point2D(xframe, y), Point2D(xframe+lyt.length_tickmajor, y))
	end
	setlinestyle(ctx, line_minor)
	for ytick in ylines.minor
		y = apply(rstrip.xf, Point2D(0, ytick)).y
		drawline(ctx, Point2D(xframe, y), Point2D(xframe+lyt.length_tickminor, y))
	end
	if fmt.splitexp
		ylabel = graphbb.ymin - tframe
		render_axisscalelabel(ctx, Point2D(xframe, ylabel), lyt.font_ticklabel, ALIGN_BOTTOM|ALIGN_LEFT, fmt, ys)
	end
Cairo.restore(ctx) #-----
end
#Render ticks for colorscale (z-values)
function render_zticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, csbb::BoundingBox,
		zext::PExtents1D, zlines::GridLines, zs::AxisScale)
	tframe = DReal(lyt.frame_colorscale.line.width) #WANTCONST TODO: Fix LineAttributes to have concrete type
	fmt = TickLabelFormatting(lyt.format_ztick, zlines.rnginfo)
	xf = LinearTransform(zext, csbb.ymax, csbb.ymin) #ymax corresponds to min z value
	xframe = csbb.xmax
	xlabel = xframe + lyt.hoffset_yticklabel
	for ztick in zlines.major
		y = apply(xf, ztick)
		render_ticklabel(ctx, ztick, Point2D(xlabel, y), lyt.font_ticklabel, ALIGN_LEFT|ALIGN_VCENTER, fmt, zs)
		drawline(ctx, Point2D(xframe, y), Point2D(xframe-lyt.length_tickmajor, y))
	end
	for ztick in zlines.minor
		y = apply(xf, ztick)
		drawline(ctx, Point2D(xframe, y), Point2D(xframe-lyt.length_tickminor, y))
	end
	if fmt.splitexp
		ylabel = csbb.ymin - tframe
		render_axisscalelabel(ctx, Point2D(xframe, ylabel), lyt.font_ticklabel, ALIGN_BOTTOM|ALIGN_RIGHT, fmt, zs)
	end
end

#Render ticks: UndefinedGridLines
#-------------------------------------------------------------------------------
function render_xticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout,
		xlines::UndefinedGridLines, ticklabels::Bool)
	graphbb = rstrip.bb
	fmt = TickLabelFormatting(NoRangeDisplayInfo())
	yframe = graphbb.ymax
	ylabel = graphbb.ymax + lyt.voffset_xticklabel
	for (x, xlabel) in [(graphbb.xmin, xlines.minline), (graphbb.xmax, xlines.maxline)]
		if ticklabels
			render_ticklabel(ctx, xlabel, Point2D(x, ylabel), lyt.font_ticklabel, ALIGN_TOP|ALIGN_HCENTER, fmt, LinScale())
		end
		drawline(ctx, Point2D(x, yframe), Point2D(x, yframe-lyt.length_tickmajor))
	end
end
function render_yticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, ylines::UndefinedGridLines)
	fmt = TickLabelFormatting(NoRangeDisplayInfo())
	graphbb = rstrip.bb
	xframe = graphbb.xmin
	xlabel = graphbb.xmin - lyt.hoffset_yticklabel
	for (y, ylabel) in [(graphbb.ymax, ylines.minline), (graphbb.ymin, ylines.maxline)]
		render_ticklabel(ctx, ylabel, Point2D(xlabel, y), lyt.font_ticklabel, ALIGN_RIGHT|ALIGN_VCENTER, fmt, LinScale())
		drawline(ctx, Point2D(xframe, y), Point2D(xframe+lyt.length_tickmajor, y))
	end
end

function render_ticks(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, xticklabels::Bool)
	#NOTE: Use coordinate grid (cgrid) to render ticks
	render_xticks(ctx, rstrip, lyt, rstrip.cgrid.xlines, xticklabels)
	render_yticks(ctx, rstrip, lyt, rstrip.cgrid.ylines)
end

#==High-level rendering
===============================================================================#

#Render axis labels, ticks, ...
#-------------------------------------------------------------------------------
function render_axes(ctx::CairoContext, rstrip::RStrip2D, lyt::PlotLayout, xticklabels::Bool)
	render_graphframe(ctx, rstrip, lyt.frame_data)
	render_ticks(ctx, rstrip, lyt, xticklabels)
end

#Last line


abstract type AbstractOptimizer end
abstract type AbstractConstrainedOptimizer <: AbstractOptimizer end
abstract type ZerothOrderOptimizer <: AbstractOptimizer end
abstract type FirstOrderOptimizer  <: AbstractOptimizer end
abstract type SecondOrderOptimizer <: AbstractOptimizer end
abstract type UnivariateOptimizer  <: AbstractOptimizer end

abstract type AbstractOptimizerState end
abstract type ZerothOrderState <: AbstractOptimizerState end

"""
Configurable options with defaults (values 0 and NaN indicate unlimited):
```
x_abstol::Real = 0.0,
x_reltol::Real = 0.0,
f_abstol::Real = 0.0,
f_reltol::Real = 0.0,
g_abstol::Real = 1e-8,
g_reltol::Real = 1e-8,
outer_x_abstol::Real = 0.0,
outer_x_reltol::Real = 0.0,
outer_f_abstol::Real = 0.0,
outer_f_reltol::Real = 0.0,
outer_g_abstol::Real = 1e-8,
outer_g_reltol::Real = 1e-8,
f_calls_limit::Int = 0,
g_calls_limit::Int = 0,
h_calls_limit::Int = 0,
allow_f_increases::Bool = true,
allow_outer_f_increases::Bool = true,
successive_f_tol::Int = 1,
iterations::Int = 1_000,
outer_iterations::Int = 1000,
store_trace::Bool = false,
show_trace::Bool = false,
extended_trace::Bool = false,
show_every::Int = 1,
callback = nothing,
time_limit = NaN
```
See http://julianlsolvers.github.io/Optim.jl/stable/#user/config/
"""
struct Options{T, TCallback}
    x_abstol::T
    x_reltol::T
    f_abstol::T
    f_reltol::T
    g_abstol::T
    g_reltol::T
    outer_x_abstol::T
    outer_x_reltol::T
    outer_f_abstol::T
    outer_f_reltol::T
    outer_g_abstol::T
    outer_g_reltol::T
    f_calls_limit::Int
    g_calls_limit::Int
    h_calls_limit::Int
    allow_f_increases::Bool
    allow_outer_f_increases::Bool
    successive_f_tol::Int
    iterations::Int
    outer_iterations::Int
    store_trace::Bool
    trace_simplex::Bool
    show_trace::Bool
    extended_trace::Bool
    show_every::Int
    callback::TCallback
    time_limit::Float64
end

function Options(;
        x_tol = nothing,
        f_tol = nothing,
        g_tol = nothing,
        x_abstol::Real = 0.0,
        x_reltol::Real = 0.0,
        f_abstol::Real = 0.0,
        f_reltol::Real = 0.0,
        g_abstol::Real = 1e-8,
        g_reltol::Real = 1e-8,
        outer_x_tol = 0.0,
        outer_f_tol = 0.0,
        outer_g_tol = nothing,
        outer_x_abstol::Real = 0.0,
        outer_x_reltol::Real = 0.0,
        outer_f_abstol::Real = 0.0,
        outer_f_reltol::Real = 0.0,
        outer_g_abstol::Real = 1e-8,
        outer_g_reltol::Real = 1e-8,
        f_calls_limit::Int = 0,
        g_calls_limit::Int = 0,
        h_calls_limit::Int = 0,
        allow_f_increases::Bool = true,
        allow_outer_f_increases::Bool = true,
        successive_f_tol::Int = 1,
        iterations::Int = 1_000,
        outer_iterations::Int = 1000,
        store_trace::Bool = false,
        trace_simplex::Bool = false,
        show_trace::Bool = false,
        extended_trace::Bool = false,
        show_every::Int = 1,
        callback = nothing,
        time_limit = NaN)
    show_every = show_every > 0 ? show_every : 1
    #if extended_trace && callback == nothing
    #    show_trace = true
    #end
    if !(x_tol == nothing)
        x_abstol = x_tol
    end
    if !(g_tol == nothing)
        g_abstol = g_tol
    end
    if !(f_tol == nothing)
        f_reltol = f_tol
    end
    if !(outer_x_tol == nothing)
        outer_x_abstol = outer_x_tol
    end
    if !(outer_g_tol == nothing)
        outer_g_abstol = outer_g_tol
    end
    if !(outer_f_tol == nothing)
        outer_f_reltol = outer_f_tol
    end
    Options(promote(x_abstol, x_reltol, f_abstol, f_reltol, g_abstol, g_reltol, outer_x_abstol, outer_x_reltol, outer_f_abstol, outer_f_reltol, outer_g_abstol, outer_g_reltol)..., f_calls_limit, g_calls_limit, h_calls_limit,
        allow_f_increases, allow_outer_f_increases, successive_f_tol, Int(iterations), Int(outer_iterations), store_trace, trace_simplex, show_trace, extended_trace,
        Int(show_every), callback, Float64(time_limit))
end

function Base.show(io::IO, o::Optim.Options)
    for k in fieldnames(typeof(o))
        v = getfield(o, k)
        if v isa Nothing
            @printf io "%24s = %s\n" k "nothing"
        else
            @printf io "%24s = %s\n" k v
        end
    end
end

function print_header(options::Options)
    if options.show_trace
        @printf "Iter     Function value   Gradient norm \n"
    end
end

function print_header(method::AbstractOptimizer)
        @printf "Iter     Function value   Gradient norm \n"
end

struct OptimizationState{Tf<:Real, T <: AbstractOptimizer}
    iteration::Int
    value::Tf
    g_norm::Tf
    metadata::Dict
end

const OptimizationTrace{Tf, T} = Vector{OptimizationState{Tf, T}}

abstract type OptimizationResults end

mutable struct MultivariateOptimizationResults{O, T, Tx, Tc, Tf, M, Tls, Tsb} <: OptimizationResults
    method::O
    initial_x::Tx
    minimizer::Tx
    minimum::Tf
    iterations::Int
    iteration_converged::Bool
    x_converged::Bool
    x_abstol::T
    x_reltol::T
    x_abschange::Tc
    x_relchange::Tc
    f_converged::Bool
    f_abstol::T
    f_reltol::T
    f_abschange::Tc
    f_relchange::Tc
    g_converged::Bool
    g_abstol::T
    g_residual::Tc
    f_increased::Bool
    trace::M
    f_calls::Int
    g_calls::Int
    h_calls::Int
    ls_success::Tls
    time_limit::Float64
    time_run::Float64
    stopped_by::Tsb
end

# pick_best_x and pick_best_f are used to pick the minimizer if we stopped because
# f increased and we didn't allow it
pick_best_x(f_increased, state) = f_increased ? state.x_previous : state.x
pick_best_f(f_increased, state, d) = f_increased ? state.f_x_previous : value(d)

function Base.show(io::IO, t::OptimizationState)
    @printf io "%6d   %14e   %14e\n" t.iteration t.value t.g_norm
    if !isempty(t.metadata)
        for (key, value) in t.metadata
            @printf io " * %s: %s\n" key value
        end
    end
    return
end

function Base.show(io::IO, tr::OptimizationTrace)
    @printf io "Iter     Function value   Gradient norm \n"
    @printf io "------   --------------   --------------\n"
    for state in tr
        show(io, state)
    end
    return
end

function Base.show(io::IO, r::MultivariateOptimizationResults)
    take = Iterators.take
    failure_string = "failure"
    if iteration_limit_reached(r)
        failure_string *= " (reached maximum number of iterations)"
    end
    if f_increased(r) && !iteration_limit_reached(r)
        failure_string *= " (objective increased between iterations)"
    end
    if isa(r.ls_success, Bool) && !r.ls_success
        failure_string *= " (line search failed)"
    end
    if time_run(r) > time_limit(r)
        failure_string *= " (exceeded time limit of $(time_limit(r)))"
    end

    @printf io " * Status: %s\n\n" converged(r) ? "success" : failure_string

    @printf io " * Candidate solution\n"
    @printf io "    Final objective value:     %e\n" minimum(r)
    @printf io "\n"

    @printf io " * Found with\n"
    @printf io "    Algorithm:     %s\n" summary(r)

    @printf io "\n"
    @printf io " * Convergence measures\n"
    if isa(r.method, NelderMead)
        @printf io "    √(Σ(yᵢ-ȳ)²)/n %s %.1e\n" g_converged(r) ? "≤" : "≰" g_tol(r)
    else
        @printf io "    |x - x'|               = %.2e %s %.1e\n"  x_abschange(r) x_abschange(r)<=x_abstol(r) ? "≤" : "≰" x_abstol(r)
        @printf io "    |x - x'|/|x'|          = %.2e %s %.1e\n"  x_relchange(r) x_relchange(r)<=x_reltol(r) ? "≤" : "≰" x_reltol(r)
        @printf io "    |f(x) - f(x')|         = %.2e %s %.1e\n"  f_abschange(r) f_abschange(r)<=f_abstol(r) ? "≤" : "≰" f_abstol(r)
        @printf io "    |f(x) - f(x')|/|f(x')| = %.2e %s %.1e\n"  f_relchange(r) f_relchange(r)<=f_reltol(r) ? "≤" : "≰" f_reltol(r)
        @printf io "    |g(x)|                 = %.2e %s %.1e\n" g_residual(r) g_residual(r)<=g_tol(r) ?  "≤" : "≰" g_tol(r)
    end

    @printf io "\n"

    @printf io " * Work counters\n"
    @printf io "    Seconds run:   %d  (vs limit %d)\n" time_run(r) isnan(time_limit(r)) ? Inf : time_limit(r)
    @printf io "    Iterations:    %d\n" iterations(r)
    @printf io "    f(x) calls:    %d\n" f_calls(r)
    if !(isa(r.method, NelderMead) || isa(r.method, SimulatedAnnealing))
        @printf io "    ∇f(x) calls:   %d\n" g_calls(r)
    end
    if isa(r.method, Newton) || isa(r.method, NewtonTrustRegion)
        @printf io "    ∇²f(x) calls:  %d\n" h_calls(r)
    end
    return
end

function Base.append!(a::MultivariateOptimizationResults, b::MultivariateOptimizationResults)
    a.iterations += iterations(b)
    a.minimizer = minimizer(b)
    a.minimum = minimum(b)
    a.iteration_converged = iteration_limit_reached(b)
    a.x_converged = x_converged(b)
    a.f_converged = f_converged(b)
    a.g_converged = g_converged(b)
    append!(a.trace, b.trace)
    a.f_calls += f_calls(b)
    a.g_calls += g_calls(b)
end


using IteratorInterfaceExtensions
using TableTraitsUtils
import DataValues

IteratorInterfaceExtensions.isiterable(x::DataFrame) = true
TableTraits.isiterabletable(x::DataFrame) = true

function TableTraits.getiterator(df::DataFrame)
    col_names_raw = [c for c in df.columns]
    col_names = Symbol.(col_names_raw)
    column_data = [eltype(df[c]) == String ? [df[c].iloc[j] for j in 1:length(df)] : values(df[c]) for c in col_names_raw]
    return create_tableiterator(column_data, col_names)
end

TableTraits.supports_get_columns_copy_using_missing(df::DataFrame) = true

function TableTraits.get_columns_copy_using_missing(df::PandasLite.DataFrame)
    # return a named tuple of columns here
    col_names_raw = [c for c in df.columns]
    col_names = Symbol.(col_names_raw)
    cols = (Array(eltype(df[c]) == String ? [df[c].iloc[j] for j in 1:length(df)] : df[c]) for c in col_names_raw)
    return NamedTuple{tuple(col_names...)}(tuple(cols...))
end

function _construct_pandas_from_iterabletable(source)
    y = create_columns_from_iterabletable(source, errorhandling = :returnvalue)
    isnothing(y) && return nothing
    columns, column_names = y[1], y[2]
    cols = Dict{Symbol,Any}(i[1] => i[2] for i in zip(column_names, columns))
    for (k,v) in pairs(cols)
        if eltype(v) <: DataValues.DataValue
            T = eltype(eltype(v))
            if T <: AbstractFloat
                cols[k] = T[get(i, NaN) for i in v]
            elseif T <: Integer
                cols[k] = Float64[DataValues.isna(i) ? NaN : Float64(get(i)) for i in v]
            else
                throw(ArgumentError("Can't create a PandasLite.DataFrame from a source that has missing data."))
            end
        end
    end
    return DataFrame(cols)
end


module ReverseSearch

export reverse_search_producer, ReverseSearchParams

VERSION < v"0.4-" && using Docile

type ReverseSearchParams
    adj
    max_degere
    sink
    f
end

@doc doc"""
Equivalent to calling

    reverse_search_producer(p.adj, p.max_degree, p.sink, p.f)

Intended for use cases where `ReverseSearchParams` cleanly encapsulate some
problem domain.
""" ->
function reverse_search_producer(p::ReverseSearchParams)
    reverse_search_producer(p.adj, p.max_degere, p.sink, p.f)
end

@doc doc"""
# Input

- `adj` should be a function mapping `(vertex, n)` to `Union(child, Union{})`, where `child` is the `n`-th child of `vertex` in the traversal tree, or `Union{}` if `n` is greater than the degree of `vertex` in the tree
- `max_degree` should be a nonnegative integer denoting the maximum degree of a vertex in the tree
- `sink` should be a vertex
- `f` should map a vertex to its parent in the traversal tree

# Output
A producer for the nodes of the graph.
""" ->
function reverse_search_producer(adj, max_degree, sink, f)
    current_vertex = sink
    neighbor_counter = 0
    while true
        while neighbor_counter < max_degree
            neighbor_counter += 1
            next_vertex = adj(current_vertex, neighbor_counter)
            if (next_vertex != Union{}) && (f(next_vertex) == current_vertex)
                # reverse traverse (away from sink)
                current_vertex = next_vertex
                neighbor_counter = 0
            end
        end
        if current_vertex != sink
            produce(current_vertex)
            # forward traverse (towards sink)
            prev_vertex = current_vertex
            current_vertex = f(current_vertex)
            neighbor_counter = 0
            while true
                neighbor_counter += 1
                if adj(current_vertex, neighbor_counter) == prev_vertex
                    break
                end
            end
        end
        if current_vertex == sink && neighbor_counter == max_degree
            break
        end
    end
    produce(sink)
end

end # module


# -*- encoding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright © 2021 Matteo Foglieni and Riccardo Gervasoni
#

@testset "test_luminosity" begin
     a = RGB{Float32}(8.0, 10.0, 22.0)
     b = RGB{Float32}(1.0, 100.0, 221.0)
     @test Raytracing.luminosity(a) ≈ 15.0
     @test Raytracing.luminosity(b) ≈ 111.0
end

@testset "test_avg_lum" begin
     rgb_matrix = [RGB(5.0, 10.0, 15.0), RGB(500.0, 1000.0, 1500.0)]
     img = Raytracing.HDRimage(2, 1, rgb_matrix)
     @test Raytracing.avg_lum(img, 0.0) ≈ 100.0
end

@testset "normalize_image" begin
     img = Raytracing.HDRimage(2, 1, [RGB(5.0, 10.0, 15.0), RGB(500.0, 1000.0, 1500.0)])
     Raytracing.normalize_image!(img, 1000.0, 100.0)
     @test Raytracing.get_pixel(img, 0, 0) ≈ RGB(0.5e2, 1.0e2, 1.5e2)
     @test Raytracing.get_pixel(img, 1, 0) ≈ RGB(0.5e4, 1.0e4, 1.5e4)

     img = Raytracing.HDRimage(2, 1, [RGB(5.0, 10.0, 15.0), RGB(500.0, 1000.0, 1500.0)])
     Raytracing.normalize_image!(img, 1000.0)
     @test Raytracing.get_pixel(img, 0, 0) ≈ RGB(0.5e2, 1.0e2, 1.5e2)
     @test Raytracing.get_pixel(img, 1, 0) ≈ RGB(0.5e4, 1.0e4, 1.5e4)
end

@testset "test_clamp_image" begin
     img = Raytracing.HDRimage(2, 1, [RGB(5.0, 10.0, 15.0), RGB(500.0, 1000.0, 1500.0)])
     Raytracing.clamp_image!(img)
     @test Raytracing.get_pixel(img, 0, 0).r >= 0 && Raytracing.get_pixel(img, 0, 0).r <= 1
     @test Raytracing.get_pixel(img, 0, 0).g >= 0 && Raytracing.get_pixel(img, 0, 0).g <= 1
     @test Raytracing.get_pixel(img, 0, 0).b >= 0 && Raytracing.get_pixel(img, 0, 0).b <= 1
end

@testset "test_γcorrection" begin
     img = Raytracing.HDRimage(2, 3, [RGB(1.0, 2.0, 3.0), RGB(4.0, 5.0, 6.0), RGB(7.0, 8.0, 9.0), RGB(10.0, 11.0, 12.0), RGB(13.0, 14.0, 15.0), RGB(16.0, 17.0, 18.0)])
     img2 = Raytracing.HDRimage(2, 3, [RGB(255., 360., 441.)/255., RGB(510., 570., 624.)/255., RGB(674., 721, 765.)/255., RGB(806., 845., 883.)/255., RGB(919., 954., 987.)/255., RGB(1020., 1051., 1081.)/255.])
     Raytracing.γ_correction!(img, 2.0)
     @test img ≈ img2
end

@testset "test_get_matrix" begin
     img = Raytracing.HDRimage(2, 3, [
               RGB(1.0, 2.0, 3.0), RGB(4.0, 5.0, 6.0), RGB(7.0, 8.0, 9.0),
               RGB(10.0, 11.0, 12.0), RGB(13.0, 14.0, 15.0), RGB(16.0, 17.0, 18.0)
               ])
     M1 = RGB{Float32}[
          RGB(1.0, 2.0, 3.0) RGB(4.0, 5.0, 6.0) ;
          RGB(7.0, 8.0, 9.0) RGB(10.0, 11.0, 12.0) ;
          RGB(13.0, 14.0, 15.0) RGB(16.0, 17.0, 18.0)
          ]
     @test M1 ≈  Raytracing.get_matrix(img)
end

##########################################################################################92

@testset "test_tone_mapping_inputs" begin
     @test_throws MethodError tone_mapping("abracadabra")
     @test_throws ArgumentError tone_mapping(["abracadabra"])
     @test_throws ArgumentError tone_mapping(["a", "b", "c", "d", "e", "f"])
end

@testset "test_parse_tonemapping_settings" begin
     @test isnothing(tone_mapping(
                         "infile"=>"prova.pfm",
                         "outfile"=>"prova.png",
                         "normalization"=>0.18,
                         "gamma"=>1.0,
                         "avg_lum"=>1.0,
                         "ONLY_FOR_TESTS"=>true,
                    ))

     @test isnothing(tone_mapping(
                         "normalization"=>0.18,
                         "infile"=>"prova.pfm",
                         "ONLY_FOR_TESTS"=>true,
                         "outfile"=>"prova.png",
                    ))

     @test_throws ArgumentError tone_mapping(
                         "infile"=>"prova.pfm",
                         "ONLY_FOR_TESTS"=>true,
                    )
     @test_throws ArgumentError tone_mapping(
                         "outfile"=>"prova.png",
                         "ONLY_FOR_TESTS"=>true,
                    )

     @test_throws ArgumentError tone_mapping(
                         "ONLY_FOR_TESTS"=>true,
                    )
end


function generate_dfpair(n, str_cols = 2, float_cols = 5)
    DfPair([
        DataFrame(union(
            Dict("scol_$i" => [randstring(rand(4:20)) for _ = 1:n] for i = 1:str_cols),
            Dict("fcol_$i" => rand(n) for i = 1:float_cols),
        )) for i = 1:2
    ]...)
end

function load_result(dir::String)
    res_dir = joinpath(dir, "result")
    Dict(split(resf, ".")[1] => ParquetFiles.load(joinpath(res_dir, resf)) |> DataFrame |> df -> hcat(df.i1, df.i2)
    for resf in readdir(res_dir))
end


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 8f9a3074-f92f-11eb-33dd-41eeab6f1ece
using Pkg

# ╔═╡ 15d15589-759d-4490-91dd-033f0eff550e
Pkg.add("LightGraphs")

# ╔═╡ 50dbc3e2-cbdf-470a-b71b-488a8423877b
begin
	Pkg.add("PyPlot")
end

# ╔═╡ c3cd9a02-a051-49be-a558-b57ecedbad5d
using GraphRecipes

# ╔═╡ 33e4b886-01f1-4438-889b-f4ebdefed9ce
using LightGraphs

# ╔═╡ 79a55dae-cd3e-4f90-9973-b710c744b6a6
using GraphPlot

# ╔═╡ 78cc9dcf-50d8-4284-9669-7323a05cd8f1
using Plots

# ╔═╡ 2ac3ef7b-7a9c-42c9-ae49-694094cbba10
#pgfplotsx()

# ╔═╡ 29f9ee4a-40a1-406a-bcdd-d763165a4e48
pyplot()

# ╔═╡ 2b807a12-be01-4dee-82f0-55e8e538406a
positions = [
	0 0;
	-1 1;
	1 1
]

# ╔═╡ 0bca08d0-20d5-4b2f-b682-7e4b3f7c9c78
n=3

# ╔═╡ 64cd57ea-3146-4d05-9e40-d9111971c502
pp = Dict(
	:a => Dict(
		:b => 1,
		:c => 2
	)
)

# ╔═╡ 7832eb0c-35fa-49c7-8ec4-e75059314d0c
const A = Float64[ rand() < 0.5 ? 0 : rand() for i=1:n, j=1:n]

# ╔═╡ abb9e1b1-20b2-4f6b-a374-a1d9bd67ae93
names = ["A", "B", "C"]

# ╔═╡ a046ced9-90b7-47c0-bf42-f5b1c50d2ca1
begin
	nvertices = 15
	G1 = Graph(nvertices)
	labels = [
		"¹H+¹H → ²H + e",
		"¹H+¹H+e → ²H",
		"²H+H → ³H",
		"³He + ³He → ⁴He + 2 ¹H",
		"³He + ⁴He → ⁷Be",
		"³He + ⁴He → ⁷Be",
		"⁷Be + e → ⁷Li + ν_e",
		"⁷Be + ¹H → ⁸B + γ",
		"⁷Li + ¹H -> ⁴He + ⁴He",
		"⁸B -> ⁷Be +e + ν",
		"⁸Be -> ⁴He + ⁴He",
	]
	#labels = ["1a", "b", "c²", "d", "e", "f", "g", "h", "i", "j", "k"]
	edgelabels = Dict()
	x = [
		-1,
		1,
		0,
		-2,
		0,
		2,
		0,
		2,
		0,
		2,
		2
	]
	y = [
		8,
		8,
		6,
		3,
		2,
		3,
		1,
		2,
		0,
		1,
		0
	]

	add_edge!(G1, 1, 3)
	edgelabels[(1,3)] = "99,75%         "
	add_edge!(G1, 2, 3)
	edgelabels[(2,3)] = "    0,25%"

	add_edge!(G1, 3, 4)
	edgelabels[(3,4)] = " I\n91%"
	add_edge!(G1, 3, 5)
	edgelabels[(3,5)] = "II\n9%"
	add_edge!(G1, 3, 6)
	edgelabels[(3,6)] = "III\n0.1% "

	add_edge!(G1, 5, 7)
	add_edge!(G1, 6, 8)

	add_edge!(G1, 7, 9)
	add_edge!(G1, 8, 10)

	add_edge!(G1, 10, 11)
end

# ╔═╡ 6d50a9a9-d650-4997-9257-0f4da6136914
plot = graphplot(
	G1,
	names=labels,
	fontsize=10,
	nodeshape=:rect,
	nodesize=0.0,
	x=x,#.*400,
	y=y,#.*400,
	size=(800,1000),
	dpi=100,
	curvature_scalar=0,
	nodecolor=:white,
	shorten=0.1,
	edgelabel=edgelabels,
	edge_label_box=true,
	edgelabel_offset=0.2,
)

# ╔═╡ b7c805b7-cd32-4248-bb8a-5ee067b1183e
typeof(G1)

# ╔═╡ f431b7f9-4c36-4435-85fc-3102e1f2dea7
labels

# ╔═╡ ee189abc-2707-4281-b5cb-73b2890ae023
savefig(plot, "pp.png")

# ╔═╡ ea90e75c-ed5b-48b2-8e7e-f4af4d0d666c
savefig(plot, "pp.svg")

# ╔═╡ 153589fc-5060-4fc2-b6b5-1834450b6847
nv(G1)

# ╔═╡ 7bc0c196-f54b-4c11-8f6e-54ad4636df3d
# set positions manually to avoid randomness!

# ╔═╡ 25cebcd0-a75a-4373-96a6-a0b778fb4f90
savefig(plot, "test.svg")

# ╔═╡ 1d325978-a695-4db9-a55a-353e8b216430
length(labels)

# ╔═╡ cf1815ee-4219-4cac-8225-200b37d13a08
ne(G1)

# ╔═╡ 18247747-32a6-4eb0-87cc-1595c2582e0e
begin
	G2 = Graph(10)
	add_edge!(G2, 1, 3)
	add_edge!(G2, 1, 2)

	# first fusion
	add_edge!(G2, 2, 4)
	add_edge!(G2, 3, 4)

	# left branch
	add_edge!(G2, 4, 5)

	# middle branch
	#add_edge!(G2, 4, 6)

	# right branch
	#add_edge!(G2, 4, 7)
	#x = [0, -1, 1, 0, 0]
	#y = [3, 2, 2, 1, 0]
	#z = [0, 0, 0, 0, 0]
end

# ╔═╡ 054e3631-29b9-45b3-8d62-0ecc5646fe87
graphplot(G2, method=:tree)#, x=x, y=y, z=z)

# ╔═╡ e60e8737-1494-4b34-bc48-ca843c0f17f9
gplot(G2)

# ╔═╡ edf2e096-01e2-4697-8a87-05c188e5b03b

# ╔═╡ 6f7611e6-210a-4b64-b44a-e649b5fb0fa4
a  = Dict()

# ╔═╡ 0e915d78-676f-4e62-b524-72817c48c710
a["1"] = 2

# ╔═╡ 1b44df22-ed90-43ef-a6d5-92f9609b7187
a

# ╔═╡ Cell order:
# ╠═8f9a3074-f92f-11eb-33dd-41eeab6f1ece
# ╠═15d15589-759d-4490-91dd-033f0eff550e
# ╠═c3cd9a02-a051-49be-a558-b57ecedbad5d
# ╠═33e4b886-01f1-4438-889b-f4ebdefed9ce
# ╠═79a55dae-cd3e-4f90-9973-b710c744b6a6
# ╠═78cc9dcf-50d8-4284-9669-7323a05cd8f1
# ╠═2ac3ef7b-7a9c-42c9-ae49-694094cbba10
# ╠═50dbc3e2-cbdf-470a-b71b-488a8423877b
# ╠═29f9ee4a-40a1-406a-bcdd-d763165a4e48
# ╠═2b807a12-be01-4dee-82f0-55e8e538406a
# ╠═0bca08d0-20d5-4b2f-b682-7e4b3f7c9c78
# ╠═64cd57ea-3146-4d05-9e40-d9111971c502
# ╠═7832eb0c-35fa-49c7-8ec4-e75059314d0c
# ╠═abb9e1b1-20b2-4f6b-a374-a1d9bd67ae93
# ╠═a046ced9-90b7-47c0-bf42-f5b1c50d2ca1
# ╠═6d50a9a9-d650-4997-9257-0f4da6136914
# ╠═b7c805b7-cd32-4248-bb8a-5ee067b1183e
# ╠═f431b7f9-4c36-4435-85fc-3102e1f2dea7
# ╠═ee189abc-2707-4281-b5cb-73b2890ae023
# ╠═ea90e75c-ed5b-48b2-8e7e-f4af4d0d666c
# ╠═153589fc-5060-4fc2-b6b5-1834450b6847
# ╠═7bc0c196-f54b-4c11-8f6e-54ad4636df3d
# ╠═25cebcd0-a75a-4373-96a6-a0b778fb4f90
# ╠═1d325978-a695-4db9-a55a-353e8b216430
# ╠═cf1815ee-4219-4cac-8225-200b37d13a08
# ╠═18247747-32a6-4eb0-87cc-1595c2582e0e
# ╠═054e3631-29b9-45b3-8d62-0ecc5646fe87
# ╠═e60e8737-1494-4b34-bc48-ca843c0f17f9
# ╠═edf2e096-01e2-4697-8a87-05c188e5b03b
# ╠═6f7611e6-210a-4b64-b44a-e649b5fb0fa4
# ╠═0e915d78-676f-4e62-b524-72817c48c710
# ╠═1b44df22-ed90-43ef-a6d5-92f9609b7187