text:
"""
    prepare_dataframe_for_dtm!(dfin::DataFrame;directory::String=mktempdir(),treat_as_categorical_variable::Vector{String}=Vector{String}(),numcol::String="",denomcol::String="",weightcol::String="",trnvalcol::String="",valpct::Float64=0.3,keycol::String="",independent_vars::Vector{String}=Vector{String}())

Prepares a DataFrame for the modelling.

returns dtmtable::DTMTable,sett::ModelSettings,df_prepped::DataFrame
"""
function prepare_dataframe_for_dtm!(dfin::DataFrame;directory::String=mktempdir(),treat_as_categorical_variable::Vector{String}=Vector{String}(),numcol::String="",denomcol::String="",weightcol::String="",trnvalcol::String="",valpct::Float64=0.3,keycol::String="",independent_vars::Vector{String}=Vector{String}(),methodForSplittingPointsSelection::String="basedOnWeightVector",print_details=true)
    @assert isdir(directory)
    @assert in(methodForSplittingPointsSelection, globalConstAllowableMethodsForDefineCandidates)
	(df_prepped, sett) = prepareDF!(dfin, treat_as_categorical_variable=treat_as_categorical_variable, numcol=numcol, denomcol=denomcol, weightcol=weightcol, trnvalcol=trnvalcol, valpct=valpct, keycol=keycol, independent_vars=independent_vars,print_details=print_details);
	# set this to false
		sett.saveJLDFile = false

	fn = joinpath(directory, "DTMResult.csv")
	dtmtable = prep_data_from_df(df_prepped, sett, fn, methodForSplittingPointsSelection=methodForSplittingPointsSelection,print_details = print_details);
    sett.print_details = print_details
	return dtmtable, sett, df_prepped
end

function prepareDF!(dfin::DataFrame;treat_as_categorical_variable::Vector{String}=Vector{String}(),numcol::String="",denomcol::String="",weightcol::String="",trnvalcol::String="",valpct::Float64=0.3,keycol::String="",independent_vars::Vector{String}=Vector{String}(),print_details=true)
    sz = size(dfin, 1)
    dfnames = propertynames(dfin)

    @assert ((valpct < 1) && (valpct > 0))
    @assert length(numcol) > 0 "Error: No argument numcol provided."
    @assert in(Symbol(numcol), dfnames) "Numerator column not found. We searched for the column $(numcol) in the vector $(string.(dfnames))"
    numDA = dfin[!,Symbol(numcol)]

    if denomcol == ""
        print_details && @info "Using constant denominator for each row (.==1)" # of the form ones(size(yourIntputDataFrame,1))"
        denomDA = ones(sz)
    else
        @assert in(Symbol(denomcol), dfnames) "Denominator column not found. We searched for the column $(denomcol) in the vector $(string.(dfnames))"
        denomDA = dfin[!,Symbol(denomcol)]
    end

    if weightcol == ""
        print_details && @info "Using constant weight for each row (.==1)" # of the form ones(size(yourIntputDataFrame,1))"
        weightDA = ones(sz)
    else
        @assert in(Symbol(weightcol), dfnames) "Weight column not found. We searched for the column $(weightcol) in the vector $(string.(dfnames))"
        weightDA = dfin[!,Symbol(weightcol)]
    end

    if trnvalcol == ""
        print_details && @info "Using random choice of Training-Validation column with proportion $(valpct) for validation"
        trnvalDA = map(x->x > valpct ? 1.0 : 0.0, rand(sz))
    else
        @assert in(Symbol(trnvalcol), dfnames) "Training-Validation column not found. We searched for the column $(trnvalcol) in the vector $(string.(dfnames))"
        trnvalDA = dfin[!,Symbol(trnvalcol)]
        # tbd ensure trnvalDA has only 0 and 1 entries
        tmp_found_vals = float.(sort(unique(trnvalDA))[:])
        tmp_expected_vals = Float64[0 1][:]
        if !isequal(tmp_found_vals, tmp_expected_vals)
            @show tmp_found_vals
            @assert false  "Error: Training-Validation column contained entries which are not equal to 1 or 0. Abort."
        end
        @assert isequal(tmp_found_vals, tmp_expected_vals)
    end

    if keycol == ""
        print_details && @info "Using canonical choice of identifier column 1:size(yourIntputDataFrame,1)"
        irkeyDA = collect(1:sz)
    else
        @assert in(Symbol(keycol), dfnames) "Identifier column not found. We searched for the column $(keycol) in the vector $(string.(dfnames))"
        irkeyDA = dfin[!,Symbol(keycol)]
    end

# remove union types (especially missing if there are any!)
    removeUnionTypes!(dfin, independent_vars)

# construct resulting DF
# requirements on the first 5 columns: irkey (=string identifier), numerator, denominator, weight, training_validation_bool (0 or 1)
    # 'main' columns
    dfres = DataFrame()
    dfres[!,:irkey] = irkeyDA
    dfres[!,:numerator] = numDA
    dfres[!,:denominator] = denomDA
    dfres[!,:weight] = weightDA
    dfres[!,:training_validation_bool] = trnvalDA

# explanatory columns
    if length(independent_vars) == 0
        independent_vars = string.(dfnames)
    end
    # delete main columns if they appear in the indep columns
    for remove_this_col in [numcol denomcol weightcol trnvalcol keycol][:]
        deleteat!(independent_vars, findall((in)([remove_this_col]), independent_vars))
    end
    n_indep = length(independent_vars)

    for x in Symbol.(treat_as_categorical_variable)
        if !in(x, dfnames)
            @show x
            @show dfnames
            error("DTM: Column $(x) not found in dfnames. You provided that column as part of treat_as_categorical_variable.")
        end
    end
    @assert issubset(Symbol.(treat_as_categorical_variable), dfnames)
    # n_char_features=0
    char_features = Vector{Symbol}()
    num_features = Vector{Symbol}()
    for nn = 1:n_indep
        this_is_a_string = false
        this_name = Symbol(independent_vars[nn])
        @assert in(Symbol(this_name), dfnames) "Error: explanatory column $(this_name) not found in data."
        if in(this_name, Symbol.(treat_as_categorical_variable))
            # change variable type in original dataframe
            if !is_categorical_column(dfin[!,this_name], this_name)
                print_details && @info "DTM: Converting the column $(this_name) from $(eltype(dfin[!,this_name])) to String."
                dfin[!,this_name] = string.(dfin[!,this_name])
                this_is_a_string = true
            else
                # @info "DTM: Variable $(this_name) is already categorical in the input data. No type conversion performed."
            end
        end
        if is_categorical_column(dfin[!,this_name], this_name)
            push!(char_features, this_name)
        else
            push!(num_features, this_name)
        end

    end

	@assert (length(char_features) + length(num_features) == n_indep) # this should always be the case!
	n_char = length(char_features)
	n_num = length(num_features)

    for x in vcat(num_features, char_features)
        dfres[!, x] = dfin[!, x]
    end

    this_sett = ModelSettings()

    this_sett.df_name_vector = string.(propertynames(dfres)[1 + global_const_shift_cols:end])
    updateSettingsMod!(this_sett, number_of_num_features=n_num, number_of_char_features=n_char)
    if print_details
        @info "Data initialization finished:"
        @show size(dfres)
        @show n_num, n_char
        @show char_features
        @show num_features
    end
    return dfres, this_sett
end

"""
tries to convert all types of Union{Missing,T} to T
"""
function removeUnionTypes!(dfin, independent_vars::Vector{String})
    for v in independent_vars
        vsymb = Symbol(v)
        col = dfin[!,vsymb]
        elt = eltype(col)
        typeOfelt = typeof(elt)
        if typeOfelt != DataType
            @info "DTM: Type of the eltype of $(v) is not a DataType but $(typeOfelt). The type of $(v) is $(elt)"
            @info "DTM: Trying to convert the type..."
            try
                a = elt.a
                b = elt.b
                tragetT = ifelse(a == Missing, b, a)
                dfin[vsymb] = convert(Vector{tragetT}, x)
                return nothing
            catch
                @warn("DTM: Type conversion failed. The model will likely not run as intended.")
            end
        end
    end
    return nothing
end

function is_categorical_column(x, nm)
    # @warn("BK need to ensure that there are NO union types!, especially we do not want the missing tpiye (for now)!")
    elt = eltype(x)
    typeOfElt = typeof(elt)
    if typeOfElt != DataType
        # @show x[1:10]
        error("DTM: The type of variable $(nm) is not of type 'DataType' but '$(typeOfElt)'. The type of the variable is $(elt). Currently only DataTypes are supported.")
    end
    return elt <: AbstractString
end

function prep_data_from_df(df_userinput::DataFrame, sett::ModelSettings, fn_with_ext::String;methodForSplittingPointsSelection::String="basedOnWeightVector",print_details = true)
    datafilename, ext = splitext(fn_with_ext) # actually one can provide fn_with_ext="c:\\temp\\my folder\\out" (so no extension is necessary)
    datafolder, outfileStringOnly = splitdir(datafilename)
    if length(outfileStringOnly) == 0
        error("DTM: fn_with_ext should be of the form C:\\some\\folder\\my_output.txt (the extension is irrelevant) \n You provided: $(fn_with_ext) \nAbort.")
    end
    dfIndata = df_userinput
    print_details && println("Preparing Data for analysis...")

    @assert check_for_missing_data(dfIndata, global_const_shift_cols) "Error dataset contains missing (NA) values! ABORT."

    key = convert(Vector{String}, string.(dfIndata[!,:irkey]))
    trn_val_idx = convert(Vector{UInt8}, UInt8.(dfIndata[!,:training_validation_bool]))
    @assert sort(unique(trn_val_idx)) == [0x00,0x01] "DTM: training validation column contains values not equal to 0 or 1."
    # construct data
    numerator = convert(Array{Float64,1}, dfIndata[!,:numerator])
    denominator = convert(Array{Float64,1}, dfIndata[!,:denominator])
    weight = convert(Array{Float64,1}, dfIndata[!,:weight])

    count_neg_denom = sum(denominator .<= 0)
    if count_neg_denom > 0
        @warn("DTM: Denominator contains observations with values <= 0.0")
        tot_sz = length(denominator)
        @warn("DTM: Negative rowcount: $(count_neg_denom) of $(tot_sz). I.e. a proportion of $(count_neg_denom / tot_sz)")
    end
    if any(denominator .== 0)
        @warn("denominator[trn] contains observations with values 0.0")
      # foundobs=findfirst(denominator,0.0)
        foundobs = something(findfirst(isequal(0.0), denominator), 0)
        foundk = key[foundobs]
        println("First observation: key=$(foundk), rownumber_trn=$(foundobs), value=$(denominator[foundobs])\r\nDenominator needs to be nonzero (negative values are allowed)\r\nConsider discarding or changing these values/rows.")
        ignoreZeroDenominatorValues = sett.ignoreZeroDenominatorValues
        if !ignoreZeroDenominatorValues
            error("DTM: Abort.")
        else
            @warn("DTM: Continuing data prep and modelling with zero denomintor observations. This is EXPERIMENTAL. Algorithm may fail.")
        end
    end
    strNegativeRatioWarning = "Negative Ratios may lead to errors during the modelling process for models which rely on multiplicative residuals! \nIt is suggested that you adjust the data such that no negative values occur!"
    if any(x->x < 0.0, denominator)
        @warn("DTM: There are training obsevations with negative values in the denominator column!")
        @warn(strNegativeRatioWarning)
    end
    # obsratiotrn=numeratortrn./denominatortrn
    if any(numerator .< 0)
        @warn("DTM: There are negative numerator values!")
        @warn(strNegativeRatioWarning)
        if sett.calculatePoissonError
            @warn("DTM: Setting calculatePoissonError to false!")
            sett.calculatePoissonError = false
        end
        if typeof(sett.crit) == PoissonDevianceSplit
            error("DTM: Negative numerator values are not allowed for Poisson Deviance! Abort. \n Crit = $(sett.critt)")
        end
    end

    df_names = sett.df_name_vector # propertynames(dfIndata)
    # this DataFrame will contain both categorical and numerical features
    features = DataFrame()

    print_details && print("Preparing numeric variables...")
    # this function modifies features -> the num features are added as pdas!
    candMatWOMaxValues = add_coded_numdata!(dfIndata, sett, trn_val_idx, sett.maxSplittingPoints, features, weight, methodForSplittingPointsSelection)
    print_details && println(" done.")
    # IMPORTANT convention  numerical data 'comes first' (i.e. first x columns are numerical), then categorical
    print_details && print("Preparing character variables...")
    for i = 1:sett.number_of_char_features
        if !(eltype(dfIndata[!,global_const_shift_cols + i + sett.number_of_num_features]) <: AbstractString)
            @show eltype(dfIndata[!,global_const_shift_cols + i + sett.number_of_num_features])
            @show dfIndata[1:min(10, size(dfIndata, 1)),global_const_shift_cols + i + sett.number_of_num_features]
            error("DTM: Character variable $(i) = $(sett.df_name_vector[i + sett.number_of_num_features]) is not <:AbstractString in the dataframe.")
            # @warn("It may be that the variable was exported as character (by SAS) even though it is infact an integer. Please check!")
        end
        try
            colnumber = global_const_shift_cols + sett.number_of_num_features + i
            thisname = Symbol(df_names[i + sett.number_of_num_features])
            thiscol_as_utf8 = dfIndata[!,thisname]
            if eltype(thiscol_as_utf8) != String
                thiscol_as_utf8 = convert(Array{String,1}, thiscol_as_utf8)
            end
            features[!,thisname] = DecisionTrees.PooledArraysDTM.PooledArray(thiscol_as_utf8)
            if length(features[!,thisname].pool) > 255
                @show nlevels = length(features[!,thisname].pool)
                @warn("DTM: Variable $(string(thisname)) has more than 255 levels, namely $(nlevels)\nThe tree may take very long to be constructed.")
            end
        catch this_error
            @show this_error
            error("DTM: Unable to initialize character variable $(i) = $(sett.df_name_vector[i + sett.number_of_num_features]) (see error message above).")
        end
    end
    mappings = deepcopy(map(i->features[!,i].pool, sett.number_of_num_features + 1:size(features, 2)))

    print_details && println(" done.")
    wlo, whi = extrema(weight)
    if wlo != whi
        print_details && print("Max weight is $(whi), min weight is $(wlo)")
        # @info("Currently the automatic choice of splitting points does not consider the weight distribution!")
    end

    pools = map(i->features[!,i].pool, 1:size(features, 2))
    pool_lengths = length.(pools)
    for i = 1:length(pool_lengths)
        if pool_lengths[i] == 1
            @info "DTM: Column $(i) = $(sett.df_name_vector[i]) has zero splitting points."
        end
    end

    # "Check if string pools are valid utf8 characters. This is specifically important when data is read in a 'wrong' manner due to encoding issues. The PyCall functions which write the Excel files will fail in such cases"
    for i = 1:length(pool_lengths)
    # @show pools
        if eltype(pools[i]) <: Number
            # nothing to do, numbers are always valid
        else
            vld = .!(isvalid.(pools[i]))
            # vld=isvalid.(pools[i])
            if any(vld)
                @warn("DTM: The variable $(sett.df_name_vector[i]) contains invalid characters for some values (see below). Please check if your data was read and encoded properly.\r\nDTM will try and remove the invalid characters from the data. You should clean the data prior to invoking DTM.")
                @show pools[i][vld]
                for zz = 1:length(vld)
                    if vld[zz]
                        before = pools[i][zz]
                        after = cleanString(pools[i][zz])
                        println("DTM: $(before) -> $(after)")
                        pools[i][zz] = after
                    end
                end
            end
        end
    end

    trnidx, validx = createIntegerIndices(trn_val_idx)
    dtmtable = DTMTable(key, trnidx, validx, numerator, denominator, weight, features, candMatWOMaxValues, mappings)

    if sett.saveJLDFile
        jldfile = string(fileroot(datafilename), ".jld2")
        print_details && println("Saving prepped data to file:\n $(jldfile)")
        isfile(jldfile) && rm(jldfile)
        # @time save(jldfile,"dtmtable",dtmtable,"candMatWOMaxValues",candMatWOMaxValues,"features",features,"mappings",mappings,"key",key,"trn_val_idx",trn_val_idx,"numerator",numerator,"denominator",denominator,"weight",weight,"oldsettings",sett)
        @time save(jldfile, "dtmtable", dtmtable, "candMatWOMaxValues", candMatWOMaxValues, "mappings", mappings, "sett", sett)
        print_details && println("JLD2 file saved. Starting modelling... \n")
    end

    return dtmtable
