### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ ae625e40-7f56-11ec-3cd5-d1244e23a3ba
begin
using PlutoUI, HTTP, Gumbo, Cascadia, CSV, JLD2

md"""# Fridge.jl
by *Ward Van Belle*
"""

end

# ╔═╡ 06db3cc2-1680-49b9-a054-281a6b35205a
md"""$(Resource("https://github.com/wardvanbelle/Fridge.jl/raw/master/FridgeLogo.png", :width => 150, :align => "right"))

A package that optimizes your fridge use while reducing your waste pile!!

This package tries to find the best recipes for you based on a recipe database. In our eyes (and the eyes of the objective function), the best recipes are the ones that use as much ingredients from your fridge as possible and that don't need extra ingredients from the grocery store."""

# ╔═╡ 3ad1af72-7096-4106-a3c7-47326695e2cb
md"""
## Checking The Ingredients
If you want to optimize fridge usage. It's best to know which ingredients are in the fridge. By checking all the ingredients and offering possible alternatives, it is easier for the algorithm to give better results. For instance, cheese may be replaced by swiss cheese.\
\
It takes two inputs:
- The fridge list: A list containing the different foods in your fridge as a string.
- The ingredient list: A list containing all the different ingredients that are used in the recipe database.

After searching for the different ingredients, it outputs an adapted fridge list.
"""

# ╔═╡ 8e58f807-1db9-495e-8679-430f953292ea
function checkIngredients(fridgeList,ingredientList)
    print("Checking if the food in your fridge is found in our database.\n\n")

    # check if the ingredients in the fridge are found in the database
    for food in fridgeList
        if food in ingredientList
            print("Found $food in the ingredient database.\n")
        else
            print("Did not find $food in the ingredient database.\n\n")
            if any(occursin.(food,ingredientList))
                print("Possible alternatives in database:\n")
                alternatives = ingredientList[occursin.(food,ingredientList)]
                for (indexNum, alternative) in enumerate(alternatives)
                    print("[$indexNum] $alternative\n")
                end
                print("If you want to take an alternative type its number, else type no.\n")
                answer = readline()
                if answer != "no"
                    correctInput = false
                    while !correctInput
                        try
                            answer = parse(Int64,answer)
                            correctInput = true
                        catch
                            print("Please only type the number of the alternative, eg. 1\n")
                            answer = readline()
                        end
                    end
                    fridgeList[fridgeList .== food] .= alternatives[answer]
                    print("Replaced $food with $(alternatives[answer])\n")
                end
            else
                print("Did not find any alternative in the ingredient database.\n")
            end
        end
    end
    print("Done checking ingredients.\n\n")

    return fridgeList
end

# ╔═╡ 264b1eb5-c421-4a69-a55c-8fbbbb7715d1
md"""
## Objective Functions
Of course, we want to be able to score how good our objective function will be. This objective function is based on the following formula where ``u`` is the number of ingredients used from the fridge, ``r`` is the number of ingredients remaining in the fridge and ``e`` are the extra ingredients needed. ``w_1``, ``w_2`` and ``w_3`` are a set of weights.

``score = w_1 * u + w_2 * r + w_3*e``
"""

# ╔═╡ 1752889a-513a-4620-add4-a9ca7fa92c8e
md"""
## The Search Algorithm
Let's now take a look at the different search algorithms that you can use in the Fridge.jl module.
"""

# ╔═╡ a65c0925-7a67-4da3-9a6d-2ade8d3529e9
md"""### Greedy Algorithm
This algorithm works like the one seen in class.
It ranks all recipes based on the previously discussed objective function. Picks the best scoring recipe. Adapts the recipe options and picks the new best recipe.
It keeps repeating this until the amount of chosen recipes is equal to `numRecipes` or if there are no recipes left to pick.\
\
As an input, it takes the earlier mentioned `fridgeList`, a dictionary containing all the recipes and their ingredients (`recipeDict`) and, the max number of recipes that a combination should have (`numRecipes`). \
\
After searching, it outputs the best-found combination.
"""

# ╔═╡ 954fe2b3-171b-43ad-a1df-cbb3414988de
md"""
### Simulated Annealing
To improve our earlier result, we can use simulated annealing. The algorithm below is based on the one seen in class. The biggest difference is the fact that we also use a Tabu list to block recipes for a certain number of cycles.

To start searching, it needs the earlier mentioned `fridgeList`, `recipeDict` and `numRecipes` but also the following things:
- `initSolution`: The initial solution from which it should start looking.
- `tabuList`: A list of recipes that should not be used in the found neighbour.
- `randRecipe`: A Boolean `true` or `false` value. When `true`, random recipes are used to create the neighbour.

After searching it also outputs the best-found combination of recipes.
"""

# ╔═╡ eee53924-fd20-44d8-a3eb-a2af9f9112b9
md"""
To optimize this searching method, there are a lot of parameters that can be tweaked.
The following options are available:
- `kT`: repetitions per temperature
- `r`: cooling rate
- `Tmax`: maximal temperature to start
- `Tmin`: minimal temperature to end
- `tabuLength`: Number of cycles that recipe needs to be blocked

You can experiment with them further on below!
"""

# ╔═╡ 7315f676-0d0e-43ae-8231-b6274dc5c8de
md"""
### Neighbours
There are two types of neighbours to use in the module. One uses a random combination of recipes, the other is based on the Greedy Search algorithm. Both are implemented in the `Neighbour()` function.
"""

# ╔═╡ 134a2dd7-69d2-449e-82ab-5bfff90ede1b
md"""A neighbour is created by first removing a recipe from the current combination `curSolution`. Next, the Neighbour function looks for other recipes that are compatible with the other recipes that are in the current combination."""

# ╔═╡ b6205bd3-41da-45e0-81f1-a480a3f0e737
md"""
## Overview Function
Finally, we want to bring all the functions together in one clean overview function.
The only things that need to be provided are:
- A list containing the different foods in your fridge as a string. `fridgeList`
- A relative or absolute path to a .csv or .jld2 file containing the recipe database. `dataPath`

Optionally you can also adapt the max amount of recipes in a combination (`numRecipes`) and wether or not random recipes should be used to create a neighbour (`randRecipe`).

"""

# ╔═╡ 6af2a6dd-ba7e-470e-8915-12ae6eff8357
md"""## Example Time
To start of we'll need to get our hands on a recipe database. Don't fear if you don't have one laying around. By using the `scrapeRecipe()` function provided in this module, you can create a .csv file containing recipes from [the cosylab recipe database](https://cosylab.iiitd.edu.in/recipedb/).

You just need to provide the following things:
- a starting recipe number
- a stopping recipe number
- the path to where the .csv file should be saved (you can copy this from your file explorer)

Next al that is left to do is to check the `Download database now` box. Since this will take a few minutes we provide you with a 🍪 and some ☕, enjoy!
"""

# ╔═╡ 656f211d-d1e8-4325-a12b-b89eca0351da
md"""
#### recipe database selection menu
The standard inputs are 106 541 to 106 866 since this downloads all the Belgian recipes. If you would like to download recipes from another country, please take a look at the explanation provided in the readme on the [GitHub repository](https://github.com/wardvanbelle/Fridge.jl).

Starting recipe number: $(@bind startNumber TextField(default="106541"))\
Stopping recipe number: $(@bind endNumber TextField(default="106866"))\
Filepath: $(@bind filePath TextField(default="../data/BelgianRecipeDB.csv"))\
Download database now: $(@bind downloadDB CheckBox())

It is recommended to only use this once. Otherwise, you will have copies of some recipes in your database. So you better uncheck the box above!
"""

# ╔═╡ bf2725c1-55e6-4c6a-9b83-53f5984cae75
md"Next it is time to look at the things we have left in our kitchen:"

# ╔═╡ fd02d76a-0ae6-4869-96bb-516c536fb91d
fridgeList = ["chocolate", "potato", "salt", "cheese"]

# ╔═╡ 34544e10-e4a1-4cfe-b0bc-43f8927bb19c
md"""
Now that we have a small database and the content of our fridge, we can start looking for a recipe combination. Keep an eye on your terminal since it is possible that you need to give some input! If you are ready, check the box below!
"""

# ╔═╡ 312fabf7-7d11-4bac-b52b-14de82d13e73
md"Ready to start searching? $(@bind readyToSearch CheckBox())"

# ╔═╡ 15a97461-7096-4ea0-acbc-edbc5db79cde
md"""
Congratulations! You made it through the notebook!
You can now continue to the **experimental section** below to play with the different parameters used in the algorithms. Enjoy the food! 🍔
"""

# ╔═╡ d49cf674-088f-47a1-9795-7641b3f01cd1
md"""## Experimental Section
We will now load the .csv file and save it in a dictionary so that it doesn't need to load every time you run the simulated annealing algorithm.
"""

# ╔═╡ 86f03d01-4f97-4487-9683-38f8dc9d826a
md"""
### Parameter Options:
**General**\
`numRecipes` : $(@bind numRecipesExp Slider(1:10, default=3, show_value=true))\
`tabuLength` : $(@bind tabuLengthExp Slider(1:15, default=5, show_value=true))\
`randRecipe` : $(@bind randRecipeExp CheckBox())\
\
**Simulated Annealing specific**\
`kT` : $(@bind kTExp Slider(20:10:150, default=100, show_value=true))\
`r` : $(@bind rExp Slider(0.1:0.05:0.95, default=0.75, show_value=true))\
`Tmax` : $(@bind TmaxExp Slider(2:10, default=4, show_value=true))\
`Tmin` : $(@bind TminExp Slider(1:9, default=1, show_value=true))\
\
**Objective specific**\
``w_1``: $(@bind w_1 Slider(0:10, default=1, show_value=true))\
``w_2``: $(@bind w_2 Slider(0:10, default=6, show_value=true))\
``w_3``: $(@bind w_3 Slider(0:10, default=2, show_value=true))\
"""

# ╔═╡ 70ffadd9-80d4-4eb9-92d9-e3d5ffa50074
md"Start experimenting: $(@bind startExperimenting CheckBox())"

# ╔═╡ c51f2b76-fe4c-4c29-87dc-c7784e5200c2
md"""
## Supporting Functions
Below you can find all the supporting functions used in this notebook. These can also be found in the src folder in the [GitHub repository](https://github.com/wardvanbelle/Fridge.jl). Please do not adapt them.
"""

# ╔═╡ 09d6393c-9b14-4401-babe-708a88ef0a16
function scrapeRecipe(scrapeBegin,scrapeEnd,csvPath)
"""
    scrapeRecipe(scrapeBegin,scrapeEnd,csvPath)

Download recipetitles and their corresponding ingredients from the recipe database of cosylab as a dictionary.
The recipes are downloaded based on their recipe number. This number can range from 2610 to 149191.
To get a recipenumber one should look at the last number of the url of a certain recipe.
For example, the recipe for 'Speculoosbavarois' is number 106585.
The recipes get automatically saved in a csv file where the first column is the recipetitle and the second column
is the list of ingredients.

## Input:
- scrapeBegin: The recipe number where the iteration should begin.
- scrapeEnd: The recipe number after which the iteration should and.
- csvPath: The path where the csv file is stored.

## Examples:

The example below downloads the recipes 2700 to 2702 and
stores them in the csv file 'recipedb.csv' in the current folder.

```julia-repl
julia> scrapeRecipe(2700,2702,"./recipedb.csv")
```
"""
    for i = scrapeBegin:scrapeEnd
        # get the webpage
        htmlText = HTTP.request("GET","https://cosylab.iiitd.edu.in/recipedb/search_recipeInfo/$i")
        htmlBody = parsehtml(String(htmlText.body))

        # get the needed elements out of the webpage
        recipeTitle = eachmatch(Selector("h3"),htmlBody.root)[1].children[1].text
        print("recipe number $i : $recipeTitle\n")
        ingredientTab = eachmatch(Selector("#ingredient_nutri"),htmlBody.root)
        ingredientTabel = eachmatch(Selector("#myTable"),ingredientTab[1])
        ingredientLinks = eachmatch(Selector("td a"),ingredientTabel[1])
        ingredientList = []
        for ingredientLink in ingredientLinks
            try
                append!(ingredientList, [ingredientLink.children[1].text])
            catch y
            end
        end

        # write the data to the DB
        if isfile(csvPath)
            CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = true)
        else
            CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = false)
        end

    end
    print("done")
