text:
################################################################################
#                            MATH SYMBOL METHODS
################################################################################
# Support additional math symbols beyond what JuMP does
function _math_symbol(::Type{JuMP.REPLMode}, name::Symbol)::String
    if name == :intersect
        return Sys.iswindows() ? "and" : "∩"
    elseif name == :prop
        return "~"
    elseif name == :partial
        return Sys.iswindows() ? "d" : "∂"
    elseif name == :expect
        return Sys.iswindows() ? "E" : "𝔼"
    elseif name == :integral
        return Sys.iswindows() ? "integral" : "∫"
    elseif name == :leq
        return Sys.iswindows() ? "<=" : "≤"
    elseif name == :geq
        return Sys.iswindows() ? ">=" : "≥"
    elseif name == :eq
        return Sys.iswindows() ? "==" : "="
    elseif name == :times
        return "*"
    elseif name == :sq
        return "²"
    elseif name == :ind_open
        return "["
    elseif name == :ind_close
        return "]"
    elseif name == :for_all
        return Sys.iswindows() ? "for all" : "∀"
    elseif name == :in
        return Sys.iswindows() ? "in" : "∈"
    elseif name == :open_set
        return "{"
    elseif name == :dots
        return Sys.iswindows() ? ".." : "…"
    elseif name == :close_set
        return "}"
    elseif name == :union
        return Sys.iswindows() ? "or" : "∪"
    elseif name == :infty
        return Sys.iswindows() ? "Inf" : "∞"
    elseif name == :open_rng
        return "["
    elseif name == :close_rng
        return "]"
    elseif name == :integer
        return "integer"
    elseif name == :succeq0
        return " is semidefinite"
    elseif name == :Vert
        return Sys.iswindows() ? "||" : "‖"
    elseif name == :sub2
        return Sys.iswindows() ? "_2" : "₂"
    else
        error("Internal error: Unrecognized symbol $name.")
    end
end

# Support additional math symbols beyond what JuMP does
function _math_symbol(::Type{JuMP.IJuliaMode}, name::Symbol)::String
    if name == :intersect
        return "\\cap"
    elseif name == :prop
        return "\\sim"
    elseif name == :partial
        return "\\partial"
    elseif name == :open_rng
        return "\\left["
    elseif name == :close_rng
        return "\\right]"
    elseif name == :expect
        return "\\mathbb{E}"
    elseif name == :integral
        return "\\int"
    elseif name == :leq
        return "\\leq"
    elseif name == :geq
        return "\\geq"
    elseif name == :eq
        return "="
    elseif name == :times
        return "\\times "
    elseif name == :sq
        return "^2"
    elseif name == :ind_open
        return "_{"
    elseif name == :ind_close
        return "}"
    elseif name == :for_all
        return "\\quad\\forall"
    elseif name == :in
        return "\\in"
    elseif name == :open_set
        return "\\{"
    elseif name == :dots
        return "\\dots"
    elseif name == :close_set
        return "\\}"
    elseif name == :union
        return "\\cup"
    elseif name == :infty
        return "\\infty"
    elseif name == :integer
        return "\\in \\mathbb{Z}"
    elseif name == :succeq0
        return "\\succeq 0"
    elseif name == :Vert
        return "\\Vert"
    elseif name == :sub2
        return "_2"
    else
        error("Internal error: Unrecognized symbol $name.")
    end
end

################################################################################
#                            INFINITE SET METHODS
################################################################################
# Return "s" if n is greater than one
_plural(n)::String = (isone(n) ? "" : "s")

# Round numbers in strings
function _string_round(f::Float64)::String
    iszero(f) && return "0" # strip sign off zero
    str = string(f)
    return length(str) >= 2 && str[end-1:end] == ".0" ? str[1:end-2] : str
end
_string_round(f)::String = string(f)

## Return the string of an infinite domain
# IntervalDomain
function domain_string(print_mode, domain::IntervalDomain)::String
    return string("[", _string_round(JuMP.lower_bound(domain)), ", ",
                  _string_round(JuMP.upper_bound(domain)), "]")
end

# DistributionDomain
function domain_string(print_mode, domain::DistributionDomain)::String
    return string(domain.distribution)
end

# CollectionDomain
function domain_string(print_mode, domain::CollectionDomain)::String
    domains = collection_domains(domain)
    num_domains = length(domains)
    cs_string = string("CollectionDomain with ", num_domains, " domain",
                       _plural(num_domains), ":")
    for s in domains
        cs_string *= string("\n ", domain_string(print_mode, s))
    end
    return cs_string
end

# Fallback
function domain_string(print_mode, domain::S) where {S <: AbstractInfiniteDomain}
    # hack fix because just calling `string(domain)` --> StackOverflow Error
    values = map(f -> getfield(domain, f), fieldnames(S))
    return string(S, "(", join(values, ", "), ")")
end

## Return in domain strings of infinite domains
# Extend to return of in domain string for interval domains
function in_domain_string(print_mode, domain::IntervalDomain)::String
    if JuMP.lower_bound(domain) != JuMP.upper_bound(domain)
        return string(_math_symbol(print_mode, :in), " ",
                      domain_string(print_mode, domain))
    else
        return string(_math_symbol(print_mode, :eq), " ",
                      _string_round(JuMP.lower_bound(domain)))
    end
end

# Extend to return of in domain string for distribution domains
function in_domain_string(print_mode, domain::DistributionDomain)::String
    dist = domain.distribution
    name = string(typeof(dist))
    bracket_index = findfirst(isequal('{'), name)
    if !isnothing(bracket_index)
        name = name[1:bracket_index-1]
    end
    if !isempty(size(dist))
        dims = size(dist)
        name *= string("(dim", _plural(length(dims)), ": (", join(dims, ", "), "))")
    end
    return string(_math_symbol(print_mode, :prop), " ", name)
end

# Extend to return of in domain string of other domains
function in_domain_string(print_mode, domain::AbstractInfiniteDomain)::String
    return string(_math_symbol(print_mode, :in), " ",
                  domain_string(print_mode, domain))
end

## Extend in domain string to consider domain restrictions
# IntervalDomain
function in_domain_string(print_mode,
    pref::GeneralVariableRef,
    domain::IntervalDomain,
    restrictions::DomainRestrictions{GeneralVariableRef})::String
    # determine if in restrictions
    in_restrictions = haskey(restrictions, pref)
    # make the string
    interval = in_restrictions ? restrictions[pref] : domain
    return in_domain_string(print_mode, interval)
end

# InfiniteScalarDomain
function in_domain_string(print_mode,
    pref::GeneralVariableRef,
    domain::InfiniteScalarDomain,
    restrictions::DomainRestrictions{GeneralVariableRef})::String
    # determine if in restrictions
    if haskey(restrictions, pref)
        bound_domain = restrictions[pref]
        if JuMP.lower_bound(bound_domain) == JuMP.upper_bound(bound_domain)
            return in_domain_string(print_mode, bound_domain)
        else
            return  string(in_domain_string(print_mode, domain), " ",
                           _math_symbol(print_mode, :intersect),
                           " ", domain_string(print_mode, bound_domain))
        end
    else
        return in_domain_string(print_mode, domain)
    end
end

################################################################################
#                           MEASURE STRING METHODS
################################################################################
## Convert measure data into a useable string for measure printing
# 1-D DiscreteMeasureData/FunctionalDiscreteMeasureData
function measure_data_string(print_mode,
    data::Union{DiscreteMeasureData{GeneralVariableRef},
                FunctionalDiscreteMeasureData{GeneralVariableRef}}
    )::String
    pref = parameter_refs(data)
    lb = JuMP.lower_bound(data)
    ub = JuMP.upper_bound(data)
    nan_bound = isnan(lb) || isnan(ub)
    if nan_bound
        return JuMP.function_string(print_mode, pref)
    else
        domain = IntervalDomain(lb, ub)
        return string(JuMP.function_string(print_mode, pref), " ",
                      in_domain_string(print_mode, domain))
    end
end

# Multi-D DiscreteMeasureData/FunctionalDiscreteMeasureData
function measure_data_string(print_mode,
    data::Union{DiscreteMeasureData{Vector{GeneralVariableRef}},
                FunctionalDiscreteMeasureData{Vector{GeneralVariableRef}}}
    )::String
    prefs = parameter_refs(data)
    lbs = JuMP.lower_bound(data)
    ubs = JuMP.upper_bound(data)
    has_bounds = !isnan(first(lbs)) && !isnan(first(ubs))
    homo_bounds = has_bounds && _allequal(lbs) && _allequal(ubs)
    names = map(p -> _remove_name_index(p), prefs)
    homo_names = _allequal(names)
    num_prefs = length(prefs)
    if homo_names && homo_bounds
        domain = IntervalDomain(first(lbs), first(ubs))
        return string(first(names), " ", in_domain_string(print_mode, domain),
                      "^", num_prefs)
    elseif has_bounds
        str_list = [JuMP.function_string(print_mode, prefs[i]) * " " *
                    in_domain_string(print_mode, IntervalDomain(lbs[i], ubs[i]))
                    for i in eachindex(prefs)]
        return _make_str_value(str_list)[2:end-1]
    elseif homo_names
        return first(names)
    else
        return _make_str_value(prefs)
    end
end

# extract the most compact parameter name possible
function _get_root_parameter_name(data::AbstractMeasureData)::String
    prefs = parameter_refs(data)
    names = map(p -> _remove_name_index(p), prefs)
    if _allequal(names)
        return first(names)
    else
        return _make_str_value(prefs)
    end
end

# Fallback for measure_data_string
function measure_data_string(print_mode, data::AbstractMeasureData)::String
    return _get_root_parameter_name(data)
end

# Make strings to represent measures in REPLMode
function variable_string(m::Type{JuMP.REPLMode}, mref::MeasureRef)::String
    data = measure_data(mref)
    data_str = measure_data_string(m, data)
    func_str = JuMP.function_string(m, measure_function(mref))
    name = JuMP.name(mref)
    if name == "integral"
        name = _math_symbol(m, :integral)
    elseif name == "expect"
        name = _math_symbol(m, :expect)
    end
    return string(name, "{", data_str, "}[", func_str, "]")
end

# Make strings to represent measures in IJuliaMode
function variable_string(m::Type{JuMP.IJuliaMode}, mref::MeasureRef)::String
    data = measure_data(mref)
    data_str = measure_data_string(m, data)
    func_str = JuMP.function_string(m, measure_function(mref))
    name = JuMP.name(mref)
    if name == "integral"
        param_name = _get_root_parameter_name(data)
        end_str = length(param_name) == 1 ? "d$param_name" : "d($param_name)"
        return string("\\int_{", data_str, "}", func_str, end_str)
    else
        name = name == "expect" ? "\\mathbb{E}" : string("\\text{", name, "}")
        return string(name, "_{", data_str, "}",
                  InfiniteOpt._math_symbol(m, :open_rng), func_str,
                  InfiniteOpt._math_symbol(m, :close_rng))
    end
end

################################################################################
#                          VARIABLE STRING METHODS
################################################################################
## helper function for getting the variable names
# REPLMode
function _get_base_name(::Type{JuMP.REPLMode}, vref)::String
    var_name = JuMP.name(vref)
    if !isempty(var_name)
        return var_name
    else
        return "noname"
    end
end

# IJuliaMode
function _get_base_name(::Type{JuMP.IJuliaMode}, vref)::String
    var_name = JuMP.name(vref)
    if !isempty(var_name)
        # TODO: This is wrong if variable name constains extra "]"
        return replace(replace(var_name, "[" => "_{", count = 1), "]" => "}")
    else
        return "noname"
    end
end

# Helper method for infinite variable construction
function _add_on_parameter_refs(
    base_name::String,
    prefs::Collections.VectorTuple
    )::String
    param_name_tuple = "("
    for i in 1:size(prefs, 1)
        element_prefs = prefs[i, :]
        type = _index_type(first(element_prefs))
        if type == DependentParameterIndex
            param_name = _remove_name_index(first(element_prefs))
        elseif length(element_prefs) == 1
            param_name = JuMP.name(first(element_prefs))
        else
            # TODO this isn't quite right with a subset of an independent container
            names = map(p -> _remove_name_index(p), element_prefs)
            if _allequal(names)
                param_name = first(names)
            else
                param_name = string("[", join(element_prefs, ", "), "]")
            end
        end
        if i != size(prefs, 1)
            param_name_tuple *= string(param_name, ", ")
        else
            param_name_tuple *= string(param_name, ")")
        end
    end
    return string(base_name, param_name_tuple)
end