end

"""
This is the core modelling function of DecisionTrees

    run_model_actual(dtmtable::DTMTable,input_setttings::ModelSettings,fn::String)
"""
function run_model_actual(dtmtable::DTMTable, input_setttings::ModelSettings, fn::String)
# this function is called with prepped julia data, this is the core modelling function (all previous ones are for preparational tasks only)
    t0RunModel0 = time_ns()
    sett = deepcopy(input_setttings)
    @assert sett.chosen_apply_tree_fn == "apply_tree_by_leaf" # currently this is the default choice. Consider the code in boosting.jl and bagging.jl -> the apply tree fn is currently hardcoded
    if sett.calculateGini
        @warn("DTM: Note that Gini and RSS metrics are experimental and may need review. Also it needs to be clarified if the metrics are evaluated only for the numerator or for the ratio=numerator/denominator.")
    end
    x = checkIfSettingsAreValid(input_setttings)
    if !(x == nothing)
        @warn "DTM: You may have provided invalid or inconsistent model settings. The model run may fail."
    end
    if (sett.fitForStatsAndCharts == "smoothedPerScore") && (sett.smoothEstimates != "1")
        @info("DTM: setting sett.smoothEstimates to 1, because sett.fitForStatsAndCharts=$(sett.fitForStatsAndCharts)")
        sett.smoothEstimates = "1"
    end
    if sett.writeIterationMatrix
        if !sett.prroduceEstAndLeafMatrices
            @info "DTM: writeIterationMatrix=$(sett.writeIterationMatrix). The estimate matrices are required for this. Setting sett.prroduceEstAndLeafMatrices to true."
        end
    end
    if sett.boolNumeratorStats
        if !sett.prroduceEstAndLeafMatrices
            @info "DTM: User has requested NumeratorStats (boolNumeratorStats=$(sett.boolNumeratorStats)). The estimate matrices are required for these statistics. Setting prroduceEstAndLeafMatrices to true."
            sett.prroduceEstAndLeafMatrices = true
        # @show sett.boolNumeratorStats,sett.prroduceEstAndLeafMatrices
        end
    end

    if (typeof(sett.crit) == mseSplit) || (typeof(sett.crit) == sseSplit) || (typeof(sett.crit) == NormalDevianceDifferenceToMeanFitWEIGHTEDSplit)
        if any(iszero, dtmtable.denominator) # must be non zero (ideally even positive)
            error("DTM: Denominator has zero values. This is not allowed for crit=$(sett.crit)")
        end
        if any(dtmtable.denominator .< 0) # must be non zero (ideally even positive)
            @warn("DTM: Denominator has negative values. You have specified crit=$(sett.crit). This may lead to unintended results as the algorithm divides by the denominator (and it is used as weight!)")
        end
    end

    path_and_fn_wo_extension, ext = splitext(fn)
# check for file extension. This does not seem to be required... (then again we should ensure that the user provides a filename stump and not only a folder name)
    if in(lowercase(ext), ["",".csv",".txt",".sas",".jl",".pdf"])
    else
        @show fn
        @show path_and_fn_wo_extension, ext
        error("DTM: Unexpected file name extension $(ext). You provided the $(fn). Please provide a file name extension such as .txt")
    end
    datafolder, outfileStringOnly = splitdir(path_and_fn_wo_extension)
    @assert isdir(datafolder) "DTM: The specified output folder $(datafolder) does not exist. Abort."

    filelistWithFilesToBeZipped = String[]
    csharp_code_file = string(path_and_fn_wo_extension, ".cs")
    vba_code_file = string(datafolder, "\\", outfileStringOnly, ".vba.txt")
    sas_code_file = string(path_and_fn_wo_extension, ".sas")
    tree_file = string(path_and_fn_wo_extension, ".txt")
    dot_graph_TXT_file = string(path_and_fn_wo_extension, ".dot.txt")
    dot_graph_visualization_file = string(path_and_fn_wo_extension, ".dot.pdf")
    this_outfile = convert(String, string(path_and_fn_wo_extension, ".csv"))
    statsfile = string(path_and_fn_wo_extension, ".stats.csv")
    jldresultsfile = string(path_and_fn_wo_extension, ".jld2")
    statsfileExcel = string(path_and_fn_wo_extension, ".xlsx")

# for now trnindx and validx should always be sorted!
    if !issorted(dtmtable.trnidx)
        @info "DTM: trnidx was not sorted. Sorting trnindex"
        sort!(dtmtable.trnidx)
    end
    if !issorted(dtmtable.validx)
        @info "DTM: validx was not sorted. Sorting validx"
        sort!(dtmtable.validx)
    end

    key = dtmtable.key
    trnidx = dtmtable.trnidx
    validx = dtmtable.validx
    features = dtmtable.features
    weight = dtmtable.weight
    numerator = dtmtable.numerator
    denominator = dtmtable.denominator

# trnidx_one_zero_full_length is added to the output file
    @assert issorted(trnidx)
    trnidx_one_zero_full_length = map(x->length(searchsorted(trnidx, x)), 1:length(key))

# define 'version', we store the sha1 hash of the current commit in sett.version
    sett.version = "--" # get_sha1() #need to fix this/ find out how to read package hash in Julia code

# initialize random number generator
    # we may want to make this independent from the data (trn), then again different data should/will lead to a different result.
# hash of DF is not working properly, thus we need to iteration over the columns of the DF (this can be simpplified later on.)
    s00 = hash(trnidx, hash(validx, hash(sett.seed, hash(numerator, hash(denominator, hash(weight))))))
    for x = 1:size(features, 2)
        s00 = hash(features[!,x], s00)
    end
    srandInt = floor(Int, 1 / 3 * hash(931, s00))
    Random.seed!(srandInt)

    if sett.subsampling_prop < 1.0
        @warn("BK: subsampling_prop <1. This might be slow in the current implementation!")
    end

# there are at least two ways to derive estimates for hold out data: either loop through the leaves of the tree or loop over the rows of the validation data setting
# also there are two types of hold out estimates: either the ones derived from training data (more meaningful case) or the ones derived from validation labels
    sumwtrn = sum(view(weight, trnidx))
    sumwval = sum(view(weight, validx))
    minweighttmp = sett.minWeight < 0.0 ? sumwtrn * (-max(-1.0, sett.minWeight)) : sett.minWeight
    @assert minweighttmp >= 0
    sumwtrn >= 2 * minweighttmp || @warn("DTM: Specified minweight is larger than half of the total training weight. No split is possible!")

    someInteger = sumwtrn / minweighttmp
    if someInteger > 100000
        @warn("DTM: Total training weight is: $(sumwtrn) and you specified minWeight=$(minweighttmp). The tree might become very large")
    end

    prnt = sett.print_details
    general_settings = convert(String, string("Write tree to txt file: $(sett.writeTree), statsByVariables=$(join(sett.statsByVariables, ','))"))