end

# ╔═╡ cd5458a5-2136-4559-b4e5-2d91abea870c
if downloadDB
	scrapeRecipe(parse(Int64,startNumber), parse(Int64,endNumber), filePath)
end

# ╔═╡ 87b72838-260f-4ffb-ac79-03860165afa1
function loadRecipeDBCSV(csvPath)
    # read the dictionary from the csv file
    print("Loading the recipe database.\n")
    tempDict = CSV.File(csvPath) |> Dict
    recipeDict = Dict()

    # parse the ingredient list to the right format
    print("Parsing ingredients to right format.\n\n")
    for recipe in keys(tempDict)
        ingredientList = tempDict[recipe]
        recipeDict[recipe] = eval(Meta.parse(ingredientList))
    end
    return recipeDict
end

# ╔═╡ 11b589ff-3a09-4395-921a-872feb190022
if isfile(filePath)
	recipeDictExp = loadRecipeDBCSV(filePath)
end

# ╔═╡ adde7105-4a4a-453b-a7a8-9c9471dc29d3
function createIngredientDatabase(recipeDict)
    # create an unique vector of all ingredients used in the recipe database
    ingredientList = []
    for ingredients in values(recipeDict)
        append!(ingredientList,ingredients)
    end
    ingredientList = unique(ingredientList)

    return ingredientList
end

# ╔═╡ 89e58385-4db9-4ae3-9495-d8a2e63f2d72
function recipeToNumVector(fridgeList,ingredientList)
"""
    recipeToNumVector(fridgeList,ingredientList)

This function changes an ingredients list of a recipe to a numeric vector. This vector has a length equal
to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe, that position in the vector is a 1.
If not it is a 0. The last index of the vector contains the amount of extra ingredients needed.

## Input:
- fridgeList: A list containing the different foods in your fridge as a string.
- ingredientList: A list containing all the ingredients used in a specific recipe.

## Output:
- numVector: This vector has a length equal to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe,
that position in the vector is a 1. If not it is a 0. The last index of the vector contains the amount of extra ingredients needed.

"""
    numVector = zeros(Int64,length(fridgeList)+1)
    for i in 1:length(fridgeList)
        numVector[i] = fridgeList[i] in ingredientList ? 1 : 0
    end
    numVector[end] = length(ingredientList) - sum(numVector)
    return numVector
end

# ╔═╡ 3ed830c9-e3f7-4632-9ff3-cdd48c00f315
function randomCombo(fridgeList, recipeDict, numRecipes)

    randCombo = Dict()
    ingredientsArray = []
    namesArray = []

    for (name,ingredients) in recipeDict
        push!(namesArray,name)
        push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients))
    end

    for i = 1:numRecipes

        randIndex = rand(1:length(namesArray))
        tempRecipeName = namesArray[randIndex]
        randCombo[tempRecipeName] = ingredientsArray[randIndex]

        if all(isone.(sum(values(randCombo))))
            break
        end

        namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0]
        ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0]

        if isempty(namesArray)
            break
        end

    end

    return randCombo
end

# ╔═╡ a6e77624-606a-49ea-941d-8180cbd94a27
compatible(x) = !any(sum(x)[1:end-1] .>= 2) # checks if two recipes use a same ingredient

# ╔═╡ 644c2809-5b2b-4426-8deb-f952372a8438
begin

# objective function used to score a single recipe
fridgeObjective(x::Array{Int64}) = w_1 * sum(x[1:end-1]) + w_2 *sum(x[1:end-1] .== 0) + w_3 *x[end]

# objective function used to score a combination of recipes
fridgeObjective(x) = compatible(x) ? sum(sum(x) .== 1)*w_1 + sum(sum(x) .== 0)*w_2 + sum(x)[end]*w_3 : Inf

end

# ╔═╡ ace6ecfa-319b-48f3-a675-51b4bc26abd0
function greedyFindCombo(fridgeList, recipeDict, numRecipes)

    bestCombo = Dict()
    ingredientsArray = []
    namesArray = []

    for (name,ingredients) in recipeDict
        push!(namesArray,name)
        push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients))
    end

    for i = 1:numRecipes

        bestOrder = sortperm(ingredientsArray, by=fridgeObjective)
        ingredientsArray = ingredientsArray[bestOrder]
        namesArray = namesArray[bestOrder]

        # break if all recipes that are left, don't use anything from the fridge
        if all(ingredientsArray[1][1:end-1] .== 0)
            break
        end

        tempRecipeName = namesArray[1]
        bestCombo[tempRecipeName] = ingredientsArray[1]

        # break if all ingredients are used
        if all(isone.(sum(values(bestCombo))))
            break
        end

        namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0]
        ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0]

        # break if there are no recipes left
        if isempty(namesArray)
            break
        end

    end

    return bestCombo
end

# ╔═╡ 7365d57b-fd33-46af-9dcd-2c07908f64a3
function Neighbour(curSolution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe)

    toRemove = rand(curSolution)[1]
    # adapt the fridgeList so that only ingredients from the removed ingredient are available
    tempFridgeList = copy(fridgeList)
    for recipe in keys(curSolution)
        if recipe != toRemove
            tempFridgeList = [i for i in fridgeList if !in(i,recipeDict[recipe])]
            numRecipes -= 1
        end
    end

    # adapt the recipeDict and use greedy search to find a new solution
    tempRecipeDict = copy(recipeDict)
    for recipe in keys(curSolution)
        delete!(tempRecipeDict,recipe)
    end

    for recipe in tabuList
        try delete!(tempRecipeDict,recipe)
        catch e
        end
    end

    if randRecipe
        neighbour = randomCombo(tempFridgeList, tempRecipeDict, numRecipes)
    else
        neighbour = greedyFindCombo(tempFridgeList, tempRecipeDict, numRecipes)
    end

    # correct recipe vectors
    for recipe in keys(neighbour)
        neighbour[recipe] = recipeToNumVector(fridgeList,recipeDict[recipe])
    end

    # here combine the two dictionaries
    tempCurSolution = copy(curSolution)
    delete!(tempCurSolution,toRemove)
    neighbour = merge(neighbour,tempCurSolution)

    return neighbour
end

# ╔═╡ 6f1d6e44-30a1-42ee-b888-4cbb105b5348
function SAFindCombo(initSolution,  fridgeList, recipeDict, numRecipes, randRecipe;
    kT=100, # repetitions per temperature
    r=0.75, # cooling rate
    Tmax=4, # maximal temperature to start
    Tmin=1, # minimal temperature to end
    tabuLength=3) # number of cycli that recipe needs to be blocked

    @assert 0 < Tmin < Tmax "Temperatures should be positive"
	@assert 0 < r < 1 "cooling rate is between 0 and 1"
	solution = initSolution
	obj = fridgeObjective([i for i in values(solution)])
    tabuList = String[i for i in keys(initSolution)]

	# current temperature
	T = Tmax
	while T > Tmin
		# repeat kT times
		for i in 1:kT
			sn = Neighbour(solution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe)  # random neighbor
			obj_sn = fridgeObjective([i for i in values(sn)])
			# if the neighbor improves the solution, keep it
			# otherwise accept with a probability determined by the
			# Metropolis heuristic
			if obj_sn < obj || rand() < exp(-(obj_sn-obj)/T)
				solution = sn
				obj = obj_sn
			end

            for recipe in keys(sn)
                if !in(recipe, tabuList)
                    if length(tabuList) < tabuLength
                        pushfirst!(tabuList,recipe)
                    else
                        pop!(tabuList)
                        pushfirst!(tabuList,recipe)
                    end
                end
            end
		end

		# decay temperature
		T *= r
	end

    return solution
end

# ╔═╡ 9bfc9f3d-bc05-4c15-8b4c-2cfce3507afc
function findBestRecipe(fridgeList, dataPath; numRecipes=3, randRecipe=false)

    # load the recipe dictionary from the db file
    recipeDict = dataPath[end-3:end] == ".csv" ? loadRecipeDBCSV(dataPath) : load(dataPath)

    # create a list of all ingredients in your database
    ingredientList = createIngredientDatabase(recipeDict)

    # check for every food in your fridge if it's in the database. If not check if there are alternatives.
    fridgeList = checkIngredients(fridgeList, ingredientList)

    # find the best greedy recipe
    greedySolution = greedyFindCombo(fridgeList, recipeDict, numRecipes)

    # find the best recipe with SA
    SASolution = SAFindCombo(greedySolution,  fridgeList, recipeDict, numRecipes, randRecipe)

    # print the solution
    print("The best recipes to make are:\n\n")
    for recipeName in keys(SASolution)
        print("$(recipeName) : $(recipeDict[recipeName])\n")
    end

    return SASolution
end

# ╔═╡ 3569550e-0157-4d7a-8cda-4df327c92b81
if readyToSearch
	findBestRecipe(fridgeList, filePath)
end

# ╔═╡ e10c7ddf-aef0-470a-be22-ac250d84fb7e
begin
if startExperimenting
	greedySolutionExp = greedyFindCombo(fridgeList, recipeDictExp, numRecipesExp)
	SASolutionExp = SAFindCombo(greedySolutionExp,  fridgeList, recipeDictExp, numRecipesExp, randRecipeExp, kT=kTExp, r=rExp, Tmax=TmaxExp, Tmin=TminExp,  tabuLength=tabuLengthExp)

	md"""
	greedy output: $([recipe * ", " for recipe in keys(greedySolutionExp)])\
	SA output: $([recipe * ", " for recipe in keys(SASolutionExp)])
	"""
else
	md"Check the `Start experimenting` checkbox to see the output of your parameter combination."
end
end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Cascadia = "54eefc05-d75b-58de-a785-1a3403f0919f"
Gumbo = "708ec375-b3d6-5a57-a7ce-8257bf98657a"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
CSV = "~0.10.2"
Cascadia = "~1.0.1"
Gumbo = "~0.8.0"
HTTP = "~0.9.17"
JLD2 = "~0.4.18"
PlutoUI = "~0.7.32"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"]
git-tree-sha1 = "9519274b50500b8029973d241d32cfbf0b127d97"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.2"

[[Cascadia]]
deps = ["AbstractTrees", "Gumbo"]
git-tree-sha1 = "95629728197821d21a41778d0e0a49bc2d58ab9b"
uuid = "54eefc05-d75b-58de-a785-1a3403f0919f"
version = "1.0.1"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "67551df041955cc6ee2ed098718c8fcd7fc7aebe"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.12.0"

[[FilePathsBase]]
deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "04d13bfa8ef11720c24e4d840c0033d145537df7"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.17"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[Gumbo]]
deps = ["AbstractTrees", "Gumbo_jll", "Libdl"]
git-tree-sha1 = "e711d08d896018037d6ff0ad4ebe675ca67119d4"
uuid = "708ec375-b3d6-5a57-a7ce-8257bf98657a"
version = "0.8.0"

