Skip to content

Commit 15fef11

Browse files
committed
Add Warcraft grid graph
1 parent 2abe7b1 commit 15fef11

File tree

11 files changed

+194
-19
lines changed

11 files changed

+194
-19
lines changed

Project.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ julia = "1.6"
2323
[extras]
2424
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
2525
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
26+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
2627
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
2728
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
2829
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -32,4 +33,15 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3233
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3334

3435
[targets]
35-
test = ["Aqua", "Documenter", "JET", "JuliaFormatter", "Random", "StableRNGs", "Statistics", "Test", "Zygote"]
36+
test = [
37+
"Aqua",
38+
"Documenter",
39+
"Graphs",
40+
"JET",
41+
"JuliaFormatter",
42+
"Random",
43+
"StableRNGs",
44+
"Statistics",
45+
"Test",
46+
"Zygote",
47+
]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
[![Coverage](https://codecov.io/gh/JuliaDecisionFocusedLearning/InferOptBenchmarks.jl/branch/main/graph/badge.svg)](https://app.codecov.io/gh/JuliaDecisionFocusedLearning/InferOptBenchmarks.jl)
66
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/JuliaDiff/BlueStyle)
77

8-
Set of benchmark problems to be solved with [InferOpt.jl](https://github.com/axelparmentier/InferOpt.jl)
8+
Set of benchmark problems to be solved with [InferOpt.jl](https://github.com/JuliaDecisionFocusedLearning/InferOpt.jl)

docs/make.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ cp(
88
)
99

1010
makedocs(;
11-
modules = [InferOptBenchmarks],
11+
modules = [InferOptBenchmarks, InferOptBenchmarks.Warcraft],
1212
authors = "Members of JuliaDecisionFocusedLearning",
1313
sitename = "InferOptBenchmarks.jl",
1414
format = Documenter.HTML(),
15-
pages = ["Home" => "index.md", "API reference" => "api.md"],
15+
pages = [
16+
"Home" => "index.md", #
17+
"API reference" => [
18+
"warcraft.md", #
19+
],
20+
],
1621
)
1722

1823
deploydocs(;

docs/src/api.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

docs/src/warcraft.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Warcraft
2+
3+
## Public
4+
5+
```@autodocs
6+
Modules = [InferOptBenchmarks.Warcraft]
7+
Private = false
8+
```
9+
10+
## Private
11+
12+
```@autodocs
13+
Modules = [InferOptBenchmarks.Warcraft]
14+
Public = false
15+
```

src/InferOptBenchmarks.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ using Random
77
using SimpleWeightedGraphs
88
using SparseArrays
99

10+
include("Warcraft/Warcraft.jl")
11+
1012
end # module InferOptBenchmarks

src/Warcraft/Warcraft.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Warcraft
2+
3+
using LinearAlgebra
4+
using SimpleWeightedGraphs
5+
using SparseArrays
6+
7+
include("grid_graph.jl")
8+
9+
export warcraft_grid_graph, index_to_coord, coord_to_index
10+
11+
end

src/Warcraft/grid_graph.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
warcraft_grid_graph(costs::AbstractMatrix; acyclic::Bool=false)
3+
4+
Convert a grid of Warcraft cell costs into a weighted directed graph from [SimpleWeightedGraphs.jl](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl), where the vertices correspond to the cells and the edges are weighted by the cost of the arrival cell.
5+
6+
This represents the Warcraft shortest paths problem of
7+
8+
> [Differentiation of Blackbox Combinatorial Solvers](https://openreview.net/forum?id=BkevoJSYPB), Vlastelica et al. (2019)
9+
10+
- If `acyclic = false`, a cell has edges to each one of its 8 neighbors.
11+
- If `acyclic = true`, a cell has edges to its south, east and southeast neighbors only (ensures an acyclic graph where topological sort will work)
12+
"""
13+
function warcraft_grid_graph(costs::AbstractMatrix{R}; acyclic::Bool = false) where {R}
14+
h, w = size(costs)
15+
V = h * w
16+
E = count_edges(h, w; acyclic)
17+
18+
sources = Int[]
19+
destinations = Int[]
20+
weights = R[]
21+
22+
sizehint!(sources, E)
23+
sizehint!(destinations, E)
24+
sizehint!(weights, E)
25+
26+
for v1 = 1:V
27+
i1, j1 = index_to_coord(v1, h, w)
28+
for Δi in (-1, 0, 1), Δj in (-1, 0, 1)
29+
i2, j2 = i1 + Δi, j1 + Δj
30+
valid_destination = 1 <= i2 <= h && 1 <= j2 <= w
31+
valid_step = if acyclic
32+
(Δi != 0 || Δj != 0) && Δi >= 0 && Δj >= 0
33+
else
34+
(Δi != 0 || Δj != 0)
35+
end
36+
if valid_destination && valid_step
37+
v2 = coord_to_index(i2, j2, h, w)
38+
push!(sources, v1)
39+
push!(destinations, v2)
40+
push!(weights, costs[v2])
41+
end
42+
end
43+
end
44+
45+
return SimpleWeightedDiGraph(sources, destinations, weights)
46+
end
47+
48+
function count_edges(h::Integer, w::Integer; acyclic::Bool)
49+
@assert h >= 2 && w >= 2
50+
if acyclic
51+
return (h - 1) * (w - 1) * 3 + ((h - 1) + (w - 1)) * 1 + 0
52+
else
53+
return (h - 2) * (w - 2) * 8 + (2(h - 2) + 2(w - 2)) * 5 + 4 * 3
54+
end
55+
end
56+
57+
function possible_neighbors(i::Integer, j::Integer)
58+
return (
59+
# col - 1
60+
(i - 1, j - 1),
61+
(i + 0, j - 1),
62+
(i + 1, j - 1),
63+
# col 0
64+
(i - 1, j + 0),
65+
(i + 1, j + 0),
66+
# col + 1
67+
(i - 1, j + 1),
68+
(i + 0, j + 1),
69+
(i + 1, j + 1),
70+
)
71+
end
72+
73+
"""
74+
coord_to_index(i, j, h, w)
75+
76+
Given a pair of row-column coordinates `(i, j)` on a grid of size `(h, w)`, compute the corresponding vertex index in the graph generated by [`warcraft_grid_graph`](@ref).
77+
"""
78+
function coord_to_index(i::Integer, j::Integer, h::Integer, w::Integer)
79+
if (1 <= i <= h) && (1 <= j <= w)
80+
v = (j - 1) * h + (i - 1) + 1 # enumerate column by column
81+
return v
82+
else
83+
return 0
84+
end
85+
end
86+
87+
"""
88+
index_to_coord(v, h, w)
89+
90+
Given a vertex index in the graph generated by [`warcraft_grid_graph`](@ref), computethe corresponding row-column coordinates `(i, j)` on a grid of size `(h, w)`.
91+
"""
92+
function index_to_coord(v::Integer, h::Integer, w::Integer)
93+
if 1 <= v <= h * w
94+
j = (v - 1) ÷ h + 1
95+
i = (v - 1) - h * (j - 1) + 1
96+
return (i, j)
97+
else
98+
return (0, 0)
99+
end
100+
end
101+
102+
function get_path(parents::AbstractVector{<:Integer}, s::Integer, d::Integer)
103+
path = [d]
104+
v = d
105+
while v != s
106+
v = parents[v]
107+
pushfirst!(path, v)
108+
end
109+
return path
110+
end

test/WarcraftTest/WarcraftTest.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module WarcraftTest
2+
3+
include("grid_graph.jl")
4+
5+
end

test/WarcraftTest/grid_graph.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using InferOptBenchmarks.Warcraft
2+
using InferOptBenchmarks.Warcraft: count_edges, get_path
3+
using Graphs
4+
using Test
5+
6+
h = 4
7+
w = 7
8+
costs = rand(h, w)
9+
10+
for acyclic in (true, false)
11+
@testset "Acyclic: $acyclic" begin
12+
g = warcraft_grid_graph(costs; acyclic = acyclic)
13+
@test nv(g) == h * w
14+
@test ne(g) == count_edges(h, w; acyclic)
15+
@test all(edges(g)) do e
16+
v1, v2 = src(e), dst(e)
17+
i1, j1 = index_to_coord(v1, h, w)
18+
i2, j2 = index_to_coord(v2, h, w)
19+
a = max(abs(i1 - i2), abs(j1 - j2)) == 1
20+
b = g.weights[v2, v1] == costs[v2]
21+
return a && b
22+
end
23+
path = get_path(dijkstra_shortest_paths(g, 1).parents, 1, nv(g))
24+
@test max(h, w) <= length(path) <= h + w
25+
end
26+
end

0 commit comments

Comments
 (0)