text:
const __bodyfunction__ = Dict{Method,Any}()

# Find keyword "body functions" (the function that contains the body
# as written by the developer, called after all missing keyword-arguments
# have been assigned values), in a manner that doesn't depend on
# gensymmed names.
# `mnokw` is the method that gets called when you invoke it without
# supplying any keywords.
function __lookup_kwbody__(mnokw::Method)
    function getsym(arg)
        isa(arg, Symbol) && return arg
        @assert isa(arg, GlobalRef)
        return arg.name
    end

    f = get(__bodyfunction__, mnokw, nothing)
    if f === nothing
        fmod = mnokw.module
        # The lowered code for `mnokw` should look like
        #   %1 = mkw(kwvalues..., #self#, args...)
        #        return %1
        # where `mkw` is the name of the "active" keyword body-function.
        ast = Base.uncompressed_ast(mnokw)
        if isa(ast, Core.CodeInfo) && length(ast.code) >= 2
            callexpr = ast.code[end-1]
            if isa(callexpr, Expr) && callexpr.head == :call
                fsym = callexpr.args[1]
                if isa(fsym, Symbol)
                    f = getfield(fmod, fsym)
                elseif isa(fsym, GlobalRef)
                    if fsym.mod === Core && fsym.name === :_apply
                        f = getfield(mnokw.module, getsym(callexpr.args[2]))
                    elseif fsym.mod === Core && fsym.name === :_apply_iterate
                        f = getfield(mnokw.module, getsym(callexpr.args[3]))
                    else
                        f = getfield(fsym.mod, fsym.name)
                    end
                else
                    f = missing
                end
            else
                f = missing
            end
        else
            f = missing
        end
        __bodyfunction__[mnokw] = f
    end
    return f
end