[[Gumbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "29070dee9df18d9565276d68a596854b1764aa38"
uuid = "528830af-5a63-567c-a44a-034ed33b8444"
version = "0.10.2+0"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.17"

[[Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InlineStrings]]
deps = ["Parsers"]
git-tree-sha1 = "8d70835a3759cdd75881426fced1508bb7b7e1b6"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.1.1"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "39f22411266cdd1621092c762a3f0648dbdc8433"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.18"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "22df5b96feef82434b07327e2d3c770a9b21e023"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "92f91ba9e5941fc781fecf5494ac1da87bdac775"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "ae6145ca68947569058866e443df69587acc1806"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.32"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "db3a23166af8aebf4db5ef87ac5b00d36eb771e2"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.0"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "2cf929d64681236a2e074ffafb8d568733d2e6af"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.3"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "15dfe6b103c2a993be24404124b8791a09460983"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.11"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "bb1064c9a84c52e277f1096cf41434b675cd368b"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.1"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[WeakRefStrings]]
deps = ["DataAPI", "InlineStrings", "Parsers"]
git-tree-sha1 = "c69f9da3ff2f4f02e811c3323c22e5dfcb584cfa"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.4.1"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─ae625e40-7f56-11ec-3cd5-d1244e23a3ba
# ╟─06db3cc2-1680-49b9-a054-281a6b35205a
# ╟─3ad1af72-7096-4106-a3c7-47326695e2cb
# ╠═8e58f807-1db9-495e-8679-430f953292ea
# ╟─264b1eb5-c421-4a69-a55c-8fbbbb7715d1
# ╠═644c2809-5b2b-4426-8deb-f952372a8438
# ╟─1752889a-513a-4620-add4-a9ca7fa92c8e
# ╟─a65c0925-7a67-4da3-9a6d-2ade8d3529e9
# ╠═ace6ecfa-319b-48f3-a675-51b4bc26abd0
# ╟─954fe2b3-171b-43ad-a1df-cbb3414988de
# ╠═6f1d6e44-30a1-42ee-b888-4cbb105b5348
# ╟─eee53924-fd20-44d8-a3eb-a2af9f9112b9
# ╟─7315f676-0d0e-43ae-8231-b6274dc5c8de
# ╠═3ed830c9-e3f7-4632-9ff3-cdd48c00f315
# ╟─134a2dd7-69d2-449e-82ab-5bfff90ede1b
# ╠═7365d57b-fd33-46af-9dcd-2c07908f64a3
# ╟─b6205bd3-41da-45e0-81f1-a480a3f0e737
# ╠═9bfc9f3d-bc05-4c15-8b4c-2cfce3507afc
# ╟─6af2a6dd-ba7e-470e-8915-12ae6eff8357
# ╟─656f211d-d1e8-4325-a12b-b89eca0351da
# ╠═cd5458a5-2136-4559-b4e5-2d91abea870c
# ╟─bf2725c1-55e6-4c6a-9b83-53f5984cae75
# ╠═fd02d76a-0ae6-4869-96bb-516c536fb91d
# ╟─34544e10-e4a1-4cfe-b0bc-43f8927bb19c
# ╟─312fabf7-7d11-4bac-b52b-14de82d13e73
# ╠═3569550e-0157-4d7a-8cda-4df327c92b81
# ╟─15a97461-7096-4ea0-acbc-edbc5db79cde
# ╟─d49cf674-088f-47a1-9795-7641b3f01cd1
# ╠═11b589ff-3a09-4395-921a-872feb190022
# ╟─86f03d01-4f97-4487-9683-38f8dc9d826a
# ╟─70ffadd9-80d4-4eb9-92d9-e3d5ffa50074
# ╟─e10c7ddf-aef0-470a-be22-ac250d84fb7e
# ╟─c51f2b76-fe4c-4c29-87dc-c7784e5200c2
# ╟─09d6393c-9b14-4401-babe-708a88ef0a16
# ╟─87b72838-260f-4ffb-ac79-03860165afa1
# ╟─adde7105-4a4a-453b-a7a8-9c9471dc29d3
# ╟─89e58385-4db9-4ae3-9495-d8a2e63f2d72
# ╟─a6e77624-606a-49ea-941d-8180cbd94a27
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


"""

```
writexyz(x::Vector{T}, file::String) where T <: AbstractVector
```

Print test xyz file.

"""
function writexyz(x::Vector{T}, file::String) where T <: AbstractVector
  f = open(file,"w")
  nx = length(x)
  println(f,nx)
  println(f,"title")
  for i in 1:nx
    println(f,"H $(x[i][1]) $(x[i][2]) $(x[i][3])")
  end
  close(f)
  nothing
end


using Reexport

@reexport module JointDistanceDistribution
    import HypothesisTests: OneSampleTTest, pvalue
    export OneSampleTTest, pvalue
    include("joint_distance_distribution.jl")
end


# Tuple Tests
let
    basetup = (1,2.0,3,4.0,5,6.0)
    for j in 1:length(basetup)
        tup = tuple(basetup[1:j])
        for i in 1:length(tup)
            @test typemax(tup)[i] == typemax(typeof(tup[i]))
            @test typemin(tup)[i] == typemin(typeof(tup[i]))
        end
    end
    @test (1,0,0,0,0) < 2
    @test (1,0,0,0,0) <= 1
    @test (1,0,0,0,0) >= 1
    @test (1,0,0,0,0) > 0

    @test 2 > (1,0,0,0,0)
    @test 1 >= (1,0,0,0,0)
    @test 1 <= (1,0,0,0,0)
    @test 0 < (1,0,0,0,0)
end
let
    S = GraphState
    A = GraphAction
    P = PathNode{S,A}

    Path([P()])
    p = Path{S,A,Float64}()
    @test state_type(p) == S
    @test action_type(p) == A
    @test node_type(p) == P
    @test cost_type(p) == Float64
    get(p,1,node_type(p))
    get(p,1)
    # @test get_cost(p) == 0
    @test length(p.path_nodes) == 0
    push!(p,P())
    @test length(p.path_nodes) == 1
    p = cat(p,P())
    @test length(p.path_nodes) == 2
    @test typeof(p[1]) == P
    p[1] = P()
    @test length(p) == length(p.path_nodes)
    p1 = copy(p)
    @test get_cost(p1) == get_cost(p)
    @test length(p1) == length(p)
end
let
    S = GraphState
    A = GraphAction
    P = PathNode{S,A}

    p = Path{S,A,Float64}(
        s0 = S(1,0)
    )

    get_path_node(p,1)
    get_path_node(p,5)
    get_a(p,1)
    @test length(extend_path(p,5)) == 5
    @test get_end_index(extend_path(p,5)) == 5
    extend_path!(p,5)
    @test length(p) == 5
    @test get_end_index(p) == 5

    get_initial_state(p)
    get_final_state(p)

    cost_model = TravelTime()
    solution = LowLevelSolution(
        cost_model = TravelTime(),
        paths = [Path{S,A,cost_type(cost_model)}()],
        costs = [0.0]
    )
    state_type(solution)
    action_type(solution)
    cost_type(solution)
    node_type(solution)
    copy(solution)
    get_paths(solution)
    get_path_costs(solution)
    get_cost(solution)
    get_cost_model(solution)
    set_solution_path!(solution,get_paths(solution)[1],1)
    set_path_cost!(solution,2.0,1)
end


####### WARNING NONE OF THESE FUNCTIONS ARE TESTED & SOME ARE VERY OLD! ########

"""
	dict2workspace{S<:Any}(dict::Dict{S, Any})
Pop key,value pairs into the workspace.
"""
function dict2workspace(dict::AbstractDict, evalmodule::Module=Main)
    for (k,v) in dict
        key2workspace(k,v, evalmodule)
    end
end

function key2workspace(k,v,evalmodule)
     eval(evalmodule,:($(Symbol(k))=$v))
end

function key2workspace(k::Symbol,evalmodule)
    eval(evalmodule,:($k=$v))
end

"""
Get sliding windows across an axis
"""
sliding_windows(len,wlen,step)= [x:x+wlen for x in 1:step:len-wlen]

### Check for file & return errormessage
function checkforfile(filename, errormessage="$filename not found")
	if !isfile(filename)
		error(errormessage)
		return false
	else
		return true
	end
end

## Get an example key from a dictionary
function getakey(dictionary::Dict)
	a = AbstractString{}
	for i in keys(dictionary); a = i; end
	a
end

###Convert a BitArray to a Integer Array (only 2-d currently)
function bit2intarray(::Type{Array{Int64,2}}, x::BitArray{2})
	xReal = zeros(Int,size(x))
	for (ind,j) in enumerate(x)
		if j
			xReal[ind] = 1
		end
	end
	xReal
end

## These functions generate random numbers fast!
### Borrowed from http://stackoverflow.com/questions/32248918/efficient-way-of-generating-random-integers-within-a-range-in-julia
function lcg(old)
	a = unsigned(2862933555777941757)
	b = unsigned(3037000493)
	a*old + b
end

function randfast(k, x::UInt)
	x = lcg(x)
	1 + rem(x, k) % Int, x
end

## To generate N random numbers within a range R
function random_generator(N, R)
	state = rand(UInt)
	if N==1
		x,state = randfast(R, state)
	else x = zeros(Int,N)
		for i = 1:N
			x[i], state = randfast(R, state)
		end
	end
	return x
end

# Interpolate around a "value" using a moving average of "width"
## towards the value starting from "around" points either side
## i.e. value is the index where the centre of the interpolation
## will take place
function interpolatearoundvalue!(vector::Array{T, 1}, value, around, width) where T<:Real
	for i in value-around:value
		vector[i] = mean(vector[i-width:i])
	end
	for i in value:value+around
		vector[i] = mean(vector[i:i+width])
	end
end

function interpolatearoundvalue!(array::Array{T, 2}, value, around, width) where T<:Real
	for ind in 1:size(array,2)
		for i in value-around:value
			array[i,ind] = mean(array[i-width:i,ind])
		end
		for i in value:value+around
			array[i,ind] = mean(array[:,ind][i:i+width])
		end
	end
end

function dimmeansnan(array)
	otherdim = size(array, 1)
	outarray = zeros(otherdim)
	for i in 1:otherdim
		temp = array[i,:]
		outarray[i] = mean(temp[!isnan(temp)])
	end
	outarray
end

function catamap(temp)
	for i in 1:size(temp,1)-1
		temp[i+1] = vcat(temp[i], temp[i+1])
	end
	temp[end]
end
function mapandcatdict(dict, key2get)
	temp = map(x -> x[key2get], values(dict))
	catamap(temp)
end

# Get mean across a dimension ignoring NaNs
notnanplus(a, b) = isnan(b) ? a : a+b
notnancount(a, b) = a + !isnan(b)
function nanmean(A, region)
	s = reducedim(notnanplus, A, region, zero(eltype(A)))
	n = reducedim(notnancount, A, region, 0)
	s ./ n
end

znorm(x,dims) = (x .- mean(x,dims))./ std(x, dims)


"""
    link!(p, q [, length])
    link!(p, q, branch)

Connect node `p` to node `q` with a new branch. The new branch will have no length if `length` is not given. Alternatively, an existing `Branch` object can be passed instead of creating a new one.

Returns the branch.
"""
function link!(p::AbstractNode, q::AbstractNode, br::AbstractBranch)
    p ≡ q && throw(InvalidTopology("`p` and `q` are the same node"))
    link_p2q = NodeLink(q, br)
    link_q2p = NodeLink(p, br)

    push!(p.links, link_p2q)
    push!(q.links, link_q2p)

    return br
end # function link!

link!(p::AbstractNode, q::AbstractNode) = link!(p, q, Branch())

link!(p::AbstractNode, q::AbstractNode, length::Float64) =
    link!(p, q, Branch(length))

"""
    unlink!(p, q)

Dissociate the nodes `p` and `q` and return the branch that was connecting them.
"""
function unlink!(p::AbstractNode, q::AbstractNode)
    for (i, link) in enumerate(p.links)
        if link.to ≡ q
            for (j, link) in enumerate(q.links)
                if link.to ≡ p
                    @inbounds br = p.links[i].branch
                    deleteat!(p.links, i)
                    deleteat!(q.links, j)

                    return br
                end
            end
        end
    end

    throw(InvalidTopology("the two nodes are not linked."))
end # function unlink!

"""
    graft!(p, q, r; prop_brlength=0.5)
    graft!(p, q, r, branch_pq, branch_qr)

Insert node `p` between the nodes `q` and `r`, where `q` and `r` are neighbours.

The length of the branch between `q` and `r` is redistributed between the new branches (between `p` and `q` and between `q` and `r`) according to the `prop_brlength` parameter, which gives the proportion of the length of the branch between `q` and `r` that is to be assigned to the branch between `p` and `q`. The branch between `q` and `r` is assigned the remainder of the original branch length.

Alternatively, branch objects can be passed to be used as the new branches.
"""
function graft!(
    p::AbstractNode,
    q::AbstractNode,
    r::AbstractNode,
    br_qp::AbstractBranch,
    br_pr::AbstractBranch
    )

    unlink!(q, r)
    link!(q, p, br_qp)
    link!(p, r, br_pr)

    return nothing
end # function graft!

function graft!(
    p::AbstractNode,
    q::AbstractNode,
    r::AbstractNode,
    prop_qp::Float64 = 0.5;
    f::Union{Function, DataType} = Branch
    )

    @assert 0.0 ≤ prop_qp ≤ 1.0
    br0 = unlink!(q, r)
    if br0.length ≠ nothing
        link!(q, p, f(br0.length * prop_qp))
        link!(p, r, f(br0.length * (1.0 - prop_qp)))
    else
        link!(q, p, f())
        link!(p, r, f())
    end

    return nothing
end # function graft!

"""
    pluck!(p, q, r [, constructor])

Disconnect node `p` from its neighbours `q` and `r`, and connect `q` and `r` to each other. An
"""
function pluck!(
    p::AbstractNode,
    q::AbstractNode,
    r::AbstractNode,
    f::Union{Function, DataType} = Branch
    )
    br_pq = unlink!(p, q)
    br_pr = unlink!(p, r)

    if isnothing(br_pq.length) || isnothing(br_pr.length)
        br_qr = link!(q, r)
    else
        br_qr = link!(q, r, f(br_pq.length + br_pr.length))
    end

    sizehint!(br_qr.datablocks, length(br_pq.datablocks))

    return nothing
end # function pluck!


#
# This file is a part of MolecularGraph.jl
# Licensed under the MIT License http://opensource.org/licenses/MIT
#

export
    getsourcenode, getsourceedge, getunionnode, getunionedge,
    disjointunion, disjointunion!

struct DisjointUnionNode <: AbstractNode
    source::Int
    sourcekey::Int
end

struct DisjointUnionEdge <: UndirectedEdge
    source::Int
    sourcekey::Int
end

struct DisjointUnionGraph <: OrderedGraph
    neighbormap::Vector{Dict{Int,Int}}
    edges::Vector{Tuple{Int,Int}}
    nodeattrs::Vector{DisjointUnionNode}
    edgeattrs::Vector{DisjointUnionEdge}
    cache::Dict{Symbol,Any}
end

getsourcenode(graph, i) = graph.nodeattrs[i]
getsourceedge(graph, i) = graph.edgeattrs[i]

function getunionnode(graph::DisjointUnionGraph, source, key)
    for (i, node) in enumerate(nodeattrs(graph))
        if node.source == source && node.sourcekey == key
            return i
        end
    end
end

function getunionedge(graph::DisjointUnionGraph, source, key)
    for (i, edge) in enumerate(edgeattrs(graph))
        if edge.source == source && edge.sourcekey == key
            return i
        end
    end
end

"""
    disjointunion(g1::UndirectedGraph, g2::UndirectedGraph,
        G::UndirectedGraph...) -> DisjointUnionGraph

Generate disjoint union graph of given graphs. The new graph with type
`DisjointUnionGraph` retains mapping to the original graphs as nodes and edges
attributes.
"""
function disjointunion(
            g1::UndirectedGraph, g2::UndirectedGraph, G::UndirectedGraph...)
    U = DisjointUnionGraph([], [], [], [], Dict())
    for (src, g) in enumerate([g1, g2, G...])
        nsort = sort(collect(nodeset(g)))
        esort = sort(collect(edgeset(g)))
        nmap = Dict(n => i + nodecount(U) for (i, n) in enumerate(nsort))
        emap = Dict(e => i + edgecount(U) for (i, e) in enumerate(esort))
        nbrs = [neighbors(g, n) for n in nsort]
        edges = [getedge(g, e) for e in esort]
        append!(U.neighbormap, relabel(nbrs, nmap, emap))
        append!(U.edges, relabel(edges, nmap))
        append!(U.nodeattrs, [DisjointUnionNode(src, k) for k in nsort])
        append!(U.edgeattrs, [DisjointUnionEdge(src, k) for k in esort])
    end
    return U
end

"""
    disjointunion!(g1::T, g2::T, G::T...) where {T<:OrderedGraph} -> T

Generate disjoint union graph of given graphs. `g1` will be overwritten by the
union graph. Unlike non-destructive `disjointunion`, `g1` does not retain any
information about other given graphs but a bit faster.
"""
function disjointunion!(g1::T, g2::T, G::T...) where {T<:OrderedGraph}
    for g in [g2, G...]
        noff = nodecount(g1)
        nmap = Dict(i => i + noff for i in 1:nodecount(g))
        eoff = edgecount(g1)
        emap = Dict(i => i + eoff for i in 1:edgecount(g))
        append!(g1.neighbormap, relabel(g.neighbormap, nmap, emap))
        append!(g1.edges, relabel(g.edges, nmap))
        isdefined(g1, :nodeattrs) && append!(g1.nodeattrs, g.nodeattrs)
        isdefined(g1, :edgeattrs) && append!(g1.edgeattrs, g.edgeattrs)
    end
end

function relabel(edges::Vector{Tuple{Int,Int}}, nmap::Dict{Int,Int})
    es = Tuple{Int,Int}[]
    for (u, v) in edges
        push!(es, (nmap[u], nmap[v]))
    end
    return es
end

function relabel(neighbormap::Vector{Dict{Int,Int}},
                 nmap::Dict{Int,Int}, emap::Dict{Int,Int})
    ns = Dict{Int,Int}[]
    for nbr in neighbormap
        d = Dict{Int,Int}()
        for (inc, adj) in nbr
            d[emap[inc]] = nmap[adj]
        end
        push!(ns, d)
    end
    return ns
end


#= # for local testing, need this:
using Test
using PhyloNetworks
using PhyloPlots
=#

@testset "unconstrained NNI moves" begin

str_level1 = "(((8,9),(((((1,2,3),4),(5)#H1),(#H1,(6,7))))#H2),(#H2,10));"
net_level1 = readTopology(str_level1);
# same topology as: rootatnode!(net_level1, -3). edges 1:22
str_nontreechild = "((((Ag,E))#H3,(#H1:7.159::0.056,((M:0.0)#H2:::0.996,(Ak,(#H3:0.08,#H2:0.0::0.004):0.023):0.078):2.49):2.214):0.026,((Az:2.13,As:2.027):1.697)#H1:0.0::0.944,Ap);"
net_nontreechild = readTopology(str_nontreechild);
# problem: the plot has an extra vertical segment, for a clade that's not in the major tree
# --> fix that in PhyloPlots (fixit)
str_hybridladder = "(#H2:::0.2,((C,((B)#H1)#H2:::0.8),(#H1,(A1,A2))),O);"
net_hybridladder = readTopology(str_hybridladder);

#=
plot(net_level1, :R, showNodeNumber=true, showEdgeNumber=true)
plot(net_nontreechild, :R, showNodeNumber=true, showEdgeNumber=true)
plot(net_hybridladder, :R, showNodeNumber=true, showEdgeNumber=true)
=#

@test isnothing(PhyloNetworks.nni!(net_level1, net_level1.edge[1], 0x01)) # external edge

@testset "level1 edge 3: BB undirected move $move" for move in 0x01:0x08
    undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[3], move);
    #location of v node (number -4)
    nodes = [n.number for n in net_level1.edge[20].node] #α's connections
    if move in [1, 2, 6, 8] #check that edge α connected to v
        @test -4 in nodes
    else
        @test !(-4 in nodes)
    end
    #location of u node (number -3)
    nodes = [n.number for n in net_level1.edge[1].node]
    if move in [2, 4, 5, 6] #check that edge γ connected to u
        @test -3 in nodes
    else
        @test !(-3 in nodes)
    end
    # check directionality
    if move in [3, 4, 5, 7]
        # keeping α, β or flipping uv keeps node -4 as child of edge 3
        @test PhyloNetworks.getChild(net_level1.edge[3]).number == -4
    else
        # switching α, β AND flipping uv or doing neither makes node -3 child of edge 3
        @test PhyloNetworks.getChild(net_level1.edge[3]).number == -3
    end
    # undo move
    PhyloNetworks.nni!(undoinfo...);
    # confirm we're back to original topology
    @test writeTopology(net_level1) == str_level1
end # of level1 edge 3: BB undirected

@testset "level1 test edge 13: BR directed move $move" for move in 0x01:0x03
    # e.hybrid and tree parent:  BR case, 3 moves because e cannot contain the root
    # 3cycle test: α connected to γ so move 1 will create 3 cycles.
    if move == 0x01
        @test isnothing(PhyloNetworks.nni!(net_level1, net_level1.edge[13], move)) # would create a 3cycle
    # move 1 would create a 3 cycle, but should work if we don't forbid 3cycles
        undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[13], move, false); #no3cycle=false
        nodes = [n.number for n in net_level1.edge[17].node]
        @test 8 in nodes # check that edge α connected to v
        nodes = [n.number for n in net_level1.edge[11].node]
        @test !(-11 in nodes)
        @test PhyloNetworks.getChild(net_level1.edge[13]).number == -11
        PhyloNetworks.nni!(undoinfo...); # undo move
        @test writeTopology(net_level1) == str_level1 # confirm we're back to original topology
    else
        undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[13], move);
        # test that move was made
        nodes = [n.number for n in net_level1.edge[17].node]
        if move == 0x03
            @test 8 in nodes # check that edge α connected to v
        else
            @test !(8 in nodes)
        end
        #location of u node (number -11)
        nodes = [n.number for n in net_level1.edge[11].node]
        if move == 0x03 # check that edge γ connected to u
            @test -11 in nodes
        else # check that edge δ connected to u
            @test !(-11 in nodes)
        end
        # check directionality
        @test PhyloNetworks.getChild(net_level1.edge[13]).number == 8
        PhyloNetworks.nni!(undoinfo...); # undo move
        @test writeTopology(net_level1) == str_level1 # confirm we're back to original topology
    end
