From 0837a30169eebd8d5e33ba9aa1a7cf1ac35ab89d Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Thu, 20 Feb 2020 21:24:42 +0100 Subject: [PATCH] Adapt to true garden shape and constraints --- Project.toml | 1 + src/GardenOptim.jl | 146 ++++++++++++++++++++++++++++++--------------- test/runtests.jl | 6 ++ 3 files changed, 106 insertions(+), 47 deletions(-) diff --git a/Project.toml b/Project.toml index 59821d8..8769ebe 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.1.0" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [compat] julia = "1" diff --git a/src/GardenOptim.jl b/src/GardenOptim.jl index 74cdb0d..1ae096e 100644 --- a/src/GardenOptim.jl +++ b/src/GardenOptim.jl @@ -1,68 +1,120 @@ module GardenOptim -using CSV using Logging +using CSV +using Tables -export update! +export loadplants, loadgarden, loadcosts, update!, randomgardenevolution!, outputgarden -function swap!(grid::Array{Int, 2}, i::Int, j::Int) - t = grid[i] - grid[i] = grid[j] - grid[j] = t - grid +function loadplants() + plants = readlines("data/plants.txt") + @info "loaded $(length(plants)) plants" + plants end -function neighbours(grid::Array{Int, 2}, idx) - m = size(grid, 1) - j, i = divrem(idx - 1, m) - i += 1 - j += 1 - neighbourindices = [(i, j-1), (i, j+1), (i-1, j), (i+1, j)] - [grid[k, l] for (k, l) in neighbourindices if 0 < k <= m && 0 < l <= m] -end - -function deltacost(grid::Array{Int, 2}, costs::Array{Float64, 2}, i::Int, j::Int) - cost = 0 - for k in neighbours(grid, i) - cost += costs[k, grid[j]] - costs[k, grid[i]] - end - for k in neighbours(grid, j) - cost += costs[k, grid[i]] - costs[k, grid[j]] - end - cost -end - -function update!(grid::Array{Int, 2}, costs::Array{Float64, 2}, beta::Float64 = 10.0) - N = length(grid) - i, j = 0, 0 - while i == j - i, j = rand(1:N, 2) - end - d = deltacost(grid, costs, i, j) - @debug "cost difference $d" - if rand() < exp(- beta * d) - @debug "swapping indices $i and $j" - return swap!(grid, i, j) - end - grid +function loadgarden(plants) + 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() df = CSV.read("data/costs.csv") df = coalesce.(df, 0) # replace missing values by 0 costs = convert(Matrix, df[:, 2:end]) - @debug "cost matrix of size $(size(costs))" + @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 randomgridevolution(costs::Array{Float64, 2}, gardensize::Int = 50, steps::Int = 10000) - m = size(costs, 1) - grid = rand(1:m, gardensize, gardensize) - for i = 1:steps - update!(grid, costs, 10.0) +function randomindex(mask::Matrix{Bool}) + while true + i = rand(1:length(mask)) + if mask[i] + return i + end end - grid +end + +function swap!(garden::Matrix{Int}, i::Int, j::Int) + t = garden[i] + garden[i] = garden[j] + garden[j] = t + garden +end + +function neighbours(garden::Matrix{Int}, idx) + m, n = size(garden) + j, i = divrem(idx - 1, m) + i += 1 + j += 1 + neighbourindices = [(i, j-1), (i, j+1), (i-1, j), (i+1, j)] + # cells filled with 0 are not part of the garden + [ + garden[k, l] for (k, l) in neighbourindices + if 0 < k <= m && 0 < l <= n && garden[k, l] != 0 + ] +end + +function deltacost(garden::Matrix{Int}, costs::Matrix{Float64}, i::Int, j::Int) + cost = 0 + for k in neighbours(garden, i) + cost += costs[k, garden[j]] - costs[k, garden[i]] + end + for k in neighbours(garden, j) + cost += costs[k, garden[i]] - costs[k, garden[j]] + end + cost +end + +function update!( + garden::Matrix{Int}, + mask::Matrix{Bool}, + costs::Matrix{Float64}, + beta::Float64 = 10.0 +) + N = length(garden) + i = randomindex(mask) + j = randomindex(mask) + while i == j + j = randomindex(mask) + end + d = deltacost(garden, costs, i, j) + @debug "cost difference $d" + if rand() < exp(- beta * d) + @debug "swapping indices $i and $j" + return swap!(garden, i, j) + end + garden +end + +function randomfillgarden!(garden::Matrix{Int}, mask::Matrix{Bool}, plantcount::Int) + garden[mask] = rand(1:plantcount, sum(mask)) + garden +end + +function randomgardenevolution!( + garden::Matrix{Int}, + mask::Matrix{Bool}, + costs::Matrix{Float64}; + steps::Int = 10000 +) + m = size(costs, 1) + garden = randomfillgarden!(garden, mask, m) + for i = 1:steps + update!(garden, mask, costs, 10.0) + end + garden +end + +function outputgarden(garden::Matrix{Int}, plants::Vector{String}) + output = vcat([""], plants)[garden .+ 1] + CSV.write("output.csv", Tables.table(output), writeheader=false) end end # module diff --git a/test/runtests.jl b/test/runtests.jl index 3080a75..8f104d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,10 @@ using GardenOptim using Test @testset "GardenOptim.jl" begin + @testset "randomindex" begin + mask = rand(Bool, 5, 5) + @test mask[GardenOptim.randomindex(mask)] + end @testset "swap" begin grid = ones(Int, 5, 5) grid[2] = 5 @@ -18,6 +22,8 @@ using Test @test length(GardenOptim.neighbours(grid, 8)) == 4 @test length(GardenOptim.neighbours(grid, 25)) == 2 @test GardenOptim.neighbours(grid, 1) == [1, 1] + grid[3] = 0 + @test length(GardenOptim.neighbours(grid, 4)) == 2 end @testset "deltacost" begin grid = ones(Int, 5, 5)