# general_settings=convert(String,string(general_settings,))
    char_levels = map(x->length(features[!,x].pool), sett.number_of_num_features + 1:sett.number_of_num_features + sett.number_of_char_features)
    num_levels = map(x->length(features[!,x].pool), 1:sett.number_of_num_features)
    if length(char_levels) > 0
        max_c0 = maximum(char_levels)
    else
        max_c0 = 0
    end
    if length(num_levels) > 0
        max_n0 = maximum(num_levels)
    else
        max_n0 = 0
    end
    max_levels_uint8 = typemax(UInt8)
    if max(max_c0, max_n0) > max_levels_uint8
        @info("DTM: At least one variable has more than $(max_levels_uint8) potential splitting points. \r\nThis is experimental and not yet fully tested.")
    end
    @assert max_c0 < nLevelsThreshold "A variable has too many levels (i.e. more than $(nLevelsThreshold)). You can try and increase this threshold but the algorithm may take quite long for the modelling"
    @assert max_n0 < nLevelsThreshold "A variable has too many levels (i.e. more than $(nLevelsThreshold)). You can try and increase this threshold but the algorithm may take quite long for the modelling"
    if size(sett.moderationvector, 1) > 1;if (sett.adaptiveLearningRate != 1.0 && sett.adaptiveLearningRate != 0.0);println("######################################## \nWarning: (bk) Adaptive Learning has been disabled as a moderation vector has been provided. \n######################################## ");end;sett.adaptiveLearningRate = 1.0;end;
    if prnt
        println("")
        println(stars)
        println("Time:\t\t", Dates.now())
        println(stars)
        println("---General Settings------------------------------------------------------------")
        println(general_settings)
        print_some_settings(sett, ["writeSasCode","writeIterationMatrix","writeResult","writeStatistics","boolCreateZipFile","writeCsharpCode","statsRandomByVariable","saveJLDFile"])
        println("---Data Summary----------------------------------------------------------------")
        println("Number of records - Trn: $(length(trnidx)) Val: $(length(validx)) Val Proportion: $(round(length(validx) / (length(trnidx) + length(validx)), sigdigits=4))")
        println("Weight - Trn: $(round(sumwtrn, sigdigits=6)) Val: $(round(sumwval, sigdigits=6)) Val Proportion: $(round(sumwval / (sumwtrn + sumwval), sigdigits=4))")
        println("Number of numeric/character variables: $(sett.number_of_num_features)/$(sett.number_of_char_features)")
    end
    if size(num_levels, 1) > 0
        prnt && println(string("Number of levels for numeric variables: (Maximal # of splitting points: $(sett.maxSplittingPoints))"))
        num_level_string = join(sort(num_levels, rev=true), ",")
        prnt && println(string(" ", num_level_string))
    else
        prnt && println("The data does not contain any numeric attributes.")
        num_level_string = ""
    end
    if size(char_levels, 1) > 0
        prnt && println(string("Number of levels for character variables: "))
        # println(arr_to_str(sort(char_levels, rev=true)))
        char_level_string = join(sort(char_levels, rev=true), ",")
        prnt && println(string(" ", char_level_string))
    else
        char_level_string = ""
        prnt && println("The data does not contain any character attributes.")
    end
    prnt && println("catSortByThreshold: $(sett.catSortByThreshold), catSortBy: $(replace(string(sett.catSortBy), "DecisionTrees." => "")), smoothEstimates: $(sett.smoothEstimates), deriveFitPerScoreFromObservedRatios: $(sett.deriveFitPerScoreFromObservedRatios)")

  # create a string which contains all model settings (this will be written to output file(s))
    model_setting_string = convert(String, string("Number of records - Trn: $(length(trnidx)) Val: $(sum(validx)) \nNumber of levels for numeric variables: (Maximal # of splitting points: $(sett.maxSplittingPoints)) "))
    model_setting_string = convert(String, string(model_setting_string, "\n", num_level_string, "\nNumber of levels for character variables: \n", char_level_string))
    model_setting_string = convert(String, string(model_setting_string, "\n", general_settings))
    model_setting_string = convert(String, string(model_setting_string, "\n", "Type: $(sett.model_type) \nMinweight: $(sett.minWeight) \nRnd: $(sett.randomw) \nNumMaxSplitPoints: $(sett.maxSplittingPoints) \nCriterion: $(replace(string(sett.crit), "DecisionTrees." => "")) \nIterations: $(sett.iterations) \nModeration Vector: $(join(sett.moderationvector, ",")) \nNScores: $(sett.nScores) \nBoolStartAtMean: $(sett.startAtMean) "))
    model_setting_string = convert(String, string(model_setting_string, "\n", "Datafolder= \n", datafolder, "\nOutfilename= \n", outfileStringOnly, " "))
    model_setting_string = convert(String, string(model_setting_string, "\n\n", "CSV column names (in the order in which Julia processes them):\n", join(sett.df_name_vector, ",")))
      # permutedims is "transpose" in julia 0.5
      # model_setting_string=convert(String,string(model_setting_string, "\n\n", "Names:Levels: \n",join(permutedims(names_and_levels, (2, 1)),":")))
    model_setting_string = convert(String, string(model_setting_string, "\n", "Folder of Julia code (decisiontree.jl): \n", dirname(@__FILE__)))
      # model_setting_string=convert(String,string(model_setting_string, "\n", "Julia started at: \n not_available "))
    model_setting_string = convert(String, string(model_setting_string, "\n", "Julia (Modelling) start time: \n$(Dates.now()) "))

    prnt && println("---Model Settings--------------------------------------------------------------")
    if sett.boolRankOptimization && "build_tree" == sett.model_type
        error("Error: (bk) I do not think that _RankOpt is currently working with a single tree....")
    end

    local resulting_model # ,resultEnsemble
    # resulting_model=EmptyEnsemble()
    # local xlData,estimatedRatio,vectorOfLeafNumbers,vectorOfLeafArrays,rawObservedRatioPerScore,est_matrixFromScores,stats,estimateUnsmoothed,estimateSmoothed,estimateFromRelativities,estimatesPerScoreForCode

    if "build_tree" == sett.model_type
        if prnt
            println("Type: $(sett.model_type), Minweight: $(sett.minWeight), Rnd: $(sett.randomw)")
            println("NumMaxSplitPoints: $(sett.maxSplittingPoints), Criterion: $(replace(string(sett.crit), "DecisionTrees." => ""))")
            println(stars)
            println(string(""))
            println(string("Building Tree... Time: $(Dates.now())"))
        end
        obsvalcols = 1
        if sett.randomw > 0
            @warn("Running single tree with positive random weight:$(sett.randomw)")
        end

    # build a simple tree
        fitted_values_tree = zero(numerator)
        T_Uint8_or_UInt16 = find_max_type(features)
        if prnt
            @time tree = build_tree!(trnidx, validx, dtmtable.candMatWOMaxValues, dtmtable.mappings, sett, numerator, denominator, weight, features, fitted_values_tree, T_Uint8_or_UInt16)
        else
            tree = build_tree!(trnidx, validx, dtmtable.candMatWOMaxValues, dtmtable.mappings, sett, numerator, denominator, weight, features, fitted_values_tree, T_Uint8_or_UInt16)
        end

        resulting_model = tree
        prnt && println("Deriving Trn Estimates... Time: $(Dates.now())")
        leaves_of_tree = create_leaves_array(tree)
        leaf_number_vector = zeros(Int, length(fitted_values_tree))
        # todo/tbd possibly improve this by only applying the function once!
        # notably fitted_values_tree is overwritten here, however the values do NOT change.
        apply_tree_by_leaf!(fitted_values_tree, leaf_number_vector, trnidx, tree.rootnode, features)
        if length(validx) > 0
            apply_tree_by_leaf!(fitted_values_tree, leaf_number_vector, validx, tree.rootnode, features)
        end
        prnt && println("Preparing output... Time: $(Dates.now())")
         # 1-dim Variable Importance
        var_imp1d_str_arr, var_imp2d_str_arr, onedimintvec_unused, twodimintvec_unused = variable_importance(leaves_of_tree, sett.df_name_vector, sett.number_of_num_features)
        nleaves = Int(size(leaves_of_tree, 1))
        res = hcat(key, trnidx_one_zero_full_length, fitted_values_tree)
        # Create output statistics
            # this is not done in an efficient manner yet..... we are copying data to get est[trnidx] etc...
        @time xlData, overallstats = createSingleTreeExcel(trnidx, validx, sett, tree, leaves_of_tree, fitted_values_tree, leaf_number_vector, numerator, denominator, weight)
        resulting_model.exceldata = xlData
        if sett.writeStatistics
            writeStatisticsFile!(statsfileExcel, xlData, filelistWithFilesToBeZipped)
        end
        z = permutedims(overallstats, (2, 1))
        dfStats = DataFrame(convert(Array{Float64,2}, z[2:size(z, 1),:]),Symbol[Symbol(x) for x in view(z, 1, :)])
        DataFrames.rename!(dfStats, Symbol[Symbol(x) for x in view(z, 1, :)])
        resulting_model.modelstats = dfStats
        if sett.saveResultAsJLDFile
            println("Saving results to jld2 file: \n $(jldresultsfile)")
            isfile(jldresultsfile) && rm(jldresultsfile)
            @time JLD2.@save jldresultsfile xlData res leaves_of_tree trnidx validx fitted_values_tree tree var_imp1d_str_arr var_imp2d_str_arr onedimintvec_unused twodimintvec_unused
            push!(filelistWithFilesToBeZipped, jldresultsfile)
        end

         # print_tree(tree)
        dot_graph = graph(resulting_model)
        model_setting_string = convert(String, string(model_setting_string, "\n", "Julia end time: \n$(Dates.now()) \n"))
        if sett.write_dot_graph
            println("Writing DOT tree structure to file: \n $(dot_graph_TXT_file)")
            isfile(dot_graph_TXT_file) && rm(dot_graph_TXT_file)
            f = open(dot_graph_TXT_file, "w");
            write(f, "\n\n", dot_graph, "\n\n");
            close(f);
            draw_dot_graph(sett.graphvizexecutable, dot_graph_TXT_file, dot_graph_visualization_file)
            push!(filelistWithFilesToBeZipped, dot_graph_TXT_file)
            isfile(dot_graph_visualization_file) && push!(filelistWithFilesToBeZipped, dot_graph_visualization_file)
        end
        if sett.writeTree
            # write settings
            println("Writing tree structure to file: \n $(tree_file)")
            isfile(tree_file) && rm(tree_file)
            f = open(tree_file, "w")
            write(f, model_setting_string)
            write(f, "\n\n", dot_graph, "\n\n")
            close(f);
            write_tree(dtmtable.candMatWOMaxValues, tree, sett.number_of_num_features, var_imp1d_str_arr, var_imp2d_str_arr, 0, tree_file, sett.df_name_vector, dtmtable.mappings)
            push!(filelistWithFilesToBeZipped, tree_file)
        end
        if sett.writeSasCode
                # write SAS Code
            if isa(tree.rootnode, Leaf)
                @warn("DTM: Tree is a single leaf. No SAS Code was produced.")
            else
                println("Writing SAS Code: \n $(sas_code_file)")
                isfile(sas_code_file) && rm(sas_code_file)
                writeSasCode(dtmtable.candMatWOMaxValues, tree, sett.number_of_num_features, sas_code_file, sett.df_name_vector, model_setting_string, dtmtable.mappings, 1.0)
                push!(filelistWithFilesToBeZipped, sas_code_file)
            end
        end
        if sett.writeResult
            println("Exporting Data: \n $(this_outfile)")
            isfile(this_outfile) && rm(this_outfile)
            DelimitedFiles.writedlm(this_outfile, res, ',')
            push!(filelistWithFilesToBeZipped, this_outfile)
        end
        # return filelistWithFilesToBeZipped,resulting_model
    end  # end build tree

    # output for boosting & bagging
    if in(sett.model_type, ["boosted_tree","bagged_tree"])

        if "boosted_tree" == sett.model_type
            @timeConditional(sett.print_details,begin
                xlData, estimatedRatio, vectorOfLeafNumbers, vectorOfLeafArrays, rawObservedRatioPerScore, est_matrixFromScores, stats, estimateUnsmoothed, estimateSmoothed, estimateFromRelativities, resulting_model = boosted_tree(dtmtable, sett)
            end)
            model_setting_string = convert(String, string(model_setting_string, "\n", "Julia end time: \n$(Dates.now()) \n "))
            # this line is for  the sas,vba and csharp code
            estimatesPerScoreForCode = sett.smoothEstimates == "0" ? rawObservedRatioPerScore : resulting_model.ScoreToSmoothedEstimate
        end

        if sett.writeSasCode
                # write SAS Code
            println("Writing SAS Code: \n $(sas_code_file)")
            isfile(sas_code_file) && rm(sas_code_file)
            if sett.roptForcedPremIncr
                warn("This does currently not work")
            else
                writeSasCode(estimatesPerScoreForCode, dtmtable.candMatWOMaxValues, resulting_model, sett.number_of_num_features, sas_code_file, sett.df_name_vector, model_setting_string, dtmtable.mappings)
            end
            push!(filelistWithFilesToBeZipped, sas_code_file)
        end
        if sett.writeCsharpCode
            println("Writing C# Code: \n $(csharp_code_file)")
            isfile(csharp_code_file) && rm(csharp_code_file)
            writeCsharpCode(vectorOfLeafArrays, estimatesPerScoreForCode, dtmtable.candMatWOMaxValues, resulting_model, csharp_code_file, model_setting_string, dtmtable.mappings, 0, sett)
            push!(filelistWithFilesToBeZipped, csharp_code_file)
        end
        if sett.writeVbaCode
            println("Writing VBA Code: \n $(vba_code_file)")
            isfile(vba_code_file) && rm(vba_code_file)
            writeVbaCode(vectorOfLeafArrays, estimatesPerScoreForCode, dtmtable.candMatWOMaxValues, resulting_model, vba_code_file, model_setting_string, dtmtable.mappings, 0, sett)
            push!(filelistWithFilesToBeZipped, vba_code_file)
        end
       # end #boosting
        #=
            elseif "bagged_tree"==sett.model_type
                #bagging
                @timeConditional(sett.print_details,begin
                @warn("bagging is not yet updated for dtm2! This will most likely fail!")
                    xlData,estimatedRatio,vectorOfLeafNumbers,vectorOfLeafArrays,rawObservedRatioPerScore,est_matrixFromScores,stats,estimateUnsmoothed,estimateSmoothed,estimateFromRelativities,resulting_model=bagged_tree(dtmtable,sett)
                end)
                model_setting_string=convert(String,string(model_setting_string, "\n", "Julia end time: \n$(Dates.now()) \n "))
            #else
                #error("DTM: Unknown Model Type: $(sett.model_type) (Within Bagging/Boosting loop)")
            end #end bagging =#

    # after boosting bagging  export etc
    # here we are in the "section":     #if in(sett.model_type,["bagged_tree","boosted_tree"])
    # output for boosting & bagging#write tree

    # resulting_model=resultEnsemble

        if sett.writeResult
            res = hcat(key, trnidx_one_zero_full_length, resulting_model.scores, estimateUnsmoothed, estimateSmoothed, estimatedRatio)
        end
    # save results as *.JLD2 file
        if sett.saveResultAsJLDFile
            println("Saving results to jld2 file: \n $(jldresultsfile)")
            isfile(jldresultsfile) && rm(jldresultsfile)
            @time JLD2.@save jldresultsfile xlData dtmtable trnidx validx vectorOfLeafNumbers vectorOfLeafArrays rawObservedRatioPerScore est_matrixFromScores stats estimateUnsmoothed estimateSmoothed estimateFromRelativities resulting_model
            push!(filelistWithFilesToBeZipped, jldresultsfile)
        end

        if sett.writeTree
                # write settings
            println("Writing tree structure to file: \n $(tree_file)")
            isfile(tree_file) && rm(tree_file)
            f = open(tree_file, "w");write(f, model_setting_string);close(f);
            write_tree(dtmtable.candMatWOMaxValues, resulting_model, sett.number_of_num_features, 0, tree_file, sett.df_name_vector, dtmtable.mappings)
            push!(filelistWithFilesToBeZipped, tree_file)
        end
    # DOT Graphs for boosted trees
        if sett.write_dot_graph
            println("Writing DOT tree structure to file: \n $(dot_graph_TXT_file)")
            fnbase,ext = splitext(dot_graph_TXT_file)
            for i=1:sett.iterations
                #txt filename
                dot_graph_TXT_file = string(fnbase,".tree.",i,ext)
                #dot graph pdf filename
                dot_graph_visualization_file = string(fnbase,".tree.",i,".pdf")

                #write each tree
                tree = gettree(resulting_model,i)
                dot_graph = graph(tree)

                isfile(dot_graph_TXT_file) && rm(dot_graph_TXT_file)
                f = open(dot_graph_TXT_file, "w");
                    write(f, "\n\n", dot_graph, "\n\n");
                close(f);
                #dot_graph_visualization_file = "C:\\Users\\BERNHA~1.KON\\AppData\\Local\\Temp\\jl_ta4Y2r\\dtmresult.dot.pdf"
                draw_dot_graph(sett.graphvizexecutable, dot_graph_TXT_file, dot_graph_visualization_file)
                push!(filelistWithFilesToBeZipped, dot_graph_TXT_file)
                isfile(dot_graph_visualization_file) && push!(filelistWithFilesToBeZipped, dot_graph_visualization_file)
            end
        end
    # Write statistics
        if sett.writeStatistics
            writeStatisticsFile!(statsfileExcel, xlData, filelistWithFilesToBeZipped)
        end
    # Write result to CSV
        if sett.writeResult
            println("Exporting Data: \n $(this_outfile)")
            isfile(this_outfile) && rm(this_outfile)
            DelimitedFiles.writedlm(this_outfile, res, ',')
            push!(filelistWithFilesToBeZipped, this_outfile)
        end
    # write estimates matrix
        if sett.writeIterationMatrix    && (length(vectorOfLeafNumbers) < length(key))
            @warn "DTM: The iteration matrices wil not be written to file. length(key)=$(length(key)), but length(vectorOfLeafNumbers)=$(length(vectorOfLeafNumbers)). \r\n"
            @warn "DTM: Please check the values of sett.writeIterationMatrix and sett.prroduceEstAndLeafMatrices"
            sett.prroduceEstAndLeafMatrices || @warn("DTM: You may want to set sett.prroduceEstAndLeafMatrices to true")
        else
            if sett.writeIterationMatrix
                # Matrix with Leaf Numbers
                leafnrfile = string(path_and_fn_wo_extension, ".leafnumbers.csv")
                vectorOfLeafNumbersMOD = hcat(key, vectorOfLeafNumbers[:,2:end])
                println("Exporting LeafNumber Matrix: \n $(leafnrfile)")
                isfile(leafnrfile) && rm(leafnrfile)
                DelimitedFiles.writedlm(leafnrfile, vectorOfLeafNumbersMOD, ',')
                push!(filelistWithFilesToBeZipped, leafnrfile)
                # MATRIX 1
                estmat_outile2 = string(path_and_fn_wo_extension, "_iteration_matrixFromScores.csv")
                res_estmatrixFromScores = hcat(key, est_matrixFromScores)
                println("Exporting Estimates Matrix (based on Scores): \n $(estmat_outile2)")
                isfile(estmat_outile2) && rm(estmat_outile2)
                DelimitedFiles.writedlm(estmat_outile2, res_estmatrixFromScores, ',')
                push!(filelistWithFilesToBeZipped, estmat_outile2)
                # MATRIX 2
                estmat_outile = string(path_and_fn_wo_extension, "_iteration_matrix.csv")
                res_estmatrix = hcat(key, resulting_model.iterationmatrix)
                println("Exporting Estimates Matrix: \n $(estmat_outile)")
                isfile(estmat_outile) && rm(estmat_outile)
                DelimitedFiles.writedlm(estmat_outile, res_estmatrix, ',')
                push!(filelistWithFilesToBeZipped, estmat_outile)
            end
        end

    end # end of:  elseif in(sett.model_type,["boosted_tree","bagged_tree"])