end # of level1 edge 13: BR directed

@testset "level1 edge 16: BB directed move $move" for move in 0x01:0x02
    # e not hybrid, tree parent:  BB case, 2 NNIs if directed, 8 if undirected
    undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[16], move);
    nodes = [n.number for n in net_level1.edge[13].node] #β's connections
    @test -11 in nodes #check β connected to u node (number -11)
    nodes = [n.number for n in net_level1.edge[14].node] #γ's connections
    if move == 1 #check that edge γ connected to v
        @test -12 in nodes
    else #check that edge γ connected to u
        @test -11 in nodes
    end
    # check directionality node -11 child of edge 16 in both cases
    @test PhyloNetworks.getChild(net_level1.edge[16]).number == -11
    PhyloNetworks.nni!(undoinfo...); # undo move
    @test writeTopology(net_level1) == str_level1 #confirm we're back to original topology
end #of level1 edge 16: BB directed

# BB directed has two allowed moves, so 0x03 should throw an exception
@test_throws Exception PhyloNetworks.nni!(net_level1, net_level1.edge[16], 0x03);

@testset "level1 edge 17: BB directed with 4cycle move $move" for move in 0x01:0x02
    # no3cycle test
        # move 2 should fail because of 3cycle problems
        # β is connected in a 4 cycle with γ so a move that makes γ and β a pair
        # would create a 3 cycle
    if move == 0x01
        undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[17], move);
        nodes = [n.number for n in net_level1.edge[12].node] #β's connections
        @test -6 in nodes #check β connected to u node (number -6)
        nodes = [n.number for n in net_level1.edge[13].node] #γ's connections
        #check that edge γ connected to v
        @test -11 in nodes
        # check directionality node -6 child of edge 17 in both cases
        @test PhyloNetworks.getChild(net_level1.edge[17]).number == -6
        PhyloNetworks.nni!(undoinfo...); # undo move
        @test writeTopology(net_level1) == str_level1 #confirm we're back to original topology
    else
        @test isnothing(PhyloNetworks.nni!(net_level1, net_level1.edge[17], move))
        undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[17], move, false) #should work if we dont check for 3cycles
        nodes = [n.number for n in net_level1.edge[12].node] #β's connections
        @test -6 in nodes #check β connected to u node (number -6)
        nodes = [n.number for n in net_level1.edge[13].node] #γ's connections
        #check that edge γ connected to u
        @test -6 in nodes
        # check directionality node -11 child of edge 17 in both cases
        @test PhyloNetworks.getChild(net_level1.edge[17]).number == -6
        PhyloNetworks.nni!(undoinfo...); # undo move
        @test writeTopology(net_level1) == str_level1 #confirm we're back to original topology
    end