# Make a string for InfiniteVariableRef
function variable_string(print_mode, vref::InfiniteVariableRef)::String
    base_name = _get_base_name(print_mode, vref)
    if !haskey(_data_dictionary(vref), JuMP.index(vref))
        return base_name
    else
        prefs = raw_parameter_refs(vref)
        return _add_on_parameter_refs(base_name, prefs)
    end
end

# Make a string for ParameterFunctionRef
function variable_string(print_mode, vref::ParameterFunctionRef)::String
    base_name = _get_base_name(print_mode, vref)
    if !haskey(_data_dictionary(vref), JuMP.index(vref))
        return base_name
    else
        prefs = raw_parameter_refs(vref)
        return _add_on_parameter_refs(base_name, prefs)
    end
end

## Make helper function for making derivative operators
# REPL
function _deriv_operator(m::Type{JuMP.REPLMode}, pref)::String
    return string(_math_symbol(m, :partial), "/",
                  _math_symbol(m, :partial), variable_string(m, pref))
end

# IJulia
function _deriv_operator(m::Type{JuMP.IJuliaMode}, pref)::String
    return string("\\frac{", _math_symbol(m, :partial), "}{",
                  _math_symbol(m, :partial), " ",
                  variable_string(m, pref), "}")
end

# TODO implement more intelligent naming for nested derivatives (i.e., use exponents)
# TODO account for container naming when variable macro is used (maybe deal with this at the macro end)
# Make a string for DerivativeRef
function variable_string(print_mode, dref::DerivativeRef)::String
    base_name = _get_base_name(print_mode, dref)
    if !haskey(_data_dictionary(dref), JuMP.index(dref))
        return base_name
    elseif base_name != "noname"
        prefs = raw_parameter_refs(dref)
        return _add_on_parameter_refs(base_name, prefs)
    else
        vref = dispatch_variable_ref(derivative_argument(dref))
        pref = operator_parameter(dref)
        return string(_deriv_operator(print_mode, pref),
                      _math_symbol(print_mode, :open_rng),
                      variable_string(print_mode, vref),
                      _math_symbol(print_mode, :close_rng))
    end
end

## Return the parameter value as an appropriate string
# Number
function _make_str_value(value)::String
    return _string_round(value)
end

# Array{<:Number}
function _make_str_value(values::Array)::String
    if length(values) == 1
        return _make_str_value(first(values))
    end
    if length(values) <= 4
        str_value = "["
        for i in eachindex(values)
            if i != length(values)
                str_value *= _string_round(values[i]) * ", "
            else
                str_value *= _string_round(values[i]) * "]"
            end
        end
        return str_value
    else
        return string("[", _string_round(first(values)), ", ..., ",
                      _string_round(last(values)), "]")
    end
end

# Make a string for PointVariableRef
# TODO improve so numerator of derivative contains the point
function variable_string(print_mode, vref::PointVariableRef)::String
    if !haskey(_data_dictionary(vref), JuMP.index(vref)) || !isempty(JuMP.name(vref))
        return _get_base_name(print_mode, vref)
    else
        ivref = dispatch_variable_ref(infinite_variable_ref(vref))
        if ivref isa InfiniteVariableRef || !isempty(JuMP.name(ivref))
            base_name = _get_base_name(print_mode, ivref)
        else
            base_name = variable_string(print_mode, ivref) # we have a derivative
        end
        prefs = raw_parameter_refs(ivref)
        values = raw_parameter_values(vref)
        name = string(base_name, "(")
        for i in 1:size(prefs, 1)
            if i != size(prefs, 1)
                name *= string(_make_str_value(values[prefs.ranges[i]]), ", ")
            else
                name *= string(_make_str_value(values[prefs.ranges[i]]), ")")
            end
        end
        return name
    end
end

# Make a string for SemiInfiniteVariableRef
# TODO improve so numerator of derivative contains the parameter tuple
function variable_string(print_mode, vref::SemiInfiniteVariableRef)::String
    if !haskey(_data_dictionary(vref), JuMP.index(vref)) || !isempty(JuMP.name(vref))
        return _get_base_name(print_mode, vref)
    else
        ivref = dispatch_variable_ref(infinite_variable_ref(vref))
        if ivref isa InfiniteVariableRef || !isempty(JuMP.name(ivref))
            base_name = _get_base_name(print_mode, ivref)
        else
            base_name = variable_string(print_mode, ivref) # we have a derivative
        end
        prefs = raw_parameter_refs(ivref)
        eval_supps = eval_supports(vref)
        raw_list = [i in keys(eval_supps) ? eval_supps[i] : prefs[i]
                    for i in eachindex(prefs)]
        param_name_tuple = "("
        for i in 1:size(prefs, 1)
            value = raw_list[prefs.ranges[i]]
            if i != size(prefs, 1)
                param_name_tuple *= string(_make_str_value(value), ", ")
            else
                param_name_tuple *= string(_make_str_value(value), ")")
            end
        end
        return string(base_name, param_name_tuple)
    end
end

# Fallback
function variable_string(print_mode, vref::JuMP.AbstractVariableRef)::String
    return _get_base_name(print_mode, vref)
end

# Extend function string for DispatchVariableRefs (REPL)
function JuMP.function_string(::Type{JuMP.REPLMode},
                              vref::DispatchVariableRef)::String
    return variable_string(JuMP.REPLMode, vref)
end

# Extend function string for DispatchVariableRefs (IJulia)
function JuMP.function_string(::Type{JuMP.IJuliaMode},
                              vref::DispatchVariableRef)::String
    return variable_string(JuMP.IJuliaMode, vref)
end

# Extend function string for GeneralVariableRefs (REPL)
function JuMP.function_string(::Type{JuMP.REPLMode},
                              vref::GeneralVariableRef)::String
    return variable_string(JuMP.REPLMode, dispatch_variable_ref(vref))
end

# Extend function string for GeneralVariableRefs (IJulia)
function JuMP.function_string(::Type{JuMP.IJuliaMode},
                              vref::GeneralVariableRef)::String
    return variable_string(JuMP.IJuliaMode, dispatch_variable_ref(vref))
end

################################################################################
#                         CONSTRAINT STRING METHODS
################################################################################
# Return domain restriction list as a string
function restrict_string(
    print_mode,
    restrictions::DomainRestrictions{GeneralVariableRef}
    )::String
    string_list = ""
    for (pref, domain) in restrictions
        string_list *= string(JuMP.function_string(print_mode, pref), " ",
                              in_domain_string(print_mode, domain), ", ")
    end
    return string_list[1:end-2]
end

## Return the parameter domain string given the object index
# IndependentParameter
function _param_domain_string(print_mode, model::InfiniteModel,
                              index::IndependentParameterIndex,
                              restrictions::DomainRestrictions{GeneralVariableRef}
                              )::String
    pref = dispatch_variable_ref(model, index)
    domain = infinite_domain(pref)
    gvref = GeneralVariableRef(model, MOIUC.key_to_index(index), typeof(index))
    return string(JuMP.function_string(print_mode, pref), " ",
                  in_domain_string(print_mode, gvref, domain, restrictions))
end

# DependentParameters
function _param_domain_string(print_mode, model::InfiniteModel,
                              index::DependentParametersIndex,
                              restrictions::DomainRestrictions{GeneralVariableRef}
                              )::String
    # parse the infinite domain
    first_gvref = GeneralVariableRef(model, MOIUC.key_to_index(index),
                                     DependentParameterIndex, 1)
    domain = _parameter_domain(dispatch_variable_ref(first_gvref))
    # iterate over the individual scalar domains if is a collection domain
    if domain isa CollectionDomain
        domain_str = ""
        for i in eachindex(collection_domains(domain))
            sdomain = collection_domains(domain)[i]
            gvref = GeneralVariableRef(model, MOIUC.key_to_index(index),
                                       DependentParameterIndex, i)
            domain_str *= string(JuMP.function_string(print_mode, gvref), " ",
                        in_domain_string(print_mode, gvref, sdomain, restrictions), ", ")
        end
        domain_str = domain_str[1:end-2]
    else
        # determine if restrictions contain equalities and filter to the restrictions of interest
        is_eq = haskey(restrictions, first_gvref) && JuMP.lower_bound(restrictions[first_gvref]) == JuMP.upper_bound(restrictions[first_gvref])
        filtered_restrictions = filter(e -> e[1].raw_index == MOIUC.key_to_index(index) &&
                                 e[1].index_type == DependentParameterIndex, restrictions)
        # build the domain string
        if is_eq
            domain_str = restrict_string(print_mode, filtered_restrictions)
        else
            domain_str = string(_remove_name_index(first_gvref), " ",
                            in_domain_string(print_mode, domain))
            if !isempty(filtered_restrictions)
                domain_str *= string(" ", _math_symbol(print_mode, :intersect),
                                     " (", restrict_string(print_mode, filtered_restrictions), ")")
            end
        end
    end
    return domain_str
end

# Return string of an infinite constraint
function JuMP.constraint_string(print_mode,
    cref::InfOptConstraintRef;
    in_math_mode = false
    )::String
    # get the function and set strings
    func_str = JuMP.function_string(print_mode, _core_constraint_object(cref))
    in_set_str = JuMP.in_set_string(print_mode, _core_constraint_object(cref))
    # check if constraint if finite
    obj_nums = _object_numbers(cref)
    if isempty(obj_nums)
        bound_str = ""
    else
        # get the parameter restrictions if there are any
        restrictions = domain_restrictions(cref)
        # prepare the parameter domains
        model = JuMP.owner_model(cref)
        bound_str = string(", ", _math_symbol(print_mode, :for_all), " ")
        for index in _param_object_indices(model)[_object_numbers(cref)]
            bound_str *= string(_param_domain_string(print_mode, model, index, restrictions),
                                ", ")
        end
        bound_str = bound_str[1:end-2]
    end
    # form the constraint string
    if print_mode == JuMP.REPLMode
        lines = split(func_str, '\n')
        lines[1 + div(length(lines), 2)] *= " " * in_set_str * bound_str
        constr_str = join(lines, '\n')
    else
        constr_str = string(func_str, " ", in_set_str, bound_str)
    end
    # format for IJulia
    if print_mode == JuMP.IJuliaMode && !in_math_mode
        constr_str = string("\$ ", constr_str, " \$")
    end
    # add name if it has one
    name = JuMP.name(cref)
    if isempty(name) || (print_mode == JuMP.IJuliaMode && in_math_mode)
        return constr_str
    else
        return string(name, " : ", constr_str)
    end
end

# return list of constraint
function JuMP.constraints_string(print_mode, model::InfiniteModel)::Vector{String}
    # allocate the string vector
    strings = Vector{String}(undef, JuMP.num_constraints(model))
    # produce a string for each constraint
    counter = 1
    for (index, object) in model.constraints
        cref = _make_constraint_ref(model, index)
        strings[counter] = JuMP.constraint_string(print_mode, cref,
                                                  in_math_mode = true)
        counter += 1
    end
    return strings
end

################################################################################
#                           OBJECTIVE STRING METHODS
################################################################################
# Return the objective string
function JuMP.objective_function_string(print_mode, model::InfiniteModel)
    return JuMP.function_string(print_mode, JuMP.objective_function(model))
end

################################################################################
#                           DATATYPE SHOWING METHODS
################################################################################
# Show infinite domains in REPLMode
function Base.show(io::IO, domain::AbstractInfiniteDomain)
    print(io, domain_string(JuMP.REPLMode, domain))
    return
end

# Show infinite domains in IJuliaMode
function Base.show(io::IO, ::MIME"text/latex", domain::AbstractInfiniteDomain)
    print(io, domain_string(JuMP.IJuliaMode, domain))
end

# TODO use ... when necessary --> Subdomain restrictions: t in [0, 1], x[1] == 0, x[2] == 0, ... x[6] == 0, a in [2, 3]
# Show DomainRestrictions in REPLMode
function Base.show(io::IO, restrictions::DomainRestrictions)
    print(io, "Subdomain restrictions (", length(restrictions), "): ",
          restrict_string(JuMP.REPLMode, restrictions))
    return
end

# Show DomainRestrictions in IJuliaMode
function Base.show(io::IO, ::MIME"text/latex", restrictions::DomainRestrictions)
    print(io, "Subdomain restrictions (", length(restrictions), "): ",
          restrict_string(JuMP.IJuliaMode, restrictions))
end

# Show constraint in REPLMode
function Base.show(io::IO, ref::InfOptConstraintRef)
    print(io, JuMP.constraint_string(JuMP.REPLMode, ref))
    return
end