# else
#    error("Unknown Model Type: $(sett.model_type)")
# end #end distinction between three model types

# this code applies to all model types
   # Create ZIP file
     # Push Log file to file list
    elapsed_until_this_point = (-t0RunModel0 + time_ns()) / 1e9
    prnt && println("Modelling finished. Time: $(Dates.now()) - Total time was $(round(elapsed_until_this_point, digits=1))s = $(round(elapsed_until_this_point / 60, digits=1))m")
    if sett.boolCreateZipFile
        logfile = string(path_and_fn_wo_extension, ".log")
        tmplogfile = string(path_and_fn_wo_extension, ".zip.log");isfile(tmplogfile) && rm(tmplogfile)
        try
            isfile(logfile) && isreadable(logfile) && cp(logfile, tmplogfile)
        catch error_while_copying_log
            @show error_while_copying_log
        end
        isfile(tmplogfile) && push!(filelistWithFilesToBeZipped, tmplogfile)
        zipFilename = string(path_and_fn_wo_extension, ".7z")
        println("Creating zip file with results \n $(zipFilename)")
        @time createZipFile(zipFilename, filelistWithFilesToBeZipped)
    end
  # result needs to have the same type, irrespective if we ran a tree or a boosted tree
  # return true

    return filelistWithFilesToBeZipped, resulting_model
end

function run_model(ARGS;nrows::Int=-1)
   # the first call of this function takes about 15 seconds (probably the first call of the module takes that much time....)
    failed_return_value = String["false"] # should be of the same type as the result of run_model
    try
        # first function which is called by run.jl:
        settingsFilename, dataFilename, datafolder, outfilename, outfileStringOnly = return_file_and_folders(ARGS)
        result_of_runmodel, resulting_model = run_model_main(settingsFilename, dataFilename, convert(String, datafolder), outfilename, outfileStringOnly, nrows=nrows)
        if typeof(result_of_runmodel) == Array{String,1}
            return result_of_runmodel
        else
            @warn("Unexpected Model result:")
            @show typeof(result_of_runmodel)
            @show result_of_runmodel
            dump(result_of_runmodel)
            return failed_return_value
        end
    catch model_error
        @show model_error
        @warn("An error occurred during modelling phase. See above.")
        return failed_return_value
    end
end

function dtm(dtmtable::DTMTable, sett::ModelSettings;file::String=joinpath(mktempdir(), defaultModelNameWtihCSVext))
    return run_model_actual(dtmtable::DTMTable, sett::ModelSettings, file::String)
end


text:
# This version assumes full matrices. Compute C-> α*A*B + β*C
@inline function mul!(C::Array{Float64,M}, A::Array{Float64,N}, B::Array{Float64,M}, α::Float64, β::Float64) where {M,N}
    C .*= β
    szB1 = size(B,1)
    szB2 = size(B,2)
    szC2 = size(C,2)
    szC1 = size(C,1)
    for j=1:szC2
        offset = (j-1)*szB1
        offset2 = (j-1)*szC1
        for i=1:szC1
            tmp = 0.0
            for k=1:szB1
                ind2 = offset + k
                tmp += A[i,k]*B[ind2]
            end
            ind = offset2 + i
            C[ind] += α*tmp
        end
    end
end

# This version assumes A is a sparse matrix. Compute C-> α*A*B + β*C
@inline function mul!(C::Array{Float64,M}, A::SparseMatrixCSC{Float64,Int64}, B::Array{Float64,M}, α::Float64, β::Float64) where {M}
    C .*= β
    szB1 = size(B,1)
    szB2 = size(B,2)
    szA2 = size(A,2)
    szC1 = size(C,1)
    csc_ind = 1
    for j=1:szA2
        offset = (j-1)*szB1
        # offset2 = (j-1)*szC1
        start = A.colptr[j]
        stop = A.colptr[j+1]
        len = stop-start
        for i=1:len
            row = A.rowval[csc_ind]
            val = A.nzval[csc_ind]
            for k=1:szB2
                ind = (k-1)*szC1 + row
                ind2 = (k-1)*szB1 + j
                C[ind] += α*val*B[ind2]
            end
            csc_ind += 1
        end
    end
end

ext: jl


text:
# MIT license
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE in the project root for full license information.

# All of the objects can be displayed with Vis.draw. Example:
# Vis.draw(hex4RGB())

colornames(colors) = uppercase.(x[1]*string((i-1)÷3 + 1) for (i,x) in pairs(colors))
export colornames

function clustercolors(lattice)
    elements = Repeat.clusterelements(lattice)
    colors = Vector{String}(undef, length(elements))

    for (i, element) in pairs(elements)
        colors[i] = pointcolor(element, lattice)
    end
    return colors
end
export clustercolors

function hex3RGB(scale::T = 1.0) where{T<:Real}
    lattice = hex3(scale)
    colors = clustercolors(lattice)
    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    return Repeat.ClusterWithProperties(hex3(scale), properties)
end
export hex3RGB

function hex4RGB(scale::T = 1.0) where{T<:Real}
    lattice = hex4(scale)
    colors = clustercolors(lattice)
    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    return Repeat.ClusterWithProperties(hex4(scale), properties)
end
export hex4RGB

function hex7RGB(scale::T = 1.0) where{T<:Real}
    lattice = hex7(scale)
    colors = clustercolors(lattice)
    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    return Repeat.ClusterWithProperties(hex7(scale), properties)
end
export hex7RGB

function hex9RGB(scale::T = 1.0) where{T<:Real}
    lattice = hex9(scale)
    colors = clustercolors(lattice)
    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    return Repeat.ClusterWithProperties(hex9(scale), properties)
end
export hex9RGB

function hex12RGB(scale::T = 1.0) where{T<:Real}

    lattice = hex12(scale)

    colors = clustercolors(lattice)

    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    return Repeat.ClusterWithProperties(lattice, properties)
end
export hex12RGB

function hex19RGB(scale::T = 1.0) where{T<:Real}
    lattice = hex19(scale)

    colors = clustercolors(lattice)

    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    # properties =  DataFrames.DataFrame(Color = colors)
    return Repeat.ClusterWithProperties(lattice, properties)
end
export hex19RGB

function hex37RGB(scale::T = 1.0) where{T<:Real}
    lattice = hexn(3,scale)
    colors = clustercolors(lattice)
    names = colornames(colors)
    properties =  DataFrames.DataFrame(Color=colors, Name=names)
    # properties =  DataFrames.DataFrame(Color = colors)
    return Repeat.ClusterWithProperties(lattice, properties)
end


text:
## Code to more easily create widgets

abstract QtWidget
abstract QtLayout <: QtWidget
AWidget = Union(QtWidget, PyObject)
Slot = Union(AWidget, Function)

## How to make these types programmatically?
type MainWindow <: QtWidget w::PyObject;  MainWindow(parent) = new(Qt[:QMainWindow](project(parent))) end
type DockWidget <: QtWidget w::PyObject;  DockWidget(parent) = new(Qt[:QDockWidget](project(parent))) end
type Widget     <: QtWidget w::PyObject;  Widget(parent)     = new(Qt[:QWidget](project(parent)))     end
type Icon       <: QtWidget w::PyObject;  Icon(parent)       = new(Qt[:QIcon](project(parent)))       end
type Label      <: QtWidget w::PyObject;  Label(parent)      = new(Qt[:QLabel](project(parent)))      end
type PushButton <: QtWidget w::PyObject;  PushButton(parent) = new(Qt[:QPushButton](project(parent))) end
type Slider     <: QtWidget w::PyObject;  Slider(parent)     = new(Qt[:QSlider](project(parent)))     end
type SpinBox    <: QtWidget w::PyObject;  SpinBox(parent)    = new(Qt[:QSpinBox](project(parent)))    end
type LineEdit   <: QtWidget w::PyObject;  LineEdit(parent)   = new(Qt[:QLineEdit](project(parent)))   end
type TextEdit   <: QtWidget w::PyObject;  TextEdit(parent)   = new(Qt[:QTextEdit](project(parent)))   end
type CheckBox   <: QtWidget w::PyObject;  CheckBox(parent)   = new(Qt[:QCheckBox](project(parent)))   end
type RadioButton<: QtWidget w::PyObject;  RadioButton(parent)= new(Qt[:QRadioButton](project(parent)))end
type ComboBox   <: QtWidget w::PyObject;  ComboBox(parent)   = new(Qt[:QComboBox](project(parent)))   end
type TableView  <: QtWidget w::PyObject;  TableView(parent)  = new(Qt[:QTableView](project(parent)))  end
## XXX
type Action     <: QtWidget w::PyObject;  Action(parent)     = new(Qt[:QAction](project(parent)))     end
type ActionGroup   <: QtWidget w::PyObject;  ActionGroup(parent)   = new(Qt[:QActionGroup](project(parent)))   end
type MenuBar    <: QtWidget w::PyObject;  MenuBar(parent)    = new(Qt[:QMenuBar](project(parent)))    end
type Menu       <: QtWidget w::PyObject;  Menu(parent)       = new(Qt[:QMenu](project(parent)))       end