end # of level1 edge 17: BB directed

@testset "level1 edge 18: RR (directed) move $move" for move in 0x01:0x04
    # RB case, 4 moves. uv edge cannot contain the root (always directed)
    undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[18], move);
    #test that move was made
    nodes = [n.number for n in net_level1.edge[19].node] #α's connections
    if move in [0x01, 0x03]
        @test -6 in nodes #check that edge α connected to v
    elseif move in [0x02, 0x04]
        @test !(-6 in nodes)
    end
    nodes = [n.number for n in net_level1.edge[12].node] #γ's connections
    if move in [0x03, 0x04] #check that edge γ connected to u (11)
        @test 11 in nodes
    elseif move in [0x01, 0x02]  #check that edge δ connected to u, not γ
        @test !(11 in nodes)
    end
    #check directionality (should point toward u, node 11)
    @test PhyloNetworks.getChild(net_level1.edge[18]).number == 11
    PhyloNetworks.nni!(undoinfo...);
    @test writeTopology(net_level1) == str_level1
end # of level1 edge 18: RR (directed)

@testset "non tree child net edge 3: RB (directed) move $move" for move in 0x01:0x04
    # RB case, 4 moves. uv edge cannot contain the root
    undoinfo = PhyloNetworks.nni!(net_nontreechild, net_nontreechild.edge[3], move);
    #test that move was made
    #location of v node (node -5)
    nodes = [n.number for n in net_nontreechild.edge[4].node] #α's connections
    if move in [0x01, 0x03]
        #check that edge α connected to v
        @test -5 in nodes
    else
        #check that edge β connected to v, not u
        @test !(-5 in nodes)
    end
    nodes = [n.number for n in net_nontreechild.edge[2].node] #δ's connections
    if move in [0x01, 0x02] #check that edge δ connected to u
        @test 3 in nodes
    else
        @test !(3 in nodes)
    end
    #check directionality (should point toward u, node 3)
    @test PhyloNetworks.getChild(net_nontreechild.edge[3]).number == 3
    #undo move
    PhyloNetworks.nni!(undoinfo...);
    #confirm we're back to original topology
    @test writeTopology(net_nontreechild) == str_nontreechild
end #of non tree child net edge 5: RB (directed)

@testset "hybrid ladder net edge 1: BR undirected (u at root, potential nonDAG, 3cycle) move $move" for move in 0x01:0x06
    # BR case, 6 moves. uv edge can contain the root. u at root
    # DAG check: u is root, so if α -> γ, moves 1 and 3 will create a nonDAG
        # α -> γ so moves 1 and 3 will fail.
        # moves 1 and 3 in the notes correspond to 1, 1' and 3, 3' (1, 4 and 3, 6)
    # 3cycle check: could create 3 cycle (α connected to γ) so moves 1 and 5 forbidden
    if move in [0x01, 0x03, 0x05, 0x06] # DAG check
        @test isnothing(PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[1], move, false))
    end
    if move in [0x01, 0x05] # 3cycle check
        @test isnothing(PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[1], move))
    end
    if move == 0x04
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[1], move, false);
        nodes = [n.number for n in net_hybridladder.edge[12].node] # α
        @test !(1 in nodes) # check that edge α is connected to v
        nodes = [n.number for n in net_hybridladder.edge[4].node] # δ's connections
        @test -2 in nodes # check that edge δ is connected to u
        # check directionality (edge should point toward u, node -2)
        @test PhyloNetworks.getChild(net_hybridladder.edge[1]).number == 1
        PhyloNetworks.nni!(undoinfo...);
        @test writeTopology(net_hybridladder) == str_hybridladder
    elseif move == 0x02
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[1], move);
        nodes = [n.number for n in net_hybridladder.edge[12].node] # α
        @test !(1 in nodes) # check that edge α not connected to v
        nodes = [n.number for n in net_hybridladder.edge[4].node] # δ's connections
        @test -2 in nodes # δ connected to u
        # check directionality (edge should point toward u, node -2)
        @test PhyloNetworks.getChild(net_hybridladder.edge[1]).number == 1
        PhyloNetworks.nni!(undoinfo...);
        @test writeTopology(net_hybridladder) == str_hybridladder
    end
end # of hybrid ladder net edge 1: BR undirected

@testset "hybrid ladder net edge 4: RR (directed) move $move" for move in 0x01:0x02
    # RR case, 2 moves. uv edge cannot contain the root (always directed)
    undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[4], move);
    #test that move was made
    nodes = [n.number for n in net_hybridladder.edge[5].node] #α's connections
    if move == 0x01 #check that edge α connected to v
        @test 4 in nodes
    else move == 0x02 #check that edge β connected to v, not α
        @test !(4 in nodes)
    end
    #check that edge δ connected to u, not γ
    nodes = [n.number for n in net_hybridladder.edge[3].node]
    @test 1 in nodes
    #check directionality (should point toward u, node 1)
    @test PhyloNetworks.getChild(net_hybridladder.edge[4]).number == 1
    #undo move
    PhyloNetworks.nni!(undoinfo...);
    #confirm we're back to original topology
    @test writeTopology(net_hybridladder) == str_hybridladder
end #of hybrid ladder net edge 4: RR (directed)

@testset "hybrid ladder net edge 5: BR undirected move $move" for move in 0x01:0x06
    # BR case, 6 moves. uv edge can contain the root
    # 3 cycle test: α connected to γ, α -> u
    #   moves 1, 5 forbidden
    # DAG test:
    #   no path from α -> γ or β -> γ so all moves should work
    if move in [0x01, 0x05]
        @test isnothing(PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[5], move)) #3cycle forbidden
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[5], move, false) #3cycles allowed
    else
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[5], move);
    end
    #tests for all moves:
    nodes = [n.number for n in net_hybridladder.edge[6].node] #α
    if move in [0x01, 0x03, 0x05, 0x06]
        @test 1 in nodes #check that edge α connected to v
    else
        @test !(1 in nodes) #check that edge α not connected to v
    end
    nodes = [n.number for n in net_hybridladder.edge[4].node] #δ's connections
    if move in [0x01, 0x02, 0x04, 0x05]
        @test -4 in nodes #δ connected to u
    else
        @test !(-4 in nodes)
    end
    #check directionality
    if move in [0x01, 0x05]
        #(edge should point toward u, node -4)
        @test PhyloNetworks.getChild(net_hybridladder.edge[5]).number == -4
    else
        @test PhyloNetworks.getChild(net_hybridladder.edge[5]).number == 1
    end
    #undo move
    PhyloNetworks.nni!(undoinfo...);
    #confirm we're back to original topology
    @test writeTopology(net_hybridladder) == str_hybridladder
end #of hybrid ladder net edge 5: BR undirected

@testset "hybrid ladder net edge 12: BB undirected (edge below root) move $move" for move in 0x01:0x08
    # BB case, 8 moves. uv edge can contain the root. no flip.
    # no3cycle: moves 1, 4, 5, 8 would create a 3 cycle because α is connected to γ
    if move in [0x01, 0x04, 0x05, 0x08]
        @test isnothing(PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[12], move))
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[12], move, false);
    else
        undoinfo = PhyloNetworks.nni!(net_hybridladder, net_hybridladder.edge[12], move);
    end
    nodes = [n.number for n in net_hybridladder.edge[1].node] #α
    if move in [0x01, 0x02, 0x06, 0x08]
        @test -3 in nodes #check that edge α connected to v
    else
        @test !(-3 in nodes) #check that edge α not connected to v
    end
    nodes = [n.number for n in net_hybridladder.edge[11].node] #δ's connections
    if move in [0x01, 0x03, 0x07, 0x08]
        @test -2 in nodes #δ connected to u
    else
        @test !(-2 in nodes)
    end
    #check directionality
    @test PhyloNetworks.getChild(net_hybridladder.edge[12]).number == -3
    #undo move
    PhyloNetworks.nni!(undoinfo...);
    # confirm we're back to original topology
    @test writeTopology(net_hybridladder) == str_hybridladder
end # of hybrid ladder net edge 12: BB undirected (edge below root)

@testset "test isdescendant and isconnected functions" begin
    net_level1 = readTopology(str_level1);
    @test PhyloNetworks.isdescendant(net_level1.node[7], net_level1.node[17]) == true # nodes -9, -6
    @test PhyloNetworks.isdescendant(net_level1.node[7], net_level1.node[3]) == false # nodes -9, -4
    @test PhyloNetworks.isdescendant(net_level1.node[15], net_level1.node[17]) == true # node -12, -6
    @test PhyloNetworks.isconnected(net_level1.node[12], net_level1.node[17]) == true #node -7, -6
    @test PhyloNetworks.isconnected(net_level1.node[12], net_level1.node[19]) == false # node -7, -3
end

#TODO add these after adding contraint functionality
# myconstraint = PhyloNetworks.TopologyConstraint(0x03,Set("8"),net_level1)
# undoinfo = PhyloNetworks.nni!(net_level1, net_level1.edge[1], myconstraint)

end # of testset on unconstrained NNIs


@testset "one_component_2d" begin

	for T in (Int, Bool, Gray{Bool}, Gray{N0f8}, Gray{N0f16}, Gray{N0f32}, Gray{Float64})
		for i = 1:20
			test_image = T.(eval(Symbol("test_image_$(i)")))

			# Call one component 2D algorithm and compare the number of components.
			labels = ImageComponentAnalysis.label_components(OneComponent2D(), test_image)
			num_components = maximum(labels)

			@test num_components == test_image_results[i]
		end
	end

end


module OmegaGrad

using Reexport

include("gradinterface.jl")

include("reversediff.jl")
@reexport using .OmegaReverseDiff

include("zygote.jl")
@reexport using .OmegaZygote

end


module Core

import ..MarketData
import ..Currencies
import ..BusinessDays
import ..InterestRates
import ..CAL_BRL
import ..ForwardDiff
import ..OptimizingIR
import ..BS

using Dates
using Printf

include("types.jl")
include("observables_algebra.jl")
include("exch.jl")
include("contract_attributes.jl")
include("fixed_income.jl")
include("events.jl")
include("builtin_contracts.jl")
include("scenario.jl")
include("pricer.jl")
include("print.jl")
include("project_cashflows.jl")
include("compiler/compiler.jl")
include("docs.jl")

end # module Core


"From http://www.pserc.cornell.edu/matpower/MATPOWER-manual.pdf Table B-4"
@enum GeneratorCostModel begin
    PIECEWISE_LINEAR = 1
    POLYNOMIAL = 2
end

"Thrown upon detection of user data that is not supported."
struct DataFormatError <: Exception
    msg::String
end

struct InvalidParameter <: Exception
    msg::String
end

PS_MAX_LOG = parse(Int, get(ENV, "PS_MAX_LOG", "50"))


function bus_gen_values(data, solution, value_key)
    bus_pg = Dict(i => 0.0 for (i, bus) in data["bus"])
    for (i, gen) in data["gen"]
        bus_pg["$(gen["gen_bus"])"] += solution["gen"][i][value_key]
    end
    return bus_pg
end

function parse_pm_pmi(file)
    pm_data = PowerModels.parse_file(file)
    pmi_data = PMI.get_pm_data(System(file))
    #TODO: remove the following for loop once PM#v0.20.0 is tagged
    for id in keys(pmi_data["bus"])
        pm_data["bus"][id]["vm"] = pmi_data["bus"][id]["vm"]
    end
    return pm_data, pmi_data
end