function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:label, :blank),Tuple{Symbol,Bool}},Type{Plots.EmptyLayout}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:label, :width, :height),Tuple{Symbol,Symbol,Measures.Length{:pct,Float64}}},Type{Plots.EmptyLayout}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:parent,),Tuple{Plots.GridLayout}},Type{Plots.Subplot},Plots.GRBackend})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:parent,),Tuple{Plots.GridLayout}},Type{Plots.Subplot},Plots.PlotlyBackend})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:parent,),Tuple{Plots.Subplot{Plots.GRBackend}}},Type{Plots.Subplot},Plots.GRBackend})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.Type)),NamedTuple{(:parent,),Tuple{Plots.Subplot{Plots.PlotlyBackend}}},Type{Plots.Subplot},Plots.PlotlyBackend})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots._make_hist)),NamedTuple{(:normed, :weights),Tuple{Bool,Array{Int64,1}}},typeof(Plots._make_hist),Tuple{Array{Float64,1}},Symbol})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots._make_hist)),NamedTuple{(:normed, :weights),Tuple{Bool,Nothing}},typeof(Plots._make_hist),Tuple{Array{Float64,1},Array{Float64,1}},Int64})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots._make_hist)),NamedTuple{(:normed, :weights),Tuple{Bool,Nothing}},typeof(Plots._make_hist),Tuple{Array{Float64,1}},Symbol})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:flip,),Tuple{Bool}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:formatter,),Tuple{typeof(RecipesPipeline.datetimeformatter)}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:grid, :lims),Tuple{Bool,Tuple{Int64,Int64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:grid, :lims, :flip),Tuple{Bool,Tuple{Int64,Int64},Bool}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:gridlinewidth, :grid, :gridalpha, :gridstyle, :foreground_color_grid),Tuple{Int64,Bool,Float64,Symbol,RGBA{Float64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:guide,),Tuple{String}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:lims, :flip, :ticks, :guide),Tuple{Tuple{Int64,Int64},Bool,StepRange{Int64,Int64},String}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:lims,),Tuple{Tuple{Float64,Float64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:lims,),Tuple{Tuple{Int64,Float64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:lims,),Tuple{Tuple{Int64,Int64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:rotation,),Tuple{Int64}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:scale, :guide),Tuple{Symbol,String}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.attr!)),NamedTuple{(:ticks,),Tuple{UnitRange{Int64}}},typeof(attr!),Plots.Axis})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.default)),NamedTuple{(:titlefont, :legendfontsize, :guidefont, :tickfont, :guide, :framestyle, :yminorgrid),Tuple{Tuple{Int64,String},Int64,Tuple{Int64,Symbol},Tuple{Int64,Symbol},String,Symbol,Bool}},typeof(default)})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.gr_polyline)),NamedTuple{(:arrowside, :arrowstyle),Tuple{Symbol,Symbol}},typeof(Plots.gr_polyline),Array{Int64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.gr_polyline)),NamedTuple{(:arrowside, :arrowstyle),Tuple{Symbol,Symbol}},typeof(Plots.gr_polyline),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.gr_polyline)),NamedTuple{(:arrowside, :arrowstyle),Tuple{Symbol,Symbol}},typeof(Plots.gr_polyline),StepRange{Int64,Int64},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.gr_polyline)),NamedTuple{(:arrowside, :arrowstyle),Tuple{Symbol,Symbol}},typeof(Plots.gr_polyline),UnitRange{Int64},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.gr_polyline)),NamedTuple{(:arrowside, :arrowstyle),Tuple{Symbol,Symbol}},typeof(Plots.gr_polyline),UnitRange{Int64},UnitRange{Int64}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.heatmap)),Any,typeof(heatmap),Any,Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.hline!)),Any,typeof(hline!),Any})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.lens!)),Any,typeof(lens!),Any,Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:alpha, :seriestype),Tuple{Float64,Symbol}},typeof(plot!),Array{GeometryBasics.Point{2,Float64},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:annotation,),Tuple{Array{Tuple{Int64,Float64,Plots.PlotText},1}}},typeof(plot!)})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:line, :seriestype),Tuple{Tuple{Int64,Symbol,Float64,Array{Symbol,2}},Symbol}},typeof(plot!),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:marker, :series_annotations, :seriestype),Tuple{Tuple{Int64,Float64,Symbol},Array{Any,1},Symbol}},typeof(plot!),Plots.Plot{Plots.GRBackend},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:marker, :series_annotations, :seriestype),Tuple{Tuple{Int64,Float64,Symbol},Array{Any,1},Symbol}},typeof(plot!),Plots.Plot{Plots.PlotlyBackend},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:marker, :series_annotations, :seriestype),Tuple{Tuple{Int64,Float64,Symbol},Array{Any,1},Symbol}},typeof(plot!),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:markersize, :c, :seriestype),Tuple{Int64,Symbol,Symbol}},typeof(plot!),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:seriestype, :inset),Tuple{Symbol,Tuple{Int64,Measures.BoundingBox{Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}},Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}}}}}},typeof(plot!),Array{Int64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:seriestype, :inset),Tuple{Symbol,Tuple{Int64,Measures.BoundingBox{Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}},Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}}}}}},typeof(plot!),Plots.Plot{Plots.GRBackend},Array{Int64,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:seriestype, :inset),Tuple{Symbol,Tuple{Int64,Measures.BoundingBox{Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}},Tuple{Measures.Length{:w,Float64},Measures.Length{:h,Float64}}}}}},typeof(plot!),Plots.Plot{Plots.PlotlyBackend},Array{Int64,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:seriestype,),Tuple{Symbol}},typeof(plot!),Array{Int64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:title,),Tuple{String}},typeof(plot!)})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:w,),Tuple{Int64}},typeof(plot!),Array{Float64,1},Array{Float64,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:w,),Tuple{Int64}},typeof(plot!),Plots.Plot{Plots.GRBackend},Array{Float64,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:xgrid,),Tuple{Tuple{Symbol,Symbol,Int64,Symbol,Float64}}},typeof(plot!),Plots.Plot{Plots.GRBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:yaxis,),Tuple{Tuple{String,Symbol}}},typeof(plot!)})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype),Tuple{Array{Float64,1},Tuple{Symbol,Float64,Plots.Stroke},Array{Float64,1},String,Symbol}},typeof(plot!),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype),Tuple{Array{Float64,1},Tuple{Symbol,Float64,Plots.Stroke},Array{Float64,1},String,Symbol}},typeof(plot!),Plots.Plot{Plots.GRBackend},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype),Tuple{Array{Float64,1},Tuple{Symbol,Float64,Plots.Stroke},Array{Float64,1},String,Symbol}},typeof(plot!),Plots.Plot{Plots.PlotlyBackend},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:annotations, :leg),Tuple{Tuple{Int64,Float64,Plots.PlotText},Bool}},typeof(plot),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:arrow,),Tuple{Int64}},typeof(plot),Array{Float64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:aspect_ratio, :seriestype),Tuple{Int64,Symbol}},typeof(plot),Array{String,1},Array{String,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:bins, :weights, :seriestype),Tuple{Symbol,Array{Int64,1},Symbol}},typeof(plot),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:color, :line, :marker),Tuple{Array{Symbol,2},Tuple{Symbol,Int64},Tuple{Array{Symbol,2},Int64,Float64,Plots.Stroke}}},typeof(plot),Array{Array{T,1} where T,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:connections, :seriestype),Tuple{Tuple{Array{Int64,1},Array{Int64,1},Array{Int64,1}},Symbol}},typeof(plot),Array{Int64,1},Array{Int64,1},Vararg{Array{Int64,1},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:fill, :seriestype),Tuple{Bool,Symbol}},typeof(plot),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:framestyle, :title, :color, :layout, :label, :markerstrokewidth, :ticks, :seriestype),Tuple{Array{Symbol,2},Array{String,2},Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}},Int64,String,Int64,UnitRange{Int64},Symbol}},typeof(plot),Array{Array{Float64,1},1},Array{Array{Float64,1},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:grid, :title),Tuple{Tuple{Symbol,Symbol,Symbol,Int64,Float64},String}},typeof(plot),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:lab, :w, :palette, :fill, :α),Tuple{String,Int64,PlotUtils.ContinuousColorGradient,Int64,Float64}},typeof(plot),StepRange{Int64,Int64},Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:label, :title, :xlabel, :linewidth, :legend),Tuple{Array{String,2},String,String,Int64,Symbol}},typeof(plot),Array{Function,1},Float64,Vararg{Float64,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:label,),Tuple{Array{String,2}}},typeof(plot),Array{AbstractArray{Float64,1},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :group, :linetype, :linecolor),Tuple{Plots.GridLayout,Array{String,1},Array{Symbol,2},Symbol}},typeof(plot),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :label, :fillrange, :fillalpha),Tuple{Tuple{Int64,Int64},String,Int64,Float64}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend},Vararg{Plots.Plot{Plots.GRBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :label, :fillrange, :fillalpha),Tuple{Tuple{Int64,Int64},String,Int64,Float64}},typeof(plot),Plots.Plot{Plots.PlotlyBackend},Plots.Plot{Plots.PlotlyBackend},Vararg{Plots.Plot{Plots.PlotlyBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :link),Tuple{Int64,Symbol}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :link),Tuple{Int64,Symbol}},typeof(plot),Plots.Plot{Plots.PlotlyBackend},Plots.Plot{Plots.PlotlyBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :palette, :bg_inside),Tuple{Int64,Array{PlotUtils.ContinuousColorGradient,2},Array{Symbol,2}}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :t, :leg, :ticks, :border),Tuple{Plots.GridLayout,Array{Symbol,2},Bool,Nothing,Symbol}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :title, :titlelocation, :left_margin, :bottom_margin, :xrotation),Tuple{Plots.GridLayout,Array{String,2},Symbol,Array{Measures.Length{:mm,Float64},2},Measures.Length{:mm,Float64},Int64}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:layout, :xlims),Tuple{Plots.GridLayout,Tuple{Int64,Float64}}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend},Vararg{Plots.Plot{Plots.GRBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:legend,),Tuple{Bool}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend},Vararg{Plots.Plot{Plots.GRBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:legend,),Tuple{Bool}},typeof(plot),Plots.Plot{Plots.PlotlyBackend},Plots.Plot{Plots.PlotlyBackend},Vararg{Plots.Plot{Plots.PlotlyBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:legend,),Tuple{Symbol}},typeof(plot),Array{Tuple{Int64,Real},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:legend,),Tuple{Symbol}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend},Vararg{Plots.Plot{Plots.GRBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:legend,),Tuple{Symbol}},typeof(plot),Plots.Plot{Plots.PlotlyBackend},Plots.Plot{Plots.PlotlyBackend},Vararg{Plots.Plot{Plots.PlotlyBackend},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:line, :lab, :ms),Tuple{Tuple{Array{Symbol,2},Int64},Array{String,2},Int64}},typeof(plot),Array{Array{T,1} where T,1},Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:line, :label, :legendtitle),Tuple{Tuple{Int64,Array{Symbol,2}},Array{String,2},String}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:line, :leg, :fill),Tuple{Int64,Bool,Tuple{Int64,Symbol}}},typeof(plot),Function,Function,Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:line, :marker, :bg, :fg, :xlim, :ylim, :leg),Tuple{Tuple{Int64,Symbol,Symbol},Tuple{Shape,Int64,RGBA{Float64}},Symbol,Symbol,Tuple{Int64,Int64},Tuple{Int64,Int64},Bool}},typeof(plot),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:line_z, :linewidth, :legend),Tuple{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Int64,Bool}},typeof(plot),Array{Float64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:m, :lab, :bg, :xlim, :ylim, :seriestype),Tuple{Tuple{Int64,Symbol},Array{String,2},Symbol,Tuple{Int64,Int64},Tuple{Int64,Int64},Symbol}},typeof(plot),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:marker,),Tuple{Bool}},typeof(plot),Array{Union{Missing, Int64},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:marker_z, :color, :legend, :seriestype),Tuple{typeof(+),Symbol,Bool,Symbol}},typeof(plot),Array{Float64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:nbins, :seriestype),Tuple{Int64,Symbol}},typeof(plot),Array{Float64,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:nbins, :show_empty_bins, :normed, :aspect_ratio, :seriestype),Tuple{Tuple{Int64,Int64},Bool,Bool,Int64,Symbol}},typeof(plot),Array{Complex{Float64},1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:proj, :m),Tuple{Symbol,Int64}},typeof(plot),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:projection, :seriestype),Tuple{Symbol,Symbol}},typeof(plot),Array{Int64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:reg, :fill),Tuple{Bool,Tuple{Int64,Symbol}}},typeof(plot),Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:ribbon,),Tuple{Tuple{LinRange{Float64},LinRange{Float64}}}},typeof(plot),UnitRange{Int64}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:ribbon,),Tuple{typeof(sqrt)}},typeof(plot),UnitRange{Int64}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:seriestype, :markershape, :markersize, :color),Tuple{Array{Symbol,2},Array{Symbol,1},Int64,Array{Symbol,1}}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:seriestype,),Tuple{Symbol}},typeof(plot),Array{Dates.DateTime,1},UnitRange{Int64},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:seriestype,),Tuple{Symbol}},typeof(plot),Array{OHLC,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:st, :xlabel, :ylabel, :zlabel),Tuple{Symbol,String,String,String}},typeof(plot),Array{Float64,1},Array{Float64,1},Vararg{Array{Float64,1},N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:title, :l, :seriestype),Tuple{String,Float64,Symbol}},typeof(plot),Array{String,1},Array{Float64,1}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:title,),Tuple{Array{String,2}}},typeof(plot),Plots.Plot{Plots.GRBackend},Plots.Plot{Plots.GRBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:title,),Tuple{Array{String,2}}},typeof(plot),Plots.Plot{Plots.PlotlyBackend},Plots.Plot{Plots.PlotlyBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:title,),Tuple{String}},typeof(plot),Plots.Plot{Plots.GRBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:title,),Tuple{String}},typeof(plot),Plots.Plot{Plots.PlotlyBackend}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:w,),Tuple{Int64}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:xaxis, :background_color, :leg),Tuple{Tuple{String,Tuple{Int64,Int64},StepRange{Int64,Int64},Symbol},RGB{Float64},Bool}},typeof(plot),Array{Float64,2}})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.plot)),NamedTuple{(:zcolor, :m, :leg, :cbar, :w),Tuple{StepRange{Int64,Int64},Tuple{Int64,Float64,Symbol,Plots.Stroke},Bool,Bool,Int64}},typeof(plot),Array{Float64,1},Array{Float64,1},Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.portfoliocomposition)),Any,typeof(portfoliocomposition),Any,Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.scatter!)),Any,typeof(scatter!),Any,Vararg{Any,N} where N})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.scatter!)),Any,typeof(scatter!),Any})
    Base.precompile(Tuple{Core.kwftype(typeof(Plots.test_examples)),NamedTuple{(:skip,),Tuple{Array{Int64,1}}},typeof(test_examples),Symbol})
    Base.precompile(Tuple{Type{Plots.GridLayout},Int64,Vararg{Int64,N} where N})
    Base.precompile(Tuple{Type{Shape},Array{Tuple{Float64,Float64},1}})
    Base.precompile(Tuple{typeof(Base.Broadcast.copyto_nonleaf!),Array{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},1},Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Tuple{Base.OneTo{Int64}},typeof(Plots.contour_levels),Tuple{Base.Broadcast.Extruded{Array{Any,1},Tuple{Bool},Tuple{Int64}},Base.RefValue{Tuple{Float64,Float64}}}},Base.OneTo{Int64},Int64,Int64})
    Base.precompile(Tuple{typeof(Base.Broadcast.materialize),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(Plots.contour_levels),Tuple{Array{Any,1},Base.RefValue{Tuple{Float64,Float64}}}}})
    Base.precompile(Tuple{typeof(Base.Broadcast.materialize),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(Plots.get_linecolor),Tuple{Array{Any,1},Base.RefValue{Tuple{Float64,Float64}}}}})
    Base.precompile(Tuple{typeof(Base.collect_similar),Array{AbstractLayout,2},Base.Generator{Array{AbstractLayout,2},typeof(Plots._update_min_padding!)}})
    Base.precompile(Tuple{typeof(Base.vect),Tuple{Int64,Float64,Plots.PlotText},Vararg{Tuple{Int64,Float64,Plots.PlotText},N} where N})
    Base.precompile(Tuple{typeof(Plots._bin_centers),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}})
    Base.precompile(Tuple{typeof(Plots._cbar_unique),Array{Int64,1},String})
    Base.precompile(Tuple{typeof(Plots._cbar_unique),Array{Nothing,1},String})
    Base.precompile(Tuple{typeof(Plots._cbar_unique),Array{PlotUtils.ContinuousColorGradient,1},String})
    Base.precompile(Tuple{typeof(Plots._cbar_unique),Array{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},1},String})
    Base.precompile(Tuple{typeof(Plots._cbar_unique),Array{Symbol,1},String})
    Base.precompile(Tuple{typeof(Plots._cycle),Array{Float64,1},Array{Int64,1}})
    Base.precompile(Tuple{typeof(Plots._cycle),Array{Float64,1},StepRange{Int64,Int64}})
    Base.precompile(Tuple{typeof(Plots._cycle),Array{Float64,1},UnitRange{Int64}})
    Base.precompile(Tuple{typeof(Plots._cycle),Base.OneTo{Int64},Array{Int64,1}})
    Base.precompile(Tuple{typeof(Plots._cycle),StepRange{Int64,Int64},Array{Int64,1}})
    Base.precompile(Tuple{typeof(Plots._do_plot_show),Plots.Plot{Plots.GRBackend},Bool})
    Base.precompile(Tuple{typeof(Plots._do_plot_show),Plots.Plot{Plots.PlotlyBackend},Bool})
    Base.precompile(Tuple{typeof(Plots._heatmap_edges),Array{Float64,1},Bool})
    Base.precompile(Tuple{typeof(Plots._plot!),Plots.Plot,Any,Any})
    Base.precompile(Tuple{typeof(Plots._preprocess_barlike),RecipesPipeline.DefaultsDict,Base.OneTo{Int64},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots._preprocess_binlike),RecipesPipeline.DefaultsDict,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots._replace_markershape),Array{Symbol,1}})
    Base.precompile(Tuple{typeof(Plots._update_min_padding!),Plots.GridLayout})
    Base.precompile(Tuple{typeof(Plots._update_subplot_args),Plots.Plot{Plots.GRBackend},Plots.Subplot{Plots.GRBackend},Dict{Symbol,Any},Int64,Bool})
    Base.precompile(Tuple{typeof(Plots._update_subplot_args),Plots.Plot{Plots.PlotlyBackend},Plots.Subplot{Plots.PlotlyBackend},Dict{Symbol,Any},Int64,Bool})
    Base.precompile(Tuple{typeof(Plots.build_layout),Plots.GridLayout,Int64})
    Base.precompile(Tuple{typeof(Plots.convert_to_polar),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1},Tuple{Int64,Float64}})
    Base.precompile(Tuple{typeof(Plots.create_grid),Expr})
    Base.precompile(Tuple{typeof(Plots.discrete_value!),Plots.Axis,Array{String,1}})
    Base.precompile(Tuple{typeof(Plots.error_coords),Array{Float64,1},Array{Float64,1},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots.fakedata),Int64,Vararg{Int64,N} where N})
    Base.precompile(Tuple{typeof(Plots.get_clims),Plots.Subplot{Plots.GRBackend},Plots.Series,Function})
    Base.precompile(Tuple{typeof(Plots.get_minor_ticks),Plots.Subplot{Plots.GRBackend},Plots.Axis,Tuple{Array{Float64,1},Array{String,1}}})
    Base.precompile(Tuple{typeof(Plots.get_minor_ticks),Plots.Subplot{Plots.GRBackend},Plots.Axis,Tuple{Array{Int64,1},Array{String,1}}})
    Base.precompile(Tuple{typeof(Plots.get_series_color),Array{Symbol,1},Plots.Subplot{Plots.GRBackend},Int64,Symbol})
    Base.precompile(Tuple{typeof(Plots.get_series_color),Array{Symbol,1},Plots.Subplot{Plots.PlotlyBackend},Int64,Symbol})
    Base.precompile(Tuple{typeof(Plots.get_xy),Array{OHLC,1}})
    Base.precompile(Tuple{typeof(Plots.gr_add_legend),Plots.Subplot{Plots.GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor),NTuple{9,Float64}},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots.gr_add_legend),Plots.Subplot{Plots.GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor),Tuple{Int64,Float64,Float64,Float64,Int64,Float64,Float64,Float64,Float64}},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots.gr_display),Plots.Subplot{Plots.GRBackend},Measures.Length{:mm,Float64},Measures.Length{:mm,Float64},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_colorbar),Plots.GRColorbar,Plots.Subplot{Plots.GRBackend},Tuple{Float64,Float64},Array{Float64,1}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_contour),Plots.Series,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,2},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_grid),Plots.Subplot{Plots.GRBackend},Plots.Axis,Segments{Tuple{Float64,Float64}}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_heatmap),Plots.Series,Array{Float64,1},Array{Float64,1},Array{Float64,2},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_heatmap),Plots.Series,Base.OneTo{Int64},Base.OneTo{Int64},Array{Float64,2},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_markers),Plots.Series,Array{Int64,1},Array{Float64,1},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_markers),Plots.Series,Base.OneTo{Int64},Array{Float64,1},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_markers),Plots.Series,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1},Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_segments),Plots.Series,Array{Float64,1},Array{Float64,1},Int64,Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_segments),Plots.Series,Base.OneTo{Int64},Array{Float64,1},Nothing,Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_segments),Plots.Series,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Array{Float64,1},Nothing,Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_draw_segments),Plots.Series,StepRange{Int64,Int64},Array{Float64,1},Int64,Tuple{Float64,Float64}})
    Base.precompile(Tuple{typeof(Plots.gr_get_ticks_size),Tuple{Array{Float64,1},Array{Any,1}},Int64})
    Base.precompile(Tuple{typeof(Plots.gr_get_ticks_size),Tuple{Array{Float64,1},Array{String,1}},Int64})
    Base.precompile(Tuple{typeof(Plots.gr_get_ticks_size),Tuple{Array{Int64,1},Array{String,1}},Int64})
    Base.precompile(Tuple{typeof(Plots.gr_label_ticks),Plots.Subplot{Plots.GRBackend},Symbol,Tuple{Array{Float64,1},Array{String,1}}})
    Base.precompile(Tuple{typeof(Plots.gr_label_ticks_3d),Plots.Subplot{Plots.GRBackend},Symbol,Tuple{Array{Float64,1},Array{String,1}}})
    Base.precompile(Tuple{typeof(Plots.gr_polaraxes),Int64,Float64,Plots.Subplot{Plots.GRBackend}})
    Base.precompile(Tuple{typeof(Plots.gr_set_gradient),PlotUtils.ContinuousColorGradient})
    Base.precompile(Tuple{typeof(Plots.heatmap_edges),Array{Float64,1},Symbol})
    Base.precompile(Tuple{typeof(Plots.heatmap_edges),Base.OneTo{Int64},Symbol,Base.OneTo{Int64},Symbol,Tuple{Int64,Int64}})
    Base.precompile(Tuple{typeof(Plots.heatmap_edges),StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},Symbol})
    Base.precompile(Tuple{typeof(Plots.heatmap_edges),UnitRange{Int64},Symbol})
    Base.precompile(Tuple{typeof(Plots.ignorenan_minimum),Array{Int64,1}})
    Base.precompile(Tuple{typeof(Plots.layout_args),Int64})
    Base.precompile(Tuple{typeof(Plots.make_fillrange_side),UnitRange{Int64},LinRange{Float64}})
    Base.precompile(Tuple{typeof(Plots.make_steps),Base.OneTo{Int64},Symbol})
    Base.precompile(Tuple{typeof(Plots.optimal_ticks_and_labels),Plots.Subplot{Plots.GRBackend},Plots.Axis,StepRange{Int64,Int64}})
    Base.precompile(Tuple{typeof(Plots.optimal_ticks_and_labels),Plots.Subplot{Plots.GRBackend},Plots.Axis,UnitRange{Int64}})
    Base.precompile(Tuple{typeof(Plots.processGridArg!),Dict{Symbol,Any},Symbol,Symbol})
    Base.precompile(Tuple{typeof(Plots.processGridArg!),RecipesPipeline.DefaultsDict,Bool,Symbol})
    Base.precompile(Tuple{typeof(Plots.processLineArg),Dict{Symbol,Any},Array{Symbol,2}})
    Base.precompile(Tuple{typeof(Plots.processLineArg),Dict{Symbol,Any},Symbol})
    Base.precompile(Tuple{typeof(Plots.processMarkerArg),Dict{Symbol,Any},Array{Symbol,2}})
    Base.precompile(Tuple{typeof(Plots.processMarkerArg),Dict{Symbol,Any},Plots.Stroke})
    Base.precompile(Tuple{typeof(Plots.processMarkerArg),Dict{Symbol,Any},RGBA{Float64}})
    Base.precompile(Tuple{typeof(Plots.processMarkerArg),Dict{Symbol,Any},Shape})
    Base.precompile(Tuple{typeof(Plots.processMarkerArg),Dict{Symbol,Any},Symbol})
    Base.precompile(Tuple{typeof(Plots.process_annotation),Plots.Subplot{Plots.GRBackend},Int64,Float64,Plots.PlotText})
    Base.precompile(Tuple{typeof(Plots.process_annotation),Plots.Subplot{Plots.PlotlyBackend},Int64,Float64,Plots.PlotText})
    Base.precompile(Tuple{typeof(Plots.process_axis_arg!),Dict{Symbol,Any},StepRange{Int64,Int64},Symbol})
    Base.precompile(Tuple{typeof(Plots.process_axis_arg!),Dict{Symbol,Any},Symbol,Symbol})
    Base.precompile(Tuple{typeof(Plots.process_axis_arg!),Dict{Symbol,Any},Tuple{Int64,Int64},Symbol})
    Base.precompile(Tuple{typeof(Plots.reset_axis_defaults_byletter!)})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Array{Measures.Length{:mm,Float64},2},Int64})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Array{PlotUtils.ContinuousColorGradient,2},Int64})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Array{RGBA{Float64},2},Int64})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Array{String,2},Int64})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Array{Symbol,2},Int64})
    Base.precompile(Tuple{typeof(Plots.slice_arg),Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}},Int64})
    Base.precompile(Tuple{typeof(Plots.straightline_data),Tuple{Float64,Float64},Tuple{Float64,Float64},Array{Float64,1},Array{Float64,1},Int64})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},AbstractArray{OHLC,1}})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Array{Complex{Float64},1}})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Plots.PortfolioComposition})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:barhist}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:bar}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:histogram2d}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:hline}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:pie}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:spy}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:sticks}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol,Any},Type{Val{:xerror}},Any,Any,Any})
    Base.precompile(Tuple{typeof(RecipesPipeline._finish_userrecipe!),Plots.Plot{Plots.GRBackend},Array{Dict{Symbol,Any},1},RecipeData})
    Base.precompile(Tuple{typeof(RecipesPipeline._finish_userrecipe!),Plots.Plot{Plots.PlotlyBackend},Array{Dict{Symbol,Any},1},RecipeData})
    Base.precompile(Tuple{typeof(RecipesPipeline.add_series!),Plots.Plot{Plots.GRBackend},RecipesPipeline.DefaultsDict})
    Base.precompile(Tuple{typeof(RecipesPipeline.add_series!),Plots.Plot{Plots.PlotlyBackend},RecipesPipeline.DefaultsDict})
    Base.precompile(Tuple{typeof(RecipesPipeline.plot_setup!),Plots.Plot{Plots.GRBackend},Dict{Symbol,Any},Array{Dict{Symbol,Any},1}})
    Base.precompile(Tuple{typeof(RecipesPipeline.plot_setup!),Plots.Plot{Plots.PlotlyBackend},Dict{Symbol,Any},Array{Dict{Symbol,Any},1}})
    Base.precompile(Tuple{typeof(RecipesPipeline.postprocess_axis_args!),Plots.Plot{Plots.PlotlyBackend},Dict{Symbol,Any},Symbol})
    Base.precompile(Tuple{typeof(RecipesPipeline.preprocess_attributes!),Plots.Plot{Plots.GRBackend},RecipesPipeline.DefaultsDict})
    Base.precompile(Tuple{typeof(RecipesPipeline.process_userrecipe!),Plots.Plot{Plots.GRBackend},Array{Dict{Symbol,Any},1},Dict{Symbol,Any}})
    Base.precompile(Tuple{typeof(RecipesPipeline.process_userrecipe!),Plots.Plot{Plots.PlotlyBackend},Array{Dict{Symbol,Any},1},Dict{Symbol,Any}})
    Base.precompile(Tuple{typeof(RecipesPipeline.unzip),Array{GeometryBasics.Point{2,Float64},1}})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.GRBackend},Array{RecipeData,1},Symbol,String})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.GRBackend},Dict{Symbol,Any},Symbol,String})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.GRBackend},RecipesPipeline.DefaultsDict,Symbol,String})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.PlotlyBackend},Array{RecipeData,1},Symbol,String})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.PlotlyBackend},Dict{Symbol,Any},Symbol,String})
    Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plots.Plot{Plots.PlotlyBackend},RecipesPipeline.DefaultsDict,Symbol,String})
    Base.precompile(Tuple{typeof(backend),Plots.PlotlyBackend})
    Base.precompile(Tuple{typeof(bbox),Float64,Float64,Float64,Float64})
    Base.precompile(Tuple{typeof(bbox),Measures.Length{:mm,Float64},Measures.Length{:mm,Float64},Measures.Length{:mm,Float64},Measures.Length{:mm,Float64}})
    Base.precompile(Tuple{typeof(contour),Any,Vararg{Any,N} where N})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Tuple{Base.OneTo{Int64}},typeof(Plots.get_colorgradient),Tuple{Array{Any,1}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Tuple{Base.OneTo{Int64}},typeof(Plots.get_fillalpha),Tuple{Array{Any,1}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Tuple{Base.OneTo{Int64}},typeof(Plots.get_linestyle),Tuple{Array{Any,1}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Tuple{Base.OneTo{Int64}},typeof(Plots.get_linewidth),Tuple{Array{Any,1}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{2},Tuple{Base.OneTo{Int64},Base.OneTo{Int64}},typeof(Plots.gr_color),Tuple{Array{RGBA{Float64},2}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(string),Tuple{Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(typeof),Tuple{Tuple{Plots.PortfolioComposition}}}}}})
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(string),Tuple{Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(typeof),Tuple{Tuple{Plots.Spy}}}}}})
    Base.precompile(Tuple{typeof(font),String,Vararg{Any,N} where N})
    Base.precompile(Tuple{typeof(frame),Animation,Plots.Plot{Plots.GRBackend}})
    Base.precompile(Tuple{typeof(iter_segments),Array{Float64,1},Array{Float64,1},UnitRange{Int64}})
    Base.precompile(Tuple{typeof(iter_segments),Base.OneTo{Int64},Array{Float64,1}})
    Base.precompile(Tuple{typeof(merge),NamedTuple{(:annotation,),Tuple{Array{Tuple{Int64,Float64,Plots.PlotText},1}}},Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}})
    Base.precompile(Tuple{typeof(merge),NamedTuple{(:zcolor, :m, :ms, :lab),Tuple{Array{Float64,1},Tuple{Symbol,Float64,Plots.Stroke},Array{Float64,1},String}},NamedTuple{(:seriestype,),Tuple{Symbol}}})
    Base.precompile(Tuple{typeof(plot!),Any})
    Base.precompile(Tuple{typeof(plot),Any,Any})
    Base.precompile(Tuple{typeof(plot),Any})
    Base.precompile(Tuple{typeof(push!),Plots.Plot{Plots.GRBackend},Float64,Array{Float64,1}})
    Base.precompile(Tuple{typeof(setindex!),Dict{Any,Any},Plots.Plot{Plots.PlotlyBackend},Int64})
    Base.precompile(Tuple{typeof(setindex!),Dict{Plots.Subplot,Any},Dict{Symbol,Any},Plots.Subplot{Plots.GRBackend}})
    Base.precompile(Tuple{typeof(spy),Any})
    Base.precompile(Tuple{typeof(stroke),Int64,Vararg{Any,N} where N})
    Base.precompile(Tuple{typeof(text),String,Int64,Symbol,Vararg{Symbol,N} where N})
    Base.precompile(Tuple{typeof(text),String,Symbol,Int64,Vararg{Any,N} where N})
    Base.precompile(Tuple{typeof(title!),AbstractString})
    Base.precompile(Tuple{typeof(vcat),Array{Any,1},Array{Tuple{Int64,Float64,Plots.PlotText},1}})
    Base.precompile(Tuple{typeof(vcat),Array{Any,1},Tuple{Int64,Float64,Plots.PlotText}})
    Base.precompile(Tuple{typeof(vline!),Any})
    Base.precompile(Tuple{typeof(xgrid!),Plots.Plot{Plots.GRBackend},Symbol,Vararg{Any,N} where N})
    Base.precompile(Tuple{typeof(yaxis!),Any,Any})
    let fbody = try __lookup_kwbody__(which(Plots.gr_polyline, (Array{Float64,1},Array{Float64,1},typeof(GR.fillarea),))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Symbol,Symbol,typeof(Plots.gr_polyline),Array{Float64,1},Array{Float64,1},typeof(GR.fillarea),))
        end
    end
    let fbody = try __lookup_kwbody__(which(Plots.text, (String,Int64,Vararg{Any,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}},typeof(text),String,Int64,Vararg{Any,N} where N,))
        end
    end
    let fbody = try __lookup_kwbody__(which(Plots.text, (String,Symbol,Vararg{Any,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}},typeof(text),String,Symbol,Vararg{Any,N} where N,))
        end
    end
    let fbody = try __lookup_kwbody__(which(Plots.title!, (AbstractString,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(title!),AbstractString,))
        end
    end
    let fbody = try __lookup_kwbody__(which(Plots.vline!, (Any,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(vline!),Any,))
        end
    end
    let fbody = try __lookup_kwbody__(which(Plots.yaxis!, (Any,Vararg{Any,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(yaxis!),Any,Vararg{Any,N} where N,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot!, ())) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot!),))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot!, (Any,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot!),Any,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot!, (Any,Vararg{Any,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot!),Any,Vararg{Any,N} where N,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot, (Any,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot),Any,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot, (Any,Vararg{Any,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot),Any,Vararg{Any,N} where N,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot, (Plots.Plot,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot),Plots.Plot,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot, (Plots.Plot,Plots.Plot,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot),Plots.Plot,Plots.Plot,))
        end
    end
    let fbody = try __lookup_kwbody__(which(RecipesBase.plot, (Plots.Plot,Plots.Plot,Vararg{Plots.Plot,N} where N,))) catch missing end
        if !ismissing(fbody)
            precompile(fbody, (Any,typeof(plot),Plots.Plot,Plots.Plot,Vararg{Plots.Plot,N} where N,))
        end
    end
end


text:
# Copyright 2017-2018 Gandalf Software, Inc. (Scott Paul Jones)
# Licensed under MIT License, see LICENSE.md

## Set traits for character types ##

ValidatedStyle(::Type{<:AbstractChar}) = UnknownValidity()
@static isdefined(Base, :AbstractChar) || (ValidatedStyle(::Type{Char}) = UnknownValidity())
ValidatedStyle(::Type{<:Chr}) = AlwaysValid()

ValidatedStyle(A::T) where {T<:AbsChar} = ValidatedStyle(T)

CharSetStyle(::Type{ASCIIChr})  = CharSetASCIICompat()
CharSetStyle(::Type{LatinChr})  = CharSetISOCompat()
CharSetStyle(::Type{_LatinChr}) = CharSetISOCompat()
CharSetStyle(::Type{UCS2Chr})   = CharSetBMPCompat()

# must check range if CS1 is smaller than CS2, even if CS2 is valid for it's range
_isvalid(::ValidatedStyle, ::Type{ASCIICharSet}, ::Type{T}, val) where {T<:CharSet} =
    is_ascii(val)

(_isvalid(::ValidatedStyle, ::Type{LatinCharSet}, ::Type{T}, val)
 where {T<:Union{Text2CharSet, Text4CharSet, UCS2CharSet, UTF32CharSet}}) =
     is_latin(val)

(_isvalid(::ValidatedStyle, ::Type{_LatinCharSet}, ::Type{T}, val)
 where {T<:Union{Text2CharSet, Text4CharSet, UCS2CharSet, UTF32CharSet}}) =
     is_latin(val)

(_isvalid(::ValidatedStyle, ::Type{UCS2CharSet}, ::Type{T}, val)
 where {T<:Union{Text2CharSet, Text4CharSet, UTF32CharSet}}) =
     is_bmp(val)

_isvalid(::ValidatedStyle, ::Type{UTF32CharSet}, ::Type{<:CharSet}, val) =
    is_unicode(val)

# no checking needed for cases where it is a superset of T
(_isvalid(::AlwaysValid, ::Type{LatinCharSet}, ::Type{T}, val)
  where {T<:Union{Text1CharSet,ASCIICharSet,_LatinCharSet}}) = true

(_isvalid(::AlwaysValid, ::Type{UCS2CharSet}, ::Type{T}, val)
  where {T<:Union{Text1CharSet,ASCIICharSet,LatinCharSet,_LatinCharSet,_UCS2CharSet}}) = true

(_isvalid(::AlwaysValid, ::Type{UTF32CharSet}, ::Type{T}, val)
  where {T<:Union{Text1CharSet,ASCIICharSet,LatinCharSet,UCS2CharSet,_LatinCharSet,_UCS2CharSet,_UTF32CharSet}}) =
    true

# no subsets allowed for these
_isvalid(::AlwaysValid, ::Type{_LatinCharSet}, ::Type{ASCIICharSet}, val) = false
(_isvalid(::AlwaysValid, ::Type{_UCS2CharSet}, ::Type{T}, val)
  where {T<:Union{Text1CharSet,ASCIICharSet,LatinCharSet,_LatinCharSet}}) = false
(_isvalid(::AlwaysValid, ::Type{_UTF32CharSet}, ::Type{T}, val)
 where {T<:Union{Text1CharSet,Text2CharSet,ASCIICharSet,LatinCharSet,
                 UCS2CharSet,_LatinCharSet,_UCS2CharSet}}) = false

(_isvalid(::AlwaysValid, ::Type{S}, ::Type{<:CodeUnitTypes}, chr)
 where {S<:Union{Text1CharSet,Text2CharSet,Text4CharSet}}) =
     chr <= typemax(S)

_isvalid(::AlwaysValid, v) = true

# By default, check that it is valid Unicode codepoint
_isvalid(::UnknownValidity, v) = _isvalid(UnknownValidity(), UTF32CharSet, charset(v), v)

is_valid(::Type{T}, chr::T) where {T<:Chr} = _isvalid(ValidatedStyle(T), chr)
is_valid(chr::T) where {T<:Chr} = _isvalid(ValidatedStyle(T), chr)

# Different character set
function is_valid(::Type{S}, chr::T) where {S<:Chr, T<:Chr}
    CS = charset(S)
    CT = charset(T)
    CS == CT ? _isvalid(ValidatedStyle(T), chr) : _isvalid(ValidatedStyle(T), CS, CT, chr)
end

# Not totally sure how to get rid of some of these, they really should be handled
# by the compiler, using the ValidatedStyle trait along with the character sets

_isvalid_chr(::Type{ASCIICharSet},  v) = is_ascii(v)
_isvalid_chr(::Type{LatinCharSet},  v) = is_latin(v)
_isvalid_chr(::Type{UCS2CharSet},   v) = is_bmp(v)
_isvalid_chr(::Type{UTF32CharSet},  v) = is_unicode(v)
_isvalid_chr(::Type{_LatinCharSet}, v) = is_latin(v)
_isvalid_chr(::Type{_UCS2CharSet},  v) = is_bmp(v)
_isvalid_chr(::Type{_UTF32CharSet}, v) = is_unicode(v)
_isvalid_chr(::Type{Text1CharSet},  v) = v <= typemax(UInt8)
_isvalid_chr(::Type{Text2CharSet},  v) = v <= typemax(UInt16)
_isvalid_chr(::Type{Text4CharSet},  v) = v <= typemax(UInt32)
_isvalid_chr(::Type{BinaryCharSet}, v) = v <= typemax(UInt8)

# Not totally sure about this, base Char is rather funky in v0.7
_isvalid_chr(::Type{UniPlusCharSet}, v) = v <= typemax(UInt32)

is_valid(::Type{T}, v::Unsigned) where {T<:Chr} =
    _isvalid_chr(charset(T), v)
is_valid(::Type{T}, v::Signed) where {T<:Chr} =
    0 <= v <= typemax(UInt32) && _isvalid_chr(charset(T), v%UInt32)

is_valid(::Type{Char}, ch::Union{Text1Chr, ASCIIChr, LatinChars, UCS2Chr, UTF32Chr}) = true
is_valid(::Type{Char}, ch::Text2Chr) = is_bmp(ch)
is_valid(::Type{Char}, ch::Text4Chr) = is_unicode(ch)
is_valid(::Type{T},    ch::Char) where {T<:Chr} = Base.isvalid(ch) && is_valid(T, ch%UInt32)

ext: jl


text:
#===================================
Define GNNGraph type as a subtype of Graphs' AbstractGraph.
For the core methods to be implemented by any AbstractGraph, see
https://juliagraphs.org/Graphs.jl/latest/types/#AbstractGraph-Type
https://juliagraphs.org/Graphs.jl/latest/developing/#Developing-Alternate-Graph-Types
=============================================#

const COO_T = Tuple{T, T, V} where {T <: AbstractVector{<:Integer}, V}
const ADJLIST_T = AbstractVector{T} where T <: AbstractVector{<:Integer}
const ADJMAT_T = AbstractMatrix
const SPARSE_T = AbstractSparseMatrix # subset of ADJMAT_T
const CUMAT_T = Union{CUDA.AnyCuMatrix, CUDA.CUSPARSE.CuSparseMatrix}

"""
    GNNGraph(data; [graph_type, ndata, edata, gdata, num_nodes, graph_indicator, dir])
    GNNGraph(g::GNNGraph; [ndata, edata, gdata])

A type representing a graph structure that also stores
feature arrays associated to nodes, edges, and the graph itself.

A `GNNGraph` can be constructed out of different `data` objects
expressing the connections inside the graph. The internal representation type
is determined by `graph_type`.

When constructed from another `GNNGraph`, the internal graph representation
is preserved and shared. The node/edge/graph features are retained
as well, unless explicitely set by the keyword arguments
`ndata`, `edata`, and `gdata`.

A `GNNGraph` can also represent multiple graphs batched togheter
(see [`Flux.batch`](@ref) or [`SparseArrays.blockdiag`](@ref)).
The field `g.graph_indicator` contains the graph membership
of each node.

`GNNGraph`s are always directed graphs, therefore each edge is defined
by a source node and a target node (see [`edge_index`](@ref)).
Self loops (edges connecting a node to itself) and multiple edges
(more than one edge between the same pair of nodes) are supported.

A `GNNGraph` is a Graphs.jl's `AbstractGraph`, therefore it supports most
functionality from that library.

# Arguments

- `data`: Some data representing the graph topology. Possible type are
    - An adjacency matrix
    - An adjacency list.
    - A tuple containing the source and target vectors (COO representation)
    - A Graphs' graph.
- `graph_type`: A keyword argument that specifies
                the underlying representation used by the GNNGraph.
                Currently supported values are
    - `:coo`. Graph represented as a tuple `(source, target)`, such that the `k`-th edge
              connects the node `source[k]` to node `target[k]`.
              Optionally, also edge weights can be given: `(source, target, weights)`.
    - `:sparse`. A sparse adjacency matrix representation.
    - `:dense`. A dense adjacency matrix representation.
    Defaults to `:coo`, currently the most supported type.
- `dir`: The assumed edge direction when given adjacency matrix or adjacency list input data `g`.
        Possible values are `:out` and `:in`. Default `:out`.
- `num_nodes`: The number of nodes. If not specified, inferred from `g`. Default `nothing`.
- `graph_indicator`: For batched graphs, a vector containing the graph assigment of each node. Default `nothing`.
- `ndata`: Node features. A named tuple of arrays whose last dimension has size `num_nodes`.
- `edata`: Edge features. A named tuple of arrays whose last dimension has size `num_edges`.
- `gdata`: Graph features. A named tuple of arrays whose last dimension has size `num_graphs`.

# Usage.

```julia
using Flux, GraphNeuralNetworks

# Construct from adjacency list representation
data = [[2,3], [1,4,5], [1], [2,5], [2,4]]
g = GNNGraph(data)

# Number of nodes, edges, and batched graphs
g.num_nodes  # 5
g.num_edges  # 10
g.num_graphs # 1

# Same graph in COO representation
s = [1,1,2,2,2,3,4,4,5,5]
t = [2,3,1,4,5,3,2,5,2,4]
g = GNNGraph(s, t)

# From a Graphs' graph
g = GNNGraph(erdos_renyi(100, 20))

# Add 2 node feature arrays
g = GNNGraph(g, ndata = (x=rand(100, g.num_nodes), y=rand(g.num_nodes)))

# Add node features and edge features with default names `x` and `e`
g = GNNGraph(g, ndata = rand(100, g.num_nodes), edata = rand(16, g.num_edges))

g.ndata.x
g.ndata.e

# Send to gpu
g = g |> gpu

# Collect edges' source and target nodes.
# Both source and target are vectors of length num_edges
source, target = edge_index(g)
```
"""
struct GNNGraph{T<:Union{COO_T,ADJMAT_T}} <: AbstractGraph{Int}
    graph::T
    num_nodes::Int
    num_edges::Int
    num_graphs::Int
    graph_indicator       # vector of ints or nothing
    ndata::NamedTuple
    edata::NamedTuple
    gdata::NamedTuple
end

@functor GNNGraph

function GNNGraph(data;
                        num_nodes = nothing,
                        graph_indicator = nothing,
                        graph_type = :coo,
                        dir = :out,
                        ndata = (;),
                        edata = (;),
                        gdata = (;),
                        )

    @assert graph_type ∈ [:coo, :dense, :sparse] "Invalid graph_type $graph_type requested"
    @assert dir ∈ [:in, :out]

    if graph_type == :coo
        graph, num_nodes, num_edges = to_coo(data; num_nodes, dir)
    elseif graph_type == :dense
        graph, num_nodes, num_edges = to_dense(data; dir)
    elseif graph_type == :sparse
        graph, num_nodes, num_edges = to_sparse(data; dir)
    end

    num_graphs = !isnothing(graph_indicator) ? maximum(graph_indicator) : 1

    ndata = normalize_graphdata(ndata, default_name=:x, n=num_nodes)
    edata = normalize_graphdata(edata, default_name=:e, n=num_edges, duplicate_if_needed=true)
    gdata = normalize_graphdata(gdata, default_name=:u, n=num_graphs)

    GNNGraph(graph,
            num_nodes, num_edges, num_graphs,
            graph_indicator,
            ndata, edata, gdata)
end

function GNNGraph(n::T; graph_type=:coo, kws...) where {T<:Integer}
    s, t = T[], T[]
    return GNNGraph(s, t; graph_type, num_nodes=n, kws...)
end

# COO convenience constructors
GNNGraph(s::AbstractVector, t::AbstractVector, v = nothing; kws...) = GNNGraph((s, t, v); kws...)
GNNGraph((s, t)::NTuple{2}; kws...) = GNNGraph((s, t, nothing); kws...)

# GNNGraph(g::AbstractGraph; kws...) = GNNGraph(adjacency_matrix(g, dir=:out); kws...)

function GNNGraph(g::AbstractGraph; kws...)
    s = Graphs.src.(Graphs.edges(g))
    t = Graphs.dst.(Graphs.edges(g))
    if !Graphs.is_directed(g)
        # add reverse edges since GNNGraph is directed
        s, t = [s; t], [t; s]
    end
    GNNGraph((s, t); num_nodes=Graphs.nv(g), kws...)
end

function GNNGraph(g::GNNGraph; ndata=g.ndata, edata=g.edata, gdata=g.gdata, graph_type=nothing)

    ndata = normalize_graphdata(ndata, default_name=:x, n=g.num_nodes)
    edata = normalize_graphdata(edata, default_name=:e, n=g.num_edges, duplicate_if_needed=true)
    gdata = normalize_graphdata(gdata, default_name=:u, n=g.num_graphs)

    if !isnothing(graph_type)
        if graph_type == :coo
            graph, num_nodes, num_edges = to_coo(g.graph; g.num_nodes)
        elseif graph_type == :dense
            graph, num_nodes, num_edges = to_dense(g.graph)
        elseif graph_type == :sparse
            graph, num_nodes, num_edges = to_sparse(g.graph)
        end
        @assert num_nodes == g.num_nodes
        @assert num_edges == g.num_edges
    else
        graph = g.graph
    end
    GNNGraph(graph,
            g.num_nodes, g.num_edges, g.num_graphs,
            g.graph_indicator,
            ndata, edata, gdata)
end

function Base.show(io::IO, g::GNNGraph)
    println(io, "GNNGraph:
    num_nodes = $(g.num_nodes)
    num_edges = $(g.num_edges)")
    g.num_graphs > 1 && println("num_graphs = $(g.num_graphs)")
    if !isempty(g.ndata)
        println(io, "    ndata:")
        for k in keys(g.ndata)
            println(io, "        $k => $(size(g.ndata[k]))")
        end
    end
    if !isempty(g.edata)
        println(io, "    edata:")
        for k in keys(g.edata)
            println(io, "        $k => $(size(g.edata[k]))")
        end
    end
    if !isempty(g.gdata)
        println(io, "    gdata:")
        for k in keys(g.gdata)
            println(io, "        $k => $(size(g.gdata[k]))")
        end
    end
end

### StatsBase/LearnBase compatibility
StatsBase.nobs(g::GNNGraph) = g.num_graphs
LearnBase.getobs(g::GNNGraph, i) = getgraph(g, i)

# Flux's Dataloader compatibility. Related PR https://github.com/FluxML/Flux.jl/pull/1683
Flux.Data._nobs(g::GNNGraph) = g.num_graphs
Flux.Data._getobs(g::GNNGraph, i) = getgraph(g, i)

#########################
Base.:(==)(g1::GNNGraph, g2::GNNGraph) = all(k -> getfield(g1,k)==getfield(g2,k), fieldnames(typeof(g1)))


text:
using BusinessDays
using Test
import Dates

println("Perftests")

d0 = Dates.Date(2015, 06, 29) ; d1 = Dates.Date(2100, 12, 20)

cal_type = BusinessDays.Brazil()
cal_sym = :Brazil
cal_str = "Brazil"

@time BusinessDays.initcache(cal_type)
bdays(cal_type, d0, d1) # force JIT compilation
@time bdays(cal_type, d0, d1)
@time for i in 1:1000 bdays(cal_type, d0, d1) end
bdays(cal_sym, d0, d1) # force JIT compilation
bdays(cal_str, d0, d1) # force JIT compilation
@time for i in 1:1000 bdays(cal_sym, d0, d1) end
@time for i in 1:1000 bdays(cal_str, d0, d1) end

N = 1000
d0vec = fill(d0, N)
d1vec = fill(d1, N)
cal_type_vec = fill(cal_type, N)
cal_sym_vec = fill(cal_sym, N)
cal_str_vec = fill(cal_str, N)

# Warmup
BusinessDays.initcache(BusinessDays.Brazil())
BusinessDays.initcache("USSettlement")
BusinessDays.initcache(:UKSettlement)
BusinessDays.bdays(cal_type, d0, d1)
BusinessDays.bdays(cal_sym, d0, d1)
BusinessDays.bdays(cal_str, d0, d1)
BusinessDays.bdays(cal_type, d0vec, d1vec)
BusinessDays.bdays(cal_sym, d0vec, d1vec)
BusinessDays.bdays(cal_str, d0vec, d1vec)
BusinessDays.bdays(cal_type_vec, d0vec, d1vec)
BusinessDays.bdays(cal_sym_vec, d0vec, d1vec)
BusinessDays.bdays(cal_str_vec, d0vec, d1vec)

println("type")
@time for i in 1:1000 BusinessDays.bdays(cal_type, d0, d1) end
@time BusinessDays.bdays(cal_type, d0vec, d1vec)
@time BusinessDays.bdays(cal_type_vec, d0vec, d1vec)
println("sym")
@time for i in 1:1000 BusinessDays.bdays(cal_sym, d0, d1) end
@time BusinessDays.bdays(cal_sym, d0vec, d1vec)
@time BusinessDays.bdays(cal_sym_vec, d0vec, d1vec)
println("str")
@time for i in 1:1000 BusinessDays.bdays(cal_str, d0, d1) end
@time BusinessDays.bdays(cal_str, d0vec, d1vec)
@time BusinessDays.bdays(cal_str_vec, d0vec, d1vec)

BusinessDays.cleancache()

println("initcache type")
@time BusinessDays.initcache(cal_type)
BusinessDays.cleancache()

println("initcache sym")
@time BusinessDays.initcache(cal_sym)
BusinessDays.cleancache()

println("initcache str")
@time BusinessDays.initcache(cal_str)
BusinessDays.cleancache()

let
    cal = BusinessDays.WeekendsOnly()
    println("WeekendsOnly no cache")
    @time for i in 1:1000 BusinessDays.bdays(cal, d0, d1) end
    BusinessDays.initcache(cal)
    println("WeekendsOnly cache enabled")
    @time for i in 1:1000 BusinessDays.bdays(cal, d0, d1) end
end


text:
using LinearAlgebra: Matrix, sqrt
using LinearAlgebra
using FFTW
using MAT

function alpha1D(k, N)
    if k>0
        return sqrt(2/N)
    else
        return sqrt(1/N)
    end
end

function alpha2D(k, l, N)
    if k==0 && l==0
        return 1/N
    elseif l==0 || k==0
        return sqrt(2)/N
    else
        return 2/N
    end
end

function mDCT(v)
    N = length(v)
    c = zeros(1, N)

    for k = 0:(N-1)
        sum = 0.0
        for i = 0:(N-1)
            sum = sum + v[i+1] * cos(k*pi*((2*i+1)/(2*N)))
        end
        c[k+1] = alpha1D(k,N) * sum
    end
    return c
end

function miDCT(c)
    N = length(c)
    v = zeros(1,N)
    for j = 0:(N-1)
        for k = 0:(N-1)
            ak= alpha1D(k,N)
            v[j+1] = v[j+1] +(c[k+1] * ak * cos(pi*k*((2*j)+1)/(2*N)))
        end
    end
    return v
end

function mDCT2n4(matrix)
    N = size(matrix,1)
    M = size(matrix,1)

    cmat = zeros(N, M)

    for k = 0:(N-1)
        for l = 0:(M-1)
            sum = 0
            for i = 0:(N-1)
                for j = 0:(M-1)
                    sum = sum + (matrix[i+1, j+1] * cos(k*pi*((2*i+1)/(2*N))) * cos(l*pi*((2*j+1)/(2*M))))
                end
            end
            cmat[k+1, l+1] = alpha2D(k,l,N) * sum
        end
    end
    return cmat
end

function miDCT2n4(cmat)
    N = size(matrix,1)
    M = size(matrix,2)
    matrix = zeros(N, M)
    sum = 0
    for i = 0:(N-1)
        for j = 0:(M-1)
            for k = 0:(N-1)
                for l = 0:(M-1)
                    ak2 = alpha2D(k,l,N)
                    matrix[i+1, j+1] = matrix[i+1, j+1] + (cmat[k+1, l+1] *ak2 *cos(pi*k*((2*i)+1)/(2*N)) * cos(pi*l*((2*j)+1)/(2*M)))
                end
            end
        end
    end
    return matrix
end

function mDCT2n3(matrix)
    N = size(matrix,1)
    M = size(matrix,2)
    cmat = zeros(N,M)

    for j = 0:(N-1)
        ind = j+1
        cmat[ind, :] = mDCT(matrix[ind,:])
    end
    cmatT = transpose(cmat)
    for k = 0:(M-1)
        cmatT[k+1,:] = mDCT(cmatT[k+1,:])
    end
    cmat = transpose(cmatT)
    return cmat
end

function miDCT2n3(cmat)
    N = size(cmat,1)
    M = size(cmat,2)
    matrix = zeros(N,M)
    matrixT = zeros(N,M)

    for j = 0:(N-1)
        matrix[j+1,:] = miDCT(cmat[j+1,:])
    end
    matrixT = transpose(matrix)
    for k = 0:(M-1)
        matrixT[k+1,:] = miDCT(matrixT[k+1,:])
    end
    matrix = transpose(matrixT)
    return matrix
end

max_stars_repo_name: illoxian/bitmap-compressor-tool

max_issues_repo_name: illoxian/bitmap-compressor-tool

max_forks_repo_name: illoxian/bitmap-compressor-tool


text:
function mergecrspcomp(;crsp=error("crsp is required"), comp=error("comp is required"),
    testexpensivemergetests::Bool = PARAM[:testexpensivemergetests])

  local crspold::DataFrame
  local compold::DataFrame
  local panel::DataFrame

  #this is just another layer of verification
  if testexpensivemergetests
    crspold = crsp |> deepcopy
    compold = comp |> deepcopy
  end

  #start with integrity checks
  (!issorted(crsp, [:permno, :date])) && sort!(crsp, [:permno, :date])
  (!issorted(comp, [:permno, :effdate])) && sort!(comp, [:permno, :fdate])

  @assert issorted(comp, [:permno, :fdate])
  @assert (nonunique(crsp, [:permno,:date]) .== false) |> all
  @assert (nonunique(comp, [:permno, :effdate]) .== false) |> all

  Ncrsp::Int = size(crsp,1)
  print("Beginning merge. Num crsp rows = $Ncrsp. . Time to complete: ")
  @time begin
    ###Merge one permno at a time, linking each crsp row to a compid (comp row)
    scrsps = groupby(crsp, :permno)
    scomps = groupby(comp, :permno)
    comp.compid = 1:size(comp,1) |> collect
    crsp.compid = Vector{MInt}(undef, size(crsp,1))

    #faster to work within the rows for a particular permno
    Threads.@threads for permno ∈ keys(scomps)
      scomp = scomps[permno]
      crsppermnokey = permno |> Tuple
      (!haskey(scrsps,crsppermnokey)) && continue
      #because the key originates from comp, cant use it directly on crsp- instead need its value
      scrsp = scrsps[crsppermnokey]

      #the idea here is to select all of the crsp rows between the effective date
      #and the end date of the comp row
      for r ∈ eachrow(scomp)
        targetrows = view(scrsp, (r.effdate .≤ scrsp.date) .& (scrsp.date .≤ r.enddate), :compid)
        if length(targetrows) > 0
          #under no circumstances should this be assigned, since date and permno should be unique
          (targetrows .=== missing) |> all || error(
            "multiple crsp rows found for a particular compid!!")
          targetrows .= r.compid
        end
      end
    end

    crsp = crsp[crsp.compid .!== missing, :]

    #now merge the linked rows
    rename!(comp, :permno=>:lpermno)
    panel = innerjoin(crsp, comp, on=:compid)
  end

  #integrity checks on the merge. Run these each time.
  @assert size(panel,1) == size(crsp,1)
  @assert (crsp.permno .== panel.permno) |> all
  @assert (crsp.date .== panel.date) |> all
  @assert (panel.permno .== panel.lpermno) |> all
  @assert (panel.effdate .≤ panel.date) |> all
  @assert (panel.date .≤ panel.enddate) |> all
  if !issorted(panel, [:permno, :date])
    sort!(panel, [:permno, :date])
  end

  @info "
  %%%%%%%%%%
    merge complete. Merged df dimensions = $(size(panel)),
    sizeof=$(sum(sizeof.(eachcol(panel)))),
    Panel rows = $(size(panel,1)),
    Match rate=$(round(size(panel,1)/Ncrsp*100,digits=4))%.
  %%%%%%%%%%%  "

  #print(describe(panel))

  testexpensivemergetests && expensivemergetests(crsp=crspold, comp=compold; panel)

  #after the integrity checks we no longer need the below columns
  select!(panel, Not([:lpermno, :effdate, :enddate]))

  #next expand out gvkey
end

#=function mergecrspcompkernel(;crsp=error("crsp is required"), comp=error("comp is required"))

  #integrity checks

  return panel
end=#

######################
###############Everything below this point relates to expensive merge comparison tests
######################

function expensivemergetests(; crsp=error("crsp is required"), comp=error("comp is required"),
  panel=error("panel is required for verification"))

  @warn "Running old merge code for verification.
  This may take a while. If this passes, no need to run this each time"

  oldmergecrspcomp(crsp=crsp|>deepcopy, comp=comp|>deepcopy; panel)
  noviewmergecrspcomp(; crsp, comp, verpanel=panel)

  @info "Completed comparison to crspcomp successfully- results fully validated"
end

#slower code (by ~20x) but clearer and more elegant
function noviewmergecrspcomp(;crsp=error("crsp is required"), comp=error("comp is required"),
  verpanel::DataFrame)

  local panel::DataFrame

  #start with integrity checks
  (!issorted(crsp, [:permno, :date])) && sort!(crsp, [:permno, :date])
  (!issorted(comp, [:permno, :effdate])) && sort!(comp, [:permno, :fdate])

  @assert issorted(comp, [:permno, :fdate])
  @assert (nonunique(crsp, [:permno,:date]) .== false) |> all
  @assert (nonunique(comp, [:permno, :effdate]) .== false) |> all

  Ncrsp::Int = size(crsp,1)
  comp.effrange = ((effdate, enddate)->effdate:Day(1):enddate).(comp.effdate, comp.enddate)
  print("Beginning NO-VIEW merge test. Num crsp rows = $Ncrsp. Time to complete: ")
  @time begin
  ###first, form an index for comp
  #build the index one column at a time
    comp.compid = 1:size(comp,1) |> collect
    fillrange(fr) = fill(fr[1], length(fr[2])) #repeats the values the appropriate number of times
    permno = mapreduce(fillrange,  append!, zip(comp.permno, comp.effrange), init=Vector{Int}())
    compdate = mapreduce(collect, append!, comp.effrange, init=Vector{Date}())
    compid = mapreduce(fillrange,  append!, zip(comp.compid, comp.effrange), init=Vector{Int}())
    #put the columns together
    compidx = DataFrame(permno=permno, date=compdate, compid=compid,  copycols=false)

    #integrity checks
    @assert issorted(compidx, [:permno, :date])
    @assert issorted(compidx, [:compid])
    @assert (nonunique(compidx, [:compid, :date]) .== false) |> all
    @assert (nonunique(compidx, [:permno, :date]) .== false) |> all

    #merge crsp and the comp idx
    crsp = innerjoin(crsp, compidx, on=[:permno, :date], validate=(true,true))

    #merge crsp and comp using compid
    rename!(comp, :permno=>:lpermno)
    panel = innerjoin(crsp, comp, on=:compid, validate=(false, true))
  end

  #integrity checks on the merge
  @assert size(panel,1) == size(crsp,1)
  @assert (crsp.permno .== panel.permno) |> all
  @assert (crsp.date .== panel.date) |> all
  @assert (panel.permno .== panel.lpermno) |> all
  @assert (panel.effdate .≤ panel.date) |> all
  @assert (panel.date .≤ panel.enddate) |> all
  if !issorted(panel, [:permno, :date])
    sort!(panel, [:permno, :date])
  end

  @assert (panel.permno .== verpanel.permno) |> all
  @assert (panel.date .== verpanel.date) |> all
  @assert (panel.effdate .== verpanel.effdate) |> all
  @assert (panel.enddate .== verpanel.enddate) |> all
  @assert (panel.gvkey .== verpanel.gvkey) |> all
  @assert (panel.compid .== verpanel.compid) |> all

  @info "NOVIEW merge complete. Match rate=$(round(size(panel,1)/Ncrsp*100,digits=4))%."

  return nothing
end

#some old merge code
#to be primarily used for occational verification
function oldmergecrspcomp(; crsp::DataFrame=error("original crsp df is required"),
    comp::DataFrame=error("original crsp df is required"),
    panel::DataFrame=error("verification panel required"))

  local univ::DataFrame
  local scomp::SubDataFrame
  local Ncrsp::Int = size(crsp,1)

  comp.lpermno = comp.permno |> deepcopy

  @assert issorted(crsp, [:permno, :date])
  @assert issorted(comp, [:lpermno, :effdate])
  leadwithin2!(comp, [:adate], :lpermno, date=:fdate)

  #create a record id to form the dictionary
  comp.compid = Vector{MInt}(1:(size(comp,1)))
  comp.validinterval = ((effdate,enddate)->
    (effdate):Day(1):(enddate)).(comp.effdate, comp.enddate)

  crsp.compid = Vector{MInt}(undef, Ncrsp)
  select!(comp, Not(:permno))

  #make the necessary allocations ahead of time
  print("matching...time to match (OLD CODE): ")
  @time begin

    scrsps::GroupedDataFrame = groupby(crsp, :permno)
    scomps::Dict = Dict([scomp.lpermno[1]=>scomp for scomp ∈ groupby(comp, :lpermno)])

    #this uses a similar matching algorithm to the main code, but uses older syntax from when
    #data frames were not as good.
    #so basically we first select a permno, match it to the comp permno, then
    #match each comp row to the crsp rows within the comp row's date range
    Threads.@threads for i ∈ 1:length(scrsps)
      local scrsp::SubDataFrame = scrsps[i]
      local permno::Int = scrsp.permno[1]

      if haskey(scomps, permno)
        local scomp = scomps[permno]
        for rscomp ∈ eachrow(scomp)
          effdate::Date =  rscomp[:effdate]
          enddate::Date = rscomp[:enddate]
          sscrsp::SubDataFrame = view(scrsp, (d->d≥effdate && d≤enddate).(scrsp[!, :date]),:)
          if size(sscrsp,1) ≥ 1
            (sum((!ismissing).(sscrsp.compid)) > 0) && error("multiple valid compids for one permno")
            sscrsp.compid .= rscomp[:compid]
          end
        end
      end
    end

    #println("sum crsp compid: $(sum(skipmissing(crsp.compid)))")
    println("OLD CODE matching complete: $(100*(Ncrsp - sum((ismissing).(crsp.compid)))/Ncrsp)% matched.")
    crsp = crsp[(!ismissing).(crsp.compid),:]
    univ = innerjoin(crsp, comp, on=:compid)
  end

  extracheckstestmerged(univ)
  println("OLD CODE Total records: $(size(univ))")

  if !issorted(univ, [:permno, :date])
    sort!(univ, [:permno, :date])
  end
  @assert size(univ,1) == size(panel,1)
  @assert (univ.permno .== panel.permno) |> all
  @assert (univ.adate .== panel.adate) |> all
  @assert (univ.gvkey .== panel.gvkey) |> all
  @assert (univ.date .== panel.date) |> all

  #=if TEST_OUTPUT
    d = Date(2015,12,31)
    CSV.write("output\\univ_mid_$d.csv", univ[(cd->cd==d).(univ[!, :date]), :])
  end=#

  return nothing
end

#do a bunch of integrity checks on the merged dataset
function extracheckstestmerged(univ::DataFrame;
  period2stale = PARAM[:compmaxadateinterval])

  Threads.@threads for r ∈ eachrow(univ)
    ismissing(r.gvkey) && continue
    #check date is valid
    d::Date = r[:date]
    (d ≥ r.ccmeffdate) || error("failed check: (d ≥ r.linkeffdate)")
    (d ≤ r.ccmenddate) || error("failed check: (d ≤ r.linkenddate)")
    (d ∈ r.validinterval) || error("failed check: (d ∈ r.validinterval)")
    (d ≥ r.adate) || error("failed check: (d > r.adate)")
    (r.Nadate === missing) || (d < r.Nadate) || error("failed check: (d ≤ r.Nadate)")

    (!(d ≤ lastdayofmonth(firstdayofmonth(r.adate) + period2stale))) && error(
      "lastdayofmonth(firstdayofmonth(r.adate) + period2stale)))
      $r")

    (r.lpermno == r.permno) || error("failed check: (r.lpermno == r.permno)")
  end

  #univ = sort(univ, [:permno, :adate, Fdate])

  sunivs::GroupedDataFrame = groupby(univ, :permno)
  Threads.@threads for i ∈ 1:length(sunivs)
    local suniv::SubDataFrame = sunivs[i]
    maxfiscal::Date= Date(1900,1,1)
    maxadate::Date = Date(1900,1,1)

    for r ∈ eachrow(suniv)
      ismissing(r.gvkey) && continue
      (r[:date] > maxadate) || error("failed check: r[Fdate] > maxadate
        permno=$(r[:permno]), r[Fdate]=$(r[Fdate]), maxadate=$maxadate\n\n$r")

      maxadate = r[:date]

      (maxfiscal ≤ r.fdate) ||  error("failed check: maxfiscal ≤ r.fdate")
      (maxfiscal ≠ r.fdate) && (maxfiscal=r.fdate)
    end

  end
end

max_stars_repo_name: clintonTE/CCA

max_issues_repo_name: clintonTE/CCA

max_forks_repo_name: clintonTE/CCA


text:
module FindPeaks1D

using SortingLab

export findpeaks1d, peakprominences1d, peakwidths1d

include("utils.jl")

"""
    localmaxima1d(x)

Finds all local maxima in a 1-D signal. The first and the last sample can't be maxima.
"""
function localmaxima1d(x::AbstractVector{T}) where {T<:Real}
    midpts = Vector{Int}(undef, 0)
    leftedges = Vector{Int}(undef, 0)
    rightedges = Vector{Int}(undef, 0)

    i = 2
    imax = length(x)
    while i < imax
        if x[i-1] < x[i]
            iahead = i+1

            while (iahead < imax) && (x[iahead] == x[i])
                iahead += 1
            end

            if x[iahead] < x[i]
                push!(leftedges, i)
                push!(rightedges, iahead-1)
                push!(midpts, (i+iahead-1)÷2)
                i = iahead
            end
        end
        i += 1
    end
    midpts, leftedges, rightedges
end

"""
    findpeaks1d(x;
                height=nothing,
                distance=nothing,
                prominence=nothing,
                width=nothing,
                wlen=nothing,
                relheight=0.5)

Find all local maxima in a 1-D signal with specified `height`, `distance`, `prominence`, `width`.

# Arguments
- `x`: 1-D signal
- `height`: the first element is the minimal and the second, if supplied, is the maximal peak height
- `distance`: the minimal peak distance
- `prominence`: the first element is the minimal and the second, if supplied, is the maximal peak prominence
- `width`: the first element is the minimal and the second, if supplied, is the maximal peak width
- `wlen`: used for calculation of the peak prominence
- `relheight`: used for calculation of peak width

# Returns
Peak indices and properties

# Examples
```julia-repl
julia> x = [13, 12, 14, 18, 19, 19, 19, 15, 11, 6, 4, 10, 8, 13, 8, 13, 3, 18, 7, 4];

julia> pkindices, pkproperties = findpeaks1d(x)
([6, 12, 14, 16, 18], Dict{String,Any}())

julia> pkindices, pkproperties = findpeaks1d(x, height=11)
([6, 14, 16, 18], Dict{String,Any}("peak_heights" => [19, 13, 13, 18]))

julia> pkindices, pkproperties = findpeaks1d(x, height=11, distance=3)
([6, 14, 18], Dict{String,Any}("peak_heights" => [19, 13, 18]))
```
"""
function findpeaks1d(x::AbstractVector{T};
                     height::Union{Nothing,<:Real,NTuple{2,<:Real}}=nothing,
                     distance::Union{Nothing,I}=nothing,
                     prominence::Union{Nothing,Real,NTuple{2,Real}}=nothing,
                     width::Union{Nothing,Real,NTuple{2,Real}}=nothing,
                     wlen::Union{Nothing,I}=nothing,
                     relheight::Real=0.5) where {T<:Real,I<:Integer}
    pkindices, leftedges, rightedges = localmaxima1d(x)
    properties = Dict{String,Any}()
    isempty(pkindices) && (return pkindices, properties)

    if height !== nothing
        pkheights = x[pkindices]
        hmin, hmax = height isa Number ? (height, nothing) : height
        keepheight = selectbyproperty(pkheights, hmin, hmax)
        pkindices = pkindices[keepheight]
        properties["peak_heights"] = pkheights
        properties = Dict{String,Any}(key => array[keepheight] for (key, array) in properties)
    end

    if distance !== nothing
        keepdist = selectbypeakdistance(pkindices, x[pkindices], distance)
        pkindices = pkindices[keepdist]
        properties = Dict{String,Any}(key => array[keepdist] for (key, array) in properties)
    end

    if (prominence !== nothing) || (width !== nothing)
        prominences, leftbases, rightbases = peakprominences1d(x, pkindices, wlen)
        properties["prominences"] = prominences
        properties["leftbases"] = leftbases
        properties["rightbases"] = rightbases
    end

    if prominence !== nothing
        pmin, pmax = prominence isa Number ? (prominence, nothing) : prominence
        keepprom = selectbyproperty(prominences, pmin, pmax)
        pkindices = pkindices[keepprom]
        properties = Dict{String,Any}(key => array[keepprom] for (key, array) in properties)
    end

    if width !== nothing
        widths, widthheights, leftips, rightips = peakwidths1d(x,
                                                               pkindices,
                                                               relheight,
                                                               properties["prominences"],
                                                               properties["leftbases"],
                                                               properties["rightbases"])
        properties["widths"] = widths
        properties["widthheights"] = widthheights
        properties["leftips"] = leftips
        properties["rightips"] = rightips
        wmin, wmax = width isa Number ? (width, nothing) : width
        keepwidth = selectbyproperty(widths, wmin, wmax)
        pkindices = pkindices[keepwidth]
        properties = Dict{String,Any}(key => array[keepwidth] for (key, array) in properties)
    end

    pkindices, properties
end

"""
    peakprominences1(x, pkindices, wlen=nothing)

Calculate the prominence of each peak in a 1-D signal.

# Arguments
- `x`: 1-D signal
- `pkindices`: peak indices
- `wlen`: a window length in samples to restrict peak finding to a window around the current peak

# Returns
- `prominences`: prominences for each peaks
- `leftbases`, `rightbases`: indices of the left and right of each peak for peaks' bases
"""
function peakprominences1d(x::AbstractVector{T},
                           pkindices::AbstractVector{I},
                           wlen::Union{Nothing,I}=nothing) where {T<:Real,I<:Integer}
    wlen = argwlenasexpected(wlen)

    prominences = Vector{T}(undef, length(pkindices))
    leftbases = Vector{Int}(undef, length(pkindices))
    rightbases = Vector{Int}(undef, length(pkindices))

    for pknr = 1:length(pkindices)
        pkindex = pkindices[pknr]
        imin = 1
        imax = length(x)
        !(imin <= pkindex <= imax) && throw(ArgumentError("peak $(pkindex) is not a valid index for `x`"))

        if 2 <= wlen
            imin = max(pkindex-wlen÷2, imin)
            imax = min(pkindex+wlen÷2, imax)
        end

        i = leftbases[pknr] = pkindex
        leftmin = x[pkindex]
        while (imin <= i) && (x[i] <= x[pkindex])
            if x[i] < leftmin
                leftmin = x[i]
                leftbases[pknr] = i
            end
            i -= 1
        end

        i = rightbases[pknr] = pkindex
        rightmin = x[pkindex]
        while (i <= imax) && (x[i] <= x[pkindex])
            if x[i] < rightmin
                rightmin = x[i]
                rightbases[pknr] = i
            end
            i += 1
        end

        prominences[pknr] = x[pkindex]-max(leftmin, rightmin)
    end
    prominences, leftbases, rightbases
end

"""
    peakwidths1d(x, pkindices, relheight=0.5, prominencedata=nothing, wlen=nothing)

Calculate the width of each peak in a 1-D signal.

# Arguments
- `x`: 1-D signal
- `pkindices`: peak indices
- `relheight`: relative height with respect to the peak heights and prominences
- `wlen`: a window length in samples to restrict peak finding to a window around the current peak

# Returns
- `widths`: width for each peak in samples
- `widthheights`: height at which the `widths` are evaluated
- `leftips`, `rightips`: interpolated left and right intersection points of a horizontal line at the
respective `widthheights`
"""
function peakwidths1d(x::AbstractVector{T},
                      pkindices::AbstractVector{I},
                      relheight::Real=0.5,
                      prominencedata::Union{Nothing,Tuple}=nothing,
                      wlen::Union{Nothing,I}=nothing) where {T<:Real,I<:Integer}
    if prominencedata === nothing
        prominencedata = peakprominences1d(x, pkindices, wlen)
    end
    prominences, leftbases, rightbases = prominencedata
    peakwidths1d(x, pkindices, relheight, prominences, leftbases, rightbases)
end
"""
    peakwidths1d(x, pkindices, relheight, prominences, leftbases, rightbases)

Calculate the width of each peak in a 1-D signal.

# Arguments
- `x`: 1-D signal
- `pkindices`: peak indices
- `relheight`: relative height with respect to the peak heights and prominences
- `prominencedata`: output of `peakprominences1d`
- `leftbases`, `rightbases`: indices of the left and right of each peak for peaks' bases

# Returns
- `widths`: width for each peak in samples
- `widthheights`: height at which the `widths` are evaluated
- `leftips`, `rightips`: interpolated left and right intersection points of a horizontal line at the respective `widthheights`
"""
function peakwidths1d(x::AbstractVector{T},
                      pkindices::AbstractVector{I},
                      relheight::Real,
                      prominences,
                      leftbases,
                      rightbases) where {T<:Real,I<:Integer}

    npkindices = length(pkindices)
    (relheight < 0) && throw(ArgumentError("`relheight` must be greater pr equal to zero"))
    !(npkindices == length(prominences) == length(leftbases) == length(rightbases)) && throw(ArgumentError("arrays in `prominencedata` must have the same length as `pkindices`"))

    widths = Vector{Float64}(undef, npkindices)
    widhtheights = Vector{Float64}(undef, npkindices)
    leftips = Vector{Float64}(undef, npkindices)
    rightips = Vector{Float64}(undef, npkindices)

    for p in 1:npkindices
        imin = leftbases[p]
        imax = rightbases[p]
        pkindex = pkindices[p]
        !(1 <= imin <= pkindex <= imax <= length(x)) && throw(ArgumentError("prominence data is invalid for peak $(pkindex)"))

        height = widhtheights[p] = x[pkindex] - prominences[p] * relheight

        i = pkindex
        while (imin < i) && (height < x[i])
            i -= 1
        end
        leftip = convert(Float64, i)
        if x[i] < height
            leftip += (height-x[i])/(x[i+1]-x[i])
        end
        i = pkindex
        while (i < imax) && height < x[i]
            i += 1
        end
        rightip = convert(Float64, i)
        if x[i] < height
            rightip -= (height-x[i])/(x[i-1]-x[i])
        end

        widths[p] = rightip - leftip
        leftips[p] = leftip
        rightips[p] = rightip
    end
    widths, widhtheights, leftips, rightips
end

end # module


text:
using ModelParams, Test

mp = ModelParams;

function param_table_test()
    @testset "ParamTable" begin
        n = 5;
        nameV = ["name$j"  for j = 1 : n];
        symbolV = ["sym$j"  for j = 1 : n];
        descrV = ["descr$j"  for j = 1 : n];
        valueV = ["val$j"  for j = 1 : n];

        pt = ParamTable(n);
        @test length(pt) == n

        for j = 1 : n
            set_row!(pt, j, nameV[j], symbolV[j], descrV[j], valueV[j]);
        end

        for j = 1 : n
            @test mp.get_symbol(pt, j) == symbolV[j]
            @test mp.get_value(pt, j) == valueV[j]
        end

        @test mp.get_descriptions(pt) == descrV

        lineV = latex_param_table(pt, "Description line");
        @test isa(lineV, Vector{String})
        @test length(lineV) == n+1
	end
end

@testset "ParamTable" begin
    param_table_test();
end

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


text:
using Parameters
using ForwardDiff
using NLsolve
using ConstLab
using Voigt
using Voigt.Unicode
using Devectorize

const VOIGT_SIZE = 9

create_component_macros("mises_mixed", (VOIGT_SIZE, 1, VOIGT_SIZE, 1))

const II = veye(VOIGT_SIZE) ⊗ veye(VOIGT_SIZE)
const Idev = eye(VOIGT_SIZE, VOIGT_SIZE) - 1/3 * II
vm(x) = sqrt(3/2) * vnorm(dev(x))

@with_kw immutable MisesMixedHardMS
    ₙεₚ::Vector{Float64} = zeros(VOIGT_SIZE)
    ₙσ::Vector{Float64} = zeros(VOIGT_SIZE)
    ₙα::Vector{Float64} = zeros(VOIGT_SIZE)
    ₙκ::Float64 = 0.0
    ₙμ::Float64 = 0.0
    loading::Bool = false
end

@with_kw immutable MisesMixedHardMP
    E::Float64
    ν::Float64
    Ee::Matrix{Float64} =   (G = E / 2(1 + ν);
                            K = E / 3(1 - 2ν);
                            Ee = 2 * G * Idev+ K * II;
                            if VOIGT_SIZE == 6
                                Ee[4:6, 4:6] /= 2.0;
                            end;
                            Ee)
    σy::Float64
    H::Float64
    r::Float64
    κ∞::Float64
    α∞::Float64
    loading::Bool = false
end

function stress(ε, dt, matpar::MisesMixedHardMP, matstat::MisesMixedHardMS)
    @unpack matpar: E, Ee, ν, σy, H, r, κ∞, α∞
    @unpack matstat: ₙεₚ, ₙα, ₙκ, ₙμ

    σₜᵣ = Ee * (ε - ₙεₚ)

    σ_red_e_tr = sqrt(3/2) * vnorm(dev(σₜᵣ - ₙα))

    Φ = σ_red_e_tr - σy - ₙκ

    if Φ < 0
        ms = MisesMixedHardMS(ₙεₚ, σₜᵣ, ₙα, ₙκ, ₙμ, false)
        return σₜᵣ, ms
    else
        # Initial guess
        X = [σₜᵣ; ₙκ; ₙα; ₙμ]

        #=
        R!(x, fx) = compute_residual!(fx, x, σₜᵣ, matpar, matstat)
        res = nlsolve(R!, X; iterations = 30, ftol = 1e-4, method=:newton, autodiff = true)
        if !NLsolve.converged(res)
            error("No convergence in material routine")
        end
        X = res.zero::Vector{Float64}
        =#

        max_iters = 30
        iter = 0
        tol = 1e-4

        n_unknowns = 2*VOIGT_SIZE + 2
        fx = zeros(n_unknowns)
        J = zeros(n_unknowns, n_unknowns)
        R2!(fx, x) = compute_residual!(fx, x, σₜᵣ, matpar, matstat)
        dRdx! = jacobian(R2!, mutates = true, output_length = n_unknowns)
       # println("|fx| \t \t |dx|")
        while true
            iter += 1
            R2!(fx, X)

            if iter == 1
          #      println("$(maximum(abs(fx))) \t NaN")
            else
           #     println("$(maximum(abs(fx))) \t $(norm(dx))")
            end

            if maximum(abs(fx)) < tol
                break
            end

            if iter == max_iters
                 error("No convergence in material routine")
            end
            dRdx!(J, X)
            dx = J \ fx
            X = X - dx
        end

        ConstLab.@unpack_mises_mixed (σ, κ, α, μ) = X

        σ_red_dev = dev(σ - α)
        σ_red_e = sqrt(3/2) * vnorm(σ_red_dev)
        εₚ = 3/2 * μ / σ_red_e * σ_red_dev
        εₚ += ₙεₚ

        ms = MisesMixedHardMS(εₚ, σ, α, κ, μ, true)

        return σ, ms
    end
end

function compute_residual!(R, x, σₜᵣ, matpar, matstat)
    @unpack matpar: E, ν, σy, H, r, κ∞, α∞
    @unpack matstat: ₙεₚ, ₙα, ₙκ, ₙμ

    ConstLab.@unpack_mises_mixed (σ, κ, α, μ) = x

    G = E / 2(1 + ν)

    σ_red_dev = dev(σ - α)
    σ_red_e = vm(σ - α)
    σ_red_dev_hat = σ_red_dev / σ_red_e

    @devec R_σ = σ - σₜᵣ + 3.*G.*μ .* σ_red_dev_hat
    R_κ = κ - ₙκ - r * H * μ * (1 - κ / κ∞)
    @devec R_α = α - ₙα - (1-r) .* H .* μ .* (σ_red_dev_hat - 1./α∞ .* α)
    R_Φ = σ_red_e - σy - κ

    ConstLab.@pack_mises_mixed R = (R_σ, R_κ, R_α, R_Φ)

    return
end

function compute_jacobian(x, matpar, matstat)
    @unpack mp: E, Ee, ν, σy, H, r, κ∞, α∞
    @unpack matstat: ₙεₚ, ₙσ, ₙα, ₙκ, ₙμ

    zerov = zeros(VOIGT_SIZE)
    zeroc = zeros(1, VOIGT_SIZE)

    σ = ₙσ
    α = ₙα
    κ = ₙκ
    μ = ₙμ

    G = E / 2(1 + ν)

    σ_red_dev = dev(σ - α)
    σ_red_e = vm(σ - α)
    σ_red_dev_hat = σ_red_dev / σ_red_e

    I = veye(9,9)
    ν = 3/2 * σ_red_dev / σ_red_e
    Q = Idev - 3 / (2(σ_red_e)^2) * (σ_red_dev ⊗ σ_red_dev)
    N = 3 / (2σ_red_e) * Q
    Nα = -N

    dRσdσ = I + 2*G*μ*N
    dRσdκ = zerov
    dRσdα = 2*G*μ*Nα
    dRσdμ = 2*G*ν

    dRκdσ = zeroc
    dRκdκ = 1 + μ*r*H / κ∞
    dRκdα = zeroc
    dRκdμ = -r*H*(1- κ/κ∞)

    dRαdσ = -μ*(1 - r) * H * 2/3 * N
    dRαdκ = zerov
    dRαdα = I - μ*(1-r) * H * (2/3 * Nα - I/α∞)
    dRαdμ = -(1-r)*H*(2/3 * ν - α/α∞)

    dRΦdσ = ν'
    dRΦdκ = -1
    dRΦdα = -ν'
    dRΦdμ = 0

    J = [dRσdσ dRσdκ dRσdα dRσdμ;
        dRκdσ dRκdκ dRκdα dRκdμ;
        dRαdσ dRαdκ dRαdα dRαdμ;
        dRΦdσ dRΦdκ dRΦdα dRΦdμ]

    return J
end

function ATS(ε, dt, matpar::MisesMixedHardMP, matstat::MisesMixedHardMS)
    @unpack matpar: Ee, E, ν
    @unpack matstat: ₙεₚ, ₙσ, ₙα, ₙκ, ₙμ, loading
    X = [ₙσ; ₙκ; ₙα; ₙμ]

    K = E / 3(1 - 2ν)

    if !loading
        return Ee
    else
        σₜᵣ = Ee * (ε - ₙεₚ)

        R2!(fx, x) = compute_residual!(fx, x, σₜᵣ, matpar, matstat)
        dRdx! = jacobian(R2!, output_length = 2*VOIGT_SIZE + 2)
        dRdX = dRdx!(X)

        dσdε = inv(dRdX)[1:VOIGT_SIZE, 1:VOIGT_SIZE] * Ee
        return dσdε
    end
end


text:
a = 3
b = 2
c = 1

fs = 100
T = 1/fs
f1 = 5
f2 = 10
f3 = 30

t = T:T:fs

x1 = a*sin.(2*pi*f1*T*t)+b*sin.(2*pi*f2*T*t)+c*sin.(2*pi*f3*T*t);

@testset "argminima/argmaxima" begin
    @test_throws DomainError Peaks.findnextextrema(<, x1, 1, 0, false)
    @test length(argmaxima(x1)) == 30
    @test length(argminima(x1)) == 30

    @test length(argmaxima(x1, 1; strict=false)) == 31
    @test length(argminima(x1, 1; strict=false)) == 31

    @test length(argmaxima(x1, 1000)) == 4
    @test length(argminima(x1, 1000)) == 4

    @test length(argmaxima(x1, 1000; strict=false)) == 5
    @test length(argminima(x1, 1000; strict=false)) == 5

    @test argmaxima([0,1,0,1,0,1,0,1,0,1,0,1,0,1,0], 2) == [4,8,12]
    @test argmaxima([0,1,0,1,0,1,0,1,0,1,0,1,0,1,0], 2; strict=false) == [2,6,10,14]

    # OffsetArrays
    x1_off = OffsetArray(x1, -200:length(x1)-201)
    @test length(argmaxima(x1_off)) == 30
    @test length(argminima(x1_off)) == 30
    @test argmaxima(x1_off) == argmaxima(x1) .- 201
    @test argminima(x1_off) == argminima(x1) .- 201
    @test length(argmaxima(x1_off, 1; strict=false)) == 31
    @test length(argminima(x1_off, 1; strict=false)) == 31
    @test argmaxima(x1_off, 1; strict=false) == argmaxima(x1, 1; strict=false) .- 201
    @test argminima(x1_off, 1; strict=false) == argminima(x1, 1; strict=false) .- 201

    @testset "Plateaus" begin
        p1 = [0,1,1,1,1,1]
        p2 = [0,0,1,1,1,1]
        p3 = [0,0,0,0,1,1]

        @test argmaxima( p1, 2; strict=false) == [2]
        @test argminima(-p1, 2; strict=false) == [2]
        @test argmaxima( p2, 2; strict=false) == [3]
        @test argminima(-p2, 2; strict=false) == [3]
        @test argmaxima( p3, 2; strict=false) == [5]
        @test argminima(-p3, 2; strict=false) == [5]

        @test isempty(argmaxima( p1))
        @test isempty(argminima(-p1))
        @test isempty(argmaxima( p2))
        @test isempty(argminima(-p2))
        @test isempty(argmaxima( p3))
        @test isempty(argminima(-p3))

        @test argmaxima(reverse( p1), 2; strict=false) == [1]
        @test argminima(reverse(-p1), 2; strict=false) == [1]
        @test argmaxima(reverse( p2), 2; strict=false) == [1]
        @test argminima(reverse(-p2), 2; strict=false) == [1]
        @test argmaxima(reverse( p3), 2; strict=false) == [1]
        @test argminima(reverse(-p3), 2; strict=false) == [1]

        @test isempty(argmaxima(reverse( p1)))
        @test isempty(argminima(reverse(-p1)))
        @test isempty(argmaxima(reverse( p2)))
        @test isempty(argminima(reverse(-p2)))
        @test isempty(argmaxima(reverse( p3)))
        @test isempty(argminima(reverse(-p3)))

        @test argmaxima( [0,1,1,1,1,0]) == [2]
        @test argminima(-[0,1,1,1,1,0]) == [2]
        @test argmaxima( [0,1,1,1,2,0]) == [5]
        @test argminima(-[0,1,1,1,2,0]) == [5]
        @test argmaxima( [0,1,1,0,2,1], 3; strict=false) == [5]
        @test argminima(-[0,1,1,0,2,1], 3; strict=false) == [5]

        # issue #4
        @test isempty(argmaxima(zeros(10)))
        @test argmaxima(zeros(10), 1; strict=false) == [1]
    end

    # A missing or NaN should not occur within the `w` of the peak
    @testset "Missings and NaNs" begin
        m1 = [0,0,1,1,1,missing,missing,0,0]
        n1 = [0,0,1,1,1,NaN,NaN,0,0]
        @test isempty(argmaxima( m1, 1; strict=true))
        @test isempty(argmaxima( n1, 1; strict=true))
        @test isempty(argminima(-m1, 1; strict=true))
        @test isempty(argminima(-n1, 1; strict=true))
        @test argmaxima( m1, 1; strict=false) == [3,8]
        @test argmaxima( n1, 1; strict=false) == [3,8]
        @test argminima(-m1, 1; strict=false) == [3,8]
        @test argminima(-n1, 1; strict=false) == [3,8]

        @test isempty(argmaxima(reverse( m1), 1; strict=true))
        @test isempty(argmaxima(reverse( n1), 1; strict=true))
        @test isempty(argminima(reverse(-m1), 1; strict=true))
        @test isempty(argminima(reverse(-n1), 1; strict=true))
        @test argmaxima(reverse( m1), 1; strict=false) == [1,5]
        @test argmaxima(reverse( n1), 1; strict=false) == [1,5]
        @test argminima(reverse(-m1), 1; strict=false) == [1,5]
        @test argminima(reverse(-n1), 1; strict=false) == [1,5]

        m2 = [0,1,2,1,missing,missing,missing]
        n2 = [0,1,2,1,NaN,NaN,NaN]
        @test argmaxima(m2, 1) == [3]
        @test argmaxima(n2, 1) == [3]
        @test isempty(argmaxima(m2, 2))
        @test isempty(argmaxima(n2, 2))
        @test argminima(-m2, 1) == [3]
        @test argminima(-n2, 1) == [3]
        @test isempty(argminima(-m2, 2))
        @test isempty(argminima(-n2, 2))
        @test argmaxima(reverse(m2), 1) == [5]
        @test argmaxima(reverse(n2), 1) == [5]
        @test isempty(argmaxima(reverse(m2), 2))
        @test isempty(argmaxima(reverse(n2), 2))
        @test argminima(reverse(-m2), 1) == [5]
        @test argminima(reverse(-n2), 1) == [5]
        @test isempty(argminima(reverse(-m2), 2))
        @test isempty(argminima(reverse(-n2), 2))

        m3 = [0,1,0,1,missing,1,0,1,0]
        n3 = [0,1,0,1,NaN,1,0,1,0]
        @test argmaxima(m3) == [2,8]
        @test argminima(-m3) == [2,8]
        @test argmaxima(n3) == [2,8]
        @test argminima(-n3) == [2,8]

        mn = [1,NaN,missing,1]
        @test isempty(argmaxima(mn))
        @test isempty(argminima(mn))
        @test isempty(argmaxima(reverse(mn)))
        @test isempty(argminima(reverse(mn)))
    end

    pks, vals = @test_nowarn findmaxima(x1)
    @test x1[pks] == vals
    pks, vals = @test_nowarn findminima(x1)
    @test x1[pks] == vals
end


text:
module day24

include("inputs.jl")

ex = inputs.ex_d24()
input = inputs.input_d24()

# https://www.redblobgames.com/grids/hexagons/#coordinates-cube
struct Tile
    # cube coordinates for hex grid (see link)
    x::Int
    y::Int
    z::Int
end

function parse_input(input)
    rgx = r"(ne|se|sw|nw|e|w)"
    return map(split(input, "\n")) do line
        [m.match for m in eachmatch(rgx, line)]
    end
end

# Part 1
function move(t::Tile, dir)
    dir == "e"  && return Tile(t.x + 1, t.y - 1, t.z + 0)
    dir == "se" && return Tile(t.x + 0, t.y - 1, t.z + 1)
    dir == "sw" && return Tile(t.x - 1, t.y + 0, t.z + 1)
    dir == "w"  && return Tile(t.x - 1, t.y + 1, t.z + 0)
    dir == "nw" && return Tile(t.x + 0, t.y + 1, t.z - 1)
    dir == "ne" && return Tile(t.x + 1, t.y + 0, t.z - 1)
    error("Direction unknown: $dir")
end

function setup_tiles(input)

    lines = parse_input(input)
    flipped = Dict{Tile, Bool}() # false = white

    for line in lines
        tile = Tile(0, 0, 0)
        for dir in line
            tile = move(tile, dir)
        end
        if haskey(flipped, tile)
            flipped[tile] = !flipped[tile]
        else
            flipped[tile] = true
        end
    end
    return flipped
end

part1(input) = setup_tiles(input) |> f->sum(values(f))
@assert part1(ex) == 10
@info "Day 24, Part 1 answer: $(part1(input))"

# Part 2
get_neighs(t) = [
    # Tile(t.x + 0, t.y + 0, t.z + 0)
    Tile(t.x + 1, t.y - 1, t.z + 0)
    Tile(t.x + 0, t.y - 1, t.z + 1)
    Tile(t.x - 1, t.y + 0, t.z + 1)
    Tile(t.x - 1, t.y + 1, t.z + 0)
    Tile(t.x + 0, t.y + 1, t.z - 1)
    Tile(t.x + 1, t.y + 0, t.z - 1)
]

count_adj(tile, tiles) = sum([tiles[nei] for nei in get_neighs(tile) if nei ∈ keys(tiles)])

function exec(input, days=100)

    tiles = setup_tiles(input)

    for day in 1:days
        # get black tiles (used to determine candidates)
        blacks = [tile for (tile, color) in tiles if color == true]

        # create enough candidate tiles (don't forget to add curr tile, t)
        candidates = Set(Iterators.flatten([(t, get_neighs(t)...) for t in blacks]))

        # setup dictionary of tiles, colors for this iteration
        tiles = Dict{Tile, Bool}()
        for c in candidates
            tiles[c] = c ∈ blacks
        end

        # keep track of changing tiles for next iteration
        next_tiles = copy(tiles)

        for (tile, color) in tiles
            nb = count_adj(tile, tiles)
            if tiles[tile] && (nb == 0 || nb > 2)
                    # println("flipped $tile to white")
                    next_tiles[tile] = false
            elseif !tiles[tile] && nb == 2
                    # println("flipped ($tile, $(tiles[tile])) to black")
                    next_tiles[tile] = true
            end
        end
        tiles = copy(next_tiles) # apply the rules at the end of the iteration
        # println("Day $day: ", count(values(tiles)))
    end
    return tiles
end

part2(input) = exec(input) |> t->sum(values(t))
@assert part2(ex) == 2208
@info "Day 24, Part 2 answer: $(part2(input))"

end