diff --git a/data/costs.csv b/data/costs.csv deleted file mode 100644 index e6976cc..0000000 --- a/data/costs.csv +++ /dev/null @@ -1,52 +0,0 @@ -name,Ail,Asperge,Ciboulette,Echalote,Oignon,Poireau,Fève,Haricot,Pois,Aneth,Carotte,Celeri,Cerfeuil,Fenouil,Panais,Persil,Aubergine,Poivron,Pomme de terre,Tomate,Chou,Chou rave,Chou rouge,Navet,Radis,Concombre,Cornichon,Cucurbita,Courgette,Melon,Artichaut,Basilic,Bette,Betterave,Capucine,Chicoree,Cresson,Epinard,Fraisier,Laitue,Maïs,Menthe,Oeillet d'inde,Sarriette,Sauge,Scorsonère,Souci,Tétragone,Tournesol,Vigne,Lavande -Ail,,,,,1,,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,1,1,,,,,,,,,,,,,,1,,,,,1,1,,,,,,,1,,,, -Asperge,,,,,,1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,-1,1,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,, -Ciboulette,,,,,,,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1,-1,,,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,, -Echalote,,,,,-1,,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1,-1,,,,,,,,1,1,,,,,,,,,,1,,,,,,,,,,,, -Oignon,1,,,-1,,-1,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1,1,-1,,,,,1,1,1,1,,1,,1,1,,,,,1,1,,,,,,1,1,,,, -Poireau,,1,,,-1,1,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1,1,,,,,,,,,,1,,,-1,,,,,1,1,1,,,,,,1,1,,,, -Feve,-1,-1,-1,-1,-1,-1,,,,,,,,,1,,,,,,1,1,1,1,1,1,1,1,1,1,,,,,,,,,,,,,,,,,,,,, -Haricot,-1,1,-1,-1,-1,-1,,1,-1,,1,1,,,1,,1,,1,,1,1,1,1,1,1,1,1,1,1,,,1,1,,,,1,1,1,2,,,,,,,1,,, -Pois,-1,-1,-1,-1,-1,-1,,-1,1,,1,1,,,,,,,1,-1,1,1,1,1,1,1,1,1,1,1,,,,,,,,,,1,,,,,,,,,,, -Aneth,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,,,,1,1,1,1,1,,,,,,,,,,,,,,,1,,,,,,,,,,, -Carotte,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,,,,,,,,1,-1,,1,,1,,1,,-1,,,,1,,,,, -Celeri,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,,,,,,,,1,,,,,,,,-1,,,,,,,,,, -Cerfeuil,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,,,,1,1,1,1,-1,,,,,,,,,,,,,,,1,,,,,,,,,,, -Fenouil,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,,,-1,1,1,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,, -Panais,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,,,,,,,,,,,,,,,-1,,,,,,,,,,, -Persil,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,,,,,,,,,,,,,,1,-1,,,,,,,,,,, -Aubergine,-1,-1,-1,-1,-1,-1,,1,,,,,,,,,,1,-1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Poivron,-1,-1,-1,-1,-1,-1,,,,,,,,,,,1,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Pomme de terre,1,-1,-1,-1,-1,-1,,1,1,,,,,,,,-1,,,-1,,,,,-1,,,,-1,,,,,,,,,,,,1,,,,,,1,,,, -Tomate,1,1,-1,-1,-1,1,,,-1,,1,1,,-1,1,1,,1,-1,1,1,,-1,,1,,,,,,,1,,-1,,,,1,,1,1,,,,,,1,1,,, -Chou,,,,,-1,,1,1,1,1,1,1,1,1,1,1,,,,1,,,,,,1,1,,,,1,,,,,,,1,-1,1,,,,1,,,,,,, -Chou rave,,,,,,,1,1,1,1,1,1,1,1,1,1,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,1,,,,, -Chou rouge,,,,,,,1,1,1,1,1,1,1,1,1,1,,,,-1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Navet,,,,,,,1,1,1,1,1,1,1,1,1,1,,,,,,,,,,,,,,,,,1,,,,,1,,,,,,,,,,,,, -Radis,,,,,,,1,1,1,1,1,1,-1,1,1,1,,,-1,1,,,,,,-1,-1,,,,1,,1,,,,1,1,,1,,,,,,1,,,,, -Concombre,,1,,,1,,1,1,1,,,,,,,,,,,,1,,,,-1,,,-1,-1,,,1,,,,,,,,1,1,,1,,,,,,,, -Cornichon,,1,,,1,,1,1,1,,,,,,,,,,,,1,,,,-1,,,-1,-1,,,1,,,,,,,,1,1,,1,,,,,,,, -Cucurbita,,,1,1,1,,1,1,1,,,,,,,,,,,,,,,,,-1,-1,,,,,,,,,,,,,,1,,,,,,,,,, -Courgette,,,1,1,1,,1,1,1,,,,,,,,,,-1,,,,,,,-1,-1,,,,,,,,,,,,,,,,,,,,,1,,, -Melon,,,,,,1,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,1,1,,,,,,,,,, -Artichaut,,,,,1,,1,1,1,,,,,,,,,,,,1,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, -Basilic,,,,,,,1,1,1,,,,,,,,,,,1,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,, -Bette,,,,,1,-1,1,1,,,1,1,,,,,,,,,,1,,1,1,,,,,,,,,,,,,,,1,,,,,,,,,,, -Betterave,1,,,,1,,1,1,,,-1,,,,,,,,,-1,,,,,,,,,,,,,,,,,,1,,1,-1,,,,,,,,,, -Capucine,,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,, -Chicorée,,,,,,,1,1,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,, -Cresson,,,,,,,1,1,1,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,, -Epinard,,,,,,1,1,1,1,,1,,,,,,,,,1,1,,,1,1,,,,,,,,,1,,,,,1,1,,,,,,,,,,, -Fraisier,1,,,1,1,1,1,1,1,,,,,,,1,,,,,-1,,,,,,,,,,,,,,,,,1,,1,,,,,1,,,,,, -Laitue,1,,,,1,1,1,1,1,1,1,,1,,-1,-1,,,,1,1,,,,1,1,1,,,1,1,,1,1,,,,1,1,1,,,,,,1,,,-1,, -Maïs,,,,,,,1,2,1,,,-1,,,,,,,1,1,,,,,,1,1,1,,1,,,,-1,,,,,,,,,,,,,,,,, -Menthe,,,,,,,1,1,1,,-1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Oeillet d'inde,,,,,,,1,1,1,,,,,,,,,,,,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,, -Sarriette,,,,,,,1,1,1,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Sauge,,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, -Scorsonère,,,,,1,1,1,1,1,,1,,,,,,,,,,,1,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, -Souci,1,,,,1,1,1,1,1,,,,,,,,,,1,1,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,, -Tétragone,,,,,,,1,1,1,,,,,,,,,,,1,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,, -Tournesol,,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-1,,,,,,,,,,, -Vigne,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Lavande,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/data/plants.txt b/data/plants.txt deleted file mode 100644 index b8b486f..0000000 --- a/data/plants.txt +++ /dev/null @@ -1,51 +0,0 @@ -Ail -Asperge -Ciboulette -Echalote -Oignon -Poireau -Feve -Haricot -Pois -Aneth -Carotte -Celeri -Cerfeuil -Fenouil -Panais -Persil -Aubergine -Poivron -Pomme de terre -Tomate -Chou -Chou rave -Chou rouge -Navet -Radis -Concombre -Cornichon -Cucurbita -Courgette -Melon -Artichaut -Basilic -Bette -Betterave -Capucine -Chicorée -Cresson -Epinard -Fraisier -Laitue -Maïs -Menthe -Oeillet d'inde -Sarriette -Sauge -Scorsonère -Souci -Tétragone -Tournesol -Vigne -Lavande diff --git a/src/GardenOptim.jl b/src/GardenOptim.jl index d74f01a..be20c29 100644 --- a/src/GardenOptim.jl +++ b/src/GardenOptim.jl @@ -1,15 +1,10 @@ module GardenOptim using Logging -using Unicode -using DataFrames using DocStringExtensions -using CSV -using JSON -using Tables -export loadclassification, loadplants, loadgarden, loadcosts +export loadplants, loadgarden, loadclassification, loadcosts export update!, randomgardenevolution!, outputgarden @template (FUNCTIONS, METHODS, MACROS) = @@ -18,112 +13,8 @@ export update!, randomgardenevolution!, outputgarden $(DOCSTRING) """ -mutable struct Classification - type::Symbol - name::Symbol - bio::String - children::Vector{Classification} - parent::Classification - - function Classification(classif::Dict{String, Any}) - children = [Classification(d) for d in get(classif, "children", [])] - type = Symbol(Unicode.normalize(classif["type"], casefold=true, stripmark=true)) - name = Symbol(Unicode.normalize(classif["name"], casefold=true, stripmark=true)) - classif = new(type, name, get(classif, "bio", ""), children) - for child in children - child.parent = classif - end - classif - end -end - -function loadclassification() - clf = JSON.parsefile("data/classification.json") - clf = Classification(clf) - @debug "loaded classification of type $(clf.type)" - clf -end - -function loadplants()::DataFrame - plants = CSV.read("data/plants.csv") - @info "loaded $(size(plants, 1)) plants" - plants.name = Symbol.(plants.name) - plants -end - -function loadgarden(plants::Vector{String})::Tuple{Matrix{Int}, Matrix{Bool}} - garden = CSV.read("data/garden.csv") - garden = coalesce.(garden, "") - mask = convert(Matrix, garden .== "empty") - garden = indexin(convert(Matrix, garden), plants) - garden = replace(garden, nothing=>0) - @assert size(garden) == size(mask) - @info "loaded garden of size $(size(garden))" - garden, mask -end - -function loadcosts()::DataFrame - df = CSV.read("data/associations.csv", copycols=true) - colnames = String.(names(df)) - colnames = Symbol.(Unicode.normalize.(colnames, casefold=true, stripmark=true)) - rename!(df, colnames) - df.name = colnames[2:end] - # df = coalesce.(df, 0.0) - @info "loaded cost matrix for $(size(df, 1)) plants" - df -end - -# function loadcosts()::Matrix{Float64} -# df = CSV.read("data/costs.csv") -# df = coalesce.(df, 0) # replace missing values by 0 -# costs = convert(Matrix, df[:, 2:end]) -# @info "loaded cost matrix of size $(size(costs))" -# # ensure the matrix is symmetric: keep the max of itself and its transpose -# costs = Float64.(max.(costs, permutedims(costs))) -# end - -function getparent(name::Symbol, classification::Classification) - if classification.name == name - return classification.parent - else - for child in classification.children - parent = getparent(name, child) - if !isnothing(parent) - return parent - end - end - end -end - -function computecost(costs::DataFrame, plant1::Symbol, plant2::Symbol, classification::Classification)::Float64 - @debug "$plant1 and $plant2" - if plant1 in names(costs) && plant2 in names(costs) - cost = costs[costs.name .== plant1, plant2][1] - else - @debug "$plant1 and $plant2 not in costs" - cost = missing - end - - if !ismissing(cost) - return cost - end - - @debug "missing" - try - parent1 = getparent(plant1, classification).name - computecost(costs, parent1, plant2, classification) - catch UndefRefError - return missing - end - - try - parent2 = getparent(plant2, classification).name - computecost(costs, plant1, parent2, classification) - catch UndefRefError - return missing - end - -end +include("classification.jl") +include("loaddata.jl") "Return a random index to be filled from the garden mask." function randomindex(mask::Matrix{Bool})::Int diff --git a/src/classification.jl b/src/classification.jl new file mode 100644 index 0000000..947a7b8 --- /dev/null +++ b/src/classification.jl @@ -0,0 +1,50 @@ +using Unicode + +mutable struct Classification + type::Symbol + name::Symbol + bio::String + children::Vector{Classification} + parent::Classification + + function Classification(classif::Dict{String, Any}) + children = [Classification(d) for d in get(classif, "children", [])] + type = Symbol(Unicode.normalize(classif["type"], casefold=true, stripmark=true)) + name = Symbol(Unicode.normalize(classif["name"], casefold=true, stripmark=true)) + classif = new(type, name, get(classif, "bio", ""), children) + for child in children + child.parent = classif + end + classif + end +end + +function Base.show(io::IO, clf::Classification) + if length(clf.children) < 2 + childrentext = " with $(length(clf.children)) child" + else + childrentext = " with $(length(clf.children)) children" + end + biotext = "" + if clf.bio != "" + biotext = " ($(clf.bio))" + end + print("Classification(", clf.type, " ", clf.name, biotext, childrentext, ")") +end + +function getfirstparent(name::Symbol, classification::Classification) + if classification.name == name + parent = classification + while parent.parent.name != :god + parent = parent.parent + end + return parent + else + for child in classification.children + parent = getfirstparent(name, child) + if !isnothing(parent) + return parent + end + end + end +end diff --git a/src/loaddata.jl b/src/loaddata.jl new file mode 100644 index 0000000..906cad9 --- /dev/null +++ b/src/loaddata.jl @@ -0,0 +1,83 @@ +using Logging +using Unicode + +using DataFrames +using CSV +using JSON + +function loadplants()::DataFrame + plants = CSV.read("data/plants.csv") + @info "loaded $(size(plants, 1)) plants" + plants.name = Symbol.(plants.name) + plants +end + +function loadgarden(plants::Vector{Symbol})::Tuple{Matrix{Int}, Matrix{Bool}} + garden = CSV.read("data/garden.csv") + garden = coalesce.(garden, "") + mask = convert(Matrix, garden .== "empty") + garden = Unicode.normalize.(garden, casefold=true, stripmark=true) + garden = indexin(convert(Matrix, garden), String.(plants)) + garden = replace(garden, nothing=>0) + @assert size(garden) == size(mask) + @info "loaded garden of size $(size(garden))" + garden, mask +end + +function loadclassification()::Classification + clf = JSON.parsefile("data/classification.json") + clf = Classification(clf) + @debug "loaded classification of type $(clf.type)" + clf +end + +function loadcostsdf()::DataFrame + df = CSV.read("data/associations.csv", copycols=true) + colnames = String.(names(df)) + colnames = Symbol.(Unicode.normalize.(colnames, casefold=true, stripmark=true)) + rename!(df, colnames) + df.name = colnames[2:end] + # df = coalesce.(df, 0.0) + @info "loaded cost matrix for $(size(df, 1)) plants" + df +end + +function computecost(plant1::Symbol, plant2::Symbol, costs_df::DataFrame, classification::Classification)::Float64 + @debug "computecost($plant1, $plant2)" + if plant1 in names(costs_df) && plant2 in names(costs_df) + cost = costs_df[costs_df.name .== plant1, plant2][1] + else + cost = missing + end + + if !ismissing(cost) + return cost + end + + parent1 = getfirstparent(plant1, classification) + parent2 = getfirstparent(plant2, classification) + if isnothing(parent1) || isnothing(parent2) + return 0.0 + end + @debug "computecost($(parent1.name), $(parent2.name))" + if parent1.name in names(costs_df) && parent2.name in names(costs_df) + cost = costs_df[costs_df.name .== parent1.name, parent2.name][1] + end + + if !ismissing(cost) + return cost + end + + return 0.0 +end + +function costsmatrix(plants::Vector{Symbol}, costs_df::DataFrame, classification::Classification)::Matrix{Float64} + [computecost(plant1, plant2, costs_df, classification) for plant1 in plants, plant2 in plants] +end + +function loadcosts() + plants = loadplants() + clf = loadclassification() + costs_df = loadcostsdf() + costs = costsmatrix(plants.name, costs_df, clf) +end