@testset "Test Power Flow" begin
    file = joinpath(PM_DATA_DIR, "matpower", "case5.m")
    for (model, solver) in MODEL_SOLVER_MAP
        @testset "Testing $model with $(solver.optimizer_constructor.name.module)" begin
            pm_result = run_pf(file, model, solver)
            pmi_result = run_pf(System(file), model, solver)

            @test pm_result["termination_status"] == pmi_result["termination_status"]
            @test isapprox(pm_result["objective"], pmi_result["objective"]; atol = 1e-2)
        end
    end
end

@testset "Test Optimal PowerFlow" begin
    file = joinpath(PM_DATA_DIR, "matpower", "case5.m")
    data = PowerModels.parse_file(file)
    pmi_data = PMI.get_pm_data(System(file))

    for (model, solver) in MODEL_SOLVER_MAP
        pm_result = run_opf(data, model, solver)
        pmi_result = run_opf(pmi_data, model, solver)

        @test pm_result["termination_status"] == pmi_result["termination_status"]
        @test isapprox(pm_result["objective"], pmi_result["objective"]; atol = 1.8e1)
    end
end

@testset "Test DC PowerFlow" begin
    file = joinpath(PM_DATA_DIR, "matpower", "case5.m")
    data, pmi_data = parse_pm_pmi(file)
    pm_native = compute_dc_pf(data)
    pm_opt = run_dc_pf(data, ipopt_solver)
    ps_native = compute_dc_pf(System(file))
    ps_opt = run_dc_pf(System(file), ipopt_solver)

    @test ps_opt["termination_status"] == pm_opt["termination_status"] == LOCALLY_SOLVED

    for (i, bus) in data["bus"]
        @test isapprox(
            pm_opt["solution"]["bus"][i]["va"],
            ps_opt["solution"]["bus"][i]["va"];
            atol = 1e-3,
        )
        @test isapprox(
            pm_native["solution"]["bus"][i]["va"],
            ps_native["solution"]["bus"][i]["va"];
            atol = 1e-3,
        )
    end
end

@testset "5-bus case" begin
    file = joinpath(PM_DATA_DIR, "matpower", "case5.m")
    data, pmi_data = parse_pm_pmi(file)

    pm_native = compute_ac_pf(data)
    pm_opt = run_ac_pf(data, ipopt_solver)

    ps_native = compute_ac_pf(pmi_data)
    ps_opt = run_ac_pf(pmi_data, ipopt_solver)

    @test ps_opt["termination_status"] == pm_opt["termination_status"] == LOCALLY_SOLVED

    bus_pg_pm_opt = bus_gen_values(data, pm_opt["solution"], "pg")
    bus_qg_pm_opt = bus_gen_values(data, pm_opt["solution"], "qg")

    bus_pg_pm_native = bus_gen_values(data, pm_native["solution"], "pg")
    bus_qg_pm_native = bus_gen_values(data, pm_native["solution"], "qg")

    bus_pg_ps_opt = bus_gen_values(pmi_data, ps_opt["solution"], "pg")
    bus_qg_ps_opt = bus_gen_values(pmi_data, ps_opt["solution"], "qg")

    bus_pg_ps_native = bus_gen_values(pmi_data, ps_native["solution"], "pg")
    bus_qg_ps_native = bus_gen_values(pmi_data, ps_native["solution"], "qg")

    for (i, bus) in data["bus"]
        @test isapprox(
            pm_opt["solution"]["bus"][i]["va"],
            ps_opt["solution"]["bus"][i]["va"];
            atol = 1e-4,
        )
        @test isapprox(
            pm_opt["solution"]["bus"][i]["vm"],
            ps_opt["solution"]["bus"][i]["vm"];
            atol = 1e-3,
        )
        @test isapprox(
            pm_native["solution"]["bus"][i]["va"],
            ps_native["solution"]["bus"][i]["va"];
            atol = 1e-7,
        )
        @test isapprox(
            pm_native["solution"]["bus"][i]["vm"],
            ps_native["solution"]["bus"][i]["vm"];
            atol = 1e-7,
        )

        @test isapprox(bus_pg_pm_opt[i], bus_pg_ps_opt[i]; atol = 1e-4)
        if !Sys.iswindows() #this is actually pretty different on Windows, not sure why
            @test isapprox(bus_qg_pm_opt[i], bus_qg_ps_opt[i]; atol = 1e-2)
        end
        @test isapprox(bus_pg_pm_native[i], bus_pg_ps_native[i]; atol = 1e-6)
        @test isapprox(bus_qg_pm_native[i], bus_qg_ps_native[i]; atol = 1e-2)
    end
end


#
#  TIMESTEP
#

"""
	gettime(ts::FixedTimestep)

Return the time (year) represented by Timestep `ts`
"""
function gettime(ts::FixedTimestep{FIRST, STEP, LAST}) where {FIRST, STEP, LAST}
	return FIRST + (ts.t - 1) * STEP
end

"""
	gettime(ts::VariableTimestep)

Return the time (year) represented by Timestep `ts`
"""
function gettime(ts::VariableTimestep)
	return ts.current
end

"""
	is_time(ts::AbstractTimestep, t::Int)

Return true or false, true if the current time (year) for `ts` is `t`
"""
function is_time(ts::AbstractTimestep, t::Int)
	return gettime(ts) == t
end

"""
	is_first(ts::AbstractTimestep)

Return true or false, true if `ts` is the first timestep to be run.
"""
function is_first(ts::AbstractTimestep)
	return ts.t == 1
end

"""
	is_timestep(ts::AbstractTimestep, t::Int)

Return true or false, true if `ts` timestep is step `t`.
"""
function is_timestep(ts::AbstractTimestep, t::Int)
	return ts.t == t
end

"""
	is_last(ts::FixedTimestep)

Return true or false, true if `ts` is the last timestep to be run.
"""
function is_last(ts::FixedTimestep{FIRST, STEP, LAST}) where {FIRST, STEP, LAST}
	return gettime(ts) == LAST
end

"""
	is_last(ts::VariableTimestep)

Return true or false, true if `ts` is the last timestep to be run.  Note that you may
run `next_timestep` on `ts`, as ths final timestep has not been run through yet.
"""
function is_last(ts::VariableTimestep{TIMES}) where {TIMES}
	return gettime(ts) == TIMES[end]
end

function finished(ts::FixedTimestep{FIRST, STEP, LAST}) where {FIRST, STEP, LAST}
	return gettime(ts) > LAST
end

function finished(ts::VariableTimestep{TIMES}) where {TIMES}
	return gettime(ts) > TIMES[end]
end

function next_timestep(ts::FixedTimestep{FIRST, STEP, LAST}) where {FIRST, STEP, LAST}
	if finished(ts)
		error("Cannot get next timestep, this is last timestep.")
	end
	return FixedTimestep{FIRST, STEP, LAST}(ts.t + 1)
end

function next_timestep(ts::VariableTimestep{TIMES}) where {TIMES}
	if finished(ts)
		error("Cannot get next timestep, this is last timestep.")
	end
	return VariableTimestep{TIMES}(ts.t + 1)
end

function Base.:-(ts::FixedTimestep{FIRST, STEP, LAST}, val::Int) where {FIRST, STEP, LAST}
	if val != 0 && is_first(ts)
		error("Cannot get previous timestep, this is first timestep.")
	elseif ts.t - val <= 0
		error("Cannot get requested timestep, precedes first timestep.")
	end
	return FixedTimestep{FIRST, STEP, LAST}(ts.t - val)
end

function Base.:-(ts::VariableTimestep{TIMES}, val::Int) where {TIMES}
	if val != 0 && is_first(ts)
		error("Cannot get previous timestep, this is first timestep.")
	elseif ts.t - val <= 0
		error("Cannot get requested timestep, precedes first timestep.")
	end
	return VariableTimestep{TIMES}(ts.t - val)
end

function Base.:-(ts::TimestepValue, val::Int)
	return TimestepValue(ts.value; offset = ts.offset - val)
end

function Base.:-(ts::TimestepIndex, val::Int)
	return TimestepIndex(ts.index - val)
end

function Base.:+(ts::FixedTimestep{FIRST, STEP, LAST}, val::Int) where {FIRST, STEP, LAST}
	if finished(ts)
		error("Cannot get next timestep, this is last timestep.")
	elseif gettime(ts) + val > LAST + 1
		error("Cannot get requested timestep, exceeds last timestep.")
	end
	return FixedTimestep{FIRST, STEP, LAST}(ts.t + val)
end

function Base.:+(ts::VariableTimestep{TIMES}, val::Int) where {TIMES}
	if finished(ts)
		error("Cannot get next timestep, this is last timestep.")
	elseif gettime(ts) + val > TIMES[end] + 1
		error("Cannot get requested timestep, exceeds last timestep.")
	end
	new_ts = VariableTimestep{TIMES}(ts.t + val)
end

function Base.:+(ts::TimestepValue, val::Int)
	return TimestepValue(ts.value; offset = ts.offset + val)
end

function Base.:+(ts::TimestepIndex, val::Int)
	return TimestepIndex(ts.index + val)
end

# Colon support
function Base.:(:)(start::T, step::Int, stop::T) where {T<:TimestepIndex}
	indices = [start.index:step:stop.index...]
	return TimestepIndex.(indices)
end

function Base.:(:)(start::T, stop::T) where {T<:TimestepIndex}
	return Base.:(:)(start, 1, stop)
end

#
#  CLOCK
#

function Clock(time_keys::Vector{Int})
    last = time_keys[end]

    if isuniform(time_keys)
        first, stepsize = first_and_step(time_keys)
        return Clock{FixedTimestep}(first, stepsize, last)
    else
        last_index = findfirst(isequal(last), time_keys)
        times = (time_keys[1:last_index]...,)
        return Clock{VariableTimestep}(times)
    end
end

function timestep(c::Clock)
	return c.ts
end

function time_index(c::Clock)
	return c.ts.t
end

"""
	gettime(c::Clock)

Return the current time of the timestep held by the `c` clock.
"""
function gettime(c::Clock)
	return gettime(c.ts)
end

function advance(c::Clock)
	c.ts = next_timestep(c.ts)
	nothing
end

function finished(c::Clock)
	return finished(c.ts)
end

function Base.reset(c::Clock)
	c.ts = c.ts - (c.ts.t - 1)
	nothing
end

function timesteps(c::Clock)
	c = deepcopy(c)
	timesteps = AbstractTimestep[]
	while !finished(c)
		push!(timesteps, timestep(c))
		advance(c)
	end
	return timesteps
end


#
# Date created: 2022-03-16
# Author: aradclif
#
#
############################################################################################
function findmax_loops(N::Int, D)
    b4 = preexpr2(:typemin)
    inner = innerloop(N, D)
    push!(inner.args, maxblock3(N, D))
    push!(b4.args, inner)
    push!(b4.args, postexpr3(N, D).args...)
    outer = outerloop(N, D)
    push!(outer.args, b4)
    outer
end
function findmax_loops_innerturbo(N::Int, D, multithreaded::Bool)
    b4 = preexpr2(:typemin)
    inner = innerloop(N, D)
    push!(inner.args, maxblock3(N, D))
    push!(b4.args, Expr(:macrocall, multithreaded ? Symbol("@tturbo") : Symbol("@turbo"), (), inner))
    push!(b4.args, postexpr3(N, D).args...)
    outer = outerloop(N, D)
    push!(outer.args, b4)
    outer
end

function bfindmax_quote(N::Int, D)
    b1 = sizeblock(N)
    b2 = Expr(:block, ntuple(d -> offsetk(d + 2), N - 2)...)
    b3 = Expr(:block, totaloffsetraw(N), Expr(:(=), :Dstar, Expr(:call, :-, :Dstar)))
    outer = findmax_loops(N, D)
    return quote
        $b1
        $b2
        $b3
        $outer
    end
end
function ivfindmax_quote(N::Int, D)
    b1 = sizeblock(N)
    b2 = Expr(:block, ntuple(d -> offsetk(d + 2), N - 2)...)
    b3 = Expr(:block, totaloffsetraw(N), Expr(:(=), :Dstar, Expr(:call, :-, :Dstar)))
    outer = findmax_loops_innerturbo(N, D, false)
    return quote
        $b1
        $b2
        $b3
        $outer
    end