type GraphicsScene <: QtWidget w::PyObject;  GraphicsScene(parent)       = new(Qt[:QGraphicsScene](project(parent)))       end
type GraphicsView <: QtWidget w::PyObject;  GraphicsView(parent)       = new(Qt[:QGraphicsView](project(parent)))       end

type VBoxLayout <: QtLayout w::PyObject;  VBoxLayout(parent) = new(Qt[:QVBoxLayout](project(parent))) end
type HBoxLayout <: QtLayout w::PyObject;  HBoxLayout(parent) = new(Qt[:QHBoxLayout](project(parent))) end
type FormLayout <: QtLayout w::PyObject;  FormLayout(parent) = new(Qt[:QFormLayout](project(parent))) end
type GridLayout <: QtLayout w::PyObject;  GridLayout(parent) = new(Qt[:QGridLayout](project(parent))) end
type TabWidget  <: QtLayout w::PyObject;  TabWidget(parent)  = new(Qt[:QTabWidget](project(parent))) end
type Splitter   <: QtLayout w::PyObject;  Splitter(parent)   = new(Qt[:QSplitter](project(parent))) end
type StackedLayout <: QtLayout w::PyObject;  StackedLayout(parent) = new(Qt[:QStackedLayout](project(parent))) end

type ButtonGroup<: QtWidget w::PyObject;  ButtonGroup(parent)= new(Qt[:QButtonGroup](project(parent)))end
type GroupBox   <: QtWidget w::PyObject;  GroupBox(parent)   = new(Qt[:QGroupBox](project(parent)))   end

type Pixmap     <: QtWidget w::PyObject;  Pixmap(parent)     = new(Qt[:QPixmap](project(parent)))    end

## inovke a method with args
## widget[:method](args)
function qinvoke(widget::AWidget, method::Symbol, args...; kwargs...)
    project(widget)[method](map(project, args)...; [(k,project(v)) for (k,v) in kwargs]...)
end
## invoke ala widget[:meth1]()[:meth2]()...[:methn](args...)
function qinvoke(widget::AWidget, methods::Vector{Symbol}, args...; kwargs...)
    f(widget, method) = widget[method]()
    if length(methods) > 1
        qinvoke(reduce(f, widget, methods[1:(end-1)]), methods[end], args..., kwargs...)
    else
        qinvoke(widget, methods[1], args..., kwargs...)
    end
end

import Base.getindex
function getindex(widget::QtWidget, i::Symbol)
    if isa(project(widget)[i], Function)
        (args...) -> qinvoke(widget, i, args...)
    else
        widget[i]
    end
end
getindex(widget::QtWidget, i::String) = project(widget)[string]

## We implement these methods for easier programming of common tasks
get_value(object::QtWidget) = XXX()
set_value(object::QtWidget, value) = XXX()
get_items(object::QtWidget) = XXX()
set_items(object::QtWidget, items) = XXX()
change_slot(object::QtWidget, slot::Slot) = XXX() ## we pass in a slot that expects the value to be passed (value) -> stuff or some PyObject

## size
width(object::QtWidget) = qinvoke(object, [:size, :width])
height(object::QtWidget) = qinvoke(object, [:size, :height])
get_size(object::QtWidget) = [get_width(object), get_size(object)]
#set_width(object::QtWidget, value::Int) = XXX()
#set_height(object::QtWidget, value::Int) = XXX()
set_size(object::QtWidget, width::Int, height::Int) = qinvoke(object, :resize, width, height)
set_size(object::QtWidget, size::Vector{Int}) = qinvoke(object, :resize, size[1], size[2])

get_enabled(object::QtWidget) = XXX()
set_enabled(object::QtWidget, value::Bool) = XXX()

get_editable(object::QtWidget) = XXX()
set_editable(object::QtWidget, value::Bool) = XXX()

get_visible(object::QtWidget) = XXX()
set_visible(object::QtWidget, value::Bool) = XXX()

focus(object::QtWidget) = setFocus(object)
##raise(object::QtWidget) = XXX()
exists(object::QtWidget) = XXX()

## does widget exist?
qt_exists(widget::QtWidget) = XXX()

qconnect(widget::QtWidget, signal::Symbol, slot::Slot) =
    PySide.qconnect(project(widget), signal, project(slot))
    qconnect(callback::Function, widget::QtWidget, signal::Symbol) = qconnect(widget, signal, callback)

## Maybes
MaybeQtWidget = Union(Nothing, QtWidget)
MaybePyObject = Union(Nothing, PyObject)
MaybeString = Union(Nothing, String)
MaybeSymbol = Union(Nothing, Symbol)

## project down to PyObject space otherwise pass through
project(widget) = widget
project(widget::QtWidget) = widget.w

## make this from members ...
## Qt use camelCase with getters not having get.
for nm in (:windowTitle,
           :setWindowTitle,
           :text,
           :setText,
           :toPlainText,
           :setPlainText,
           :html,
           :setHtml,
           :value,
           :setValue,
           :setModel,
           :setIcon,
           :load,
           :setPixmap,
           :addAction,
           :addMenu,
           :addSeparator,

           :setLayout,
           :addWidget,
           :addLayout,
           :addTab,
           :addRow,
           :insertRow,
           :setWidget
           )
    meth = string(nm)
    @eval ($nm)(widget::AWidget, args...; kwargs...) = qinvoke(project(widget), symbol($meth), args...; kwargs...)
end

## set focus
setFocus(widget::AWidget, reason::String) = qinvoke(widget, :setFocus, qt_enum(reason))
setFocus(widget::AWidget) = qinvoke(widget, :setFocus, "OtherFocusReason")

## raise and show window
function raise(widget::AWidget)
    qinvoke(widget, :show)
    convert(Function, project(widget)[:raise])()
end

##################################################
## Containers

## MainWindow
##  https://groups.google.com/forum/?fromgroups#!topic/julia-users/-BThHzBiYZs for details on waiting.
MainWindow() = MainWindow(nothing)
setCentralWidget(widget::MainWindow, child::AWidget) = qinvoke(widget, :setCentralWidget, child)

## DockWidget Possible areas
## Qt::LeftDockWidgetArea	0x1
## Qt::RightDockWidgetArea	0x2
## Qt::TopDockWidgetArea	0x4
## Qt::BottomDockWidgetArea	0x8
## Qt::AllDockWidgetAreas	DockWidgetArea_Mask
## Qt::NoDockWidgetArea
addDockWidget(widget::MainWindow, area::String, child::DockWidget, orientation::String) =
    qinvoke(widget, :addDockWidget, qt_enum(area), child, qt_enum(orientation))
addDockWidget(widget::MainWindow, area::String, child::DockWidget) = qinvoke(widget, :addDockWidget, qt_enum(area), child)

## Widget
Widget() = Widget(nothing)
get_value(widget::Widget) = windowTitle(widget)
set_value(widget::Widget, value::String) = setWindowTitle(widget, value)

##################################################
## Layouts

## adjust spacing, layout of widget, layout
setAlignment(widget::AWidget, alignment::String) = qinvoke(widget, :setAlignment, qt_enum(alignment))
setSpacing(lyt::QtLayout, px::Integer) = qinvoke(lyt, :setSpacing, px)

## julia array-like interfaces for adding, inserting and deleting a widget
push!(lyt::QtLayout, widget::AWidget) = addWidget(lyt, widget)
function push!(parent::Widget, child::AWidget)
    lyt = qinvoke(parent, :layout)
    if isa(lyt, Nothing) lyt = VBoxLayout(parent); setLayout(parent, lyt) end
    addWidget(lyt, child)
end
push!(parent::MainWindow, child::AWidget) = setCentralWidget(parent, child)

## delete
setindex!(lyt::QtLayout, widget::AWidget, i::Int) = qinvoke(lyt, :insertWidget, i, widget)
function delete!(lyt::QtLayout, widget::AWidget)
    qinvoke(lyt, :removeWidget, widget)
    qinvoke(widget, :hide)
end

getindex!(lyt::QtLayout, i::Int) = qinvoke(qinvoke(lyt, :itemAt, i-1), :widget)
pop!(lyt::QtLayout) = delete!(lyt, lyt[qinvoke(lyt, :count)])

## VBoxLayout

## HBoxLayout

## FormLayout

## XXX need grid layout
function addWidget(lyt::GridLayout, widget::AWidget, row::Int, column::Int, rowSpan::Int, columnSpan::Int, alignment::String)
    qinvoke(lyt, :addWidget, widget, row, column, rowSpan, columnSpan, qt_enum(alignment))
end
addWidget(lyt::GridLayout, widget::AWidget, row::Int, column::Int, rowSpan::Int, columnSpan::Int) = addWidget(lyt, widget, row, column, rowSpan, columnSpan, "AlignLeft")
addWidget(lyt::GridLayout, widget::AWidget, row::Int, column::Int, alignment::String) = qinvoke(lyt, :addWidget, widget, row, column, qt_enum(alignment))
addWidget(lyt::GridLayout, widget::AWidget, row::Int, column::Int, alignment::String) = addWidget(lyt, widget, row, column, "AlignLeft")

## lyt[i,j:k] = widget interface
function setindex!(lyt::GridLayout, widget::AWidget, i::Union(Int, Range, UnitRange), j::Union(Int, Range, UnitRange))
    if isa(i, Union(Range, UnitRange)) | isa(j, Union(Range, UnitRange))
        addWidget(lyt, widget, minimum(i), minimum(j), maximum(i) - minimum(i) + 1, maximum(j) - minimum(j) + 1)
    else
        addWidget(lyt, widget, i, j)
    end
end

addLayout(lyt::GridLayout, widget::QtLayout, row::Int, column::Int, rowSpan::Int, columnSpan::Int) = addLayout(lyt, widget, row, column, rowSpan, columnSpan, "AlignLeft")
addLayout(lyt::GridLayout, widget::QtLayout, row::Int, column::Int, alignment::String) = qinvoke(lyt, :addLayout, widget, row, column, qt_enum(alignment))
addLayout(lyt::GridLayout, widget::QtLayout, row::Int, column::Int, alignment::String) = addLayout(lyt, widget, row, column, "AlignLeft")
## lyt[i,j:k] = lyt interface
function setindex!(lyt::GridLayout, widget::QtLayout, i::Union(Int, Range, UnitRange), j::Union(Int, Range, UnitRange))
    if isa(i, Union(Range, UnitRange)) | is(j, Union(Range, UnitRange))
        addLayout(lyt, widget, minimum(i), minimum(j), (maximum(i) - minimum(i)) + 1, maximum(j) - minimum(j) + 1)
    else
        addLayout(lyt, widget, i, j)
    end
end

## QTabWidget
addTab(lyt::TabWidget, widget::AWidget, label::String) = qinvoke(lyt, :addTab, widget, label)
addTab(lyt::TabWidget, widget::AWidget, icon::Icon, label::String) = qinvoke(lyt, :addTab, widget, icon, label)
get_value(lyt::TabWidget) = qinvoke(lyt, :currentIndex) + 1
set_value(lyt::TabWidget, i::Int) = qinvoke(lyt, :setCurrentIndex, i - 1)
change_slot(lyt::TabWidget, slot::Function) = qconnect(lyt, :currentChanged, (value) -> slot(value + 1))
change_slot(lyt::TabWidget, slot::AWidget) =  qconnect(lyt, :currentChanged, project(slot))

## QStackedLayout
get_value(lyt::StackedLayout) = qinvoke(lyt, :currentIndex) + 1
set_value(lyt::StackedLayout, i::Int) = qinvoke(lyt, :setCurrentIndex, i - 1)
change_slot(lyt::StackedLayout, slot::Function) = qconnect(lyt, :currentChanged, (value) -> slot(value + 1))
change_slot(lyt::StackedLayout, slot::AWidget) =  qconnect(lyt, :currentChanged, project(slot))

## QSplitter
function Splitter(orientation::String, parent::AWidget)
    sp = Splitter(parent)
    qinvoke(sp, :setOrientation, qt_enum(orientation))
    sp
end

##################################################
## Controls
## Icon Icon("filename.gif")

## Pixmap (Uses file name, not parent object

## Label
function Label(parent, label::String)
    l = Label(parent)
    set_value(l, label)
    l
end
get_value(widget::Label) = text(widget)
set_value(widget::Label, value) = setText(widget, value)
change_slot(widget::Label, slot::Slot) = XXX() #  nothing doing

## Button (PushButton alias)
function Button(parent::QtWidget, text::MaybeString=nothing, image::Union(Nothing, Icon)=nothing)
    btn = PushButton(parent)
    if isa(text, String) set_value(btn, text) end
    if isa(image, Icon) setIcon(btn, image) end
    btn
end

get_value(widget::PushButton) = text(widget)
set_value(widget::PushButton, value::String) = setText(widget, value)
change_slot(widget::PushButton, slot::Function) = qconnect(widget, :pressed, () -> slot(get_value(widget)))

