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
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#18#19")) && precompile(Tuple{getfield(ImageView, Symbol("#18#19")),MouseButton{UserUnit}})
    isdefined(ImageView, Symbol("#20#21")) && precompile(Tuple{getfield(ImageView, Symbol("#20#21")),ZoomRegion{RoundingIntegers.RInt},Int})
    isdefined(ImageView, Symbol("#20#21")) && precompile(Tuple{getfield(ImageView, Symbol("#20#21")),ZoomRegion{RoundingIntegers.RInt}})
    isdefined(ImageView, Symbol("#84#87")) && precompile(Tuple{getfield(ImageView, Symbol("#84#87")),Float32,Float32})
    isdefined(ImageView, Symbol("#84#87")) && precompile(Tuple{getfield(ImageView, Symbol("#84#87")),Float64,Float64})
    isdefined(ImageView, Symbol("#92#95")) && precompile(Tuple{getfield(ImageView, Symbol("#92#95")),AnnotationBox})
    isdefined(ImageView, Symbol("#92#95")) && precompile(Tuple{getfield(ImageView, Symbol("#92#95")),AnnotationPoints{Array{Tuple{Int,Int},1}}})
    let fbody = try __lookup_kwbody__(which(contrast_gui_layout, (Signal{Float32},Signal{Float32},StepRangeLen{Float32,Float64,Float64},))) catch missing end
        if !ismissing(fbody)
            @assert precompile(fbody, (String,typeof(contrast_gui_layout),Signal{Float32},Signal{Float32},StepRangeLen{Float32,Float64,Float64},))
            @assert precompile(fbody, (String,typeof(contrast_gui_layout),Signal{Float64},Signal{Float64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},))
        end
    end
    let fbody = try __lookup_kwbody__(which(imshow, (AbstractArray,Signal{CLim{Float16}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))) catch missing end
        if !ismissing(fbody)
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float16}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float32}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
        end
    end
    let fbody = try __lookup_kwbody__(which(imshow, (AbstractArray,Signal{CLim{Float64}},))) catch missing end
        if !ismissing(fbody)
            @assert precompile(fbody, (Tuple{Symbol,Symbol},String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},))
        end
    end
    let fbody = try __lookup_kwbody__(which(imshow, (AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{2,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))) catch missing end
        if !ismissing(fbody)
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{2,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{3,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{:z,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{true,1,Tuple{Axis{:R,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{N0f16}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{:S,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,2,Tuple{Axis{:z,Base.OneTo{Int}},Axis{:time,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{RoundingIntegers.RInt}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),AbstractArray,Signal{CLim{RoundingIntegers.RUInt8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}},Signal{Dict{UInt,Any}},))
        end
    end
    let fbody = try __lookup_kwbody__(which(imshow, (Any,Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,2,Tuple{Axis{1,Base.OneTo{Int}},Axis{4,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))) catch missing end
        if !ismissing(fbody)
            @assert precompile(fbody, (String,Symbol,typeof(imshow),Any,Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,2,Tuple{Axis{1,Base.OneTo{Int}},Axis{4,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
            @assert precompile(fbody, (String,Symbol,typeof(imshow),Any,Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,2,Tuple{Axis{3,Base.OneTo{Int}},Axis{4,Base.OneTo{Int}}}},Signal{Dict{UInt,Any}},))
        end
    end
    @assert precompile(Tuple{Core.kwftype(typeof(AnnotationLine)),NamedTuple{(:linewidth, :color),Tuple{Int,RGB{N0f8}}},typeof(AnnotationLine),Int,Int,Int,Int})
    @assert precompile(Tuple{Core.kwftype(typeof(AnnotationPoint)),NamedTuple{(:shape, :size, :color),Tuple{Char,Int,RGB{N0f8}}},typeof(AnnotationPoint),Int,Int})
    @assert precompile(Tuple{Core.kwftype(typeof(AnnotationPoint)),NamedTuple{(:shape, :size, :color, :linecolor, :scale),Tuple{Char,Int,RGB{N0f8},RGB{N0f8},Bool}},typeof(AnnotationPoint),Int,Int})
    @assert precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:color, :fontsize),Tuple{RGB{N0f8},Int}},Type{AnnotationText},Int,Int,String})
    @assert precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:linewidth, :color),Tuple{Int,RGB{N0f8}}},Type{AnnotationBox},Int,Int,Int,Int})
    @assert precompile(Tuple{Core.kwftype(typeof(contrast_gui)),NamedTuple{(:wname,),Tuple{String}},typeof(contrast_gui),Signal{Bool},Signal{StatsBase.Histogram{Int,1,Tuple{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}}},Signal{CLim{Float64}}})
    @assert precompile(Tuple{Core.kwftype(typeof(contrast_gui_layout)),NamedTuple{(:wname,),Tuple{String}},typeof(contrast_gui_layout),Signal{Float32},Signal{Float32},StepRangeLen{Float32,Float64,Float64}})
    @assert precompile(Tuple{Core.kwftype(typeof(contrast_gui_layout)),NamedTuple{(:wname,),Tuple{String}},typeof(contrast_gui_layout),Signal{Float64},Signal{Float64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:axes, :name),Tuple{Tuple{Symbol,Symbol},String}},typeof(imshow),AxisArray{Gray{N0f8},3,Array{Gray{N0f8},3},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Signal{CLim{Float64}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{2,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{3,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{:z,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Gray{N0f8},3,Array{Gray{N0f8},3},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{true,1,Tuple{Axis{:R,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Gray{N0f8},3,MappedArrays.ReadonlyMappedArray{Gray{N0f8},3,Array{Gray{N0f8},3},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{3,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{Gray{N0f8},3,MappedArrays.ReadonlyMappedArray{Gray{N0f8},3,Array{Gray{N0f8},3},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,1,Tuple{Axis{:S,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),AxisArray{RGB24,4,MappedArrays.ReadonlyMappedArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},typeof(identity)},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}},Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,2,Tuple{Axis{:z,Base.OneTo{Int}},Axis{:time,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{Float16,2,Array{Float16,2},typeof(identity)},Signal{CLim{Float16}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{Float32,2,Array{Float32,2},typeof(identity)},Signal{CLim{Float32}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{Float64,2,Array{Float64,2},typeof(identity)},Signal{CLim{Float64}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Signal{CLim{RoundingIntegers.RInt}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{N0f16,2,Base.ReinterpretArray{N0f16,2,UInt16,Array{UInt16,2}},typeof(identity)},Signal{CLim{N0f16}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{N0f8,2,Base.ReinterpretArray{N0f8,2,UInt8,Array{UInt8,2}},typeof(identity)},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{N0f8,2,Base.ReshapedArray{N0f8,2,Base.ReinterpretArray{N0f8,1,UInt8,Array{UInt8,1}},Tuple{}},typeof(identity)},Signal{CLim{N0f8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{RGB{Float32},2,Array{RGB{Float32},2},typeof(identity)},Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{RGB{Float64},2,Base.ReshapedArray{RGB{Float64},2,Base.ReinterpretArray{RGB{Float64},3,Float64,Array{Float64,3}},Tuple{}},typeof(identity)},Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{RGB{N0f8},2,Array{RGB{N0f8},2},typeof(identity)},Signal{CLim{RGB{Float64}}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),MappedArrays.ReadonlyMappedArray{UInt8,2,Array{UInt8,2},typeof(identity)},Signal{CLim{RoundingIntegers.RUInt8}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{Base.OneTo{Int},StepRange{Int,Int}},false},Signal{CLim{RoundingIntegers.RInt}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},Base.OneTo{Int}},false},Signal{CLim{RoundingIntegers.RInt}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshow)),NamedTuple{(:name, :aspect),Tuple{String,Symbol}},typeof(imshow),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},StepRange{Int,Int}},false},Signal{CLim{RoundingIntegers.RInt}},Signal{ZoomRegion{RoundingIntegers.RInt}},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{Core.kwftype(typeof(imshowlabeled)),NamedTuple{(:name,),Tuple{String}},typeof(imshowlabeled),Array{Float64,2},Array{Int,2}})
    @assert precompile(Tuple{Core.kwftype(typeof(scalebar)),NamedTuple{(:x, :y),Tuple{Float64,Float64}},typeof(scalebar),Dict{String,Any},Int})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{Float16}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{Float32}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{Float64}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{N0f16}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{N0f8}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{RGB{Float64}}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{RoundingIntegers.RInt}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{Type{Dict},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{RoundingIntegers.RUInt8}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}}})
    @assert precompile(Tuple{typeof(Base.grow_to!),Dict{String,Dict{String,Any}},Tuple{Pair{String,Dict{String,Any}},Pair{String,Signal{CLim{N0f8}}},Pair{String,Dict{String,Any}},Pair{String,Signal{Dict{UInt,Any}}}},Int})
    @assert precompile(Tuple{typeof(_mappedarray),Function,Array{UInt8,2}})
    if Base.VERSION >= v"1.6.0-DEV.1083"   # julia #37559
        @assert precompile(Tuple{typeof(_mappedarray),Function,AxisArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4},true},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}}})
        @assert precompile(Tuple{typeof(_mappedarray),Function,Base.ReinterpretArray{N0f8,2,UInt8,Array{UInt8,2},true}})
    else
        @assert precompile(Tuple{typeof(_mappedarray),Function,AxisArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}}})
        @assert precompile(Tuple{typeof(_mappedarray),Function,Base.ReinterpretArray{N0f8,2,UInt8,Array{UInt8,2}}})
    end
    @assert precompile(Tuple{typeof(closeall)})
    @assert precompile(Tuple{typeof(contrast_gui),Signal{Bool},Array{Any,1},Signal{CLim{RGB{Float64}}}})
    @assert precompile(Tuple{typeof(contrast_gui),Signal{Bool},Array{Signal{StatsBase.Histogram{Int,1,Tuple{StepRangeLen{Float32,Float64,Float64}}}},1},Signal{CLim{N0f16}}})
    @assert precompile(Tuple{typeof(default_axes),AxisArray{Gray{N0f8},2,Array{Gray{N0f8},2},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}})
    @assert precompile(Tuple{typeof(default_axes),AxisArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}}})
    @assert precompile(Tuple{typeof(default_clim),AxisArray{Float64,2,SubArray{Float64,2,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{UnitRange{Int},Int,UnitRange{Int}},false},Tuple{Axis{:x,UnitRange{Int}},Axis{:z,UnitRange{Int}}}}})
    @assert precompile(Tuple{typeof(default_clim),AxisArray{Float64,2,SubArray{Float64,2,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int},Int},false},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}}}}})
    @assert precompile(Tuple{typeof(default_clim),AxisArray{Gray{N0f8},2,SubArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}})
    @assert precompile(Tuple{typeof(default_clim),AxisArray{Gray{N0f8},2,SubArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},3,Array{Gray{N0f8},3},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int},Int},false},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Float16,2,MappedArrays.ReadonlyMappedArray{Float16,2,Array{Float16,2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Float32,2,MappedArrays.ReadonlyMappedArray{Float32,2,Array{Float32,2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Float64,2,MappedArrays.ReadonlyMappedArray{Float64,2,Array{Float64,2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},StepRange{Int,Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{UnitRange{Int},StepRange{Int,Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{N0f16,2,MappedArrays.ReadonlyMappedArray{N0f16,2,Base.ReinterpretArray{N0f16,2,UInt16,Array{UInt16,2}},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{N0f8,2,MappedArrays.ReadonlyMappedArray{N0f8,2,Base.ReinterpretArray{N0f8,2,UInt8,Array{UInt8,2}},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{N0f8,2,MappedArrays.ReadonlyMappedArray{N0f8,2,Base.ReshapedArray{N0f8,2,Base.ReinterpretArray{N0f8,1,UInt8,Array{UInt8,1}},Tuple{}},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(default_clim),SubArray{UInt8,2,MappedArrays.ReadonlyMappedArray{UInt8,2,Array{UInt8,2},typeof(identity)},Tuple{UnitRange{Int},UnitRange{Int}},false}})
    @assert precompile(Tuple{typeof(prep_contrast),GtkReactive.Canvas{UserUnit},Signal{AxisArray{Gray{N0f8},2,SubArray{Gray{N0f8},2,Array{Gray{N0f8},3},Tuple{UnitRange{Int},UnitRange{Int},Int},false},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}},Nothing})
    @assert precompile(Tuple{typeof(prep_contrast),GtkReactive.Canvas{UserUnit},Signal{SubArray{Gray{N0f8},2,Array{Gray{N0f8},2},Tuple{UnitRange{Int},UnitRange{Int}},false}},Signal{CLim{N0f8}}})
    @assert precompile(Tuple{typeof(prep_contrast),GtkReactive.Canvas{UserUnit},Signal{SubArray{RGB{N0f8},2,Array{RGB{N0f8},2},Tuple{UnitRange{Int},UnitRange{Int}},false}},Signal{CLim{RGB{Float64}}}})
    @assert precompile(Tuple{typeof(prep_contrast),GtkReactive.Canvas{UserUnit},Signal{SubArray{RGB{N0f8},2,Array{RGB{N0f8},3},Tuple{UnitRange{Int},UnitRange{Int},Int},false}},Nothing})
    @assert precompile(Tuple{typeof(sliceinds),AxisArray{RGB24,4,MappedArrays.ReadonlyMappedArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},typeof(identity)},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}},Tuple{Int,Int},Axis{:z,Int},Vararg{Any,N} where N})
    @assert precompile(Tuple{typeof(annotate!),Dict{String,Any},AnnotationBox})
    @assert precompile(Tuple{typeof(annotate!),Dict{String,Any},AnnotationLines{Float64,Tuple{Tuple{Float64,Float64},Tuple{Float64,Float64}}}})
    @assert precompile(Tuple{typeof(annotate!),Dict{String,Any},AnnotationPoints{Array{Tuple{Int,Int},1}}})
    @assert precompile(Tuple{typeof(annotate!),Dict{String,Any},AnnotationPoints{Tuple{Float64,Float64}}})
    @assert precompile(Tuple{typeof(annotate!),Dict{String,Any},AnnotationText})
    @assert precompile(Tuple{typeof(imlink),AxisArray{Gray{N0f8},3,Array{Gray{N0f8},3},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Vararg{Any,N} where N})
    @assert precompile(Tuple{typeof(imshow!),GtkReactive.Canvas{UserUnit},AxisArray{Gray{N0f8},2,Array{Gray{N0f8},2},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}})
    @assert precompile(Tuple{typeof(imshow!),GtkReactive.Canvas{UserUnit},Signal{AxisArray{Gray{N0f8},2,Array{Gray{N0f8},2},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}}},Signal{ZoomRegion{RoundingIntegers.RInt}},Signal{Dict{UInt,Any}}})
    @assert precompile(Tuple{typeof(imshow),GtkReactive.Canvas{UserUnit},AbstractArray{T,2} where T})
    @assert precompile(Tuple{typeof(push!),Signal{CLim{Float64}},CLim{Float64}})
    @assert precompile(Tuple{typeof(push!),Signal{CLim{N0f16}},CLim{Float32}})
    @assert precompile(Tuple{typeof(roi),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},Tuple{Int,Int}})
    @assert precompile(Tuple{typeof(roi),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},Tuple{Symbol,Symbol}})
    @assert precompile(Tuple{typeof(roi),AxisArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}},Tuple{Symbol,Symbol}})
    @assert precompile(Tuple{typeof(roi),AxisArray{Gray{N0f8},3,MappedArrays.ReadonlyMappedArray{Gray{N0f8},3,Array{Gray{N0f8},3},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},Tuple{Symbol,Symbol}})
    @assert precompile(Tuple{typeof(roi),AxisArray{RGB24,4,MappedArrays.ReadonlyMappedArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},typeof(identity)},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}},Tuple{Symbol,Symbol}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,1,Tuple{Axis{2,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,1,Tuple{Axis{3,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{Float64,3,MappedArrays.ReadonlyMappedArray{Float64,3,Array{Float64,3},typeof(identity)},Tuple{Axis{:x,Base.OneTo{Int}},Axis{:y,Base.OneTo{Int}},Axis{:z,Base.OneTo{Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,1,Tuple{Axis{:z,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{Gray{N0f8},2,MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{Gray{N0f8},3,MappedArrays.ReadonlyMappedArray{Gray{N0f8},3,Array{Gray{N0f8},3},typeof(identity)},Tuple{Axis{:P,StepRange{Int,Int}},Axis{:R,StepRange{Int,Int}},Axis{:S,StepRange{Int,Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,1,Tuple{Axis{:S,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{typeof(slice2d),AxisArray{RGB24,4,MappedArrays.ReadonlyMappedArray{RGB24,4,Base.ReinterpretArray{RGB24,4,UInt32,Array{UInt32,4}},typeof(identity)},Tuple{Axis{:x,UnitRange{Int}},Axis{:y,UnitRange{Int}},Axis{:z,StepRange{Int,Int}},Axis{:time,UnitRange{Int}}}},ZoomRegion{RoundingIntegers.RInt},SliceData{false,2,Tuple{Axis{:z,Base.OneTo{Int}},Axis{:time,Base.OneTo{Int}}}}})
    @assert precompile(Tuple{typeof(slice2d),MappedArrays.ReadonlyMappedArray{Float16,2,Array{Float16,2},typeof(identity)},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),MappedArrays.ReadonlyMappedArray{Gray{N0f8},2,Array{Gray{N0f8},2},typeof(identity)},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),MappedArrays.ReadonlyMappedArray{N0f8,2,Base.ReinterpretArray{N0f8,2,UInt8,Array{UInt8,2}},typeof(identity)},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{Base.OneTo{Int},StepRange{Int,Int}},false},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},Base.OneTo{Int}},false},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
    @assert precompile(Tuple{typeof(slice2d),SubArray{Int,2,MappedArrays.ReadonlyMappedArray{Int,2,Array{Int,2},typeof(identity)},Tuple{StepRange{Int,Int},StepRange{Int,Int}},false},ZoomRegion{RoundingIntegers.RInt},SliceData{false,0,Tuple{}}})
end


text:
@doc doc"""Represents a NeoVim buffer.""" ->
type Buffer
    nvim::Nvim
    data::MsgPack.Ext
end

# TODO: using these in a for-loop does not guarantee that every line will be
# iterated over as buffer length could change in the for-loop
Base.length(b::Buffer) = buffer_line_count(b)
Base.endof(b::Buffer) = length(b)

function Base.getindex(b::Buffer, idx::Integer)
    # TODO: use 0.4 syntax
    idx = convert(Int, idx)
    if idx == 0
        throw(BoundsError())
    elseif idx < 0
        buffer_get_line(b, idx)
    else
        buffer_get_line(b, idx-1)
    end
end
function Base.getindex(b::Buffer, rng::UnitRange)
    buffer_get_line_slice(b, rng.start-1, rng.stop-1, true, true)
end
Base.getindex(b::Buffer, rng::StepRange) = [b[i] for i in rng]
# TODO: rethink this API (and use 0.4 syntax)
Base.getindex(b::Buffer, opt::Symbol) = buffer_get_option(b, string(opt))
Base.getindex(b::Buffer, var::ByteString) = buffer_get_var(b, var)

Base.setindex!(b::Buffer, val, idx::Integer) = buffer_set_line(b, idx, val)
function Base.setindex!(b::Buffer, val, rng::UnitRange)
    buffer_set_line_slice(b, rng.start-1, rng.stop-1, true, true, val)
end
function Base.setindex!(b::Buffer, val, rng::StepRange)
    for (i, v) in zip(rng, val)
        b[i] = v
    end
end
Base.setindex!(b::Buffer, val, opt::Symbol) = buffer_set_option(b, string(opt), val)
Base.setindex!(b::Buffer, val, var::ByteString) = buffer_set_var(b, var, val)

Base.insert!(b::Buffer, lnum, lines) = buffer_insert(b, lnum, lines)

ext: jl


text:
using Blink
using CSV, DataFrames, Interact, Plots

mutable struct Viewer
    window::Window
    observables::Dict{String, Observable}
    widgets::Dict{String, Widget}
end

function makebuttons(view::Viewer)
    buttons = button.(string.(names(view.observables["data"])))
    for (btn, name) in zip(buttons, names(view.observables["data"]))
        map!(t -> histogram(view.observables["data"][name]), view.observables["plt"], btn)
    end
    dom"div"(hbox(buttons))
end

function makebuttons(df, view)
    buttons = button.(string.(names(df)))
    for (btn, name) in zip(buttons, names(df))
        map!(t -> histogram(df[name]), view.observables["plt"], btn)
    end
    dom"div"(hbox(buttons))
end

function read(dir::String)
    buttons = button.(string.(readdir(dir)))
    dom"div"(vbox(buttons))
end

function Viewer()

    view = Viewer(Window(), Dict{String, Observable}(), Dict{String, Widget}())
    window = view.window
    o = view.observables
    w = view.widgets

    w["loadbutton"] = filepicker()
    o["folder_list"] = Observable{Any}(dom"div"())

    map!(read, o["folder_list"], w["loadbutton"])
    # map!((df) -> makebuttons(df, view), o["columnbuttons"], o["data"])
    ui = dom"div"(w["loadbutton"], o["folder_list"])

    body!(window, ui)

    return view

end

Base.close(v::Viewer) = begin; close(v.window); end

function create_ui()

    # Make a Blink Window
    w = Window()
    ui = button()
    body!(w, ui)
    on(println, ui)

    return w
end

close_ui(w) = close(w)


text:
################################################################################
#
#   gkz.jl : functions for Granger, Kleinjung, Zumbrägel algorithm
#
################################################################################

# Ascent phase in the GKZ algorithm

"""
    ascent(Q::fq_nmod_poly)

Find an irreducible factor of degree two of `Q` in an extension.
"""
function ascent(Q::fq_nmod_poly)

    # We compute d such that deg(Q) = 2^d
    d::Int = log2(degree(Q))

    # Then we embed `Q` in degree two extensions and keep only one factor of
    # degree 2^(d-1), and we do the same with this factor, until we have a
    # factor of degree 2.

    F = base_ring(Q)
    deg = degree(F)
    c::Int = characteristic(F)
    P = Q

    for j in 1:d-1

        # We create the name of the variable of the extension
        z = string("z", deg*2^j)

        # We create the extension
        Fext = FiniteField(c, deg*2^j, z)[1]

        # We embed the polynomial in that extension
        img = findImg(Fext, F)
        F = Fext
        T = string("T", deg*2^j)
        R = PolynomialRing(Fext, T)[1]

        # And we compute a factor
        P = anyFactor(embedPoly(R, P, img))
    end

    return P
end

"""
    ascent{Y <: PolyElem}(Q::Y, h0::Y, h1::Y)

Compute an embedding of `h0` and `h1` by going through all the fields in the
tower appearing in the power-of-2 algorithm.
"""
function ascent{Y <: PolyElem}(Q::Y, h0::Y, h1::Y)

    # We compute d such that deg(Q) = 2^d
    d::Int = log2(degree(Q))

    # Then we create all the fields of the extension appearing in the
    # powers-of-2 algorithm and we embed h0 and h1 at each step.

    F = base_ring(Q)
    deg = degree(F)
    c::Int = characteristic(F)
    P = Q
    t0, t1 = h0, h1

    for j in 1:d-1

        # We create the name of the variable of the extension
        z = string("z", deg*2^j)

        # We create the extension
        Fext = FiniteField(c, deg*2^j, z)[1]

        # We create the associated polynomial rings
        img = findImg(Fext, F)
        F = Fext
        T = string("T", deg*2^j)
        R = PolynomialRing(Fext, T)[1]

        # And we embed the polynomials, and in the case of `P` we keep only one
        # factor
        P = anyFactor(embedPoly(R, P, img))
        t0, t1 = embedPoly(R, t0, img), embedPoly(R, t1, img)
    end

    return P, t0, t1
end

"""
    latticeBasis(Q::fq_nmod_poly, h0::fq_nmod_poly, h1::fq_nmod_poly)

Find a basis of the latice L_Q of the form (u0, X + u1), (X + v0, v1).

``L_Q = {(w0, w1) \in F_{q^k}[X]^2 | w0h0 + w1h1 = 0 (mod Q)}``
"""
function latticeBasis(Q::fq_nmod_poly, h0::fq_nmod_poly, h1::fq_nmod_poly)

    # We compute our variables a, a0, a1, b, b0, b1
    h0b = h0 % Q
    h1b = h1 % Q

    Qb = monic(Q)
    a, b = -coeff(Qb, 1), -coeff(Qb, 0)

    a0, b0 = coeff(h0b, 1), coeff(h0b, 0)
    a1, b1 = coeff(h1b, 1), coeff(h1b, 0)

    # We set some variables
    t = (a0*b1-b0*a1)^(-1)
    u = a0^(-1)

    # We compute the result
    v1 = (b0*u*(a*a0+b0) - a0*b)*a0*t
    u1 = (b0*u*(a*a1+b1) - a1*b)*a0*t
    v0 = -u*(a1*v1 + a*a0 + b0)
    u0 = -u*(a1*u1 + a*a1 + b1)

    return u0, u1, v0, v1
end

"""
    onTheFlyAbc(Q::fq_nmod_poly, h0::fq_nmod_poly, h1::fq_nmod_poly, q::Int)

Perform the 'on-the-fly' elimination of degree 2 elements.

In other words, return a, b, c such that the polynomial P = X^(q+1) + aX^q + bX + c
splits completely in the base field of `Q` and such that h1*P (mod h1*X^q - h0)
= 0 mod Q.
"""
function onTheFlyAbc(Q::fq_nmod_poly, h0::fq_nmod_poly, h1::fq_nmod_poly, q::Int)

    # We set some usefull variables
    R = parent(Q)
    T = gen(R)
    F = base_ring(Q)

    # We pick B such that T^(q+1) - BT + B splits completely
    B = randomSplitElem(R, q)

    # We find ui's and vi's such that (u0, T+u1), (T+v0, v1) is a basis of the
    # lattice L_Q = {(w0, w1) | w0h0 + w1h1 = 0 (mod Q)}
    u0, u1, v0, v1 = latticeBasis(Q, h0, h1)

    # We write the polynomial arising from the conditions wanted
    P = (-u0^q*T^q+T-v0^q)^(q+1)-B*(-u0*T^2+(u1-v0)*T+v1)^q

    # We find a root
    r = anyRoot(P)

    # If there is no root, we try the process with an other B
    while r == nothing
        B = randomSplitElem(R, q)
        P = (-u0^q*T^q+T-v0^q)^(q+1)-B*(-u0*T^2+(u1-v0)*T+v1)^q
        r = anyRoot(P)
    end

    # And we return a, b, c
    return r*u0+v0, r, r*u1+v1
end

"""
    onTheFlyElimination(Q::fq_nmod_poly, h0::fq_nmod_poly,
                        h1::fq_nmod_poly, q::Int)

Eliminate the polynomial `Q`. In other words, return polynomials L_i of degree 1
such that Π_i P_i^e_i = Q (mod h1×X^q - h0) for some coefficients e_i.
"""
function onTheFlyElimination(Q::fq_nmod_poly, h0::fq_nmod_poly,
                             h1::fq_nmod_poly, q::Int)

    # We define some usefull variables
    R = parent(Q)
    T = gen(R)

    # We find suitable constants a, b, c to perform the elimination
    a, b, c = onTheFlyAbc(Q, h0, h1, q)

    # We define the a polynomial that splits into linear factors
    P = T^(q+1) + a*T^q + b*T + c
    fact = factor(P)

    # And such that it is also divisible by Q modulo h1×X^q - h0, in the sense
    # that we have h1×P = L×Q (mod h1×Q - h0), with L of degree 1
    L = divexact((T+a)*h0+(b*T+c)*h1, Q)

    # Finally we return the factors of `P` and the linear polynomial `L`
    res = Array(fq_nmod_poly, q+2)
    j = 1
    for f in fact
        res[j] = f[1]
        j += 1
    end

    res[j] = L
    return res
end

"""
    norm2(Q::fq_nmod_poly, q::Integer)

Compute the norm of `Q` with respect to the field `F_q`, where the base field of
`Q` is an extension of degree 2 of `F_q`.
"""
function norm2(Q::fq_nmod_poly, q::Integer)

    # We compute the (only) conjugate of `Q`, meaning that all coefficients of
    # `Q` are raised to the power `q`
    conj = parent(Q)([coeff(Q, i)^q for i in 0:degree(Q)])

    # And we return the norm, i.e. the product of `Q` and its conjugate
    return Q*conj
end

"""
    norm4(Q::fq_nmod_poly, q::Integer)

Compute the norm of `Q` with respect to the field `F_q`, where the base field of
`Q` is an extension of degree 4 of `F_q`.
"""
function norm4(Q::fq_nmod_poly, q::Integer)

    # We compute the different conjugates and we compute the product as the same
    # time, the conjugate are just the polynomial `Q` with coefficients raised
    # to the power q^i
    res = Q
    conj = Q

    for j in 1:3
        conj = parent(conj)([coeff(conj, i)^q for i in 0:degree(conj)])
        res *= conj
    end

    # We return the product, i.e. the norm
    return res
end

"""
    descentGKZ{Y <: PolyElem}(Q::Y, h0::Y, h1::Y, Rinit::PolyRing)

Perform the descent of the GKZ algorithm.

I.e. return a list of polynomials P_i and associated coefficients e_i such that
Π_i P_i^e_i = P, where `P` is the polynomial such that `Q` is the result of the
ascent in GKZ algorithm.
"""
function descentGKZ{Y <: PolyElem}(Q::Y, h0::Y, h1::Y, Rinit::PolyRing)

    # We first compute f such that the base field of `Q` is an extension of
    # degree 2^f of the base field of h0
    F = base_ring(Q)
    p::Int = characteristic(F)
    ff = base_ring(Rinit)
    q::Int = length(ff)
    c::Int = floor(log(p, q))
    d::Int = log(p, length(F))/c
    f::Int = log2(d)
    t0, t1 = h0, h1

    # Through the descent, we count the number of times we have h1 in an
    # equation
    cpt_h1 = BigInt()

    # We will use a composite type to store the involved polynomials and their
    # coefficients, L contains only Q and L2 is empty
    L = weightedList(Q)
    L2 = weightedList()

    # We start the descent through the tower
    #
    #   F_q^(2^f)
    #    |
    #   F_q^(2^(f-1))
    #    |
    #    :
    #    |
    #   F_q^(2^2)
    #    |
    #   F_q
    #
    # The last step is treated differently after that loop.

    for j in f:-1:3

        # We precompute data to be able to perform the projection
        # towards the floor below efficiently

        l = length(L)
        R = parent(L[1][1])
        exp = c*2^(j-1)
        n = BigInt(p)^exp
        s = string("z", exp)
        G = FiniteField(p, exp, s)[1]
        T = string("T", exp)
        Rg = PolynomialRing(G, T)[1]
        M, piv = projectFindInv(base_ring(R), G)

        # For each element in L, we perform an on-the-fly elimination, we
        # project the obtained polynomials and store them in L2

        for i in 1:l

            # We perforn the elimination
            P, coef = L[i][1], L[i][2]
            A = onTheFlyElimination(P, t0, t1, q)

            # We add 2*coef because h1 will appear in the equation, and then it
            # will appear a second time when compute its norm, because h1 is its
            # own conjugate, so ||h1|| = h1²
            cpt_h1 += 2*coef

            for k in 1:(q+1)
                N = norm2(A[k], n)
                push!(L2, projectLinAlgPoly(Rg, N, M, piv), coef)
            end

            N = norm2(A[q+2], n)
            push!(L2, projectLinAlgPoly(Rg, N, M, piv), -coef)

        end

        # We project t0 and t1 (aka h0, h1) in the floor below in the tower
        t0 = projectLinAlgPoly(Rg, t0, M, piv)
        t1 = projectLinAlgPoly(Rg, t1, M, piv)

        # We replace L by L2 to perform the same thing in the next floor
        L = L2

        # And we empty the list L2
        L2 = weightedList()
    end

    # We treat the case
    #
    # F_q⁴
    #  |
    # F_q
    #
    # where after the degree two elimination, we compute the norm of a degree 4
    # extension instead of a degree 2 extension like we did for the first steps.

    l = length(L)
    R = parent(L[1][1])
    Rg = Rinit
    G = base_ring(Rg)
    M, piv = projectFindInv(base_ring(R), G)

    for i in 1:l

        P, coef = L[i][1], L[i][2]
        A = onTheFlyElimination(P, t0, t1, q)

        # Here we have ||h1||=h1⁴
        cpt_h1 += 4*coef

        for k in 1:(q+1)
            N = norm4(A[k], q)
            push!(L2, projectLinAlgPoly(Rg, N, M, piv), coef)
        end

        N = norm4(A[q+2], q)
        push!(L2, projectLinAlgPoly(Rg, N, M, piv), -coef)

    end

    t1 = projectLinAlgPoly(Rg, t1, M, piv)

    # We add t1 = h1 in the resulting list
    push!(L2, t1, cpt_h1)

    return L2
end


text:
module fitpredict

export fit, predict

using LinearAlgebra
using Optim

using ..regularization

function fit(y, X, reg::Union{Nothing, AbstractRegularizer}=nothing, opts::Union{Nothing, Optim.Options}=nothing)
    # Fit
    nclasses = maximum(y)
    nx       = size(X, 2)
    probs    = fill(0.0, nclasses)
    B0       = fill(0.0, nx * (nclasses - 1))
    fg!      = get_fg!(reg, probs, y, X)  # Debug with opts = Optim.Options(show_trace=true)
    mdl      = isnothing(opts) ? optimize(Optim.only_fg!(fg!), B0, LBFGS()) : optimize(Optim.only_fg!(fg!), B0, LBFGS(), opts)
    theta    = mdl.minimizer
    B        = reshape(theta, nx, nclasses - 1)
    reg isa BoxRegularizer && regularize!(B, reg)  # Constrain B to the box specified by reg

    # Collect stderror
    f      = TwiceDifferentiable(b -> loglikelihood_for_hessian!(y, X, b), theta; autodiff=:forward)
    hess   = Optim.hessian!(f, theta)
    ntheta = length(theta)
    if rank(hess) == ntheta
        varcov = inv(-hess)   # varcov(theta) = inv(FisherInformation) = inv(-Hessian)
        se     = [sqrt(abs(varcov[i,i])) for i = 1:ntheta]  # abs for negative values very close to 0
        se     = reshape(se, nx, nclasses - 1)
    else
        se = fill(NaN, nx, nclasses - 1)
    end
    (params=B, stderror=se)
end

predict(B, x) = update_probs!(fill(0.0, 1 + size(B, 2)), B, x)

################################################################################
# unexported

# L1 or L2 regularization
get_fg!(reg, probs, y, X) = (_, gradB, B) -> regularize(reg, B, gradB) - loglikelihood!(probs, y, X, B, gradB)

# No regularization
function get_fg!(reg::Nothing, probs, y, X)
    (_, gradB, B) -> begin
        fill!(gradB, 0.0)
        -loglikelihood!(probs, y, X, B, gradB)
    end
end

# Box regularization
function get_fg!(reg::BoxRegularizer, probs, y, X)
    nx       = size(X, 2)
    nclasses = length(probs)
    outB     = fill(0.0, nx * (nclasses - 1))
    (_, gradB, B) -> begin
        regularize!(outB, B, reg, gradB)
        -loglikelihood!(probs, y, X, outB, gradB)
    end
end

function loglikelihood!(probs, y, X, b::Vector, gradb::Vector)
    nx    = size(X, 2)
    nj    = Int(length(b) / nx)
    B     = reshape(b, nx, nj)
    gradB = reshape(gradb, nx, nj)
    loglikelihood!(probs, y, X, B, gradB)
end

function loglikelihood!(probs, y, X, B::Matrix, gradB::Matrix)
    LL = 0.0
    for (i, yi) in enumerate(y)
        x = view(X, i, :)
        update_probs!(probs, B, x)
        update_gradient!(gradB, probs, yi, x)
        LL += log(max(probs[yi], 1e-12))
    end
    LL
end

function update_probs!(probs, B, x)
    probs[1] = 0.0  # bx for first category is 0
    max_bx   = 0.0  # Find max bx for numerical stability
    nclasses = length(probs)
    for c = 2:nclasses
        bx       = dot(x, view(B, :, c - 1))
        probs[c] = bx
        max_bx   = bx > max_bx ? bx : max_bx
    end
    probs .= exp.(probs .- max_bx)  # Subtract max_bx first for numerical stability. Then prob[c] <= 1.
    normalize!(probs, 1)
end

function update_gradient!(gradB, probs, yi, x)
    nclasses = length(probs)
    nx = length(x)
    for c = 2:nclasses
        p = probs[c]
        for (j, xj) in enumerate(x)
            gradB[j, c - 1] += p * xj  # Negative gradient because function is to be minimized
        end
    end
    yi == 1 && return nothing
    for (j, xj) in enumerate(x)
        gradB[j, yi - 1] -= xj  # Negative gradient because function is to be minimized
    end
    nothing
end

"Called only for calculating the Hessian after optimization"
function loglikelihood_for_hessian!(y, X, b::AbstractVector{T}) where T
    nx    = size(X, 2)
    nj    = Int(length(b) / nx)
    B     = reshape(b, nx, nj)
    probs = zeros(T, 1 + nj)  # Must accommodate ForardDiff.Dual
    LL    = 0.0
    for (i, yi) in enumerate(y)
        x = view(X, i, :)
        update_probs!(probs, B, x)
        LL += log(max(probs[yi], 1e-12))
    end
    LL
end

end


text:
__precompile__()
"""
    EasyHousehold

a module for utility optimization problems in household department;
uses adjusted analytical solution; DP has been roughly deprecated.
"""
    module EasyHousehold
        import Roots: find_zero, Bisection  # using root searching method in package Root (faster than Bisection() written by myself)

    # ---------------------------------------------
    """
        u(c::Real, l::Real, q::Real, γ::Real, α::Real)

    cross-sectional utility function;
    a divisible CES one: ``u = \\frac{1}{1-\\gamma^{-1}} [ ((1-q)c)^{1-\\gamma^{-1}} + \\alpha l^{1-\\gamma^{-1}}   ] ``;
    where ``\\gamma`` is inter-temporal elasticity of substitution, ``\\alpha`` is leisure preference;
    c is consumption, l is leisure, q is ratio of total medical expenditure to total consumption (c);
    returns a utility value

    p.s.:
    1. minor amounts are added to avoid Inf at zero
    2. no validation (it should have been ``c,l>0`` and ``0 \\leq q \\leq 1``)
    """
    function u(c::Real, l::Real, q::Real, γ::Real, α::Real)
        uval = 1/(1-1/γ) * (  ( (1-q) * c + 1E-12 )^(1-1/γ)  +  α * ( l + 1E-12 )^(1-1/γ)  )
        return uval::Real
    end

    # ---------------------------------------------
    """
        LifeDecision( a0::Real, Φ0::Real, Pc::Dict, d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int )

    solves lifetime utility optimization problem through adjusted-analytical method;
    where a0 & Φ0 are initial values when paths begin (for personal asset **a** and individual medical account **Φ**);
    and where **Pc** is a dictionary of constant parameters defined in *proc_InitPars.jl* (not all members used but you may pass it for convenience);
    and **d** is a dictionary of data used in decision making (listed below);
    and **S** is MAX_AGE, **Sr** is RETIRE_AGE;
    no bequest left;
    returns a collection (NamedTuple) of solved paths (listed below);

    p.s.: I strictly assert type of **d** to avoid possible numerical errors

    ## Essential Members of **Pc**
    1. μ [num]: consumption tax rate
    2. σ [num]: wage tax rate
    3. α [num]: leisure preference (used in utiltiy function)
    4. γ [num]: inter-temporal elasticity of substitution (used in utility function)
    5. δ [num]: utility discounting rate (used in Lagrange function)

    ## Essential Members of **d** (all 1d arrays/vectors)
    1. length = S (MAX-AGE)
        1. :r : interest rates
        2. :F : mortality, the last element should be 0
        3. :q : ratio of total medical expenditure to total consumption
        4. :p : ratio of outpatient to inpatient expenditure
        5. :cpB : co-payment rate of inpatient expenditure
    2. length = Sr (RETIRE-AGE)
        1. :ϕ : contribution rate from household to UE-BMI
        2. :ζ : contribution rate from firm to UE-BMI
        3. :η : contribution rate from firm to PAYG pension
        4. :θ : contribution rate from household to PAYG pension
        5. :z : collection rate of the PAYG pension
        6. :w : wage levels
        7. :𝕒 : transfer **RATE** (not amount!!!) from firm contribution to working households
    3. length = S-Sr
        1. :Λ : pension benefit **amounts**
        2. :𝕡 : transfer **AMOUNT** (not rate!!!) from firm contribution to retired households
    4. p.s.: 𝕡 is amount of capital because we need external information of the macro-economy to get their values; it is not a good idea to do this in such an isolated household decision making function

    ## Members of returned NamedTuple (ordered, as well by name):
    1. 𝒜 [Vector{Float64},len=S]: wealth path
    2. a [Vector{Float64},len=S]: personal asset path
    3. Φ [Vector{Float64},len=S]: individual medical account
    4. c [Vector{Float64},len=S]: consumption path
    5. l [Vector{Float64},len=Sr]: leisure path in **working** years

    ## Depends on: function [module]
    1. u [EasyHousehold]: cross-sectional utility function
    2. AddAbbrs! [EasyHousehold]: define a series of abbreviations and add them to data dictionary
    3. GetC [EasyHousehold]: get consumption path from data and abbreviations
    4. Get𝒜, Geta, GetΦ [EasyHousehold]: get paths of wealth, personal asset, individual medical account
    5. find_zero [Roots]: search a c(1) which makes 𝒜dead == 0
    """
    function LifeDecision( a0::Real, Φ0::Real, Pc::Dict, d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int )
        ## Section 1: validation 合法性检查
            # 1.1 too-short path length
            @assert( S>2 & 1<=Sr<=S, "too short path to solve, requiring: S > 2 and 1 ≦ Sr ≦ S" )
            # 1.2 interest rate cannot be -1 (zero domain)
            @assert( all(d[:r] .!= -1.0), "invalid interest rate found: -1"   )
            # 1.3 utility discounting rate cannot be -1 (zero domain)
            @assert( Pc[:δ] != -1.0, "invalid utility discounting rate δ: -1" )
            # 1.4 mortality in the very last year/age must be 0 (if not, correct it)
            d[:F][end] = 0.0
        ## Section 2: prepare members of result collection 准备结果变量
            Ret = Dict(
                :𝒜 => fill(a0 + Φ0, S),  # wealth
                :a => fill(a0, S),  # personal asset
                :Φ => fill(Φ0, S),  # individual medical account
                :c => zeros(S),  # total consumption
                :l => zeros(Sr),  # leisure
            )

        ## Section 3: abbreviations 定义一批缩写以简化运算
        # NOTE: pls refer to academic documents to learn mathematics about these defined abbreviation variables
        # NOTE: use an in-place function to clear format code; the results are written/added into d
            AddAbbrs!(a0,Φ0,Pc,d,S,Sr)  # using the same parameter combinition & order for convenience

        ## Section 4: get un-limited soltion & check endowment constraints 先求一个不含禀赋约束的解路径，并检查是否满足（本来就是内点解，不需要矫正）
            # 1. get un-limited consumption path (validation integrated)
            Ret[:c] = GetC( d, S, Sr, Ret[:𝒜][1], givenleisure = nothing )
            # 2. get un-limited leisure path through leisure → consumption relationship
            for s in 1:Sr
                Ret[:l][s] = Ret[:c][s] * (1.0 - d[:q][s]) / d[:ℛ][s] ^ Pc[:γ]
            end
            # 3. check if all leisure is valid, set a flag
            flag_NeedAdjust = false
            all( 0.0 .<= Ret[:l] .<= 1.0 )  ||  (flag_NeedAdjust = true)

        ## Section 5: adjust leisure path if it touches bounds, then adjust consumptions correspondingly (to meet budgets) 若闲暇touch到了上下界，那么修正闲暇并随即修正消费（使满足预算约束）
        # NOTE: the section only runs when flag_NeedAdjust == true
        if flag_NeedAdjust
            # 1. find out which points are out of the bounds [0,1]
            LocOut0 = Ret[:l] .< 0.0; LocOut1 = Ret[:l] .> 1.0
            # 2. forcely adjust these points to be just at the bounds
            Ret[:l][LocOut0] .= 0.0; Ret[:l][LocOut1] .= 1.0
            # 3. get consumption path based on limited leisure (validation integrated)
            Ret[:c] = GetC( d, S, Sr, Ret[:𝒜][1], givenleisure = Ret[:l] )
        end

        ## Section 6: use convenient functions to get full asset, wealth and individual medical account paths 使用额外的函数计算完整的资产路径
            # 1. total wealth (𝒜 = a + Φ), returns a path and a real number of wealth at dead moment
            Ret[:𝒜], tmp𝒜dead = Get𝒜( d, Ret[:c], Ret[:l], S, Sr, a0 + Φ0 )
            # 2. individual medical account (Φ), returns a path (non-negative) and a gap path
            Ret[:Φ], tmpGap = GetΦ( d, Ret[:c], Ret[:l], S, Sr, Φ0 )
            # 2. personal asset (a), only returns a path, but use tmpGap to adjust the path
            Ret[:a] = Geta( d, Ret[:c], Ret[:l], S, Sr, a0 )
            Ret[:a] .+= tmpGap

        ## Section 7: check if the relationship 𝒜 = a + Φ met; then check if no-bequest constraint met 检查是否符合相加关系，以及是否满足无遗产约束            return Ret
            @assert( all(isapprox.(Ret[:𝒜], Ret[:a] .+ Ret[:Φ], atol = 1E-6)) , "relationship scrA = a + Phi not met (unlimited path)"   )
            if isapprox(tmp𝒜dead, 0.0, atol = 1E-6)
                return Ret::Dict
            end

        ## Section 8: search a c(1) which results in 𝒜dead == 0 搜一个c(1)，使得死亡时财富为0
        # NOTE: keep Euler equation determined; keep leisure path determined (cauz we've adjusted it)
        # NOTE: use bisection method to search the zero point (scriptA_dead = 0)
            # 1. an object function, receiving c(1), returns 𝒜dead
            objfunc(tmpc1::Real) = begin
                tmppath_c = tmpc1 .* d[:𝒯]  # get a temporary consumption path
                return Get𝒜(d,tmppath_c,Ret[:l], S, Sr, a0 + Φ0)[2]  # only return the 2nd element (𝒜 at dead moment)
            end
            # 2. search a root, using c(1) on the unlimited path as initial guess
            # NOTE: the monotonicity (decreasing) of function 𝒜dead = 𝒜dead( c(1) ) can be wasily proved, and 𝒜dead(0) > 0 ensured
            # NOTE: using bisection searching
                # 2.1 set search range
                tmpVal = Ret[:c][1] # use unlimited c(1) as initial guess
                tmpBisecRange = [0.0, 0.0] # a range for bisection searching, where the left bound must be 0 (𝒜dead(0) >= 0), so we need to ensure 𝒜dead(rightbound) < 0
                while objfunc(tmpVal) >= 0.0  # there are two cases: tmpVal > 0 or tmpVal < 0; if <0, just use tmpVal as the right bound of search, if >0, find a tmpVal large enough to make 𝒜dead(tmpVal) < 0
                    tmpVal *= 2.0
                end
                tmpBisecRange[2] = tmpVal
                # 2.2 search c(1) through Bisection searching
                Ret[:c][1] = find_zero(objfunc, tmpBisecRange, Bisection())
            # 3. get complete consumption path (through Euler Equation)
            Ret[:c][:] = Ret[:c][1] .* d[:𝒯]

        ## Section 9: get 𝒜, a, Φ paths 得到财富、资产和个人医保账户的路径
        # NOTE: nearly the same as section 6
            # 1. total wealth (𝒜 = a + Φ), returns a path and a real number of wealth at dead moment
            Ret[:𝒜], tmp𝒜dead = Get𝒜( d, Ret[:c], Ret[:l], S, Sr, a0 + Φ0 )
            # 2. individual medical account (Φ), returns a path (non-negative) and a gap path
            Ret[:Φ], tmpGap = GetΦ( d, Ret[:c], Ret[:l], S, Sr, Φ0 )
            # 2. personal asset (a), only returns a path, but use tmpGap to adjust the path
            Ret[:a] = Geta( d, Ret[:c], Ret[:l], S, Sr, a0 )
            Ret[:a] .+= tmpGap

        ## Section 10: validate results 结果合法性验证
            # 1. relationship: 𝒜 = a + Φ
            @assert( all(isapprox.(Ret[:𝒜], Ret[:a] .+ Ret[:Φ], atol = 1E-6)) , "relationship scrA = a + Phi not met (unlimited path)"   )
            # 2. constraint: c >= 0
            @assert( all(Ret[:c] .>= 0.0) , "consumptions are requested to be greater than or equal to 0" )

        return Ret::Dict
    end
    # ---------------------------------------------
    """
        AddAbbrs!( a0::Real, Φ0::Real, Pc::Dict, d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int  )

    a **in-place** method, adding abbreviations to **d**;
    receives the same parameter combinition as LifeDecision() for convenience;
    return nothing;
    """
    function AddAbbrs!( a0::Real, Φ0::Real, Pc::Dict, d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int  )
        # NOTE: pls refer to academic documents to learn mathematics about these defined abbreviation variables
        # Abbrevation Level: 1
            # total pension contribution rate on nomial wage level w
            d[:π] = d[:z] .* ( d[:θ] .+ d[:η] ) ./ ( 1.0 .+ d[:z] .* d[:η] .+ d[:ζ] )
            # total medical contribution rate on nomial wage level w
            d[:πM] = ( d[:ϕ] .+ d[:ζ] ) ./ ( 1.0 .+ d[:z] .* d[:η] .+ d[:ζ] )
        # Abbreviation Level: 2
            d[:𝒶] = 1.0 .- d[:F]  # survival probability
            d[:𝒷] = 1.0 .- Pc[:σ] .- d[:π] .- d[:πM]  # multiplier on nomial wage level in personal asset budgets
            d[:𝒹] = d[:q] .* ( d[:p] .+ (1.0 .- d[:cpB]) ) ./ (1.0 .+ d[:p]) # multiplier on total consumption c to get the part of inpatient expenditure covered by PAYG pool fund of UE-BMI
            d[:𝒻] = ( d[:ϕ] .+ d[:𝕒] .* d[:ζ] ) ./ ( 1.0 .+ d[:z] .* d[:η] .+ d[:ζ] )  # multiplier on nomial wage level, which denotes the transferred part from firm contribution to individual medical account Φ
            d[:ℊ] = -1.0 .* d[:q] .* d[:p] ./ (1.0 .+ d[:p])  # multiplier on total consumption, denoting full bill of outpatient expenditure
            d[:𝒽] = d[:q] .* (1.0 .- d[:cpB]) ./ (1.0 .+ d[:p])  # multiplier on total consumption, denoting full bill of inpatient expenditure
            d[:𝒿] = d[:Λ] .+ d[:𝕡]  # total incomes (pension benefits & transfer payments from UE-BMI) in retired years
        # Abbreviation Level: 2.5
            # capital discounting factors
            d[:V] = cumprod( 1.0 ./ (1.0 .+ d[:r]) ) # NOTE: Julia supports any-precision floating computation, which allows us to directly use cumproduction rather than convert it to logarithms.
            # adjust the Discounting factors by mortalities (pls refer to academic documents to learn why to do so)
            d[:V] ./= d[:𝒶]
            # utility discounting factors (mortality considered) (mark:\tilde\beta)
            d[:βtilde] = ( 1.0 ./ (1.0 .+ Pc[:δ]) ) .^ ( 0:(S-1) )
            d[:βtilde] .*= d[:𝒶]
        # Interval: Essential Validation
            @assert( all(0.0 .< d[:𝒽] .< 1.0) , "scrh is required to be in the open range (0,1)"  ) # NOTE: or there will be numerical collapses in consumptions; however, in fact, according to the definition of \scripth, we have secured the condition in previous validations in "AmmoReload_DATA_w()" function; if much worse performance here, consider ignore/comment this validation process
            @assert( all(d[:𝒷] .+ d[:𝒻] .> 0.0) , "scrb + scrf should be greater than 0"  ) # NOTE: or all wage incomes are contributed/taxed, no left to consume
        # Abbreviation Level: 3
            # part of inter-temporal function c(s) = 𝒯[c(1)], as a multiplier
            # NOTE: len = S - 1; 𝒫[s] works on c(s)
            d[:𝒫] = (1.0 .+ d[:r][2:S]) ./ (1.0 .+ Pc[:δ])
            d[:𝒫] .*= (1.0 .- d[:𝒽][1:S-1]) ./ (1.0 .- d[:𝒽][2:S])
            # another component of 𝒯(c) function
            d[:𝒬] = (1.0 .- d[:q][1:S-1]) ./ (1.0 .- d[:q][2:S])
            # multiplier in leisure → consumption relationship; NOTE: len = Sr - 1
            d[:ℛ] = (1.0 .- d[:q][1:Sr]) .* (d[:𝒷] .+ d[:𝒻]) .* d[:w] ./ (1.0 .- d[:𝒽][1:Sr])
            d[:ℛ] ./= Pc[:α]
        # Interval: Essential Validation
            @assert( all(d[:𝒫] .> 0) , "invalid scrP which leads to invalid consumpton path"   )
            @assert( all(d[:𝒬] .> 0) , "invalid scrQ which leads to invalid consumpton path"   )
            @assert( all(d[:ℛ] .> 0) , "invalid scrR which leads to invalid consumpton path"   )
        # Abbreviation Level: 4
            # multiplier in accumulated Euler equatino function c(s) = 𝒯[c(1)]
            # NOTE: len = S; because c(1) = 𝒯[c(1)]
            d[:𝒯] = ones(S)
            for s in 1:S-1
                d[:𝒯][s+1] = d[:𝒫][s] ^ Pc[:γ] * d[:𝒬][s] ^ (1.0 - Pc[:γ])
            end
            d[:𝒯] = cumprod(d[:𝒯])
        # Interval: Essential Validation
            @assert( all(d[:𝒯] .> 0.0) , "invalid scrT which leads to invalid consumption path"  )

        return nothing
    end

    # ---------------------------------------------
    """
        GetC( d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int, 𝒜0::Real ; givenleisure::Union{Nothing,Vector{Float64}} = nothing )

    use data & abbreviations to get path of consumption;
    if an exogenous leisure path (len=Sr) given, use it, or use leisure → consumption (unlimited problem) relationship
    *givenleisure* is nothing by default, which means use leisure → consumption relationship;
    returns a Vector{Float64} of consumption (len=S);
    """
    function GetC( d::Dict{Symbol,Vector{Float64}}, S::Int, Sr::Int, 𝒜0::Real ; givenleisure::Union{Nothing,Vector{Float64}} = nothing )
        path_c = ones(S)  # prepare an empty path for consumption
        if givenleisure == nothing
            # 1. define abbreviations in final equation about c(1)
                # left side of final equation about c(1) (a number, as domain)
                # NOTE: for unlimited solution (without endowment constraints)
                tmp𝒳 = sum( d[:V][1:Sr] .* (d[:𝒷] .+ d[:𝒻]) .* d[:w] .* (1.0 .- d[:𝒬][1:Sr]) .* d[:𝒯][1:Sr] )
                tmp𝒳 += sum( d[:V] .* (1.0 .- d[:𝒽]) .* d[:𝒯] )
                # right side of final equation about c(1) (a number)
                tmp𝒴 = sum( d[:V][1:Sr] .* (d[:𝒷] .+ d[:𝒻]) .* d[:w] )
                tmp𝒴 += d[:V][1] * 𝒜0
                tmp𝒴 += sum( d[:V][Sr+1:S] .* d[:𝒿] )
            # Interval: Essential Validation
            @assert( tmp𝒳 != 0.0 , "zero scriptX leads to Inf consumption!"   )
            # 2. get consumption in the 1st year
            path_c[1] = ( tmp𝒴 / tmp𝒳 ) * (1.0 + d[:r][1])  # discount to s = 1
            # 3. use c(s) = 𝒯[c(1)] to get complete consumption path
            path_c[:] = path_c[1] .* d[:𝒯]
        else  # (if leisure path given)
            # 1. define abbreviations
                tmp𝒳 = sum( d[:V] .* (1.0 .- d[:𝒽]) .* d[:𝒯] )
                tmp𝒴 = sum( d[:V][1:Sr] .* (d[:𝒷] .+ d[:𝒻]) .* d[:w] .* (1.0 .- givenleisure) )
                tmp𝒴 += d[:V][1] * 𝒜0
                tmp𝒴 += sum( d[:V][Sr+1:S] .* d[:𝒿] )
            # Interval: Essential Validation
            @assert( tmp𝒳 != 0.0 , "zero scriptX leads to Inf consumption!"   )
            # 2. get new consumption path
            path_c[1] = ( tmp𝒴 / tmp𝒳 ) * (1.0 + d[:r][1])  # discount to s = 1
            path_c[:] = path_c[1] .* d[:𝒯]
        end
        # check if all consumptions are valid
        @assert( all(0.0 .<= path_c .< Inf) , "negative or Inf consumption found in unlimited problem"  )
        return path_c::Vector{Float64}
    end

    # ---------------------------------------------
    """
        Get𝒜( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector, S::Int, Sr::Int, 𝒜0::Real )

    get lifetime wealth path (𝒜) using inter-temporal budgets;
    requiring consumption path (path_c, len=S) & leisure path (path_l, len=Sr);
    return a collection in order:
    1. path_𝒜 [len=S]: lifetime wealth path
    2. 𝒜dead [Real]: left wealth when dead (end of the last year, the beginning of S+1 year)

    p.s.: the 𝒜dead is used to search c(1) which make 𝒜 meet the constraint of no bequest
    """
    function Get𝒜( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector , S::Int, Sr::Int, 𝒜0::Real )
        # 1. malloc
        path_𝒜 = fill(𝒜0,S); 𝒜dead = 0.0
        # 2. working years
        for s in 1:Sr
            path_𝒜[s+1] = (1+d[:r][s]) * path_𝒜[s] + ( d[:𝒷][s] + d[:𝒻][s] ) * d[:w][s] * (1.0 - path_l[s]) - (1.0 - d[:𝒽][s]) * path_c[s]
            path_𝒜[s+1] /= d[:𝒶][s]  # adjusted by mortality
        end
        # 3. retired years
        # NOTE: the loop is disigned for case: S-Sr>=1, if S=Sr, only need to run section 4
        if S - Sr >= 1
            for s in Sr:S-1
                path_𝒜[s+1] = (1+d[:r][s]) * path_𝒜[s] + d[:𝒿][s-Sr+1] - (1.0 - d[:𝒽][s]) * path_c[s]
                path_𝒜[s+1] /= d[:𝒶][s]  # adjusted by mortality
            end
        end
        # 4. get wealth when dead (at end of year S)
        𝒜dead = (1+d[:r][S]) * path_𝒜[S] + d[:𝒿][S-Sr] - (1.0 - d[:𝒽][S]) * path_c[S]
        # returns
        return path_𝒜::Vector, 𝒜dead::Real
    end

    # ---------------------------------------------
    """
        Geta( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector , S::Int, Sr::Int, a0::Real )

    gets personal asset (a) by inter-temporal budgets;
    assumes all outpatient bills are fully paid by Φ, does not consider possible gaps;
    returns a Vector of personal asset
    """
    function Geta( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector , S::Int, Sr::Int, a0::Real )
        # 1. malloc
        path_a = fill(a0,S)
        # 2. working years
        for s in 1:Sr
            path_a[s+1] = (1.0 + d[:r][s]) * path_a[s] + d[:𝒷][s] * d[:w][s] * (1.0 - path_l[s]) - (1.0 - d[:𝒹][s]) * path_c[s]
            path_a[s+1] /= d[:𝒶][s]
        end
        # 3. retired years
        # NOTE: the loop is for the case S-Sr>=1, if S=Sr, just return
        if S - Sr >= 1
            for s in Sr:S-1
                path_a[s+1] = (1.0 + d[:r][s]) * path_a[s] + d[:Λ][s-Sr+1] - (1.0 - d[:𝒹][s]) * path_c[s]
                path_a[s+1] /= d[:𝒶][s]
            end
        end
        # return
        return path_a::Vector
    end

    # ---------------------------------------------
    """
        GetΦ( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector , S::Int, Sr::Int, Φ0::Real )

    gets individual medical account path (Φ);
    has Φ(s) >= 0 constraint;
    returns a collection in order:
    1. path_Φ [Vector]: individual medical account path
    2. path_gap [Vector]: min.( 0.0 , Φ ), latent gap which will be covered by personal asset (a)

    p.s.: add path_a from Geta() to path_gap, to get adjusted personal asset path (considered non-negative constraint of Φ)
    """
    function GetΦ( d::Dict{Symbol,Vector{Float64}}, path_c::Vector, path_l::Vector , S::Int, Sr::Int, Φ0::Real )
        # 1. malloc
        path_Φ = fill(Φ0, S); path_gap = zeros(S)
        # 2. working years
        for s in 1:Sr
            path_Φ[s+1] = (1.0 + d[:r][s]) * path_Φ[s] + d[:𝒻][s] * d[:w][s] * (1.0 - path_l[s]) + d[:ℊ][s] * path_c[s]
            path_Φ[s+1] /= d[:𝒶][s]
        end
        # 3. retired years
        # NOTE: the loop is for the case S-Sr>=1, if S=Sr, just return
        if S - Sr >= 1
            for s in Sr:S-1
                path_Φ[s+1] = (1.0 + d[:r][s]) * path_Φ[s] + d[:𝕡][s-Sr+1] + d[:ℊ][s] * path_c[s]
                path_Φ[s+1] /= d[:𝒶][s]
            end
        end
        # 4. get gaps, and adjust Φ path to non-negative
        for s in 1:S
            path_gap[s] = min( 0.0, path_Φ[s] )
            path_Φ[s] = max( 0.0, path_Φ[s] )
        end
        # return
        return path_Φ::Vector, path_gap::Vector
    end

    # ---------------------------------------------
    """
        GetTestData(a0::Real,Φ0::Real,S::Int,Sr::Int)

    generates a template **d** and **Pc** for testing LifeDecision()
    returns two Dict{Symbol,Vector{Float64}} in order: (Pc, d)
    """
    function GetTestData(S::Int,Sr::Int)
        d = Dict(
            # -------- len = S
            :r => fill(0.0603498,S), # interest rates
            :F => Array(LinRange(0.0002,0.187,S)) , # mortality
            :q => fill(0.15,S), # m2c ratio
            :p => fill(1.11,S), # MA/MB ratio
            :cpB => fill(0.30,S), # co-payment rate of inpatient expenditure
            # -------- len = Sr
            :ϕ => fill(0.02,Sr), # contribution: agent → UEBMI
            :ζ => fill(0.85,Sr), # contribution: firm → UEBMI
            :η => fill(0.20,Sr), # contribution: firm → pension
            :θ => fill(0.08,Sr), # contribution: agent → pension
            :z => fill(0.85,Sr), # collection rate of pension
            :w => fill(3.20496,Sr), # wage level
            :𝕒 => fill(0.30,Sr), # transfer rate from firm contribution of UEBMI to working agents
            # -------- len = S - Sr
            :Λ => fill(0.344050,S-Sr), # pension benefit amounts
            :𝕡 => fill(0.00,S-Sr), # transfer amounts from firm contribution of UEBMI to retired agents
        )
        Pc = Dict(
            :κ => 0.05,  # depreciation rate 折旧率
            :μ => 0.10,  # consumption tax rate 消费税率
            :σ => 0.24,  # income tax rate 工资税率
            :δ => 1/0.99 - 1,  # utility discounting rate 效用折现率，若令效用折现因子为0.99，则对应0.0101010101...
            :α => 1.5,  #　leisure preference than consumption 闲暇对消费的偏好系数
            :γ => 0.5,  # inter-temporal substitution elasticity 跨期替代弹性
        )
        return Pc = Pc::Dict, d::Dict{Symbol,Vector{Float64}}
    end

# ==============================================================
end # module ends
#


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

module X3Tests

using x2

println("hello")

end # module

max_stars_repo_name: greimel/julia

max_issues_repo_name: greimel/julia

max_forks_repo_name: greimel/julia


text:
using DelimitedFiles

function readlrdata()

    fname = joinpath(dirname(@__FILE__), "lr_nuts.data")
    z = readdlm(fname)
    x = z[:,1:end-1]
    y = z[:,end] .- 1
    return x, y
end


text:
using Ipopt, Printf, Serialization

# You need to run fit.jl before running this script.
include("shared.jl")
const res = deserialize("fit_results.serialized")

function printf_sym_matrix(M::Matrix{Float64})
  (s1, s2) = size(M)
  for j in 1:(s1-1)
    for k in 1:(s2-1)
      if k >= j
        @printf "%1.2f & " M[j,k]
      else
        @printf "\\cdot & "
      end
    end
    @printf "%1.2f\\\\ \n" M[j,end]
  end
  for k in 1:(s2-1)
    @printf "\\cdot & "
  end
  @printf "%1.2f" M[end,end]
end

function write_matrix_to_file(fname, M, prefix="../../../../manuscript/tables/")
  open(prefix*fname, "w") do io
    redirect_stdout(io) do
      printf_sym_matrix(M)
    end
  end
end

#=
# look at hess vs e-fish at initializer:
write_matrix_to_file("fdefish_at_ones.tex", fishfd(ones(3)))
write_matrix_to_file("fdhess_at_ones.tex",  hessfd(ones(3)))
write_matrix_to_file("adhess_at_ones.tex",  _nllh(ones(3)))
write_matrix_to_file("refhess_at_ones.tex",  high_fd_hessian(ones(3)))

# look at hess vs e-fish at MLE:
write_matrix_to_file("fdefish_at_mle.tex",  fishfd(res[3][4]))
write_matrix_to_file("fdhess_at_mle.tex",   hessfd(res[3][4]))
write_matrix_to_file("adhess_at_mle.tex",   _nllh(res[3][4]))
write_matrix_to_file("refhess_at_mle.tex",  high_fd_hessian(res[3][4]))
=#


text:
# ******************************************************************************************
# Notices:
#
# Copyright © 2022 United States Government as represented by the Administrator of the
# National Aeronautics and Space Administration.  All Rights Reserved.
#
# Disclaimers
#
# No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
# EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
# THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY
# WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION,
# IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER,
# CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
# RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM
# USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
# LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND
# DISTRIBUTES IT "AS IS."
#
# Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED
# STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT.
# IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES,
# EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON,
# OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND
# HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
# AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.  RECIPIENT'S SOLE REMEDY FOR ANY
# SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
# ******************************************************************************************

"""
    num_leaves(cell::Cell)

Count number of leaves below cell.
"""
num_leaves(cell::Cell) = sum(1 for _ in allleaves(cell))

"""
    max_depth(cell::Cell)

Determine maximum depth below cell.
"""
max_depth(cell::Cell) = isleaf(cell) ? 1 : 1 + maximum(max_depth.(children(cell)))

"""
Total volume of cell proven to satisfy condition or complement.
"""
function proven_volume(cell::Cell, member::Bool=true)
    vol = 0.0
    for l in allleaves(cell)
        if l.data.proven && l.data.member == member
            vol += prod(l.boundary.widths)
        end
    end
    return vol
end

"""
    coverage(cell::Cell, member::Union{Bool,Nothing}=nothing)

Proportion of cell volume proven to satisfy condition, complement, or either.
"""
function coverage(cell::Cell, member::Union{Bool,Nothing}=nothing)
    if member === nothing
        vol = proven_volume(cell, true) + proven_volume(cell, false)
    else
        vol = proven_volume(cell, member)
    end

    return vol / prod(cell.boundary.widths)
end

"""
Compute balance of tree over processors, scaled to [0, 1].
"""
function balance_score(cell::Cell)
    nworkers() == 0 && return NaN

    counts = [0 for _ in workers()]
    for c in allcells(cell)
        if c.data.pid > 0
            counts[c.data.pid - 1] += 1
        end
    end

    dist = counts / sum(counts)
    n = length(dist)
    d = sqrt(sum(dist.^2))
    return sqrt(n) * (1 - d) / (sqrt(n) - 1)
end

"""
Compute size of tree in memory and return formatted variables.
"""
function memory_size(cell::Cell)
    sz = Base.summarysize(cell)
    prefixes = ["G", "M", "k", ""]
    sizes = [1_000_000_000, 1_000_000, 1_000, 1]
    val, pre = 0.0, ""

    for (s, p) in zip(sizes, prefixes)
        if sz >= s
            val = sz / s
            pre = p
            break
        end
    end
    return val, pre
end

"""
    print_metrics(cell::Cell)

Print metrics about proof coverage and tree size.
"""
function print_metrics(cell::Cell)
    vtrue = coverage(cell, true)
    vfalse = coverage(cell, false)
    nleaves = num_leaves(cell)
    val, pre = memory_size(cell)
    dep = max_depth(cell)
    bal = balance_score(cell)

    println("Vol. proportion proven true  : $(vtrue)")
    println("Vol. proportion proven false : $(vfalse)")
    println("Vol. proportion unproven     : $(1 - vtrue - vfalse)")
    println("Total size of spanning tree  : $(nleaves) leaves")
    println("Total size of tree in memory : $val $(pre)B")
    println("Maximum depth of tree        : $dep")
    println("Processor balancing score    : $bal")
end