# Show constraint in IJuliaMode
function Base.show(io::IO, ::MIME"text/latex", ref::InfOptConstraintRef)
    print(io, JuMP.constraint_string(JuMP.IJuliaMode, ref))
end

# Show the backend information associated with the optimizer model
function JuMP.show_backend_summary(io::IO, model::InfiniteModel)
    println(io, "Optimizer model backend information: ")
    JuMP.show_backend_summary(io, optimizer_model(model))
    return
end

# Show the objective function type
function JuMP.show_objective_function_summary(io::IO, model::InfiniteModel)
    println(io, "Objective function type: ",
            JuMP.objective_function_type(model))
    return
end

# Return constraint summary in JuMP like manner
function JuMP.show_constraints_summary(io::IO, model::InfiniteModel)
    for (F, S) in JuMP.list_of_constraint_types(model)
        n_constraints = JuMP.num_constraints(model, F, S)
        println(io, "`$F`-in-`$S`: $n_constraints constraint",
                _plural(n_constraints))
    end
    return
end

# Have the infinitemodel object show similar to Model
function Base.show(io::IO, model::InfiniteModel)
    # indicate is an infinite model
    println(io, "An InfiniteOpt Model")
    # show objective ssnse info
    sense = JuMP.objective_sense(model)
    if sense == MOI.MAX_SENSE
        print(io, "Maximization")
    elseif sense == MOI.MIN_SENSE
        print(io, "Minimization")
    else
        print(io, "Feasibility")
    end
    println(io, " problem with:")
    # show finite parameter info
    num_finite_params = num_parameters(model, FiniteParameter)
    println(io, "Finite Parameter", _plural(num_finite_params), ": ",
            num_finite_params)
    # show infinite parameter info
    num_infinite_params = num_parameters(model, InfiniteParameter)
    println(io, "Infinite Parameter", _plural(num_infinite_params), ": ",
            num_infinite_params)
    # show variable info
    num_vars = JuMP.num_variables(model)
    println(io, "Variable", _plural(num_vars), ": ", num_vars)
    # show the derivative info
    num_derivs = num_derivatives(model)
    println(io, "Derivative", _plural(num_derivs), ": ", num_derivs)
    # show measure info
    num_meas = num_measures(model)
    println(io, "Measure", _plural(num_meas), ": ", num_meas)
    # show objective function info
    if sense != MOI.FEASIBILITY_SENSE
        JuMP.show_objective_function_summary(io, model)
    end
    # show constraint info
    JuMP.show_constraints_summary(io, model)
    # show other info
    names_in_scope = sort!(collect(keys(JuMP.object_dictionary(model))))
    if !isempty(names_in_scope)
        print(io, "Names registered in the model: ",
              join(string.(names_in_scope), ", "), "\n")
    end
    JuMP.show_backend_summary(io, model)
end