## Slider
## Need integers here
function Slider{T <: Integer}(parent::QtWidget, orientation::String, range::Union(Range{T}, UnitRange{T}))
    sl = Slider(parent)
    setOrientation(sl, orientation)
    set_items(sl, range)
    set_value(sl, minimum(range))
    sl
end
Slider(parent::QtWidget,  range::Union(Range, UnitRange)) = Slider(parent, "Horizontal", range)
setOrientation(widget::Slider, orientation::String) = qinvoke(widget, :setOrientation, qt_enum(orientation)) ## Horizontal, Vertical

get_value(widget::Slider) = value(widget)
set_value(widget::Slider, value) = setValue(widget, value)
function set_items(widget::Slider, items::Union(Range, UnitRange))
    qinvoke(widget, :setRange, minimum(items), maximum(items))
    qinvoke(widget, :setPageStep, step(items))
end
change_slot(widget::Slider, slot::Slot) = qconnect(widget, :valueChanged, project(slot))

## SpinBox
function SpinBox{T <: Integer}(parent::QtWidget, range::Union(Range{T}, UnitRange{T}))
    sp = SpinBox(parent)
    set_items(sp, range)
    set_value(sp, minimum(range))
    sp
end

get_value(widget::SpinBox) = value(widget)
set_value(widget::SpinBox, value) = setValue(widget, value)
function set_items{T <: Integer}(widgets::SpinBox, items::Union(Range{T}, UnitRange{T}))
    qinvoke(widget, :setRange, minimum(items), maximum(items))
    qinvoke(widget, :setSingleStep, step(items))
end
change_slot(widget::SpinBox, slot::Slot) = qconnect(widget, :valueChanged, project(slot))

## LineEdit
function LineEdit(parent::QtWidget, text::String)
    e = LineEdit(parent)
    set_value(text)
    e
end
setPlaceholderText(widget::LineEdit, value::String) = qinvoke(widget, :setPlaceHolderText, value)
## icons, validator,

get_value(widget::LineEdit) = text(widget)
set_value(widget::LineEdit, value) = setText(widget, string(value))
function get_items(widget::LineEdit)    #  for completion
    completer = qinvoke(widget, :completer)
    model = qinvoke(completer, :model)  # StandardItemModel
    get_items(StandardItemModel(model))
end
function set_items(widget::LineEdit, items=Vector)
    completer = qinvoke(widget, :completer)
    if isa(completer, Nothing)
        completer = Qt[:QCompleter](project(widget))
        qinvoke(widget, :setCompleter, completer)
    end

    model = qinvoke(completer, :model)
    if isa(model, Nothing)
        model = StandardItemModel(completer)
        qinvoke(completer, :setModel, model)
    else
        model = StandardItemModel(model)
    end
    set_items(model, items)
end

change_slot(widget::LineEdit, slot::Slot) = qconnect(widget, :textChanged, project(slot))

## TextEdit
## XXX Issues here as properties are not what are advertised: no text, html, plainText
get_value(widget::TextEdit) = qinvoke(widget, :toPlainText)
set_value(widget::TextEdit, value::String) = qinvoke(widget, :setPlainText, value)
change_slot(widget::TextEdit, slot::Function) = qconnect(widget, :textChanged, () -> slot(get_value(widget)))

## Check
get_value(widget::CheckBox) = qinvoke(widget, :isChecked)
set_value(widget::CheckBox, value::Bool) = qinvoke(widget, :setCheckState, qt_enum(value ? "Checked" : "Unchecked"))
get_items(widget::CheckBox) = text(widget)
set_items(widget::CheckBox, value::String) = setText(widget, value)
change_slot(widget::CheckBox, slot::Slot) = qconnect(widget, :stateChanged, (int) -> slot(get_value(widget)))

## RadioButton
get_value(widget::RadioButton) = qinvoke(widget, :isChecked)
set_value(widget::RadioButton, value::Bool) = qinvoke(widget, :setChecked, value)
get_items(widget::RadioButton) = text(widget)
set_items(widget::RadioButton, value::String) = setText(widget, value)

## Make a Radio Group instance
## an exclusive colletion of radio buttons, packed horizontally or vertically
## Not a Qt Widget, but made of GroupBox, ButtonGroup and RadioButtons
type RadioGroup <: QtWidget
    w
    button_group
end
function RadioGroup{T <: String}(w::Widget, labels::Vector{T}, selected::Integer, orientation::String)
    bgp = ButtonGroup(w)
    gp = Qt[:QGroupBox](project(w))
    rg = RadioGroup(gp, bgp)

    lyt = orientation == "Horizontal" ? HBoxLayout(gp) : VBoxLayout(gp)
    qinvoke(gp, :setLayout, lyt)
       btns = [begin r = RadioButton(w); set_items(r, label); r end for label in labels]
    set_items(bgp, btns)
    map(u -> addWidget(lyt, u), btns)
    set_value(bgp, selected)
    rg
end
RadioGroup{T <: String}(w::Widget, labels::Vector{T}, selected::Integer) = RadioGroup(w, labels, selected, "Horizontal")
RadioGroup{T <: String}(w::Widget, labels::Vector{T}, orientation::String) = RadioGroup(w, labels, 1, orientation)
RadioGroup{T <: String}(w::Widget, labels::Vector{T}) = RadioGroup(w, labels, 1, "Horizontal")

get_value(widget::RadioGroup) = get_value(widget.button_group)
set_value(widget::RadioGroup, value) = set_value(widget.button_group, value)
get_items(widget::RadioGroup) = get_items(widget.button_group)
## XX set_items
change_slot(widget::RadioGroup, slot::Slot) = change_slot(widget.button_group, slot)

## ButtonGroup
function get_value(widget::ButtonGroup)
    ## qinvoke(widget, :checkedId)   ## doesn't work
    btns = qinvoke(widget, :buttons)
    checked = map(u -> qinvoke(u, :isChecked), btns) | bool
    get_items(widget)[findfirst(checked)] ## return value not index
end
set_value(widget::ButtonGroup, i::Integer) = qinvoke(qinvoke(widget, :buttons)[i], :setChecked, true)
set_value(widget::ButtonGroup, value::String) = set_value(widget, findfirst(value .== get_items(widget)))
get_items(widget::ButtonGroup) = String[qinvoke(u, :text) for u in  qinvoke(widget, :buttons)]  # return labels, not buttons
set_items(widget::ButtonGroup, items) = map(u -> addButton(widget, u), items)
change_slot(widget::ButtonGroup, slot::PyObject) = qconnect(widget, :buttonReleased, slot)
change_slot(widget::ButtonGroup, slot::Function) = qconnect(widget, :buttonReleased, btn -> slot(get_value(widget)))
addButton(widget::ButtonGroup, child::AWidget) = qinvoke(widget, :addButton, child)

##################################################
## Models

## ComboBox
## items can be a vector -- or a DataFrame with (label, value, [icon])
function ComboBox{T <: String}(parent, items::Vector{T}, selected::Integer)
    cb = ComboBox(parent)
    set_items(cb, items)
    set_value(cb, selected)
    cb
end
ComboBox{T <: String}(parent, items::Vector{T}) = ComboBox(parent, items, 1)

function get_items(widget::ComboBox)
    m = qinvoke(widget, :model)
    get_items(StandardItemModel(m))
end

function set_items{T <: String}(widget::ComboBox, items::Vector{T})
    model = qinvoke(widget, :model)
    if isa(model, Nothing)
        model = StandardItemModel(widget)
        qinvoke(widget, :setModel, model)
    else
        model = StandardItemModel(model)
    end
    set_items(model, items)
end

function get_value(widget::ComboBox; idx=nothing)
    if idx == nothing idx = qinvoke(widget, :currentIndex) end
    m = qinvoke(widget,:model)
    cur_index = qinvoke(m, :index, idx, 0)
    qinvoke(cur_index, :data)
end
set_value(widget::ComboBox, value::Integer) = qinvoke(widget, :setCurrentIndex, value-1)
function set_value(widget::ComboBox, value::String)
    items = get_items(widget)
    if any(items .== value)
        idx = findin(items, [value])[1]
    else
        idx = 0
    end
    set_value(widget, idx)
end
## How to get in activated(int)
change_slot(widget::ComboBox, slot::Function) = qconnect(widget, :activated, (idx) -> slot(get_value(widget; idx=idx)))
change_slot(widget::ComboBox, slot::Widget) = qconnect(widget, :activated, widget)

## Tables...

##################################################
## Standard Item Model

type StandardItemModel <: QtWidget
    w::PyObject
    function StandardItemModel(parent)
        isaStandardItemModel(parent) = ismatch(r"StandardItemModel", string(qinvoke(parent, :__class__)))
        isaStandardItemModel(parent) ? new(project(parent)) : new(Qt[:QStandardItemModel](project(parent)))
    end
end

function get_items(model::StandardItemModel)
    m = project(model)
    indexes = map(i -> qinvoke(m, :index, i-1, 0), 1:qinvoke(m, :rowCount))
    String[qinvoke(u, :data) for u in indexes]
end

function set_items{T <: String}(model::StandardItemModel, items::Vector{T})
    qinvoke(model, :clear)
    map(i -> qinvoke(model, :setItem, i-1, Qt[:QStandardItem](items[i])),  1:length(items))
end

## set icon with label, value, icon
function set_items{S <: String, T<:String}(model::StandardItemModel, items::Vector{S}, labels::Vector{T}, icon::Vector{Icon})
    qinvoke(model, :clear)
    for i in 1:length(items)
        item = Qt[:QStandardItem](labels[i])
        ## set data value from items[i]
        qinvoke(item, :setData, items[i])
        qinvoke(item, :setIcon, icon[i]) ## XXX Does this work?
        qinvoke(model, :setItem, i-1, item)
    end
end

set_items{T<:String}(model::StandardItemModel, items::Vector{T}, icon::Vector{Icon}) = set_items(model, items, items, icon)

##################################################
## Menus
function Menu(title::String, parent::AWidget)
    menu = Menu(parent)
    menu[:setTitle](title)
    menu
end

function Action(parent::AWidget, text::MaybeString, icon::MaybeString=nothing,
                shortcut::MaybePyObject=nothing, tooltip::MaybeString=nothing
                )
    a = Action(parent)
    if !isa(text, Nothing)  setText(a, text) end
    if !isa(icon, Nothing)  setIcon(a, icon) end
    if !isa(shortcut, Nothing) a[:setShortcut](shortcut) end # Qt.QKeySequence object
    if !isa(tooltip, Nothing)  setToolTip(a, tooltip) end
    a
end
change_slot(parent::Action, slot::Slot) = qconnect(parent, :triggered, slot)

## dialogs, don't have classes

## Message Box
## icon is symbol in :NoIcon, :Question, :Information, :Warning, :Critical
function MessageBox(parent, text::MaybeString=nothing; title::MaybeString=nothing, informative_text::MaybeString=nothing, icon::MaybeSymbol=nothing)
    mb = Qt[:QMessageBox](project(parent))
    if !isa(title, Nothing) setWindowTitle(mb, title) end
    if !isa(text, Nothing) setText(mb, text) end
    if !isa(informative_text, Nothing)  setInformativeText(mb, informative_text) end
    if !isa(icon, Nothing)  mb[:setIcon](mb[icon]) end
    convert(Function, mb[:exec])()
end

## InputDialog
function InputDialog(parent, initial::MaybeString=nothing; title::MaybeString=nothing, label::MaybeString=nothing)
    dlg = Qt[:QInputDialog](parent)
    if !isa(title, Nothing)   setWindowTitle(dlg, title)  end
    if !isa(label, Nothing)   dlg[:setLabelText](label)   end
    if !isa(initial, Nothing) dlg[:setTextValue](initial) end
    out = convert(Function, dlg[:exec])()
    out == 1 ? dlg[:textValue]() : ""
end

## how: file, files, dir, save
function FileDialog(parent, how::String, args...)
    fd = Qt[:QFileDialog]()

    if how == "file"
        out = fd[:getOpenFileName](parent, args...)
    elseif how == "files"
        out = fd[:getOpenFileNames](parent, args...)
    elseif how == "dir"
        out = fd[:getExistingDirectory](parent, args...)
    elseif how == "save"
        out = fd[:getSaveFileName](parent, args...)
    else
        out = fd[:getOpenFileName](parent, args...)
    end
    out[1]
end


text:
@symbol_func function calc_sigma_v(cur_reactor::AbstractReactor)
  is_positive(cur_reactor.T_bar) || return NaN

  cur_nu_n = cur_reactor.nu_n

  cur_func = function (cur_rho)
    cur_temp = T_rho(cur_reactor, cur_rho)

    cur_value = 1.0 - cur_rho ^ 2

    cur_value ^= 2 * cur_nu_n

    cur_value *= cur_rho

    cur_value *= _bosch_hale(cur_temp)

    cur_value
  end

  cur_reactor.is_symbolic &&
    ( cur_func = cur_func(rho_sym) )

  cur_sigma_v = norm_int(cur_func)

  cur_sigma_v *= ( 1 + cur_nu_n ) ^ 2

  cur_sigma_v *= 1e21

  cur_sigma_v
