Code
# load libraries and source functions
library(dplyr)
library(glue)
library(ompr)
library(ompr.highs)
library(highs)
library(purrr)
library(ggplot2)
library(maps)
library(mapdata)
theme_set(theme_light())
Vertex coloring problem in a graph is to find the minimum number of colors such that no two connected vertices have the same color. A special case is map coloring problem where the vertices are states/countries in a map and edges are pairs of states/countries that are adjacent to each other. Here we illustrate the map coloring problem with OMPR modeling language and HiGHS solver. Specifically, we want to find the minimum number of colors and the color of each state in US so that no two adjacent states have the same color. Only portions of R code are shown here. The .qmd
file is in this location
# load libraries and source functions
library(dplyr)
library(glue)
library(ompr)
library(ompr.highs)
library(highs)
library(purrr)
library(ggplot2)
library(maps)
library(mapdata)
theme_set(theme_light())
We use the state adjacency data from Gregg Lind’s site and process it in a format that we can use for OMPR model.
#
# US states adjacency data from
# https://writeonly.wordpress.com/2009/03/20/adjacency-list-of-states-of-the-united-states-us/
#
= readLines("data/US_state_adjacency.txt")
x
= list()
edgelist = 0
e for (i in 1:length(x)) {
= strsplit(x[i], ",")[[1]]
stloc = length(stloc)
ns if(ns > 1) {
for (i in 2:ns) {
= e + 1
e = c(stloc[1], stloc[i])
edgelist[[e]]
}
}
}
= as.data.frame(do.call(rbind, edgelist))
edge_df names(edge_df) = c("from", "to")
= bind_rows(edge_df %>% distinct(from) %>% rename(state = from),
nodes_df %>% distinct(to) %>% rename(state = to)) %>% distinct(state)
edge_df = nodes_df %>% mutate(id = seq(1, nrow(nodes_df)))
nodes_df
= inner_join(edge_df, nodes_df %>% rename(from = state), by = "from") %>% rename(fromid = id)
edge_df = inner_join(edge_df, nodes_df %>% rename(to = state), by = "to") %>% rename(toid = id)
edge_df
= edge_df %>% filter(fromid < toid) edge_df
There are many mathematical formulations of this problem. We use a basic formulation for the example here.
\[ \begin{array}{llr} \min & \sum_{c=1}^Cy_c & (a)\\ & \sum_{c=1}^Cx_{ic} = 1, \;i=1,2,\ldots,N & (b)\\ & x_{ic} + x_{jc} \leq y_c, \; \mbox{when }i, j \mbox{ are adjacent} & (c)\\ & x_{ic} \; binary \\ & y_c \; binary \end{array} \]
where:
# OMPR model
= nrow(nodes_df)
ns = 4
nc = edge_df %>% mutate(edge_str = glue("{fromid}_{toid}")) %>% pull(edge_str)
edge_str
= MIPModel()
mdl = mdl %>% add_variable(x[i, c], i = 1:ns, c = 1:nc, type = "integer", lb = 0, ub = 1)
mdl = mdl %>% add_variable(y[c], c = 1:nc, type = "integer", lb = 0, ub = 1)
mdl = mdl %>% set_objective(sum_over(y[c], c=1:nc), sense = "min")
mdl = mdl %>% add_constraint(sum_over(x[i, c], c = 1:nc) == 1, i = 1:ns)
mdl = mdl %>% add_constraint(x[i, c] + x[j, c] <= y[c], i = 1:ns, j = 1:ns, c = 1:nc, glue("{i}_{j}") %in% edge_str) mdl
Solving the model shows that 4 colors are sufficient to color the US map (Hawaii and Alaska are excluded).
# solve model
= mdl %>% solve_model(highs_optimizer())
sol
# Get solution and visualize in a map
"status"]] sol[[
[1] "optimal"
"objective_value"]] sol[[
[1] 4
= sol %>% get_solution(x[i, c]) %>% filter(value == 1) %>%
soldf rename(id = i, colid = c)
= inner_join(soldf, nodes_df, by = "id")
soldf
= c("1" = "red", "2" = "blue", "3" = "green", "4" = "yellow")
colmap = soldf %>% mutate(stcol = colmap[colid]) soldf
The solution is visualized in a map using code from this location
#
# https://jtr13.github.io/cc19/different-ways-of-plotting-u-s-map-in-r.html
#
<- map_data("state")
state = tibble(stname = tolower(state.name), stabb = state.abb)
state_name_abb
= inner_join(state, state_name_abb %>% rename(region = stname), by = "region")
state = inner_join(state, soldf %>% select(state, stcol) %>% rename(stabb = state), by = "stabb")
state
ggplot(data=state, aes(x=long, y=lat, fill=stcol, group=group)) +
geom_polygon(color = "white") +
guides(fill="none") +
theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank(),
axis.title.y=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank()) +
ggtitle('U.S. Map with States') +
coord_fixed(1.3)