text:
@testset "laplace" begin
    function generate_data()
        X = range(0, 23.5; length=48)
        # The random number generator changed in 1.6->1.7. The following vector was generated in Julia 1.6.
        # The generating code below is only kept for illustrative purposes.
        #! format: off
        Y = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
        #! format: on
        # Random.seed!(1)
        # fs = @. 3 * sin(10 + 0.6X) + sin(0.1X) - 1
        # # invlink = normcdf
        # invlink = logistic
        # ps = invlink.(fs)
        # Y = [rand(Bernoulli(p)) for p in ps]
        return X, Y
    end

    dist_y_given_f(f) = Bernoulli(logistic(f))

    function build_latent_gp(theta)
        variance = softplus(theta[1])
        lengthscale = softplus(theta[2])
        kernel = variance * with_lengthscale(SqExponentialKernel(), lengthscale)
        return LatentGP(GP(kernel), dist_y_given_f, 1e-8)
    end

    function optimize_elbo(
        build_latent_gp,
        theta0,
        xs,
        ys,
        optimizer,
        optim_options;
        newton_warmstart=true,
        newton_callback=nothing,
    )
        objective = build_laplace_objective(
            build_latent_gp, xs, ys; newton_warmstart, newton_callback
        )
        objective_grad(θ) = only(Zygote.gradient(objective, θ))

        training_results = Optim.optimize(
            objective, objective_grad, theta0, optimizer, optim_options; inplace=false
        )

        lf = build_latent_gp(training_results.minimizer)
        f_post = posterior(LaplaceApproximation(; f_init=objective.f), lf(xs), ys)
        return f_post, training_results
    end

    @testset "predictions" begin
        rng = MersenneTwister(123456)
        N_cond = 5
        N_a = 6
        N_b = 7

        # Specify prior.
        f = GP(Matern32Kernel())
        # Sample from prior.
        x = collect(range(-1.0, 1.0; length=N_cond))
        noise_scale = 0.1
        fx = f(x, noise_scale^2)
        y = rand(rng, fx)

        jitter = 0.0  # not needed in Gaussian case
        lf = LatentGP(f, f -> Normal(f, noise_scale), jitter)
        # in Gaussian case, Laplace converges to f_opt in one step; we need the
        # second step to compute the cache at f_opt rather than f_init!
        f_approx_post = posterior(LaplaceApproximation(; maxiter=2), lf(x), y)

        @testset "AbstractGPs API" begin
            a = collect(range(-1.2, 1.2; length=N_a))
            b = randn(rng, N_b)
            AbstractGPs.TestUtils.test_internal_abstractgps_interface(
                rng, f_approx_post, a, b
            )
        end

        @testset "equivalence to exact GPR for Gaussian likelihood" begin
            f_exact_post = posterior(f(x, noise_scale^2), y)
            xt = vcat(x, randn(rng, 3))  # test at training and new points

            m_approx, c_approx = mean_and_cov(f_approx_post(xt))
            m_exact, c_exact = mean_and_cov(f_exact_post(xt))

            @test m_approx ≈ m_exact
            @test c_approx ≈ c_exact
        end
    end

    @testset "gradients" begin
        @testset "approx_lml" begin
            X, Y = generate_data()

            Random.seed!(123)
            theta0 = rand(2)
            function objective(theta)
                lf = build_latent_gp(theta)
                lml = approx_lml(LaplaceApproximation(), lf(X), Y)
                return -lml
            end
            fd_grad = only(FiniteDifferences.grad(central_fdm(5, 1), objective, theta0))
            ad_grad = only(Zygote.gradient(objective, theta0))
            @test ad_grad ≈ fd_grad rtol = 1e-6
        end

        @testset "_newton_inner_loop derivatives not defined" begin
            Random.seed!(54321)

            xs = [0.2, 0.3, 0.7]
            ys = [1, 1, 0]
            theta0 = 1.234

            function eval_newton_inner_loop(theta)
                k = with_lengthscale(Matern52Kernel(), exp(theta))
                K = kernelmatrix(k, xs)
                f, cache = ApproximateGPs._newton_inner_loop(
                    dist_y_given_f, ys, K; f_init=zero(xs), maxiter=100
                )
                return f
            end

            eval_newton_inner_loop(theta0)  # forward pass works
            @test_throws ErrorException Zygote.gradient(eval_newton_inner_loop, theta0)
        end

        @testset "newton_inner_loop chain rules" begin
            Random.seed!(54321)

            xs = [0.2, 0.3, 0.7]
            ys = [1, 1, 0]
            L = randn(3, 3)

            function newton_inner_loop_from_L(dist_y_given_f, ys, L; kwargs...)
                K = L'L
                return ApproximateGPs.newton_inner_loop(dist_y_given_f, ys, K; kwargs...)
            end

            function ChainRulesCore.frule(
                (Δself, Δdist_y_given_f, Δys, ΔL),
                ::typeof(newton_inner_loop_from_L),
                dist_y_given_f,
                ys,
                L;
                kwargs...,
            )
                K = L'L
                # K̇ = L̇'L + L'L̇
                ΔK = ΔL'L + L'ΔL
                return frule(
                    (Δself, Δdist_y_given_f, Δys, ΔK),
                    ApproximateGPs.newton_inner_loop,
                    dist_y_given_f,
                    ys,
                    K;
                    kwargs...,
                )
            end

            function ChainRulesCore.rrule(
                ::typeof(newton_inner_loop_from_L), dist_y_given_f, ys, L; kwargs...
            )
                K = L'L
                f_opt, newton_from_K_pullback = rrule(
                    ApproximateGPs.newton_inner_loop, dist_y_given_f, ys, K; kwargs...
                )

                function newton_from_L_pullback(Δf_opt)
                    (∂self, ∂dist_y_given_f, ∂ys, ∂K) = newton_from_K_pullback(Δf_opt)
                    # Re⟨K̄, K̇⟩ = Re⟨K̄, L̇'L + L'L̇⟩
                    # = Re⟨K̄, L̇'L⟩ + Re⟨K̄, L'L̇⟩
                    # = Re⟨K̄L', L̇'⟩ + Re⟨LK̄, L̇⟩
                    # = Re⟨LK̄', L̇⟩ + Re⟨LK̄, L̇⟩
                    # = Re⟨LK̄' + LK̄, L̇⟩
                    # = Re⟨L̄, L̇⟩
                    # L̄ = L(K̄' + K̄)
                    ∂L = @thunk(L * (∂K' + ∂K))

                    return (∂self, ∂dist_y_given_f, ∂ys, ∂L)
                end

                return f_opt, newton_from_L_pullback
            end

            fkwargs = (; f_init=zeros(length(ys)), maxiter=100)
            test_frule(newton_inner_loop_from_L, dist_y_given_f, ys, L; fkwargs)
            test_rrule(newton_inner_loop_from_L, dist_y_given_f, ys, L; fkwargs)
        end
    end

    @testset "optimization" begin
        X, Y = generate_data()
        theta0 = [5.0, 1.0]

        @testset "reference optimum" begin
            function objective(theta)
                lf = build_latent_gp(theta)
                return -approx_lml(LaplaceApproximation(), lf(X), Y)
            end

            @testset "NelderMead" begin
                expected_thetahat = [7.708967951453345, 1.5182348363613536]

                res = Optim.optimize(objective, theta0, NelderMead())
                #@info res

                @test res.minimizer ≈ expected_thetahat rtol = 1e-4
            end

            @testset "gradient-based" begin
                expected_thetahat = [7.709076337653239, 1.51820292019697]

                objective_grad(θ) = only(Zygote.gradient(objective, θ))
                res = Optim.optimize(
                    objective, objective_grad, theta0, LBFGS(); inplace=false
                )
                #@info res

                @test res.minimizer ≈ expected_thetahat
            end
        end

        @testset "warmstart vs coldstart" begin
            args = (
                build_latent_gp, theta0, X, Y, LBFGS(), Optim.Options(; iterations=1000)
            )

            n_newton_coldstart = 0
            count_coldstart!(_, _) = (n_newton_coldstart += 1)

            _, res_cold = optimize_elbo(
                args...; newton_warmstart=false, newton_callback=count_coldstart!
            )
            #@info "Coldstart:\n$res_cold"

            n_newton_warmstart = 0
            count_warmstart!(_, _) = (n_newton_warmstart += 1)

            _, res_warm = optimize_elbo(
                args...; newton_warmstart=true, newton_callback=count_warmstart!
            )
            #@info "Warmstart:\n$res_warm"

            @info "Newton steps: $n_newton_coldstart (coldstart) vs $n_newton_warmstart (warmstart)"
            @test n_newton_coldstart - n_newton_warmstart > 100
            @test res_cold.minimizer ≈ res_warm.minimizer
        end
    end

    @testset "laplace_steps" begin
        X, Y = generate_data()
        Random.seed!(123)
        theta0 = rand(2)
        lf = build_latent_gp(theta0)
        lfx = lf(X)

        res_array = ApproximateGPs.laplace_steps(lfx, Y)
        res = res_array[end]
        @test res.q isa MvNormal
    end
end

ext: jl


text:
#  Jasper den Ouden 02-08-2012
# Placed in public domain.

module SDL

import GetC.@getCFun

include("../deps/deps.jl")

#TODO: read struct info from SDL_GetVideoInfo into this composite type
#type SDL_VideoInfo
	#hw_available::Uint32 #:1
	#wm_available::Uint32 #:1
	#UnusedBits1::Uint32  #:6
	#UnusedBits2::Uint32  #:1
	#blit_hw::Uint32      #:1
	#blit_hw_CC::Uint32   #:1
	#blit_hw_A::Uint32    #:1
	#blit_sw::Uint32      #:1
	#blit_sw_CC::Uint32   #:1
	#blit_sw_A::Uint32    #:1
	#blit_fill::Uint32    #:1
	#UnusedBits3::Uint32  #:16
	#video_mem::Uint32
	#vfmt::Ptr{Void}
	#current_w::Int32
	#current_h::Int32
#end

@getCFun libSDL SDL_FreeSurface SDL_FreeSurface(ptr::Ptr{Void})::Void
export SDL_FreeSurface
@getCFun libSDL SDL_GL_SwapBuffers SDL_GL_SwapBuffers()::Void
export SDL_GL_SwapBuffers
@getCFun libSDL SDL_Init SDL_Init(flags::Uint32)::Int32
export SDL_Init
@getCFun libSDL SDL_SetVideoMode SDL_SetVideoMode(width::Int32,height::Int32,bpp::Int32,flags::Uint32)::Ptr{Void}
export SDL_SetVideoMode
@getCFun libSDL SDL_GetVideoInfo SDL_GetVideoInfo()::Ptr{Void}
export SDL_GetVideoInfo
@getCFun libSDL SDL_WM_SetCaption SDL_WM_SetCaption(title::Ptr{Uint8},icon::Ptr{Uint8})::Int32
export SDL_WM_SetCaption
@getCFun libSDL SDL_GL_SetAttribute SDL_GL_SetAttribute(attr::Uint32,value::Int32)::Void
export SDL_GL_SetAttribute
@getCFun libSDL SDL_Quit SDL_Quit()::Void
export SDL_Quit

#TODO: The C function returns a Uint32. While the un-commented code returns an intuitive
#number, I'm not comfortable with specifying an invalid return type.
#@getCFun libSDL SDL_GetTicks SDL_GetTicks()::Uint32
#export SDL_GetTicks
@getCFun libSDL SDL_GetTicks SDL_GetTicks()::Int32
export SDL_GetTicks

#TODO: read up on the event system in SDL
#type SDL_Event
    #etype::Uint8
    #active::Ptr{Void}
    #key::Ptr{Void}
    #motion::Ptr{Void}
    #button::Ptr{Void}
    #jaxis::Ptr{Void}
    #jball::Ptr{Void}
    #jhat::Ptr{Void}
    #jbutton::Ptr{Void}
    #resize::Ptr{Void}
    #expose::Ptr{Void}
    #quit::Ptr{Void}
    #user::Ptr{Void}
    #syswm::Ptr{Void}
#end
#export SDL_Event

#function SDL_PollEvent(Event::SDL_Event)
    #iostr = IOString()
    #pack(iostr, Event)
    #errnum = ccall((:SDL_PollEvent, libSDL), Int32, (Ptr{Void},), iostr.data)
    #Event2 = unpack(iostr, Event)
    #return Event.etype
#end
#export SDL_PollEvent

@getCFun libSDL SDL_PumpEvents SDL_PumpEvents()::Void
export SDL_PumpEvents
@getCFun libSDL SDL_EnableKeyRepeat SDL_EnableKeyRepeat(delay::Int32,interval::Int32)::Int32
export SDL_EnableKeyRepeat

function SDL_GetKeyState()
    numkeys = Array(Int32,1)
    keystate = ccall((:SDL_GetKeyState, libSDL), Ptr{Uint8}, (Ptr{Int32}, ), numkeys)
    keystate = bool(pointer_to_array(keystate, (int(1),int(numkeys[1]))))
    return keystate[2:end] #TODO: find a better way to solve the off-by-one indexing issue
end
export SDL_GetKeyState

function SDL_GetMouseState()
    x = Array(Int32,1)
    y = Array(Int32,1)
    button = ccall((:SDL_GetMouseState, libSDL), Uint8, (Ptr{Int32}, Ptr{Int32}), x, y)
    return int(x[1]), int(y[1]), button
end
export SDL_GetMouseState

function SDL_GetRelativeMouseState()
    x = Array(Int32,1)
    y = Array(Int32,1)
    button = ccall((:SDL_GetRelativeMouseState, libSDL), Uint8, (Ptr{Int32}, Ptr{Int32}), x, y)
    return int(x[1]), int(y[1]), button
end
export SDL_GetRelativeMouseState

@getCFun libSDL SDL_JoystickEventState SDL_JoystickEventState(state::Int32)::Int32
export SDL_JoystickEventState
@getCFun libSDL SDL_JoystickOpen SDL_JoystickOpen(index::Int32)::Ptr{Void}
export SDL_JoystickOpen
@getCFun libSDL SDL_JoystickGetButton SDL_JoystickGetButton(joystick::Ptr{Void},button::Int32)::Uint8
export SDL_JoystickGetButton
@getCFun libSDL SDL_JoystickGetAxis SDL_JoystickGetAxis(joystick::Ptr{Void},axis::Int32)::Int16
export SDL_JoystickGetAxis

const SDL_INIT_VIDEO              = 0x00000020
const SDL_SWSURFACE               = 0x00000000
const SDL_HWSURFACE               = 0x00000001
const SDL_ASYNCBLIT               = 0x00000004
const SDL_ANYFORMAT               = 0x10000000
const SDL_HWPALETTE               = 0x20000000
const SDL_DOUBLEBUF               = 0x40000000
const SDL_FULLSCREEN              = 0x80000000
const SDL_OPENGL                  = 0x00000002
const SDL_OPENGLBLIT              = 0x0000000A
const SDL_RESIZABLE               = 0x00000010
const SDL_NOFRAME                 = 0x00000020
const SDL_HWACCEL                 = 0x00000100
const SDL_SRCCOLORKEY             = 0x00001000
const SDL_RLEACCELOK              = 0x00002000
const SDL_RLEACCEL                = 0x00004000
const SDL_SRCALPHA                = 0x00010000
const SDL_PREALLOC                = 0x01000000
const SDL_GL_RED_SIZE             = 1
const SDL_GL_GREEN_SIZE           = 2
const SDL_GL_BLUE_SIZE            = 3
const SDL_GL_ALPHA_SIZE           = 4
const SDL_GL_BUFFER_SIZE          = 5
const SDL_GL_DOUBLEBUFFER         = 6
const SDL_GL_DEPTH_SIZE           = 7
const SDL_GL_STENCIL_SIZE         = 8
const SDL_GL_ACCUM_RED_SIZE       = 9
const SDL_GL_ACCUM_GREEN_SIZE     = 10
const SDL_GL_ACCUM_BLUE_SIZE      = 11
const SDL_GL_ACCUM_ALPHA_SIZE     = 12
const SDL_GL_STEREO               = 13
const SDL_GL_MULTISAMPLEBUFFERS   = 14
const SDL_GL_MULTISAMPLESAMPLES   = 15
const SDL_GL_ACCELERATED_VISUAL   = 16
const SDL_GL_SWAP_CONTROL         = 17
const SDL_NOEVENT                 = 0
const SDL_ACTIVEEVENT             = 1
const SDL_KEYDOWN                 = 2
const SDL_KEYUP                   = 3
const SDL_MOUSEMOTION             = 4
const SDL_MOUSEBUTTONDOWN         = 5
const SDL_MOUSEBUTTONUP           = 6
const SDL_JOYAXISMOTION           = 7
const SDL_JOYBALLMOTION           = 8
const SDL_JOYHATMOTION            = 9
const SDL_JOYBUTTONDOWN           = 10
const SDL_JOYBUTTONUP             = 11
const SDL_QUIT                    = 12
const SDL_SYSWMEVENT              = 13
const SDL_EVENT_RESERVEDA         = 14
const SDL_EVENT_RESERVEDB         = 15
const SDL_VIDEORESIZE             = 16
const SDL_VIDEOEXPOSE             = 17
const SDL_EVENT_RESERVED2         = 18
const SDL_EVENT_RESERVED3         = 19
const SDL_EVENT_RESERVED4         = 20
const SDL_EVENT_RESERVED5         = 21
const SDL_EVENT_RESERVED6         = 22
const SDL_EVENT_RESERVED7         = 23
const SDL_USEREVENT               = 24
const SDL_NUMEVENTS               = 32
const SDL_BUTTON_LEFT             = 1
const SDL_BUTTON_MIDDLE           = 2
const SDL_BUTTON_RIGHT            = 3
const SDLK_UNKNOWN                = 0
const SDLK_FIRST                  = 0
const SDLK_BACKSPACE              = 8
const SDLK_TAB                    = 9
const SDLK_CLEAR                  = 12
const SDLK_RETURN                 = 13
const SDLK_PAUSE                  = 19
const SDLK_ESCAPE                 = 27
const SDLK_SPACE                  = 32
const SDLK_EXCLAIM                = 33
const SDLK_QUOTEDBL               = 34
const SDLK_HASH                   = 35
const SDLK_DOLLAR                 = 36
const SDLK_AMPERSAND              = 38
const SDLK_QUOTE                  = 39
const SDLK_LEFTPAREN              = 40
const SDLK_RIGHTPAREN             = 41
const SDLK_ASTERISK               = 42
const SDLK_PLUS                   = 43
const SDLK_COMMA                  = 44
const SDLK_MINUS                  = 45
const SDLK_PERIOD                 = 46
const SDLK_SLASH                  = 47
const SDLK_0                      = 48
const SDLK_1                      = 49
const SDLK_2                      = 50
const SDLK_3                      = 51
const SDLK_4                      = 52
const SDLK_5                      = 53
const SDLK_6                      = 54
const SDLK_7                      = 55
const SDLK_8                      = 56
const SDLK_9                      = 57
const SDLK_COLON                  = 58
const SDLK_SEMICOLON              = 59
const SDLK_LESS                   = 60
const SDLK_EQUALS                 = 61
const SDLK_GREATER                = 62
const SDLK_QUESTION               = 63
const SDLK_AT                     = 64
const SDLK_LEFTBRACKET            = 91
const SDLK_BACKSLASH              = 92
const SDLK_RIGHTBRACKET           = 93
const SDLK_CARET                  = 94
const SDLK_UNDERSCORE             = 95
const SDLK_BACKQUOTE              = 96
const SDLK_a                      = 97
const SDLK_b                      = 98
const SDLK_c                      = 99
const SDLK_d                      = 100
const SDLK_e                      = 101
const SDLK_f                      = 102
const SDLK_g                      = 103
const SDLK_h                      = 104
const SDLK_i                      = 105
const SDLK_j                      = 106
const SDLK_k                      = 107
const SDLK_l                      = 108
const SDLK_m                      = 109
const SDLK_n                      = 110
const SDLK_o                      = 111
const SDLK_p                      = 112
const SDLK_q                      = 113
const SDLK_r                      = 114
const SDLK_s                      = 115
const SDLK_t                      = 116
const SDLK_u                      = 117
const SDLK_v                      = 118
const SDLK_w                      = 119
const SDLK_x                      = 120
const SDLK_y                      = 121
const SDLK_z                      = 122
const SDLK_DELETE                 = 127
const SDLK_WORLD_0                = 160
const SDLK_WORLD_1                = 161
const SDLK_WORLD_2                = 162
const SDLK_WORLD_3                = 163
const SDLK_WORLD_4                = 164
const SDLK_WORLD_5                = 165
const SDLK_WORLD_6                = 166
const SDLK_WORLD_7                = 167
const SDLK_WORLD_8                = 168
const SDLK_WORLD_9                = 169
const SDLK_WORLD_10               = 170
const SDLK_WORLD_11               = 171
const SDLK_WORLD_12               = 172
const SDLK_WORLD_13               = 173
const SDLK_WORLD_14               = 174
const SDLK_WORLD_15               = 175
const SDLK_WORLD_16               = 176
const SDLK_WORLD_17               = 177
const SDLK_WORLD_18               = 178
const SDLK_WORLD_19               = 179
const SDLK_WORLD_20               = 180
const SDLK_WORLD_21               = 181
const SDLK_WORLD_22               = 182
const SDLK_WORLD_23               = 183
const SDLK_WORLD_24               = 184
const SDLK_WORLD_25               = 185
const SDLK_WORLD_26               = 186
const SDLK_WORLD_27               = 187
const SDLK_WORLD_28               = 188
const SDLK_WORLD_29               = 189
const SDLK_WORLD_30               = 190
const SDLK_WORLD_31               = 191
const SDLK_WORLD_32               = 192
const SDLK_WORLD_33               = 193
const SDLK_WORLD_34               = 194
const SDLK_WORLD_35               = 195
const SDLK_WORLD_36               = 196
const SDLK_WORLD_37               = 197
const SDLK_WORLD_38               = 198
const SDLK_WORLD_39               = 199
const SDLK_WORLD_40               = 200
const SDLK_WORLD_41               = 201
const SDLK_WORLD_42               = 202
const SDLK_WORLD_43               = 203
const SDLK_WORLD_44               = 204
const SDLK_WORLD_45               = 205
const SDLK_WORLD_46               = 206
const SDLK_WORLD_47               = 207
const SDLK_WORLD_48               = 208
const SDLK_WORLD_49               = 209
const SDLK_WORLD_50               = 210
const SDLK_WORLD_51               = 211
const SDLK_WORLD_52               = 212
const SDLK_WORLD_53               = 213
const SDLK_WORLD_54               = 214
const SDLK_WORLD_55               = 215
const SDLK_WORLD_56               = 216
const SDLK_WORLD_57               = 217
const SDLK_WORLD_58               = 218
const SDLK_WORLD_59               = 219
const SDLK_WORLD_60               = 220
const SDLK_WORLD_61               = 221
const SDLK_WORLD_62               = 222
const SDLK_WORLD_63               = 223
const SDLK_WORLD_64               = 224
const SDLK_WORLD_65               = 225
const SDLK_WORLD_66               = 226
const SDLK_WORLD_67               = 227
const SDLK_WORLD_68               = 228
const SDLK_WORLD_69               = 229
const SDLK_WORLD_70               = 230
const SDLK_WORLD_71               = 231
const SDLK_WORLD_72               = 232
const SDLK_WORLD_73               = 233
const SDLK_WORLD_74               = 234
const SDLK_WORLD_75               = 235
const SDLK_WORLD_76               = 236
const SDLK_WORLD_77               = 237
const SDLK_WORLD_78               = 238
const SDLK_WORLD_79               = 239
const SDLK_WORLD_80               = 240
const SDLK_WORLD_81               = 241
const SDLK_WORLD_82               = 242
const SDLK_WORLD_83               = 243
const SDLK_WORLD_84               = 244
const SDLK_WORLD_85               = 245
const SDLK_WORLD_86               = 246
const SDLK_WORLD_87               = 247
const SDLK_WORLD_88               = 248
const SDLK_WORLD_89               = 249
const SDLK_WORLD_90               = 250
const SDLK_WORLD_91               = 251
const SDLK_WORLD_92               = 252
const SDLK_WORLD_93               = 253
const SDLK_WORLD_94               = 254
const SDLK_WORLD_95               = 255
const SDLK_KP0                    = 256
const SDLK_KP1                    = 257
const SDLK_KP2                    = 258
const SDLK_KP3                    = 259
const SDLK_KP4                    = 260
const SDLK_KP5                    = 261
const SDLK_KP6                    = 262
const SDLK_KP7                    = 263
const SDLK_KP8                    = 264
const SDLK_KP9                    = 265
const SDLK_KP_PERIOD              = 266
const SDLK_KP_DIVIDE              = 267
const SDLK_KP_MULTIPLY            = 268
const SDLK_KP_MINUS               = 269
const SDLK_KP_PLUS                = 270
const SDLK_KP_ENTER               = 271
const SDLK_KP_EQUALS              = 272
const SDLK_UP                     = 273
const SDLK_DOWN                   = 274
const SDLK_RIGHT                  = 275
const SDLK_LEFT                   = 276
const SDLK_INSERT                 = 277
const SDLK_HOME                   = 278
const SDLK_END                    = 279
const SDLK_PAGEUP                 = 280
const SDLK_PAGEDOWN               = 281
const SDLK_F1                     = 282
const SDLK_F2                     = 283
const SDLK_F3                     = 284
const SDLK_F4                     = 285
const SDLK_F5                     = 286
const SDLK_F6                     = 287
const SDLK_F7                     = 288
const SDLK_F8                     = 289
const SDLK_F9                     = 290
const SDLK_F10                    = 291
const SDLK_F11                    = 292
const SDLK_F12                    = 293
const SDLK_F13                    = 294
const SDLK_F14                    = 295
const SDLK_F15                    = 296
const SDLK_NUMLOCK                = 300
const SDLK_CAPSLOCK               = 301
const SDLK_SCROLLOCK              = 302
const SDLK_RSHIFT                 = 303
const SDLK_LSHIFT                 = 304
const SDLK_RCTRL                  = 305
const SDLK_LCTRL                  = 306
const SDLK_RALT                   = 307
const SDLK_LALT                   = 308
const SDLK_RMETA                  = 309
const SDLK_LMETA                  = 310
const SDLK_LSUPER                 = 311
const SDLK_RSUPER                 = 312
const SDLK_MODE                   = 313
const SDLK_COMPOSE                = 315
const SDLK_HELP                   = 315
const SDLK_PRINT                  = 316
const SDLK_SYSREQ                 = 317
const SDLK_BREAK                  = 318
const SDLK_MENU                   = 319
const SDLK_POWER                  = 320
const SDLK_EURO                   = 321
const SDLK_UNDO                   = 322
const SDL_DEFAULT_REPEAT_DELAY    = 500
const SDL_DEFAULT_REPEAT_INTERVAL = 30
export SDL_INIT_VIDEO
export SDL_SWSURFACE
export SDL_HWSURFACE
export SDL_ASYNCBLIT
export SDL_ANYFORMAT
export SDL_HWPALETTE
export SDL_DOUBLEBUF
export SDL_FULLSCREEN
export SDL_OPENGL
export SDL_OPENGLBLIT
export SDL_RESIZABLE
export SDL_NOFRAME
export SDL_HWACCEL
export SDL_SRCCOLORKEY
export SDL_RLEACCELOK
export SDL_RLEACCEL
export SDL_SRCALPHA
export SDL_PREALLOC
export SDL_GL_RED_SIZE
export SDL_GL_GREEN_SIZE
export SDL_GL_BLUE_SIZE
export SDL_GL_ALPHA_SIZE
export SDL_GL_BUFFER_SIZE
export SDL_GL_DOUBLEBUFFER
export SDL_GL_DEPTH_SIZE
export SDL_GL_STENCIL_SIZE
export SDL_GL_ACCUM_RED_SIZE
export SDL_GL_ACCUM_GREEN_SIZE
export SDL_GL_ACCUM_BLUE_SIZE
export SDL_GL_ACCUM_ALPHA_SIZE
export SDL_GL_STEREO
export SDL_GL_MULTISAMPLEBUFFERS
export SDL_GL_MULTISAMPLESAMPLES
export SDL_GL_ACCELERATED_VISUAL
export SDL_GL_SWAP_CONTROL
export SDL_NOEVENT
export SDL_ACTIVEEVENT
export SDL_KEYDOWN
export SDL_KEYUP
export SDL_MOUSEMOTION
export SDL_MOUSEBUTTONDOWN
export SDL_MOUSEBUTTONUP
export SDL_JOYAXISMOTION
export SDL_JOYBALLMOTION
export SDL_JOYHATMOTION
export SDL_JOYBUTTONDOWN
export SDL_JOYBUTTONUP
export SDL_QUIT
export SDL_SYSWMEVENT
export SDL_EVENT_RESERVEDA
export SDL_EVENT_RESERVEDB
export SDL_VIDEORESIZE
export SDL_VIDEOEXPOSE
export SDL_EVENT_RESERVED2
export SDL_EVENT_RESERVED3
export SDL_EVENT_RESERVED4
export SDL_EVENT_RESERVED5
export SDL_EVENT_RESERVED6
export SDL_EVENT_RESERVED7
export SDL_USEREVENT
export SDL_NUMEVENTS
export SDL_BUTTON_LEFT
export SDL_BUTTON_RIGHT
export SDL_BUTTON_MIDDLE
export SDLK_UNKNOWN
export SDLK_FIRST
export SDLK_BACKSPACE
export SDLK_TAB
export SDLK_CLEAR
export SDLK_RETURN
export SDLK_PAUSE
export SDLK_ESCAPE
export SDLK_SPACE
export SDLK_EXCLAIM
export SDLK_QUOTEDBL
export SDLK_HASH
export SDLK_DOLLAR
export SDLK_AMPERSAND
export SDLK_QUOTE
export SDLK_LEFTPAREN
export SDLK_RIGHTPAREN
export SDLK_ASTERISK
export SDLK_PLUS
export SDLK_COMMA
export SDLK_MINUS
export SDLK_PERIOD
export SDLK_SLASH
export SDLK_0
export SDLK_1
export SDLK_2
export SDLK_3
export SDLK_4
export SDLK_5
export SDLK_6
export SDLK_7
export SDLK_8
export SDLK_9
export SDLK_COLON
export SDLK_SEMICOLON
export SDLK_LESS
export SDLK_EQUALS
export SDLK_GREATER
export SDLK_QUESTION
export SDLK_AT
export SDLK_LEFTBRACKET
export SDLK_BACKSLASH
export SDLK_RIGHTBRACKET
export SDLK_CARET
export SDLK_UNDERSCORE
export SDLK_BACKQUOTE
export SDLK_a
export SDLK_b
export SDLK_c
export SDLK_d
export SDLK_e
export SDLK_f
export SDLK_g
export SDLK_h
export SDLK_i
export SDLK_j
export SDLK_k
export SDLK_l
export SDLK_m
export SDLK_n
export SDLK_o
export SDLK_p
export SDLK_q
export SDLK_r
export SDLK_s
export SDLK_t
export SDLK_u
export SDLK_v
export SDLK_w
export SDLK_x
export SDLK_y
export SDLK_z
export SDLK_DELETE
export SDLK_WORLD_0
export SDLK_WORLD_1
export SDLK_WORLD_2
export SDLK_WORLD_3
export SDLK_WORLD_4
export SDLK_WORLD_5
export SDLK_WORLD_6
export SDLK_WORLD_7
export SDLK_WORLD_8
export SDLK_WORLD_9
export SDLK_WORLD_10
export SDLK_WORLD_11
export SDLK_WORLD_12
export SDLK_WORLD_13
export SDLK_WORLD_14
export SDLK_WORLD_15
export SDLK_WORLD_16
export SDLK_WORLD_17
export SDLK_WORLD_18
export SDLK_WORLD_19
export SDLK_WORLD_20
export SDLK_WORLD_21
export SDLK_WORLD_22
export SDLK_WORLD_23
export SDLK_WORLD_24
export SDLK_WORLD_25
export SDLK_WORLD_26
export SDLK_WORLD_27
export SDLK_WORLD_28
export SDLK_WORLD_29
export SDLK_WORLD_30
export SDLK_WORLD_31
export SDLK_WORLD_32
export SDLK_WORLD_33
export SDLK_WORLD_34
export SDLK_WORLD_35
export SDLK_WORLD_36
export SDLK_WORLD_37
export SDLK_WORLD_38
export SDLK_WORLD_39
export SDLK_WORLD_40
export SDLK_WORLD_41
export SDLK_WORLD_42
export SDLK_WORLD_43
export SDLK_WORLD_44
export SDLK_WORLD_45
export SDLK_WORLD_46
export SDLK_WORLD_47
export SDLK_WORLD_48
export SDLK_WORLD_49
export SDLK_WORLD_50
export SDLK_WORLD_51
export SDLK_WORLD_52
export SDLK_WORLD_53
export SDLK_WORLD_54
export SDLK_WORLD_55
export SDLK_WORLD_56
export SDLK_WORLD_57
export SDLK_WORLD_58
export SDLK_WORLD_59
export SDLK_WORLD_60
export SDLK_WORLD_61
export SDLK_WORLD_62
export SDLK_WORLD_63
export SDLK_WORLD_64
export SDLK_WORLD_65
export SDLK_WORLD_66
export SDLK_WORLD_67
export SDLK_WORLD_68
export SDLK_WORLD_69
export SDLK_WORLD_70
export SDLK_WORLD_71
export SDLK_WORLD_72
export SDLK_WORLD_73
export SDLK_WORLD_74
export SDLK_WORLD_75
export SDLK_WORLD_76
export SDLK_WORLD_77
export SDLK_WORLD_78
export SDLK_WORLD_79
export SDLK_WORLD_80
export SDLK_WORLD_81
export SDLK_WORLD_82
export SDLK_WORLD_83
export SDLK_WORLD_84
export SDLK_WORLD_85
export SDLK_WORLD_86
export SDLK_WORLD_87
export SDLK_WORLD_88
export SDLK_WORLD_89
export SDLK_WORLD_90
export SDLK_WORLD_91
export SDLK_WORLD_92
export SDLK_WORLD_93
export SDLK_WORLD_94
export SDLK_WORLD_95
export SDLK_KP0
export SDLK_KP1
export SDLK_KP2
export SDLK_KP3
export SDLK_KP4
export SDLK_KP5
export SDLK_KP6
export SDLK_KP7
export SDLK_KP8
export SDLK_KP9
export SDLK_KP_PERIOD
export SDLK_KP_DIVIDE
export SDLK_KP_MULTIPLY
export SDLK_KP_MINUS
export SDLK_KP_PLUS
export SDLK_KP_ENTER
export SDLK_KP_EQUALS
export SDLK_UP
export SDLK_DOWN
export SDLK_RIGHT
export SDLK_LEFT
export SDLK_INSERT
export SDLK_HOME
export SDLK_END
export SDLK_PAGEUP
export SDLK_PAGEDOWN
export SDLK_F1
export SDLK_F2
export SDLK_F3
export SDLK_F4
export SDLK_F5
export SDLK_F6
export SDLK_F7
export SDLK_F8
export SDLK_F9
export SDLK_F10
export SDLK_F11
export SDLK_F12
export SDLK_F13
export SDLK_F14
export SDLK_F15
export SDLK_NUMLOCK
export SDLK_CAPSLOCK
export SDLK_SCROLLOCK
export SDLK_RSHIFT
export SDLK_LSHIFT
export SDLK_RCTRL
export SDLK_LCTRL
export SDLK_RALT
export SDLK_LALT
export SDLK_RMETA
export SDLK_LMETA
export SDLK_LSUPER
export SDLK_RSUPER
export SDLK_MODE
export SDLK_COMPOSE
export SDLK_HELP
export SDLK_PRINT
export SDLK_SYSREQ
export SDLK_BREAK
export SDLK_MENU
export SDLK_POWER
export SDLK_EURO
export SDLK_UNDO
export SDL_DEFAULT_REPEAT_DELAY
export SDL_DEFAULT_REPEAT_INTERVAL

end


text:
# These tests should never contact AWS

@testset "Mocked AWS Interactions" begin

CFG = AWSConfig()

function dls_patch(output)
    @patch function CloudWatchLogs.describe_log_streams(config; kwargs...)
        output
    end
end

put_patch = @patch function CloudWatchLogs._put_log_events(stream::CloudWatchLogStream, events::AbstractVector{CloudWatchLogs.LogEvent})
    return Dict("nextSequenceToken" => "3")
end

function submit_patch(log_dump)
    @patch function submit_logs(stream::CloudWatchLogStream, events::AbstractVector{CloudWatchLogs.LogEvent})
        append!(log_dump, events)
        return length(events)
    end
end

function throttle_patch()
    first_time = true
    @patch function CloudWatchLogs._put_log_events(stream::CloudWatchLogStream, events::AbstractVector{CloudWatchLogs.LogEvent})
        if first_time
            first_time = false
            response = HTTP.Messages.Response(400, "")
            http_error = HTTP.ExceptionRequest.StatusError(400, "", "", response)
            throw(AWSException("ThrottlingException", "", "", http_error))
        end

        return Dict()
    end
end

streams = [
    Dict(
        "storageBytes" => 1048576,
        "arn" => "arn:aws:logs:us-east-1:123456789012:log-group:my-log-group-1:log-stream:my-log-stream-1",
        "creationTime" => 1393545600000,
        "firstEventTimestamp" => 1393545600000,
        "lastEventTimestamp" => 1393567800000,
        "lastIngestionTime" => 1393589200000,
        "logStreamName" => "my-log-stream-1",
        "uploadSequenceToken" => "88602967394531410094953670125156212707622379445839968487",
    ),
    Dict(
        "storageBytes" => 5242880,
        "arn" => "arn:aws:logs:us-east-1:123456789012:log-group:my-log-group-2:log-stream:my-log-stream-2",
        "creationTime" => 1396224000000,
        "firstEventTimestamp" => 1396224000000,
        "lastEventTimestamp" => 1396235500000,
        "lastIngestionTime" => 1396225560000,
        "logStreamName" => "my-log-stream-2",
        "uploadSequenceToken" => "07622379445839968487886029673945314100949536701251562127",
    ),
]

@testset "Sequence Token" begin
    # no streams
    apply(dls_patch(Dict("logStreams" => []))) do
        @test_throws LOGGER StreamNotFoundException CloudWatchLogs.new_sequence_token(CFG, "group", "stream")
    end

    # prefix-only match
    apply(dls_patch(Dict("logStreams" => streams))) do
        @test_throws LOGGER StreamNotFoundException CloudWatchLogs.new_sequence_token(CFG, "my-log-group-1", "my-log-stream")
    end

    # match
    apply(dls_patch(Dict("logStreams" => streams[1:1]))) do
        @test CloudWatchLogs.new_sequence_token(CFG, "my-log-group-1", "my-log-stream-1") == "88602967394531410094953670125156212707622379445839968487"
    end
end

@testset "CloudWatchLogStream" begin
    apply([dls_patch(Dict("logStreams" => streams)), put_patch]) do
        stream = CloudWatchLogStream(CFG, "my-log-group-1", "my-log-stream-1")
        @test CloudWatchLogs.sequence_token(stream) == "88602967394531410094953670125156212707622379445839968487"
        # no AWS requests are sent so it shouldn't matter that these timestamps are so small
        @test submit_logs(stream, [CloudWatchLogs.LogEvent("Help", 10), CloudWatchLogs.LogEvent("Alert", 124)]) == 2
        # should be the sequence token returned from the API call in put_patch
        @test CloudWatchLogs.sequence_token(stream) == "3"
    end
end

@testset "CloudWatchLogHandler" begin
    logs = CloudWatchLogs.LogEvent[]
    apply([dls_patch(Dict("logStreams" => streams[1:1])), submit_patch(logs)]) do
        cwlh = CloudWatchLogHandler(CFG, "my-log-group-1", "my-log-stream-1", DefaultFormatter("{msg}"))
        logger = Logger("CWLHTest"; propagate=false)
        push!(logger, cwlh)
        for c = 'a':'e'
            warn(logger, "$c")
        end
        sleep(5 * CloudWatchLogs.PUTLOGEVENTS_RATE_LIMIT)  # probably max time we might have to wait

        messages = map(le -> le.message, logs)
        timestamps = map(le -> le.timestamp, logs)

        @test messages == map(string, 'a':'e')
        @test issorted(timestamps)
    end
end

@testset "Throttled" begin
    start_time = CloudWatchLogs.unix_timestamp_ms()
    apply([dls_patch(Dict("logStreams" => streams)), throttle_patch()]) do
        stream = CloudWatchLogStream(CFG, "my-log-group-1", "my-log-stream-1")
        event = LogEvent("log", start_time)

        setlevel!(LOGGER, "debug") do
            @test_log LOGGER "debug" "ThrottlingException" begin
                submit_log(stream, event)
            end
        end
    end
end

end


text:
"""
    RotationError{T<:Real, D<:ErrorMap} <: StaticVector{3,T}

A three-parameter rotation error, converted to/from a `QuatRotation` using the
`ErrorMap` `D`.

# Usage
A `RotationError` is typically created using one of the following methods

    rotation_error(R1::Rotation, R2::Rotation, error_map::ErrorMap)
    R1 ⊖ R2

which compute the difference between the rotations `R1` and `R2` and convert the
result to a 3D rotation error using `error_map`.

The error can be "added" back to a rotation using the inverse operation:

    add_error(R1::Rotation, e::RotationError)
    R1::Rotation ⊕ e::RotationError
"""
struct RotationError{T,D} <: StaticVector{3,T}
    err::SVector{3,T}
    map::D
    @inline function RotationError(err::SVector{3,T}, map::D) where {T,D <: ErrorMap}
        new{T,D}(err, map)
    end
end

@inline QuatRotation(e::RotationError)::Rotation = e.map(e.err)

"""
    rotation_error(R1::Rotation, R2::Rotation, error_map::ErrorMap)

Compute the `RotationError` by calculating the "difference" between `R1` and `R2`, i.e.
`R2\\R1`, then mapped to a three-parameter error using `error_map`.

Can be equivalently called using the default map with `R1 ⊖ R2`

If `error_map::IdentityMap`, then `SVector(R1\\R2)::SVector{3}` is used as the error. Note
this only works for three-parameter rotation representations such as `RodriguesParam` or `MRP`.
"""
function rotation_error(R1::Rotation, R2::Rotation, error_map::ErrorMap)
    return RotationError(inv(error_map)(R2\R1), error_map)
end

function rotation_error(R1::Rotation, R2::Rotation, error_map::IdentityMap)
    err = params(R2\R1)
    if length(err) != 3
        throw(ArgumentError("R2\\R1 must be a three-dimensional parameterization, got $(length(err))"))
    end
    return RotationError(err, error_map)
end

#   set the default error map to the CayleyMap
@inline ⊖(R1::Rotation, R2::Rotation) = rotation_error(R1, R2, CayleyMap())

#   default to the identity map for Rodrigues Params and MRPs
@inline ⊖(R1::RodriguesParam, R2::RodriguesParam) = rotation_error(R1, R2, IdentityMap())
@inline ⊖(R1::MRP,            R2::MRP           ) = rotation_error(R1, R2, IdentityMap())

# Static Arrays interface
function (::Type{E})(t::NTuple{3}) where E <: RotationError
    E(t[1], t[2], t[3])
end
Base.@propagate_inbounds Base.getindex(e::RotationError, i::Int) = e.err[i]
@inline Base.Tuple(e::RotationError) = Tuple(e.err)

# Compose a rotation with an error
"""
    add_error(R1::Rotation, e::RotationError)

"Adds" the rotation error `e` to the rotation `R1` by converting `e` to a quaternion via
its `ErrorMap` and then composing with `R1`.

Equivalent to

    R1 ⊕ e
"""
function add_error(R1::Rotation, e::RotationError)
    R1 * QuatRotation(e)
end

function add_error(R1::R, e::RotationError{<:Any, IdentityMap}) where R <: Rotation
    # must be able to construct R from a SVector{3}
    if length(params(R1)) != 3
        throw(ArgumentError("Can't construct a rotation of type $R from three parameters"))
    end
    R1 * R(e.err)
end

@inline ⊕(R1::Rotation, e::RotationError) = add_error(R1, e)


text:
abstract type AbstractAWSConfig end
mutable struct AWSConfig <: AbstractAWSConfig
    credentials::Union{AWSCredentials,Nothing}
    region::String
    output::String
end

credentials(aws::AWSConfig) = aws.credentials
region(aws::AWSConfig) = aws.region

function AWSConfig(;
    profile=nothing,
    creds=AWSCredentials(profile=profile),
    region=nothing,
    output="json",
)
    region = @something region aws_get_region(profile=profile)
    return AWSConfig(creds, region, output)
end

"""
    aws_user_arn(aws::AWSConfig) -> String

Retrieve the `User ARN` from the `AWSConfig`, if not present query STS to update the
`user_arn`.

# Arguments
- `aws::AWSConfig`: AWSConfig used to retrieve the user arn
"""
function aws_user_arn(aws::AWSConfig)
    creds = aws.credentials

    if isempty(creds.user_arn)
        _update_creds!(aws)
    end

    return creds.user_arn
end

"""
    aws_account_number(aws::AWSConfig) -> String

Retrieve the `AWS account number` from the `AWSConfig`, if not present query STS to update
the `AWS account number`.

# Arguments
- `aws::AWSConfig`: AWSConfig used to retrieve the AWS account number
"""
function aws_account_number(aws::AWSConfig)
    creds = aws.credentials

    if isempty(creds.account_number)
        _update_creds!(aws)
    end

    return creds.account_number
end

function _update_creds!(aws_config::AWSConfig)
    r = AWSServices.sts("GetCallerIdentity"; aws_config=aws_config)
    r = r["GetCallerIdentityResult"]
    creds = aws_config.credentials

    creds.user_arn = r["Arn"]
    creds.account_number = r["Account"]

    return creds
end


text:
using HDF5

function save_params(file::HDF5File, rbm::RBM, name::String)
    write(file, "$(name)___weight", rbm.W')
    write(file, "$(name)___vbias", rbm.visible.bias)
    write(file, "$(name)___hbias", rbm.hidden.bias)
end

function load_params(file::HDF5File, rbm::RBM, name::String)
    rbm.W = read(file, "$(name)___weight")'
    rbm.visible.bias = read(file, "$(name)___vbias")
    rbm.hidden.bias = read(file, "$(name)___hbias")
end

function save_params(file::HDF5File, dbn::DBN)
    for i=1:length(dbn)
        save_params(file, dbn[i], getname(dbn, i))
    end
end
save_params(path::String, dbn::DBN) = h5open(path, "w") do h5
    save_params(h5, dbn)
end

function load_params(file, dbn::DBN)
    for i=1:length(dbn)
        load_params(file, dbn[i], getname(dbn, i))
    end
end
load_params(path::String, dbn::DBN) = h5open(path) do h5
    load_params(h5, dbn)
end


text:
ϕ = (1+sqrt(5))/2   # golden ratio

d = [1, ϕ, ϕ^2, ϕ^3]
r = d/2
A = π*r.^2

a = (d[3] + d[2])/2
b = (d[3] + d[1])/2
c = (d[2] + d[1])/2

C = acosd((a^2 + b^2 - c^2)/(2*a*b))

x = [b*sind(C), 0, 0, 0]
y = [(d[3] - d[4])/2 + b*cosd(C), (d[4] - d[2])/2,(d[3] - d[4])/2, 0]

x_balance = zeros(360)
y_balance = zeros(360)
xy_balance = zeros(360)
x'*A
y'*A
x'*A + y'*A
for θ = 1:360 # {deg}
    R = [cosd(θ) -sind(θ); sind(θ) cosd(θ)]

    xynew = R*[x';y']
    xnew = xynew[1,:]
    ynew = xynew[2,:]

    x_balance[θ] = xnew'*A
    y_balance[θ] = ynew'*A
    xy_balance[θ] = xnew'*A + ynew'*A
end

using PyPlot
plot(x_balance,label="x_balance")
plot(y_balance,label="y_balance")
plot(xy_balance,label="xy_balance")

grid("on")
legend()

θˣ = findall(x->x==minimum(abs.(x_balance),2),x_balance)

θᶜᶜ = 323.5 # {deg} counterclockwise
θᶜ = 360 - θᶜᶜ # {deg} clockwise

x_final = zeros(4)
y_final = zeros(4)
for θ = θᶜᶜ # {deg}
    R = [cosd(θ) -sind(θ); sind(θ) cosd(θ)]
    global x_final, y_final
    xynew = R*[x';y']
    x_final = xynew[1,:]
    y_final = xynew[2,:]
end

x_final
y_final


text:
# Assumes the space dimension and field dimension are the same.  In other words, when the
# space coordinate indices are (i,j,k), then the field has three vector components.
# Therefore, for the output field array G[i,j,k,w], we assume w = 1:3.

# The functions add the calculated values to the existing values of the output array.
# Therefore, if the derivative values themselves are desired, pass the output array
# initialized with zeros.

export apply_grad!

# Wrapper to apply the discrete gradient by default
#
# Note that when the positional arguments are SVector's, this function may call the concrete
# implementation directly instead of the next wrapper function taking ∆l⁻¹ even if the
# keyword arguments are not SVector's, because methods are dispatched based only on the
# types of positional arguments.  Therefore, we convert the keyword arguments into SVector's.
apply_grad!(G::AbsArrNumber,  # output field; in 3D, G[i,j,k,w] is w-component of G at (i,j,k)
            f::AbsArrNumber,  # input array of scalar; in 3D, f[i,j,k] is f at (i,j,k)
            ::Val{OP},  # Val(:(=)) or Val(:(+=)): set (=) or add (+=) operator to use
            isfwd::AbsVecBool,  # isfwd[w] = true|false: create ∂w by forward|backward difference
            ∆l⁻¹::Tuple{Vararg{Number}}=ntuple(x->1.0,length(isfwd)),  # ∆l⁻¹[w]: inverse of uniform distance between grid planes in w-direction
            isbloch::AbsVecBool=fill(true,length(isfwd)),  # boundary conditions in K dimensions
            e⁻ⁱᵏᴸ::AbsVecNumber=ones(length(isfwd));  # Bloch phase factors in K dimensions
            α::Number=1.0  # scale factor to multiply to result before adding it to g: g += α ∇⋅F
            ) where {OP} =
    (N = size(f); K = length(isfwd); apply_grad!(G, f, Val(OP), isfwd, fill.(∆l⁻¹,N), isbloch, e⁻ⁱᵏᴸ; α))

# Wrapper for converting AbstractVector's to SVec's
apply_grad!(G::AbsArrNumber,  # output field; in 3D, G[i,j,k,w] is w-component of G at (i,j,k)
            f::AbsArrNumber,  # input array of scalar; in 3D, f[i,j,k] is f at (i,j,k)
            ::Val{OP},  # Val(:(=)) or Val(:(+=)): set (=) or add (+=) operator to use
            isfwd::AbsVecBool,  # isfwd[w] = true|false: create ∂w by forward|backward difference
            ∆l⁻¹::NTuple{K,AbsVecNumber},  # ∆l⁻¹[w]: inverse of distances between grid planes in x-direction
            isbloch::AbsVecBool=fill(true,K),  # boundary conditions in K dimensions
            e⁻ⁱᵏᴸ::AbsVecNumber=ones(K);  # Bloch phase factors in K dimensions
            α::Number=1.0  # scale factor to multiply to result before adding it to g: g += α ∇⋅F
            ) where {K,OP} =
    apply_grad!(G, f, Val(OP), SBool{K}(isfwd), ∆l⁻¹, SBool{K}(isbloch), SVec{K}(e⁻ⁱᵏᴸ); α)

# Concrete implementation
function apply_grad!(G::AbsArrNumber{K₊₁},  # output field; in 3D, G[i,j,k,w] is w-component of G at (i,j,k)
                     f::AbsArrNumber{K},  # input array of scalar; in 3D, f[i,j,k] is f at (i,j,k)
                     ::Val{OP},  # Val(:(=)) or Val(:(+=)): set (=) or add (+=) operator to use
                     isfwd::SBool{K},  # isfwd[w] = true|false: create ∂w by forward|backward difference
                     ∆l⁻¹::NTuple{K,AbsVecNumber},  # ∆l[w]: inverse of distances between grid planes in x-direction
                     isbloch::SBool{K},  # boundary conditions in K dimensions
                     e⁻ⁱᵏᴸ::SNumber{K};  # Bloch phase factors in K dimensions
                     α::Number=1.0  # scale factor to multiply to result before adding it to g: g += α ∇⋅F
                     ) where {K,K₊₁,OP}
    @assert K₊₁==K+1

    for nw = 1:K  # direction of differentiation
        nv = nw  # component of output field generated by ∂w (index of matrix block)
        Gv = selectdim(G, K₊₁, nv)  # nv-th component of output field
        apply_∂!(Gv, f, Val(OP), nw, isfwd[nw], ∆l⁻¹[nw], isbloch[nw], e⁻ⁱᵏᴸ[nw]; α) # Gv += α ∂f/∂w
    end

    return nothing
end


text:
@testset "deprecation" begin
    @testset "restrict" begin
        A = rand(N0f8, 4, 5, 3)
        @test restrict(A, [1, 2]) == restrict(A, (1, 2))
    end
end


text:
module BlackBoxOptimizationBenchmarking

    using Distributions, Memoize, Optim
    using LinearAlgebra, NamedDims

    import Base: show
    import Optim: minimizer, minimum, optimize
    export BBOBFunction

## Constants and Functions

    const maximum_dimension = 100

    T_asy(x::Vector{T}, β) where T <: Number = [x[i] > 0 ? x[i]^(1+β*(i-1)/(length(x)-1)*√x[i]) : x[i] for i=1:length(x)]

    function T_asy(x::Matrix{T}, β) where T <: Number
        z = similar(x)
        for i=1:size(x, 1)
            z[i, :] = T_asy(x[i, :], β)
        end
        z
    end

    function T_osz(xi::T) where T <: Number
        xhat = xi != zero(T) ? log(abs(xi)) : zero(T)
        c1 = xi > 0 ? 10. : 5.5
        c2 = xi > 0 ? 7.9 : 3.1

        sign(xi) * exp(xhat + 0.049*(sin(c1*xhat)+sin(c2*xhat) ))
    end

    function T_osz(x::Vector{T}) where T <: Number
        z = similar(x)
        for i=1:length(x)
            z[i] = T_osz(x[i])
        end
        z
    end

    function T_osz(x::Matrix{T}) where T <: Number
        z = similar(x)
        for i=1:size(x, 1)
            z[i, :] = T_osz(x[i, :])
        end
        z
    end

    function Λ(α, D)
        m = zeros(D, D)
        for i=1:D
            m[i, i] = α^(1/2 *(i-1)/(D-1))
        end
        m
    end

    C(i, D, n) = 10^(2*(i-1)/(D-1))#conditioning

    f_pen(x) =  sum(max(0, abs(xi)-5)^2 for xi in x)

    const one_pm = sign.(randn(maximum_dimension))

    #rotation matrices (probably a bit wrong)
    @memoize function Q(D)
        r = randn(D); r = r/norm(r)
        Q = [r nullspace(Matrix(r'))]
    end
    @memoize function R(D)
        r = randn(D); r = r/norm(r)
        R = [r nullspace(Matrix(r'))]
    end

    ∑(x) = sum(x)

## BBOBFunction

    struct BBOBFunction{F<:Function}
        name::String
        f::F
        x_opt::Array{Float64, 1}
        f_opt::Float64
    end

    (f::BBOBFunction)(x) = f.f(x)
    show(io::IO, f::BBOBFunction) =  print(io, f.name)
    Base.broadcastable(f::BBOBFunction) = Ref(f)

    minimum(f::BBOBFunction) = f.f_opt
    minimizer(f::BBOBFunction, D) = f.x_opt[1:D]

    test_x_opt(f::BBOBFunction) = @assert f(f.x_opt) ≈ f.f_opt

## helpers to define function

    const BBOBFunctions = BBOBFunction[]
    list_functions() = BBOBFunctions

    fun_symbols(n) = (map(Symbol, ["F$(n)", "f$(n)", "x$(n)_opt", "f$(n)_opt"])..., )

    macro BBOBFunction(name, simple_name, n)
        F, f, x_opt, f_opt = fun_symbols(n)
        fname = Symbol(simple_name)

        esc(quote
            $fname = BBOBFunction($name, $f, $x_opt, $f_opt)
            push!(BBOBFunctions, $fname)
        end)
    end

    """
        Define x1_opt and f1_opt.

    """
    macro define_x_and_f_opt(n)
        F, f, x_opt, f_opt = fun_symbols(n)
        esc(quote
            const $x_opt = rand(Uniform(-4, 4), maximum_dimension)
            const $f_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))
        end)
    end

## Functions

    ## f1, Sphere Function

    @define_x_and_f_opt(1)

    """ Sphere Function """
    f1(x) = ∑( (x[i] .- x1_opt[i])^2 for i=1:length(x) ) + f1_opt

    @BBOBFunction("Sphere", "sphere", 1)

    ## f2, Ellipsoidal Function

    @define_x_and_f_opt(2)

    """ Ellipsoidal Function """
    function f2(x)
        D = length(x)
        z = T_osz(x .- x2_opt[1:D])
        ∑( 10^(6*(i-1)/(D-1)) * z[i]^2 for i=1:length(x) ) + f2_opt
    end

    @BBOBFunction("Ellipsoidal", "ellipsoidal", 2)

    ## f3, Rastrigin Function

    @define_x_and_f_opt(3)

    """ Rastrigin Function """
    function f3(x)
        D = length(x)
        z = Λ(10, D) * T_asy(T_osz(x .- x3_opt[1:D]), 0.2)
        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + f3_opt
    end

    @BBOBFunction("Rastrigin", "rastrigin", 3)

    ## f4, Buche-Rastrigin Function

    @define_x_and_f_opt(4)

    """ Buche-Rastrigin Function """
    function f4(x)
        D = length(x)
        z = T_osz(x .- x4_opt[1:D])
        s = [isodd(i) ? 10*10^(0.5*(i-1)/(D-1)) : 10^(0.5*(i-1)/(D-1)) for i=1:D]

        for i=1:D
            @inbounds z[i] = s[i]*z[i]
        end

        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + 100*f_pen(x) + f4_opt
    end

    @BBOBFunction("Buche-Rastrigin", "buche_rastrigin", 4)

    ## f5, Linear Slope

    const x5_opt = 5*one_pm
    const f5_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))

    """ Linear Slope """
    function f5(x)
        D = length(x)
        z = [ x5_opt[i]*x[i] < 25 ? x[i] : x5_opt[i] for i=1:D ]
        s = [sign(x5_opt[i])*10^((i-1)/(D-1))  for i=1:D]

        ∑( 5*abs(s[i]) -s[i]*z[i] for i=1:D ) + f5_opt
    end

    @BBOBFunction("Linear Slope", "linear_slope", 5)

    ## f6, Attractive Sector Function

    @define_x_and_f_opt(6)

    """ Attractive Sector Function """
    function f6(x)
        D = length(x)

        z = Q(D)*Λ(10, D)*R(D)*(x .- x6_opt[1:D])

        @inbounds for i=1:D
            z[i] = x6_opt[i]*z[i] > 0 ? 100*z[i] : z[i]
        end

        T_osz( ∑( z[i]^2 for i=1:D ))^0.9 + f6_opt
    end

    @BBOBFunction("Attractive Sector", "attractive_sector", 6)

    ## f7, Step Ellipsoidal Function

    @define_x_and_f_opt(7)

    """ Step Ellipsoidal Function """
    function f7(x)
        D = length(x)

        z = Λ(10, D)*R(D)*(x .- x7_opt[1:D])
        zhat_1 = copy(z[1])

        @inbounds for i=1:D
            z[i] = z[i] > 0.5 ? floor(0.5 + z[i]) : floor(0.5 + 10*z[i])/10
        end
        z = Q(D)*z

        0.1*max(abs(zhat_1)/(10^4), ∑( 10^(2*(i-1)/(D-1)) * z[i]^2 for i=1:D ) ) + f_pen(x) + f7_opt
    end

    @BBOBFunction("Step Ellipsoidal Function", "step_ellipsoidal", 7)

    ## f8, Rosenbrock Function, original

    @define_x_and_f_opt(8)

    """ Rosenbrock Function, original """
    function f8(x)
        D = length(x)

        z = max(1, √D/8)*(x .- x8_opt[1:D]) .+ 1

        ∑( 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1 ) + f8_opt
    end

    @BBOBFunction("Rosenbrock Function, original", "rosenbrock_original", 8)

    ## f9, Rosenbrock Function, rotated

    @define_x_and_f_opt(9)

    """ Rosenbrock Function, rotated """
    function f9(x)
        D = length(x)

        z = max(1, √D/8)*R(D)*(x .- x9_opt[1:D]) .+ 1

        ∑( 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1 ) + f9_opt
    end

    @BBOBFunction("Rosenbrock Function, rotated", "rosenbrock_rotated", 9)

    ## f10, Ellipsoidal Function

    @define_x_and_f_opt(10)

    """ Ellipsoidal Function """
    function f10(x)
        D = length(x)

        z = T_osz( R(D)*(x .- x10_opt[1:D]))

        ∑( C(i, D, 6)*z[i]^2 for i=1:D ) + f10_opt
    end

    @BBOBFunction("Ellipsoidal Function", "ellipsoidal_second", 10)

    ## f11, Discus Function

    @define_x_and_f_opt(11)

    """ Discus Function """
    function f11(x)
        D = length(x)

        z = T_osz( R(D)*(x .- x11_opt[1:D]))

        10^6*z[1]^2 + ∑( z[i]^2 for i=2:D ) + f11_opt
    end

    @BBOBFunction("Discus Function", "discus", 11)

    ## f12, Bent Cigar Function

    @define_x_and_f_opt(12)

    """ Bent Cigar Function """
    function f12(x)
        D = length(x)

        z = R(D)*T_asy( R(D)*(x .- x12_opt[1:D]), 0.5)

        z[1]^2 + 10^6*∑( z[i]^2 for i=2:D ) + f12_opt
    end

    @BBOBFunction("Bent Cigar Function", "bent_cigar", 12)

    ## f13, Sharp Ridge Function

    @define_x_and_f_opt(13)

    """ Sharp Ridge Function """
    function f13(x)
        D = length(x)

        z = Q(D)*Λ(10, D)*R(D)*(x .- x13_opt[1:D])

        z[1]^2 + 100*√(∑(z[i]^2 for i=2:D )) + f13_opt
    end

    @BBOBFunction("Sharp Ridge Function", "sharp_ridge", 13)

    ## f14, Different Powers Function

    @define_x_and_f_opt(14)

    """ Different Powers Function """
    function f14(x)
        D = length(x)

        z = R(D)*(x .- x14_opt[1:D])

        √(∑(abs(z[i])^(2+4(i-1)/(D-1)) for i=1:D )) + f14_opt
    end

    @BBOBFunction("Different Powers Function", "different_powers", 14)

    ## f15, Rastrigin Function

    @define_x_and_f_opt(15)

    """ Rastrigin Function """
    function f15(x)
        D = length(x)

        z = R(D)*Λ(10, D)*Q(D)*T_asy(T_osz( R(D)*(x .- x15_opt[1:D]) ), 0.2)
        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + f15_opt
    end

    @BBOBFunction("Rastrigin Function", "rastrigin2", 15)

    ## f16, Weierstrass Function

    @define_x_and_f_opt(16)

    const f0 = ∑( 1/2^k * cos(2π*3^k*1/2) for k=0:11 )

    """ Weierstrass Function """
    function f16(x)
        D = length(x)

        z = R(D)*Λ(1/100, D)*Q(D)*T_osz( R(D)*(x .- x16_opt[1:D]) )
        s = ∑( ∑( 1/2^k * cos(2π*3^k*(z[i]+1/2)) for k=0:11 ) for i=1:D )
        10*( 1/D*s - f0 )^3 + 10/D*f_pen(x) + f16_opt
    end

    @BBOBFunction("Weierstrass Function", "weierstrass", 16)

    ## f17, Schaffers F7 Function

    @define_x_and_f_opt(17)

    """ Schaffers F7 Function"""
    function f17(x)
        D = length(x)

        z = Λ(10, D)*Q(D)*T_asy( R(D)*(x .- x17_opt[1:D]), 0.5)
        s = [√(z[i]^2 + z[i+1]^2) for i=1:D-1]

        (1/(D-1)*∑( √s[i]*(1 + sin(50*s[i]^1/5 )^2 ) for i=1:D-1 ))^2 + 10*f_pen(x) + f17_opt
    end

    @BBOBFunction("Schaffers F7 Function", "schaffers_F7", 17)

    ## f18, Schaffers F7 Function, moderately ill-conditioned

    @define_x_and_f_opt(18)

    """ Schaffers F7 Function, moderately ill-conditioned """
    function f18(x)
        D = length(x)

        z = Λ(1000, D)*Q(D)*T_asy( R(D)*(x .- x18_opt[1:D]), 0.5)
        s = [√(z[i]^2 + z[i+1]^2) for i=1:D-1]

        (1/(D-1)*∑( √s[i]*(1 + sin(50*s[i]^1/5 )^2 ) for i=1:D-1 ))^2 + 10*f_pen(x) + f18_opt
    end

    @BBOBFunction("Schaffers F7 Function, moderately ill-conditioned", "schaffers_F7_ill_conditioned", 18)

    ## f19, Composite Griewank-Rosenbrock Function F8F2

    @define_x_and_f_opt(19)

    """ Composite Griewank-Rosenbrock Function F8F2 """
    function f19(x)
        D = length(x)

        z = max(1, √D/8)*R(D)*(x .- x19_opt[1:D]) .+ 1
        s = [ 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1]

        10/(D-1)*∑( s[i]/4000 -cos(s[i]) for i=1:D-1 ) + 10 + f19_opt
    end

    @BBOBFunction("Composite Griewank-Rosenbrock Function F8F2", "composite_griewank_rosenbrock", 19)

    ## f20, Schwefel Function
    # https://github.com/numbbo/coco/issues/837

    const x20_opt = 4.2096874633/2 * one_pm
    const f20_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))

    """ Schwefel Function """
    function f20(x)
        D = length(x)

        x = 2*one_pm[1:D] .* x

        z = [i==1 ? x[1] : x[i] + 0.25*(x[i-1] .- 2*abs(x20_opt[i-1]) ) for i=1:D]
        z = 100*( Λ(10, D)*(z- 2*abs.(x20_opt[1:D]) ) + 2*abs.(x20_opt[1:D]) )

        -1/(100*D)*∑( z[i]*sin(√(abs(z[i]))) for i=1:D ) + 4.189828872724339 + 100*f_pen(z/100) + f20_opt
    end

    @BBOBFunction("Schwefel Function", "schwefel_function",  20)

## Tests

    map(test_x_opt, BBOBFunctions)

## utilities

    include("benchmark.jl")

    #function run_benchmark()
    #    include(joinpath(Pkg.dir(), "BlackBoxOptimizationBenchmarking", "src", "run_benchmark.jl"))
    #end

    #x = [collect(linspace(-10, 10, 500)) collect(linspace(-10, 10, 500))]
    #
    #T_osz(x)
    #
    #y = [f3(x[i, :]) for i=1:size(x, 1)]
    #
    #plot(x=x[:, 1], y=y, Geom.line)

##
end # module


text:
using TypeClasses.Utils

"""
    flip_types(value::T{S{A}})::S{T{A}}

reverses the two outer containers, e.g. making an Array of Options into an Option of an Array.
"""
function flip_types end

"""
    default_flip_types_having_pure_combine_apEltype(container)

Use this helper function to ease the definition of `flip_types` for your own type.

Note that the following interfaces are assumed:
- iterable
- pure
- combine
- ap on eltype

And in case of empty iterable in addition the following:
- neutral
- pure on eltype

We do not overload `flip_types` directly because this would require dispatching on whether `isAp(eltype(T))`.
But relying on `eltype` to define different semantics is strongly discouraged.
"""
function default_flip_types_having_pure_combine_apEltype(iter::T) where T
  first = iterate(iter)
  if first === nothing
    # only in this case we actually need `pure(eltype(T))` and `neutral(T)`
    # for non-empty sequences everything works for Types without both
    pure(eltype(T), neutral(T))
  else
    b, state = first
    # we need to abstract out details so that combine can actually work
    # note that because of its definition, pure(ABC, x) == pure(A, x)
    # start = feltype_unionall_implementationdetails(fmap(traitsof, a -> pure(traitsof, T, a), x)) # we can only combine on S
    start = map(c -> pure(T, c), b)  # we can only combine on ABC
    Base.foldl(Iterators.rest(iter, state); init = start) do acc, b
      mapn(acc, b) do acc′, c  # working in applicative context B
        acc′ ⊕ pure(T, c)  # combining on T
      end
    end
  end
end


text:
module TestMeasures

using MLJBase, Test
import Distributions
using CategoricalArrays
import Random.seed!
using Statistics
using LossFunctions

@testset "aggregation" begin
    v = rand(5)
    @test aggregate(v, mae) ≈ mean(v)
    @test aggregate(v, TruePositive()) ≈ sum(v)
    @test aggregate(v, rms) ≈ sqrt(mean(v.^2))
    λ = rand()
    @test aggregate(λ, rms) === λ
    @test aggregate(aggregate(v, l2), l2) == aggregate(v, l2)
end

@testset "metadata" begin
    measures()
    measures(m -> m.target_scitype <: AbstractVector{<:Finite} &&
             m.supports_weights)
    info(rms)
    @test true
end

@testset "coverage" begin
    # just checking that the traits work not that they're correct
    @test orientation(BrierScore()) == :score
    @test orientation(auc) == :score
    @test orientation(rms) == :loss

    @test reports_each_observation(auc) == false
    @test is_feature_dependent(auc) == false

    @test MLJBase.distribution_type(BrierScore{UnivariateFinite}) ==
        MLJBase.UnivariateFinite
end

mutable struct DRegressor <: Deterministic end
MLJBase.target_scitype(::Type{<:DRegressor}) =
    AbstractVector{<:Continuous}

mutable struct D2Regressor <: Deterministic end
MLJBase.target_scitype(::Type{<:D2Regressor}) =
    AbstractVector{Continuous}

mutable struct DClassifier <: Deterministic end
MLJBase.target_scitype(::Type{<:DClassifier}) =
    AbstractVector{<:Finite}

mutable struct PClassifier <: Probabilistic end
MLJBase.target_scitype(::Type{<:PClassifier}) =
    AbstractVector{<:Finite}

@testset "default_measure" begin
    @test MLJBase.default_measure(DRegressor()) == rms
    @test MLJBase.default_measure(D2Regressor()) == rms
    @test MLJBase.default_measure(DClassifier()) == misclassification_rate
    @test MLJBase.default_measure(PClassifier()) == cross_entropy

    @test MLJBase.default_measure(DRegressor) == rms
    @test MLJBase.default_measure(D2Regressor) == rms
    @test MLJBase.default_measure(DClassifier) == misclassification_rate
    @test MLJBase.default_measure(PClassifier) == cross_entropy
end

include("continuous.jl")
include("finite.jl")
include("loss_functions_interface.jl")
include("confusion_matrix.jl")

end
true