end

_bosch_hale(::SymEngine.Basic) = symbols(:sigma_v_ave)

function _bosch_hale(cur_temp::AbstractFloat)
  cur_theta = _bosch_hale_theta(cur_temp)

  cur_xi = _bosch_hale_xi(cur_theta)

  cur_sigma_v_ave = bosch_hale_coeffs[1]

  cur_sigma_v_ave *= cur_theta

  cur_sigma_v_ave *= sqrt(cur_xi)

  cur_sigma_v_ave /= sqrt(reduced_mass)

  cur_sigma_v_ave /= sqrt(cur_temp) ^ 3

  cur_sigma_v_ave *= exp(-3 * cur_xi)

  cur_sigma_v_ave *= 1e-6

  cur_sigma_v_ave
end

function _bosch_hale_theta(cur_temp::AbstractFloat)
  cur_coeffs = bosch_hale_coeffs

  cur_numerator = cur_coeffs[4] + cur_temp * cur_coeffs[6]
  cur_numerator = cur_coeffs[2] + cur_temp * cur_numerator

  cur_numerator *= cur_temp

  cur_denominator = cur_coeffs[5] + cur_temp * cur_coeffs[7]
  cur_denominator = cur_coeffs[3] + cur_temp * cur_denominator

  cur_denominator *= cur_temp
  cur_denominator += 1

  cur_theta = cur_temp

  cur_theta /= (
    1 - cur_numerator / cur_denominator
  )

  cur_theta
end

function _bosch_hale_xi(cur_theta::AbstractFloat)
  cur_xi = gamov_const ^ 2

  cur_xi /= 4 * cur_theta

  cur_xi ^= (1/3)

  cur_xi
end


text:
using Base.Iterators
using PredictiveControl
using Test

import Base: iterate

struct TestIter end

mutable struct TestIterState
    iter::Int
    flag1::Vector{Int}
    flag2::Vector{Int}
    flag3::Vector{Int}

    function TestIterState()
        return new( 0, Int[], Int[], Int[] )
    end
end

function iterate( iter::TestIter )
    state = TestIterState()
    return (state, state)
end

function iterate( iter::TestIter, state::TestIterState )
    state.iter = state.iter + 1
    return (state, state)
end

function applyflag1( state::TestIterState )
    push!( state.flag1, state.iter )
end

function applyflag2( state::TestIterState )
    push!( state.flag2, state.iter )
end

function applyflag3( state::TestIterState )
    push!( state.flag3, state.iter )
end

let iter1 = TestIter()
    iter1 = take( iter1, 10 )
    iter1 = enumerate( iter1 )
    iter1 = PredictiveControl.FGM.apply( iter1, applyflag1 )

    x = nothing
    for y in iter1
        x = y
    end

    (_, finalstate) = x

    # One element extra because it started with 1 element before the 10 iterations
    @test length( finalstate.flag1 ) == 10
    @test length( finalstate.flag2 ) == 0
    @test length( finalstate.flag3 ) == 0
end

let iter2 = TestIter()
    iter2 = take( iter2, 10 )
    iter2 = enumerate( iter2 )
    iter2 = PredictiveControl.FGM.apply( iter2, applyflag1 )
    iter2 = PredictiveControl.FGM.apply( iter2, applyflag2, 2 )

    x = nothing
    for y in iter2
        x = y
    end

    (_, finalstate) = x

    @test length( finalstate.flag1 ) == 10
    @test length( finalstate.flag2 ) == 5
    @test length( finalstate.flag3 ) == 0
end

let iter3 = TestIter()
    iter3 = take( iter3, 10 )
    iter3 = enumerate( iter3 )
    iter3 = PredictiveControl.FGM.apply( iter3, applyflag1 )
    iter3 = PredictiveControl.FGM.apply( iter3, applyflag2, 2 )
    iter3 = PredictiveControl.FGM.apply( iter3, applyflag3, 5 )

    x = nothing
    for y in iter3
        x = y
    end

    (_, finalstate) = x

    @test length( finalstate.flag1 ) == 10
    @test length( finalstate.flag2 ) == 5
    @test length( finalstate.flag3 ) == 2
end


text:
CUDA.alloc(0)

memcheck ||  @test_throws OutOfGPUMemoryError CuArray{Int}(undef, 10^20)

@testset "@allocated" begin
    @test (CUDA.@allocated CuArray{Int32}(undef,1)) == 4
end

@testset "@timed" begin
    out = CUDA.@timed CuArray{Int32}(undef, 1)
    @test isa(out.value, CuArray{Int32})
    @test out.gpu_bytes > 0
end

@testset "@time" begin
    ret, out = @grab_output CUDA.@time CuArray{Int32}(undef, 1)
    @test isa(ret, CuArray{Int32})
    @test occursin("1 GPU allocation: 4 bytes", out)

    ret, out = @grab_output CUDA.@time Base.unsafe_wrap(CuArray, CuPtr{Int32}(12345678), (2, 3))
    @test isa(ret, CuArray{Int32})
    @test !occursin("GPU allocation", out)
end

@testset "reclaim" begin
    CUDA.reclaim(1024)
    CUDA.reclaim()

    @test CUDA.@retry_reclaim(42, 42) == 42
    @test CUDA.@retry_reclaim(42, 41) == 41
end

@testset "timings" begin
    CUDA.enable_timings()
    CUDA.disable_timings()
end


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule LibVPX_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("LibVPX")
JLLWrappers.@generate_main_file("LibVPX", UUID("dd192d2f-8180-539f-9fb4-cc70b1dcf69a"))
end  # module LibVPX_jll


text:
# -----------------------------------------------
# Mixed Support Functions:
# -----------------------------------------------

macro __test(exp)
    print("[ ", exp, " ]")
    try
        eval(exp)
        println(" - Test Passed")
    catch
        println(" - Test Failed")
        error("#--- TESTING ABORTED ---#")
    end
end

macro p(exp)
    print("Calling [ ", exp, " ]")
    eval(exp)
end

function present(in::Any)
    show(IOContext(STDOUT, limit=true), "text/plain", in)
end

function isvectorarray{T<:Number}(A::Array{T})
    if !issingletonarray(A)
        return (size(A)[1] == 1 || size(A)[2] == 1)
    end
end

function isrowarray{T<:Number}(A::Array{T})
    if !issingletonarray(A)
        return size(A)[1] == 1
    else
        return false
    end
end

function iscolumnarray{T<:Number}(A::Array{T})
    if !issingletonarray(A)
        return size(A)[2] == 1
    else
        return false
    end
end

function issingletonarray{T<:Number}(A::Array{T})
    return (length(size(A)) == 1 && length(A) == 1)
end

function row{T<:Number}(A::Array{T,1})
    if !issingletonarray(A)
        return size(A)[1] > size(A)[2] ? A' : A
    else
        return A
    end
end

function aprod{T1<:Number, T2<:Number}(A::Array{T1}, B::Array{T2})
    if (issingletonarray(A) || issingletonarray(B))
        return A .* B
    else
        return A * B
    end
end

function aprod{T1<:Number, T2<:Number}(A::T1, B::Array{T2})
    return A .* B
end

function aprod{T1<:Number, T2<:Number}(A::Array{T1}, B::T2)
    return A .* B
end

function hasintercept{T<:Number}(A::Array{T})
    if !issingletonarray(A)
        n = size(A)[1]
        return A[:,1] == ones(n)
    else
        return false
    end
end

function addintercept{T<:Number}(A::Array{T})
    if (!hasintercept(A))
        return hcat(ones(size(A)[1],1), A)
    else
        return A
    end
end

function nointercept{T<:Number}(A::Array{T})
    if (hasintercept(A))
        return A[:,2:end]
    else
        return A
    end
end

function iscategorical{T<:Number}(A::Array{T})
    return mapreduce(x -> (x == 1 || x == 0), &, A)
end


text:
module MultiLJ

using JuLIP
using JuLIP.Potentials: pairs
import JuLIP: energy, forces, cutoff

export MLJ

@pot struct MLJ{T} <: AbstractCalculator
   Z2idx::Vector{Int}
   V::Matrix{T}
   rcut::Float64
end

cutoff(V::MLJ) = V.rcut

function MLJ(Z, ϵ, σ; rcutfact = 2.5)
   @assert length(Z) == length(unique(Z))
   # create a mapping from atomic numbers to indices
   Z2idx = zeros(Int, maximum(Z))
   Z2idx[Z] = 1:length(Z)
   # generate the potential
   rcut = maximum(σ) * rcutfact
   V = [ LennardJones(σ[a,b], ϵ[a, b]) * C2Shift(σ[a,b] * rcutfact)
         for a = 1:length(Z), b = 1:length(Z) ]
   return MLJ(Z2idx, V, rcut)
end

function energy(V::MLJ, at::Atoms)
   E = 0.0
   for (i, j, r, R) in pairs(at, cutoff(V))
      a = V.Z2idx[at.Z[i]]
      b = V.Z2idx[at.Z[j]]
      E += 0.5 * V.V[a, b](r)
   end
   return E
end

function forces(V::MLJ, at::Atoms)
   F = zeros(JVecF, length(at))
   for (i, j, r, R) in pairs(at, cutoff(V))
      a = V.Z2idx[at.Z[i]]
      b = V.Z2idx[at.Z[j]]
      f = 0.5 * grad(V.V[a,b], r, R)
      F[i] += f
      F[j] -= f
   end
   return F
end

end

# --------------
#   test code
# --------------

using JuLIP, MultiLJ, StaticArrays

# parameters from PHYSICAL REVIEW B, VOLUME 64, 184201
#                 Crystals of binary Lennard-Jones solids
#                 T F Middleton, J Hernandez-Rojas, P N Mortenson, D J Wales

ϵ = [1.0 1.5; 1.5 0.5]
σ = [1.0 0.8; 0.8 0.88]
z = [1, 2]
V = MultiLJ.MLJ(z, ϵ, σ)