end
function ivtfindmax_quote(N::Int, D)
    b1 = sizeblock(N)
    b2 = Expr(:block, ntuple(d -> offsetk(d + 2), N - 2)...)
    b3 = Expr(:block, totaloffsetraw(N), Expr(:(=), :Dstar, Expr(:call, :-, :Dstar)))
    outer = findmax_loops_innerturbo(N, D, true)
    return quote
        $b1
        $b2
        $b3
        $outer
    end
end

function vfindmax_quote(N::Int, D)
    b1 = sizeblock(N)
    b2 = Expr(:block, ntuple(d -> offsetk(d + 2), N - 2)...)
    b3 = Expr(:block, totaloffsetraw(N), Expr(:(=), :Dstar, Expr(:call, :-, :Dstar)))
    outer = findmax_loops(N, D)
    return quote
        $b1
        $b2
        $b3
        @turbo $outer
    end
end

function vtfindmax_quote(N::Int, D)
    b1 = sizeblock(N)
    b2 = Expr(:block, ntuple(d -> offsetk(d + 2), N - 2)...)
    b3 = Expr(:block, totaloffsetraw(N), Expr(:(=), :Dstar, Expr(:call, :-, :Dstar)))
    outer = findmax_loops(N, D)
    return quote
        $b1
        $b2
        $b3
        @tturbo $outer
    end
end

@generated _bfindmax!(B::AbstractArray{T, N}, C::AbstractArray{Tₒ, N},
                      A::AbstractArray{T, N}, dims::D) where {T, Tₒ, N, D} = bfindmax_quote(N, D)
function bfindmax(A::AbstractArray{T, N}, dims::NTuple{M, Int}) where {T, N, M}
    Dᴮ = ntuple(d -> d ∈ dims ? 1 : size(A, d), N)
    Dᴮ′ = ntuple(d -> d ∈ dims ? Val(1) : size(A, d), N)
    B = similar(A, Dᴮ)
    C = similar(A, Int, Dᴮ)
    _bfindmax!(B, C, A, Dᴮ′)
    B, CartesianIndices(A)[C]
end

@generated _vfindmax!(B::AbstractArray{T, N}, C::AbstractArray{Tₒ, N},
                      A::AbstractArray{T, N}, dims::D) where {T, Tₒ, N, D} = vfindmax_quote(N, D)
function vfindmax(A::AbstractArray{T, N}, dims::NTuple{M, Int}) where {T, N, M}
    if ntuple(identity, Val(N)) ⊆ dims
        return vfindmax1(A)
    elseif 1 ∈ dims
        return ivfindmax(A, dims)
    else
        Dᴮ = ntuple(d -> d ∈ dims ? 1 : size(A, d), N)
        Dᴮ′ = ntuple(d -> d ∈ dims ? Val(1) : size(A, d), N)
        B = similar(A, Dᴮ)
        C = similar(A, Int, Dᴮ)
        _vfindmax2!(B, C, A, Dᴮ′)
        return B, CartesianIndices(A)[C]
    end
end

@generated _vtfindmax!(B::AbstractArray{T, N}, C::AbstractArray{Tₒ, N},
                       A::AbstractArray{T, N}, dims::D) where {T, Tₒ, N, D} = vtfindmax_quote(N, D)
function vtfindmax(A::AbstractArray{T, N}, dims::NTuple{M, Int}) where {T, N, M}
    if ntuple(identity, Val(N)) ⊆ dims
        return vtfindmax1(A)
    elseif 1 ∈ dims
        return ivtfindmax(A, dims)
    else
        Dᴮ = ntuple(d -> d ∈ dims ? 1 : size(A, d), N)
        Dᴮ′ = ntuple(d -> d ∈ dims ? Val(1) : size(A, d), N)
        B = similar(A, Dᴮ)
        C = similar(A, Int, Dᴮ)
        _vtfindmax!(B, C, A, Dᴮ′)
        return B, CartesianIndices(A)[C]
    end
end

# For handling cases where the first dimension is being reduced. Alas, still fails
# for some cases, e.g. A ∈ ℝᴵˣᴶˣᴷˣᴸ, dims=(1,2,3)
@generated _ivfindmax!(B::AbstractArray{T, N}, C::AbstractArray{Tₒ, N},
                       A::AbstractArray{T, N}, dims::D) where {T, Tₒ, N, D} = ivfindmax_quote(N, D)
function ivfindmax(A::AbstractArray{T, N}, dims::NTuple{M, Int}) where {T, N, M}
    Dᴮ = ntuple(d -> d ∈ dims ? 1 : size(A, d), N)
    Dᴮ′ = ntuple(d -> d ∈ dims ? Val(1) : size(A, d), N)
    B = similar(A, Dᴮ)
    C = similar(A, Int, Dᴮ)
    _ivfindmax!(B, C, A, Dᴮ′)
    B, CartesianIndices(A)[C]
end
@generated _ivtfindmax!(B::AbstractArray{T, N}, C::AbstractArray{Tₒ, N},
                        A::AbstractArray{T, N}, dims::D) where {T, Tₒ, N, D} = ivtfindmax_quote(N, D)
function ivtfindmax(A::AbstractArray{T, N}, dims::NTuple{M, Int}) where {T, N, M}
    Dᴮ = ntuple(d -> d ∈ dims ? 1 : size(A, d), N)
    Dᴮ′ = ntuple(d -> d ∈ dims ? Val(1) : size(A, d), N)
    B = similar(A, Dᴮ)
    C = similar(A, Int, Dᴮ)
    _ivtfindmax!(B, C, A, Dᴮ′)
    B, CartesianIndices(A)[C]
end

function vfindmax1(A::AbstractArray{T, N}) where {T, N}
    m = typemin(T)
    j = 0
    @turbo for i ∈ eachindex(A)
        y = A[i] > m
        m = y ? A[i] : m
        j = y ? i : j
    end
    m, CartesianIndices(A)[j]
end
function vtfindmax1(A::AbstractArray{T, N}) where {T, N}
    m = typemin(T)
    j = 0
    @tturbo for i ∈ eachindex(A)
        y = A[i] > m
        m = y ? A[i] : m
        j = y ? i : j
    end
    m, CartesianIndices(A)[j]
end

################
# Handle scalar dims by wrapping in Tuple
vfindmax(A::AbstractArray{T, N}, dims::Int) where {T, N} = vfindmax(A, (dims,))
# Convenience dispatches to match JuliaBase
vfindmax(A::AbstractArray{T, N}; dims=:) where {T, N} = vfindmax(A, dims)
vfindmax(A::AbstractArray{T, N}) where {T, N} = vfindmax1(A)
vfindmax(A::AbstractArray{T, N}, ::Colon) where {T, N} = vfindmax1(A)
vtfindmax(A::AbstractArray{T, N}, dims::Int) where {T, N} = vtfindmax(A, (dims,))
# Convenience dispatches to match JuliaBase
vtfindmax(A::AbstractArray{T, N}; dims=:) where {T, N} = vtfindmax(A, dims)
vtfindmax(A::AbstractArray{T, N}) where {T, N} = vtfindmax1(A)
vtfindmax(A::AbstractArray{T, N}, ::Colon) where {T, N} = vtfindmax1(A)

# @timev vfindmax1(A)
# @timev vtfindmax1(A)
# @timev vfindmax2(A, (2,));
# @timev vfindmax3(A, (2,));
# @timev vtfindmax2(A, (2,));
# @timev vtfindmax3(A, (2,));


macro loop_xyz(i, j, k, grid, expr)
    return esc(
        quote
            @loop for $k in (1:$grid.Nz; (blockIdx().z - 1) * blockDim().z + threadIdx().z)
                @loop for $j in (1:$grid.Ny; (blockIdx().y - 1) * blockDim().y + threadIdx().y)
                    @loop for $i in (1:$grid.Nx; (blockIdx().x - 1) * blockDim().x + threadIdx().x)
                        $expr
                    end
                end
            end
        end)
end

macro loop_xy(i, j, grid, expr)
    return esc(
        quote
            @loop for $j in (1:$grid.Ny; (blockIdx().y - 1) * blockDim().y + threadIdx().y)
                @loop for $i in (1:$grid.Nx; (blockIdx().x - 1) * blockDim().x + threadIdx().x)
                    $expr
                end
            end
        end)
end

macro loop_xz(i, k, grid, expr)
    return esc(
        quote
            @loop for $k in (1:$grid.Nz; (blockIdx().y - 1) * blockDim().y + threadIdx().y)
                @loop for $i in (1:$grid.Nx; (blockIdx().x - 1) * blockDim().x + threadIdx().x)
                    $expr
                end
            end
        end)
end


bottles(n) = n==0 ? "No more bottles" :
             n==1 ? "1 bottle" :
             "$n bottles"

for n = 99:-1:1
    println("""
        $(bottles(n)) of beer on the wall
        $(bottles(n)) of beer
        Take one down, pass it around
        $(bottles(n-1)) of beer on the wall
    """)
end


"""
    proteinMutation(genotype::GEGenotype, gpExperimentInfo::GEInfo,
                        rng::Random.AbstractRNG, integerMutation::Function, varargs...)

Perform codons based mutation a **Grammatical Evolution** Individual, by means of
an integer mutation.

# Self-provided Arguments
- `genotype::GEGenotype`: individual genotype.
- `gpExperimentInfo::GEInfo`: information about grammatical evolution.
- `rng::Random.AbstractRNG`: random number generator for random number consistency along an experiment.

# User Arguments
- `integerMutation::Function`: integer mutation function.
- `varargs`: arguments for integerMutation.

!!! note
    `Selfprovided Arguments` are already provided by the library, so only `User Arguments` must be provided.
"""
function proteinMutation(genotype::GEGenotype, gpExperimentInfo::GEInfo,
                        rng::Random.AbstractRNG, integerMutation::Function, varargs...)

    protein, introns = pruneProtein(genotype)

    child = integerMutation(protein, rng, varargs...)

    child = generateTree(gpExperimentInfo, rng::Random.AbstractRNG, vcat(child._representation, introns))

    return child
end

"""
    proteinMutationGrow(genotype::GEGenotype, gpExperimentInfo::GEInfo,
                            rng::Random.AbstractRNG, integerMutation::Function, varargs...)

As proteinMutation, but instead, a growGenerator is used to ensure that all individuals are valids.
It is advisable to used along proteinCrossGrow.

# Self-provided Arguments
- `genotype::GEGenotype`: individual genotype.
- `gpExperimentInfo::GEInfo`: information about grammatical evolution.
- `rng::Random.AbstractRNG`: random number generator for random number consistency along an experiment.

# User Arguments
- `integerCross::Function`: integer crossover function.
- `varargs`: arguments for integerCross.

!!! note
    `Selfprovided Arguments` are already provided by the library, so only `User Arguments` must be provided.
"""
function proteinMutationGrow(genotype::GEGenotype, gpExperimentInfo::GEInfo,
                            rng::Random.AbstractRNG, integerMutation::Function, varargs...)

    protein, introns = pruneProtein(genotype)

    child = integerMutation(protein, rng, varargs...)

    child = growGenerator(gpExperimentInfo, rng::Random.AbstractRNG, vcat(child._representation, introns))

    return child
end


using Base.Test
using Klara

fnames = (
  :value,
  :loglikelihood,
  :logprior,
  :logtarget,
  :gradloglikelihood,
  :gradlogprior,
  :gradlogtarget,
  :tensorloglikelihood,
  :tensorlogprior,
  :tensorlogtarget,
  :dtensorloglikelihood,
  :dtensorlogprior,
  :dtensorlogtarget
)

println("    Testing BasicContMuvParameterState constructors and methods...")

v = Float64[1., 1.5]
s = BasicContMuvParameterState(v)

@test eltype(s) == Float64
@test s.value == v
for i in 2:4
  @test isnan(getfield(s, fnames[i]))
end
for i in 5:7
  @test length(getfield(s, fnames[i])) == 0
end
for i in 8:10
  @test size(getfield(s, fnames[i])) == (0, 0)
end
for i in 11:13
  @test size(getfield(s, fnames[i])) == (0, 0, 0)