#
n = 2
x, o = 0:n, ones(n)
X = [ kron(x, o, o)[:]'; kron(o, x, o)'; kron(o, o, x)' ]
X = vecs( X + 0.05 * rand(size(X)) )
Nat = length(X)
at = Atoms(X, zeros(X), ones(Nat), rand(z, Nat), 5 * eye(3), false;  calc = V)

# check that energy and forces evaluate ok
energy(V, at)
forces(V, at)

# finite-difference test
JuLIP.Testing.fdtest(V, at)


text:
module BenchSplitBy
using BenchmarkTools
using Transducers

const SUITE = BenchmarkGroup()

chunks_longer_than(xs, n) =
    xs |> SplitBy(iszero; keepend = Val(true)) |> Map(length) |> Filter(>=(n))

count_chunks_longer_than(xs, n, ex) =
    Transducers.fold(+, chunks_longer_than(xs, n) |> Map(_ -> 1), ex; init = 0)

function man_count_chunks_longer_than(xs, n)
    c = 0
    ilast = firstindex(xs) - 1
    for i in eachindex(xs)
        x = @inbounds xs[i]
        if iszero(x)
            if i - ilast >= n
                c += 1
            end
            ilast = i
        end
    end
    if lastindex(xs) - ilast >= n
        c += 1
    end
    return c
end

# TODO: find a better way to test benchmark code
if lowercase(get(ENV, "TRANSDUCERS_JL_TEST_BENCHMARKS", "false")) == "true"
    using Test
    using Random: MersenneTwister
else
    macro testset(_) end
end

@testset for seed in 1:20
    rng = MersenneTwister(seed)
    xs = rand(rng, 0:9, 2^20)
    @test man_count_chunks_longer_than(xs, 5) ==
            count_chunks_longer_than(xs, 5, SequentialEx()) ==
            count_chunks_longer_than(xs, 5, ThreadedEx())
end

let s = SUITE["count"] = BenchmarkGroup()
    xs = rand(0:9, 2^20)
    s["man"] = @benchmarkable man_count_chunks_longer_than($xs, 5)
    s["foldl"] = @benchmarkable count_chunks_longer_than($xs, 5, SequentialEx())
    s["reduce"] = @benchmarkable count_chunks_longer_than($xs, 5, ThreadedEx())
end

end  # module
BenchSplitBy.SUITE


text:
export CPUStat

struct CPUStat

    pid::Int
    cpu::Array{Float64,1}
    mem::Array{Float64,1}

end


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "STYRENE"
version = v"0.1.0"

# Collection of sources required to complete build
sources = [
    GitSource("https://github.com/bbopt/styrene.git", "f7eb5c566cf7f152d53bbc1490d03568d00d22f1")
]

# Bash recipe for building across all platforms
script = raw"""
mkdir -p $bindir
cd $WORKSPACE/srcdir/styrene/blackbox/surrogate
make COMPILATOR="c++" LIBS="-lm" EXE="${bindir}/surrogate${exeext}"
cd $WORKSPACE/srcdir/styrene/blackbox/truth
make COMPILATOR="c++" LIBS="-lm" EXE="${bindir}/truth${exeext}"
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = expand_cxxstring_abis(supported_platforms(;experimental=true))

# The products that we will ensure are always built
products = [
    ExecutableProduct("surrogate", :surrogate),
    ExecutableProduct("truth", :truth)
]

# Dependencies that must be installed before this package can be built
dependencies = Dependency[]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6")

max_stars_repo_name: sharanry/Yggdrasil

max_issues_repo_name: sharanry/Yggdrasil

max_forks_repo_name: sharanry/Yggdrasil


text:
using Test, Sandbox

include("SandboxConfig.jl")
include("UserNamespaces.jl")
include("Docker.jl")
include("Sandbox.jl")


text:
function load(x, pc, N, mapping::Union{Product,ChebyshevSparse})
	return repeat([phi(x, pc, mapping)], N)
end

function load(x, pc, N, mapping::ChebyshevTower)
	return [phi(x, pc*i, mapping) for i in 1:N]
end

function new_circuit(feature_map_circuit::AbstractBlock, var::AbstractBlock, x, theta, N::Int, pc, n=1, v=0; mapping::FeatureMap)
	tmp = load(x, pc, N, mapping)
    f = [i == n ? tmp[i] .+ v : tmp[i] for i in 1:N]

	return chain(N, dispatch(feature_map_circuit, f), dispatch(var, theta))
end


text:
import ..options
import ..@log

using .Schedulers

"""
Core algorithm of the library: the equality saturation step.
"""
function eqsat_step!(g::EGraph, theory::Vector{<:Rule}, curr_iter, mod::Module,
        scheduler::AbstractScheduler, match_hist::MatchesBuf, params::SaturationParams)

    report = Report(g)
    instcache = Dict{Rule, Dict{Sub, Int64}}()

    setiter!(scheduler, curr_iter)

    if options.multithreading
        search_stats = @timed eqsat_search_threaded!(g, theory, scheduler)
    else
        search_stats = @timed eqsat_search!(g, theory, scheduler)
    end
    matches = search_stats.value
    report.search_stats = report.search_stats + discard_value(search_stats)

    # matches = setdiff!(matches, match_hist)

    # println("============ WRITE PHASE ============")
    # println("\n $(length(matches)) $(length(match_hist))")
    # if length(matches) > matchlimit
    #     matches = matches[1:matchlimit]
    # #     # mmm = Set(collect(mmm)[1:matchlimit])
    # #     #
    # end
    # println(" diff length $(length(matches))")

    apply_stats =  @timed eqsat_apply!(g, matches, report, mod, params)
    report = apply_stats.value
    report.apply_stats = report.apply_stats + discard_value(apply_stats)

    # union!(match_hist, matches)

    # display(egraph.parents); println()
    # display(egraph.classes); println()
    if report.reason === nothing && cansaturate(scheduler) && isempty(g.dirty)
        report.reason = Saturated()
    end
    rebuild_stats = @timed rebuild!(g)
    report.rebuild_stats = report.rebuild_stats + discard_value(rebuild_stats)

    # TODO produce proofs with match_hist
    total_time!(report)

    return report, g
end

"""
Given an [`EGraph`](@ref) and a collection of rewrite rules,
execute the equality saturation algorithm.
"""
saturate!(g::EGraph, theory::Vector{<:Rule}; mod=@__MODULE__) =
    saturate!(g, theory, SaturationParams(); mod=mod)

function saturate!(g::EGraph, theory::Vector{<:Rule}, params::SaturationParams;
    mod=@__MODULE__,)
    curr_iter = 0

    sched = params.scheduler(g, theory, params.schedulerparams...)
    match_hist = MatchesBuf()
    tot_report = Report()

    while true
        curr_iter+=1

        options.printiter && @info("iteration ", curr_iter)

        report, egraph = eqsat_step!(g, theory, curr_iter, mod, sched, match_hist, params)

        tot_report = tot_report + report

        # report.reason == :matchlimit && break
        if !(report.reason isa Nothing)
            break
        end

        if curr_iter >= params.timeout
            tot_report.reason = Timeout()
            break
        end

        if params.eclasslimit > 0 && g.numclasses > params.eclasslimit
            tot_report.reason = EClassLimit(params.eclasslimit)
            break
        end

        if reached(g, params.goal)
            tot_report.reason = GoalReached()
            break
        end
    end
    # println(match_hist)

    # display(egraph.classes); println()
    tot_report.iterations = curr_iter
    @log tot_report
    # GC.enable(true)

    return tot_report
end


text:
using LinearAlgebra
using Test
using Plots

#=
Sun:
    1.989e30 kg
    0 AU ☻

Earth:
    5.972e24 kg
    1 AU

Mercury:
    3.30e23 kg
    0.38 AU

Mars:
    6.4219e23 kg
    1.52 AU

Venus:
    4.869e24 kg
    0.72 AU
=#

# Initial system state

# Time settings
Δt = 0.05
t_max = 100
t = Int(div(t_max, Δt))

# Masses of bodies
m = [1.989e30, 5.972e24, 3.30e23, 6.4219e23, 4.869e24]
M = sum(m)
m /= M
M = sum(m)
n = length(m)

# Gravitational constant
G = 1.0

# Matrices of shape [body, time, value]
R = zeros(n, t, 2)
V = zeros(n, t, 2)
A = zeros(n, t, 2)

# Initial values for position and velocity
R[:, 1, :] = [
    0 0;
    1 0;
    0.38 0;
    1.52 0;
    0.72 0]
V[:, 1, 2] = map(x -> x > 0 ? sqrt(G * M/x) : 0, R[:, 1, 1])

"""
 Compute acceleration vector for timestep `t` and body `i`

# Arguments
- `R`: Matrix of shape `[body, time, position vector]`
- `G`: Gravitational constant
- `m`: Vector of masses of bodies
- `t`: Index of time step into `R`
- `i`: Index of body into `R`/`G`/`m`

# Returns
``a_i(t)`` Acceleration vector of body `i` and timestep `t`
"""
function acceleration_i(R, G, m, t, i)
    a_new = []

    for j = 1:length(R[:,1,1])
        if i == j
            continue
        end
        r_i = R[i, t, :]
        r_j = R[j, t, :]
        r_ij = r_j - r_i

        a_ij = r_ij * G * m[j] / norm(r_ij)^3
        push!(a_new, a_ij)
    end
    return sum(a_new)
end

"""
Compute complete acceleration matrix for timestep `t`

See `acceleration_i` for details on params

# Returns
``A(t)`` Acceleration matrix of timestep `t`
"""
function acceleration(R, G, m, t)
    return hcat(map(i->acceleration_i(R, G, m, t, i), 1:n)...)'
end

"""
Euler-Method: Compute the new position and
velocity for all the bodys in the system for the
next timestep.

# Arguments
- `Δt`: timestep length
- `t`: Index of time step into `V`, `R` and `A`
- `V`: Matrix of shape `[body, time, velocity vector]`
- `R`: Matrix of shape `[body, time, position vector]`
- `A`: Matrix of shape `[body, time, acceleration vector]`

# Returns
``v_new(t), r_new(t)`` Velocitiy and position of one body
at the next timestep
"""
function euler_method(Δt, t, V, R, A)
    v_new = V[:,t,:] + A[:,t,:] * Δt
    r_new = R[:,t,:] + V[:,t,:] * Δt

    return v_new, r_new
end

"""
Euler-Cormer- Method: Compute the new position and
velocity for all the bodys in the system for the
next timestep.

# Arguments
- `Δt`: timestep length
- `t`: Index of time step into `V`, `R` and `A`
- `V`: Matrix of shape `[body, time, velocity vector]`
- `R`: Matrix of shape `[body, time, position vector]`
- `A`: Matrix of shape `[body, time, acceleration vector]`

# Returns
``v_new(t), r_new(t)`` Velocitiy and position of one body
at the next timestep
"""
function euler_cormer(Δt, t, V, R, A)
    v_new = V[:,t,:] + A[:,t,:] * Δt
    r_new = R[:,t,:] + 0.5 * (V[:,t,:] + v_new) * Δt

    return v_new, r_new
end

"""
Verlet-Algotihm: Compute the new position and
velocity for all the bodys in the system for the
next timestep. Speical case for t = t_start.
(Here t_start = 1).

# Arguments
- `Δt`: timestep length
- `t`: Index of time step into `V`, `R` and `A`
- `V`: Matrix of shape `[body, time, velocity vector]`
- `R`: Matrix of shape `[body, time, position vector]`
- `A`: Matrix of shape `[body, time, acceleration vector]`

# Returns
``v_new(t), r_new(t)`` Velocitiy and position of one body
at the next timestep
"""
function verlet(Δt, t, V, R, A)

    if t == 1
        r_help = R[:,1,:] - V[:,1,:] * Δt + 0.5 * A[:,1,:] * Δt^2
        r_new = 2 * R[:,t,:] - r_help + A[:,t,:] * Δt^2
        v_new = (r_new - r_help) / (2 * Δt)
    else
        r_new = 2 * R[:,t,:] - R[:,t-1,:] + A[:,t,:] * Δt^2
        v_new = (r_new - R[:,t-1,:]) / (2 * Δt)
    end

    return v_new, r_new
end

#=
iterate over solvers; solve our example problem with each one
and animate each solution
=#

function animate_solver(solver, R, G, m, t, V, Δt, labels)
    for time = 1:(t - 1)
        A[:, time, :] = acceleration(R, G, m, time)
        v_new, r_new = solver(Δt, time, V, R, A)

        V[:, time + 1, :] = v_new
        R[:, time + 1, :] = r_new
    end

    anim = @animate for t = 1:size(R, 2)
        Rx = [R[i, t, 1] for i in 1:size(R, 1)]
        Ry = [R[i, t, 2] for i in 1:size(R, 1)]
        scatter(Rx', Ry', xlims = (-2.5, 2.5), ylims = (-2.5, 2.5), label = labels)
    end every 2

    name = string(solver)
    gif(anim, "Solar_$name.gif", fps = 30)
end

for solver in [verlet]
    animate_solver(solver, R, G, m, t, V, Δt, ["Sun", "Earth", "Mercury", "Mars", "Venus"])
end

max_stars_repo_name: JoshuaSimon/N-Body-Problem

max_issues_repo_name: JoshuaSimon/N-Body-Problem

max_forks_repo_name: JoshuaSimon/N-Body-Problem


text:
# This file is a part of TypeDBClient.  License is MIT: https://github.com/Humans-of-Julia/TypeDBClient.jl/blob/main/LICENSE

function match(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result =  stream(transaction, QueryManagerRequestBuilder.match_req(query, options))
    isempty(db_result) && return []

    return reduce(vcat, map(ConceptMap, [entry.query_manager_res_part.match_res_part.answers for entry in db_result]))
end

function match_aggregate(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result =  execute(transaction, QueryManagerRequestBuilder.match_aggregate_req(query, options))
    return _read_proto_number(db_result.query_manager_res.match_aggregate_res.answer)
end

function match_group(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result =  stream(transaction, QueryManagerRequestBuilder.match_group_req(query, options))
    isempty(db_result) && return []

    return reduce(vcat, [ConceptMapGroup(item.query_manager_res_part.match_group_res_part.answers) for item in db_result])
end

function match_group_aggregate(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result =  stream(transaction, QueryManagerRequestBuilder.match_group_aggregate_req(query, options))
    isempty(db_result) && return []

    return reduce(vcat, [NumericGroup(item.query_manager_res_part.match_group_aggregate_res_part.answers) for item in db_result])
end

function insert(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result = stream(transaction, QueryManagerRequestBuilder.insert_req(query, options))
    isempty(db_result) && return []

    return reduce(vcat, map(ConceptMap, [entry.query_manager_res_part.insert_res_part.answers for entry in db_result]))
end

"""
    delete(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
To delete something without using certain functions you can use a TypeQL string put this as an argument to the
delete function.
"""
function delete(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    execute(transaction, QueryManagerRequestBuilder.delete_req(query, options))
    return nothing
end

function update(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    db_result = stream(transaction, QueryManagerRequestBuilder.update_req(query, options))
    isempty(db_result) && return []

    return reduce(vcat, map(ConceptMap, [entry.query_manager_res_part.update_res_part.answers for entry in db_result]))
end

function explain(transaction::AbstractCoreTransaction, explainable::AbstractExplainable, options = Proto.Options())
    @error "explain isn't implemented yet"
    # return (_Explanation.of(ex) for rp in self.stream(query_manager_explain_req(explainable.explainable_id(), options.proto())) for ex in rp.explain_res_part.explanations)
end

function define(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    execute(transaction, QueryManagerRequestBuilder.define_req(query, options))
    return nothing
end

function undefine(transaction::AbstractCoreTransaction, query::AbstractString, options = Proto.Options())
    execute(transaction, QueryManagerRequestBuilder.undefine_req(query, options))
    return nothing
end

function commit(transaction::AbstractCoreTransaction)
    !is_open(transaction) && throw(TypeDBClientException(CLIENT_TRANSACTION_CLOSED))
    try
        execute(transaction, TransactionRequestBuilder.commit_req())
        safe_close(transaction)
    catch ex
        @info ex
        rethrow(ex)
    end

    return true
end