end
@test s.diagnosticvalues == []
@test s.size == length(v)
@test s.diagnostickeys == Symbol[]

@test diagnostics(s) == Dict{Symbol, Any}()

v = Float16[0.24, 5.5, -6.3]
dv = [false]
ssize = length(v)
s = BasicContMuvParameterState(v, [:gradlogtarget], [:accept], nothing, nothing, dv)

@test eltype(s) == Float16
@test s.value == v
@test length(s.gradlogtarget) == ssize
for i in 2:4
  @test isnan(getfield(s, fnames[i]))
end
for i in 5:6
  @test length(getfield(s, fnames[i])) == 0
end
for i in 8:10
  @test size(getfield(s, fnames[i])) == (0, 0)
end
for i in 11:13
  @test size(getfield(s, fnames[i])) == (0, 0, 0)
end
@test s.diagnosticvalues == dv
@test s.size == ssize
@test s.diagnostickeys == [:accept]

@test diagnostics(s) == Dict(:accept => dv[1])

ssize = 3
s = BasicContMuvParameterState(ssize)

@test eltype(s) == Float64
@test isa(s.value, Vector{Float64})
@test length(s.value) == ssize
for i in 2:4
  @test isnan(getfield(s, fnames[i]))
end
for i in 5:7
  @test length(getfield(s, fnames[i])) == 0
end
for i in 8:10
  @test size(getfield(s, fnames[i])) == (0, 0)
end
for i in 11:13
  @test size(getfield(s, fnames[i])) == (0, 0, 0)
end
@test s.diagnosticvalues == []
@test s.size == ssize
@test s.diagnostickeys == Symbol[]

@test diagnostics(s) == Dict{Symbol, Any}()

dv = [true]
ssize = 5
s = BasicContMuvParameterState(ssize, [:tensorlogtarget], [:accept], BigFloat, nothing, nothing, dv)

@test eltype(s) == BigFloat
@test isa(s.value, Vector{BigFloat})
@test length(s.value) == ssize
@test size(s.tensorlogtarget) == (ssize, ssize)
for i in 2:4
  @test isnan(getfield(s, fnames[i]))
end
for i in 5:7
  @test length(getfield(s, fnames[i])) == 0
end
for i in 8:9
  @test size(getfield(s, fnames[i])) == (0, 0)
end
for i in 11:13
  @test size(getfield(s, fnames[i])) == (0, 0, 0)
end
@test s.diagnosticvalues == dv
@test s.size == ssize
@test s.diagnostickeys == [:accept]

@test diagnostics(s) == Dict(:accept => dv[1])

z = BasicContMuvParameterState(Float64[-0.12, 12.15], [:gradloglikelihood], [:accept], nothing, nothing, [false])
w = deepcopy(z)
@test isequal(z, w)


######## GVar, a bundle that records gradient
"""
    GVar{T,GT} <: IWrapper{T}
    GVar(x)

Attach a gradient field to `x`.
The gradient type `GT` can be a `GVar` for storing second order gradients, the `AutoBcast` type for broadcasting or the same type of `x`.
"""
struct GVar{T,GT} <: IWrapper{T}
    x::T
    g::GT
    function GVar{T,GT}(x::T, g::GT) where {T,GT}
        new{T,GT}(x, g)
    end
    function GVar(x::T, g::T) where T<:Real
        new{T,T}(x, g)
    end
    function GVar{T,GT}(x::T) where {T, GT}
        new{T,GT}(x, zero(GT))
    end
    function GVar(x::T, g::GT) where {T,GT}
        new{T,GT}(x, g)
    end
end

# `GVar` and `~GVar` on composite types
@generated function GVar(x::Type{T}) where T
    :($(getfield(T.name.module, nameof(T))){$(GVar.(T.parameters)...)})
end
@generated function GVar(x::Type{T}, y::Type{T}) where T
    :($(getfield(T.name.module, nameof(T))){$(GVar.(T.parameters, T.parameters)...)})
end
@generated function (_::Type{Inv{GVar}})(x::Type{T}) where T
    :($(getfield(T.name.module, nameof(T))){$((~GVar).(T.parameters)...)})
end
# `GVar` and `~GVar` on composite vars
@generated function GVar(x::T) where T
    Expr(:new, GVar(T), [:(GVar(x.$NAME)) for NAME in fieldnames(T)]...)
end
@generated function GVar(x::T, g::T) where T
    Expr(:new, GVar(T, T), [:(GVar(x.$NAME, g.$NAME)) for NAME in fieldnames(T)]...)
end
@generated function (_::Type{Inv{GVar}})(x::T) where T
    Expr(:new, (~GVar)(T), [:((~GVar)(x.$NAME)) for NAME in fieldnames(T)]...)
end

for T in [:Real]
    ## differentiable elementary types
    @eval GVar(::Type{ET}) where ET<:$T = GVar{ET,ET}
    @eval GVar(::Type{ET}, ::Type{ET}) where ET<:$T = GVar{ET,ET}
    @eval (_::Type{Inv{GVar}})(::Type{GVar{ET,GT}}) where {ET<:$T,GT} = ET

    ## differentiable elementary vars
    @eval GVar(x::$T) = GVar(x, zero(x))
    @eval @inline function (_::Type{Inv{GVar}})(x::GVar{<:$T})
        @invcheck x.g zero(x.x)
        x.x
    end
end

for T in [:Integer, :Bool, :Function, :String, :Char, :Nothing]
    ## non-differentiable elementary types
    @eval GVar(::Type{ET}) where ET<:$T = ET
    @eval GVar(::Type{ET}, ::Type{ET}) where ET<:$T = GVar{ET,ET}
    @eval (_::Type{Inv{GVar}})(::Type{ET}) where ET<:$T = ET

    ## non-differentiable elementary vars
    @eval GVar(x::$T) = x
    @eval (_::Type{Inv{GVar}})(x::$T) = x
end

for T in [:Tuple, :AbstractArray]
    ## broadcastable elementary types
    @eval GVar(x::$T) = GVar.(x)
    @eval GVar(x::$T, y::$T) = GVar.(x, y)
    @eval (_::Type{Inv{GVar}})(x::$T) = (~GVar).(x)
end

# no gradient wrapper
GVar(x::NoGrad) = (~NoGrad)(x)

# define on complex numbers to fix ambiguity errors
GVar(x::Complex) = Complex(GVar(x.re), GVar(x.im))
GVar(x::Complex, y::Complex) = Complex(GVar(x.re, y.re), GVar(x.im, y.im))
(_::Type{Inv{GVar}})(x::Complex) = Complex((~GVar)(x.re), (~GVar)(x.im))

Base.copy(b::GVar) = GVar(b.x, copy(b.g))
Base.zero(x::GVar) = GVar(Base.zero(x.x), Base.zero(x.g))
Base.zero(::Type{<:GVar{T,GT}}) where {T,GT} = GVar(zero(T), zero(GT))
Base.one(x::GVar) = GVar(Base.one(x.x), Base.zero(x.g))
Base.one(::Type{<:GVar{T}}) where T = GVar(one(T))
Base.adjoint(b::GVar) = GVar(b.x', b.g')
Base.isapprox(x::GVar, y::GVar; kwargs...) = isapprox(x.x, y.x; kwargs...) && isapprox(x.g, y.g; kwargs...)

# define kernel and field views
"""
    grad(var)

Get the gradient field of `var`.
"""
@fieldview grad(gv::GVar) = gv.g
@fieldview value(gv::GVar) = gv.x
# TODO: fix the problem causing this patch, the field type can not change?!
chfield(x::GVar, ::typeof(value), xval::GVar) = GVar(xval, x.g)

@generated function grad(x::T) where T
    isprimitivetype(T) && throw("not supported type to obtain gradients: $T.")
    Expr(:new, typegrad(T), [:(grad(x.$NAME)) for NAME in fieldnames(T)]...)
end
typegrad(x) = x
@generated function typegrad(x::Type{T}) where T
    if isprimitivetype(T)
        T
    else
        :($(getfield(T.name.module, nameof(T))){$(typegrad.(T.parameters)...)})
    end
end
typegrad(::Type{GVar{ET,GT}}) where {ET,GT} = ET
grad(gv::T) where T<:Real = zero(T)
grad(gv::AbstractArray{T}) where T = grad.(gv)
grad(gv::Function) = 0
grad(gv::String) = ""
grad(t::Tuple) = grad.(t)
chfield(x::T, ::typeof(grad), g::T) where T = (@invcheck iszero(g) || g≈0; x)
chfield(x::GVar, ::typeof(grad), g::GVar) = GVar(x.x, g)
chfield(x::Complex{<:GVar}, ::typeof(grad), g::Complex) = Complex(GVar(value(x.re), g.re), GVar(value(x.im), g.im))

# NOTE: superwarning: check value only to make ancilla gradient descardable.
NiLangCore.deanc(x::GVar, val::GVar) = NiLangCore.deanc(value(x), value(val))
function deanc(x::T, val::T) where {T<:AbstractArray}
   x === val || deanc.(x, val)
end

# constructors and deconstructors
Base.iszero(x::GVar) = iszero(x.x)

## variable mapping
function (_::Type{Inv{GVar}})(x::GVar{<:GVar,<:GVar})
    Partial{:x}(x)
end

Base.show(io::IO, gv::GVar) = print(io, "GVar($(gv.x), $(gv.g))")
Base.show(io::IO, ::MIME"plain/text", gv::GVar) = Base.show(io, gv)

# used in log number iszero function.
Base.isfinite(x::GVar) = isfinite(x.x)
# interfaces

"""
    @nograd f(args...)

Mark `f(args...)` as having no gradients.
"""
macro nograd(ex)
    @smatch ex begin
        :($f($(args...))) => begin
            newargs = []
            for arg in args
                push!(newargs, @smatch arg begin
                    :($x::GVar) => :($x.x)
                    :($x::VecGVar) => :($x.x)
                    :($x::GVar{$tp}) => :($x.x)
                    _ => NiLangCore.get_argname(arg)
                end
                )
            end
            esc(quote
                @i function $f($(args...))
                    $f($(newargs...))
                end
            end)
        end
        _ => error("expect `f(args...)`, got $ex")
    end
end

# load data from stack
function loaddata(::Type{TG}, x::T) where {T,TG<:GVar{T}}
    TG(x)
end

function loaddata(::Type{T}, x::T) where T <: GVar
    x
end

function loaddata(::Type{AGT}, x::AT) where {T, GT, AT<:AbstractArray{T}, AGT<:AbstractArray{GVar{T,T}}}
    map(x->GVar(x, zero(x)), x)
end

# ULogarithmic
_content(x::ULogarithmic) = x.log
NiLang.AD.GVar(x::ULogarithmic) = exp(ULogarithmic, GVar(_content(x), zero(_content(x))))
(_::Type{Inv{GVar}})(x::ULogarithmic{GVar{TE}}) where TE = exp(ULogarithmic{TE}, (~GVar)(_content(x)))

Base.one(x::ULogarithmic{GVar{T,GT}}) where {T, GT} = one(ULogarithmic{GVar{T,GT}})
Base.one(::Type{ULogarithmic{GVar{T,GT}}}) where {T,GT} = exp(ULogarithmic, GVar(zero(T), zero(GT)))
Base.zero(x::ULogarithmic{GVar{T,GT}}) where {T,GT} =zero(ULogarithmic{GVar{T,GT}})
Base.zero(::Type{ULogarithmic{GVar{T,T}}}) where T = exp(ULogarithmic, GVar(zero(T), zero(T)))

function NiLang.loaddata(::Type{Array{<:ULogarithmic{GVar{T,T}}}}, data::Array{<:ULogarithmic{T}}) where {T}
    GVar.(data)
end

# the patch for dicts
function GVar(d::Dict)
    Dict([(k=>GVar(v)) for (k, v) in d])
end

function (_::Type{Inv{GVar}})(d::Dict)
    Dict([(k=>(~GVar)(v)) for (k, v) in d])
end

function grad(d::Dict)
    Dict([(k=>grad(v)) for (k, v) in d])
end