R/11-grafos-NPdif.R

Defines functions search_cut_genetic mutate_binary_sequence crossover_sequences improve_cut_flip build_cut_greedy compute_cut_weight plot_cut build_cut_random sum_g find_cover_BB search_cover_ants search_cover_random build_cover_random is_cover improve_cover_flip build_cover_approx build_cover_greedy plot_cover

Documented in build_cover_approx build_cover_greedy build_cover_random build_cut_greedy build_cut_random compute_cut_weight crossover_sequences find_cover_BB improve_cover_flip improve_cut_flip is_cover mutate_binary_sequence plot_cover plot_cut search_cover_ants search_cover_random search_cut_genetic sum_g

##' Plot of a vertex cover in a graph.
##'
##' It plots a graph, then superimposes a vertex cover in a different
##'     color.  It also draws the covered edges, to help in detecting
##'     non-covers by inspection.
##' 
##' @title Vertex cover plotting
##' @param X Cover to be plotted; an output list returned by some
##'     cover-building function, see below.
##' @param G Graph on which to superimpose the cover.
##' @return This function is called for its side effect of plotting.
##' @importFrom graphics legend
##' @importFrom graphics text
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique.
##' @examples 
##' set.seed(1)
##' g <- sample_gnp(25, p=0.25)   # Random graph
##' X1 <- build_cover_greedy(g)
##' plot_cover(X1, g)
##'
##' st <- 1:5   # Not a vertex cover
##' plot_cover(list(set = st, size = length(st)), g)  # See covered edges
##' 
##' @encoding UTF-8
##' @md 
##' @export 
plot_cover <- function(X, G) {
    n <- gorder(G)
    z <- layout_with_gem(G)
    plot(G, layout=z)
    eG <- as_edgelist(G)
    gcov <- make_empty_graph(n, directed=FALSE)
    for (v in X$set) {
	gcov <- gcov %>% add_edges(t(eG[as.numeric(incident(G,v)),]))
    }
    gcov <- simplify(gcov)
    vcol <- rep("pink", n)
    lcol <- rep("black", n)
    vcol[X$set] <- "red4"
    lcol[X$set] <- "white"
    plot(gcov, layout = z, add = TRUE, edge.color = "red3",
	 edge.width = 2, edge.lty = "dashed",
	 vertex.color = vcol, vertex.label.color = lcol)
    legend("topleft", legend = c("Vertex set", "Covered edges"),
	   lwd = c(NA,2), pch = c(21, NA), col = c("black", "red3"),
	   pt.bg = c("red4", NA), pt.cex = c(2.5, NA),
	   lty = c("solid", "dashed"))
    text(0, 1.2,
	 labels = paste0("Size = ", X$size, ", Uncovered edges = ",
			 gsize(G) - gsize(gcov)))
}


##' This routine uses a greedy algorithm to build a cover selecting
##'     the highest degree vertex first and removing its incident
##'     edges.
##'
##' This algorithm builds a vertex cover since no edge remains to be
##'     covered when it returns.  However, it is no guaranteed that
##'     the cover found by this algorithm has minimum cardinality.
##'
##' @title Greedy algorithm for vertex cover in a graph
##' @param G Graph
##' @return A list with two components: $set contains the cover, $size
##'     contains the number of vertices of the cover.
##' @author Cesar Asensio
##' @references Korte, Vygen \emph{Combinatorial Optimization. Theory
##'     and Algorithms.}
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique, [plot_cover] plots a cover.
##' @examples 
##' ## Example with known cover
##' K25 <- make_full_graph(25)   # Cover of size 24
##' X0 <- build_cover_greedy(K25)
##' X0$size  # 24
##' plot_cover(X0, K25)
##' plot_cover(list(set = c(1,2), size = 2), K25)
##'
##' ## Vertex-cover of a random graph
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)
##' X1 <- build_cover_greedy(g)
##' X1$size   # 17
##' plot_cover(X1, g)
##'
##' @encoding UTF-8
##' @md 
##' @export 
build_cover_greedy <- function(G) {
    n <- gorder(G)
    X <- rep(FALSE, n)
    h <- G
    while (gsize(h) > 0) {
	dv <- degree(h)
	v <- which.max(dv)
	h[v,] <- FALSE
	X[v] <- TRUE
    }
    list(set = as.numeric(V(G)[X]), size = sum(X))
}

##' Gavril's 2-approximation algorithm to build a vertex cover.
##'
##' This algorithm computes a maximal matching and takes the ends of
##'     the edges in the matching as a vertex cover.  No edge is
##'     uncovered by this vertex subset, or the matching would not be
##'     maximal; therefore, the vertex set thus found is indeed a
##'     vertex cover.
##'
##' Since no vertex can be incident to two edges of a matching M, at
##'     least |M| vertices are needed to cover the edges of the
##'     matching; thus, any vertex cover X should satisfy |X| >= |M|.
##'     Moreover, the vertices incident to the matching are always a
##'     vertex cover, which implies that, if X* is a vertex cover of
##'     minimum sise, |X*| <= 2|M|.
##' 
##' @title 2-approximation algorithm for vertex cover
##' @param G Graph
##' @return A list with two components: $set contains the cover, $size
##'     contains the number of vertices of the cover.
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique, [plot_cover] plots a cover.
##' @references Korte, Vygen \emph{Combinatorial Optimization. Theory
##'     and Algorithms.}
##' @examples 
##' ## Example with known vertex cover
##' K25 <- make_full_graph(25)   # Cover of size 24
##' X0 <- build_cover_approx(K25)
##' X0$size  # 24
##' plot_cover(X0, K25)
##'
##' ## Vertex-cover of a random graph
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)
##' X2 <- build_cover_approx(g)
##' X2$size   # 20
##' plot_cover(X2, g)
##'
##' @encoding UTF-8
##' @md 
##' @export 
build_cover_approx <- function(G) {
    n <- gorder(G)
    q <- gsize(G)
    X <- rep(FALSE, n)
    h <- G
    eG <- as_edgelist(G)
    M <- rep(FALSE, q)
    for (i in 1:q) {
	e <- eG[i,]
	X[e] <- TRUE
	M[i] <- TRUE
	h[e,] <- FALSE
	if (gsize(h) == 0) break
    }
    list(set = as.numeric(V(G)[X]), size = sum(X))    
}

##' Local search to improve a cover by using "neighboring" vertex
##'     subsets differing in just one element from the initial subset.
##' 
##' Given some cover specified by a vertex subset X in a graph, this
##'     routine scans the neighboring subsets obtained from X by
##'     removing a vertex from X looking for a smaller cover.  If such
##'     a cover is found, it replaces the starting cover and the
##'     search starts again.  This iterative procedure continues until
##'     no smaller cover can be found.  Of course, the resulting cover
##'     is only a local minimum.
##'
##' @title Improving a cover with local search
##' @param G A graph
##' @param X A cover list with components $set, $size as returned by
##'     routines [build_cover_greedy] or [build_cover_approx].  X
##'     represents the cover to be improved
##' @return A list with two components: $set contains the subset of
##'     V(g) representing the cover and $size contains the number of
##'     vertices of the cover.
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique, [plot_cover] plots a cover.
##' @examples 
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)  # Random graph
##'
##' X1 <- build_cover_greedy(g)
##' X1$size    # 17
##' plot_cover(X1, g)
##'
##' X2 <- build_cover_approx(g)
##' X2$size    # 20
##' plot_cover(X2, g)
##'
##' X3 <- improve_cover_flip(g, X1)
##' X3$size    # 17 : Not improved
##' plot_cover(X3,g)
##'
##' X4 <- improve_cover_flip(g, X2)
##' X4$size    # 19 : It is improved by a single vertex
##' plot_cover(X4,g)
##'
##' # Vertex subsets or n-1 elements are always vertex covers:
##' for (i in 1:25) {
##'    X3 <- improve_cover_flip(g, list(set = setdiff(1:25,i), size = 24))
##'    print(X3$size)
##' } # 19 18 18 18 18 18 17 20 19 17 17 18 18 18 17 19 20 19 19 17 19 19 19 19 19
##'  
##' @encoding UTF-8
##' @md 
##' @export 
improve_cover_flip <- function(G, X) {
    n <- gorder(G)
    vG <- 1:n
    eG <- as_edgelist(G)
    Xmin <- Xb <- Xv <- vG %in% X$set
    sX <- sXmin <- sXmin.old <- X$size
    while(TRUE) {
	for (i in vG) {
	    Xv <- Xb
	    if (Xv[i]) {
		Xv[i] <- FALSE
	    } else {
		next
	    }
	    if (is_cover(vG[Xv], eG)) {
		Xmin <- Xv
		sXmin <- sum(Xmin)
	    }
	}
	if (sXmin < sXmin.old) {
	    sX <- sXmin.old <- sXmin
	    Xb <- Xmin
	} else {
	    break
	}
    }
    list(set = vG[Xmin], size = sXmin)
}

##' Check if some vertex subset of a graph covers all its edges.
##'
##' The routine simply goes through the edge list of the graph to see
##'      if both ends of each edge are inside the vertex subset to be
##'      checked.  When an edge with both ends ouside X is
##'      encountered, the routine returns FALSE; otherwise, it returns
##'      TRUE.
##' 
##' @title Check vertex cover
##' @param X Vertex subset to check.
##' @param eG Edgelist of the graph as returned by [as_edgelist]
##' @return Boolean: TRUE if X is a vertex cover of the graph
##'     represented by eG, FALSE otherwise.
##' @author Cesar Asensio
##' @seealso [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique, [plot_cover] plots a cover.
##' @examples
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)  # Random graph
##' eg <- as_edgelist(g)
##'
##' X1 <- build_cover_greedy(g)
##' is_cover(X1$set, eg)   # TRUE
##' is_cover(c(1:10),eg)   # FALSE
##' plot_cover(list(set = 1:10, size = 10), g)  # See uncovered edges
##' 
##' @encoding UTF-8
##' @md 
##' @export 
is_cover <- function(X, eG) {
    q <- nrow(eG)
    verdict <- TRUE
    for (i in 1:q) {
	e <- eG[i,]
	if (sum(e %in% X) == 0) {
	    verdict <- FALSE
	    break
	}
    }
    verdict
}


##' Random algorithm for vertex-cover.
##'
##' It builds N random vertex sets by inserting elements with
##'     probability p, and it verifies if the subset so chosen is a
##'     vertex cover by running [is_cover] on it.  It is very
##'     difficult to find a good vertex cover in this way, so this
##'     algorithm is very inefficient and it finds no specially good
##'     covers.
##'
##' Currently, this function is *not* exported.  The random sampling
##'     performed by [search_cover_random] is faster and more
##'     efficient.
##' 
##' @title Random vertex covers
##' @param G Graph.
##' @param N Number of random vertex set to try.
##' @param p Probability of each element to be selected.
##' @return A list with four components: $set contains the subset of
##'     V(g) representing the cover and $size contains the number of
##'     vertices of the cover; $found is the number of vertex covers
##'     found and $failed is the number of generated subset that were
##'     not vertex covers. 
##' @author Cesar Asensio
##' @examples
##' n <- 25
##' g <- sample_gnp(n, p=0.25)  # Random graph
##' X5 <- build_cover_random(g,10000,p=0.65)
##' X5$size            # 19
##' plot_cover(X5, g)
##' X6 <- improve_cover_flip(g, X5)   # Improved : 17
##' plot_cover(X6, g)
##' 
##' @encoding UTF-8
##' @md 
##' @export 
build_cover_random <- function(G, N, p = 0.75) {
    n <- gorder(G)
    vG <- 1:n
    eG <- as_edgelist(G)
    Xmin <- X <- rep(TRUE, n)
    sXmin <- n
    sX <- num <- numF <- 0
    h <- rep(0,n)
    for (i in 1:N) {
	X <- sample(c(TRUE,FALSE), n, replace = TRUE, prob = c(p,1-p))
	if (is_cover(vG[X], eG)) {
	    num <- num + 1
	    sX <- sum(X)
	    h[sX] <- h[sX] + 1
	    if (sX < sXmin) {
		Xmin <- X
		sXmin <- sX
	    }
	} else { numF <- numF + 1}
    }
    list(set = vG[Xmin], size = sXmin, found = num, failed = numF,
	 hist = h)
}


##' Random algorithm for vertex-cover.
##'
##' This routine performs N iterations of the simple procedure of
##'     selecting a random sample of size k on the vertex set of a
##'     graph and check if it is a vertex cover, counting successes
##'     and failures.  The last cover found is returned, or an empty
##'     set if none is found.
##' 
##' @title Random vertex covers
##' @param G Graph.
##' @param N Number of random vertex set to try.
##' @param k Cardinality of the random vertex sets generated by the
##'     algorithm.
##' @param alpha Exponent of the probability distribution from which
##'     vertices are drawn: P(v) ~ d(v)^alpha.
##' @return A list with four components: $set contains the subset of
##'     V(g) representing the cover and $size contains the number of
##'     vertices of the cover (it coincides with k).  $found is the
##'     number of vertex covers found and $failed is the number of
##'     generated subset that were not vertex covers.
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_ants] looks for a random cover using a
##'     version of the ant-colony optimization heuristic,
##'     [find_cover_BB] finds covers using a branch-and-bound
##'     technique, [plot_cover] plots a cover.
##' @examples 
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)  # Random graph
##' X7 <- search_cover_random(g, 10000, 17, alpha = 3)
##' plot_cover(X7, g)
##' X7$found    # 21 (of 10000) covers of size 17
##'
##' ## Looking for a cover of size 16...
##' X8 <- search_cover_random(g, 10000, 16, alpha = 3)  # ...we don't find any!
##' plot_cover(X8, g)   # All edges uncovered
##' X8$found    # 0
##' 
##' @encoding UTF-8
##' @md 
##' @export 
search_cover_random <- function(G, N, k, alpha = 1) {
    n <- gorder(G)
    vG <- 1:n
    eG <- as_edgelist(G)
    p <- degree(G)^alpha
    Xmin <- X <- c()
    num <- 0
    for (i in 1:N) {
	X <- sample(vG, k, prob = p)
	if (is_cover(X, eG)) {
	    Xmin <- X
	    num <- num + 1
	}
    }
    list(set = Xmin, size = length(Xmin), found = num, failed = N - num)
}

##' Ant colony optimization (ACO) heuristic algorithm to search for a
##'     vertex cover of small size in a graph.  ACO is a random
##'     algorithm; as such, it yields better results when longer
##'     searches are run.  To guess the adequate parameter values
##'     resulting in better performance in particular instances
##'     requires some experimentation, since no universal values of
##'     the parameters seem to be appropriate to all examples.
##'
##' ACO is an optimization paradigm that tries to replicate the
##'     behavior of a colony of ants when looking for food.  Ants
##'     leave after them a soft pheromone trail to help others follow
##'     the path just in case some food has been found.  Pheromones
##'     evaporate, but following again the trail reinforces it, making
##'     it easier to find and follow.  Thus, a team of ants search a
##'     vertex cover in a graph, leaving a pheromone trail on the
##'     chosen vertices.  At each step, each ant decides the next
##'     vertex to add based on the pheromone level and on the degree
##'     of the remaining vertices, according to the formula \eqn{P(v)
##'     ~ phi(v)^alpha*exp(beta*d(v))}, where \eqn{phi(v)} is the
##'     pheromone level, \eqn{d(v)} is the degree of the vertex
##'     \eqn{v}, and \eqn{alpha}, \eqn{beta} are two exponents to
##'     broad or sharpen the probability distribution.  After each
##'     vertex has been added to the subset, its incident adges are
##'     removed, following a randomized version of the greedy
##'     heuristic.  In a single iteration, each ant builds a vertex
##'     cover, and the best of them is recorded.  Then the pheromone
##'     level of the vertices of the best cover are enhanced, and the
##'     remaining pheromones begin to evaporate.
##' 
##' Default parameter values have been chosen in order to find the
##'     optimum in the examples considered below.  However, it cannot
##'     be guarateed that this is the best choice for all cases.  Keep
##'     in mind that no polynomial time exact algorithm can exist for
##'     the VCP, and thus harder instances will require to fine-tune
##'     the parameters.  In any case, no guarantee of optimality of
##'     covers found by this method can be given, so they might be
##'     improved further by other methods.
##' 
##' @title Ant colony optimization algorithm for Vertex-Cover
##' @param g Graph.
##' @param K Number of ants per iteration.
##' @param N Number of iterations.
##' @param alpha Exponent of the pheronome index, see details.
##' @param beta Exponent of the vertex degree, see details.
##' @param dt Pheromone increment.
##' @param rho Pheromone evaporation rate.
##' @param verb Boolean; if TRUE (default) it echoes to the console
##'     the routine progress .
##' @return A list with three components: $set contains the subset of
##'     V(g) representing the cover and $size contains the number of
##'     vertices of the cover; $found is the number of vertex covers
##'     found in subsequent iterations (often they are repeated, that
##'     is, different explorations may find the same vertex cover).
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [find_cover_BB] finds covers using a
##'     branch-and-bound technique, [plot_cover] plots covers.
##' @examples
##' set.seed(1)
##' g <- sample_gnp(25, p=0.25)                  # Random graph
##'
##' X6 <- search_cover_ants(g, K = 20, N = 10)
##' plot_cover(X6, g)
##' X6$found
##' 
##' @encoding UTF-8
##' @md 
##' @export 
search_cover_ants <- function(g, K, N, alpha = 2, beta = 2, dt = 1,
			      rho = 0.1, verb=TRUE) {
    n <- gorder(g)
    Q <- gsize(g)
    V <- as.numeric(V(g))
    dg <- degree(g)
    pher <- rep(1, n)
    Co.min <- matrix(0, nrow = N, ncol = n)
    Nu.min <- rep(0, N)
    zeroM <- matrix(0, nrow = K, ncol = n)
    zeroV <- rep(0, K)
    if (verb) {
	cat("\nAnt-Colony algorithm for VERTEX-COVER\n\n   ",
	    N, " iterations with ", K, " ants ",
	    "(alpha = ", alpha, " / beta = ", beta,
	    " / rho = ", rho,")\n",
	    "   Graph instance: \"", g$name,"\" (n = ", n,
	    ", q = ", Q, ")\n",
	    "   Iteration: ", sep="")
    }
    for (i in 1:N) {       # Iteration of explorations
	if (verb) { cat(i, ",", sep="") }
	covers <- zeroM
	numins <- zeroV
	for (j in 1:K) {   # Send K explorer ants
	    h <- g
	    q <- Q
	    cov <- rep(0,n)
	    while (q>0) {  # Randomly build cover
		dh <- degree(h)[cov == 0]
		p <- exp(beta*dh)*pher[cov == 0]^alpha
		vp <- sample(V[cov == 0], size = 1, prob = p)
		cov[vp] <- 1
		h[vp,] <- FALSE
		q <- gsize(h)
	    }
	    covers[j,] <- cov       # Keep cover...
	    numins[j] <- sum(cov)   # ...its size...
	}
	min <- which.min(numins)    # ...and select the smaller one
	Nu.min[i] <-  numins[min]
	Co.min[i,] <- covers[min,]
	pher <- (1-rho)*pher             # Pheromone evaporation
	for (k in 1:n) {                 # Pheromone enhancement
	    pher[covers[min,] == 1] <- pher[covers[min,] == 1] + rho*dt
	}
    }
    nu.min <- min(Nu.min)
    co.min <- c()
    for (k in 1:nrow(Co.min)) {
	if (Nu.min[k] == nu.min) {
	    co.min <- rbind(co.min, V[Co.min[k,] == 1])
	}
    }
    found <- nrow(co.min)
    if (verb) {
	cat("DONE\n   Found cover with ", nu.min,
	    " vertices:\n", "    (", sep="")
	cat(co.min[1,], sep=",")
	cat(") [1 of ", found, "]\n\n",sep="")
    }
    list(set = co.min[1,], size = nu.min, found = found)
}

##' This routine performs a version of the Branch-and-Bound algorithm
##'     for the VCP.  It is an exact algorithm with exponential
##'     worst-case running time; therefore, it can be run only with a
##'     small number of vertices.
##'
##' The algorithm traverses a binary tree in which each bifurcation
##'     represents if a vertex is included in or excluded from a
##'     partial cover.  The leaves of the tree represent vertex
##'     subsets; the algorithm checks if at some point the partial
##'     cover cannot become a full cover because of too many uncovered
##'     edges with too few remaining vertices to decide.  In this way,
##'     the exponential complexity is somewhat reduced.  Furthermore,
##'     the vertices are considered in decreasing degree order, as in
##'     the greedy algorithm, so that some cover is found in the early
##'     steps of the algorithm and thus a good upper bound on the
##'     solution can be used to exclude more subsets from being
##'     explored.  The full algorithm has been extracted from the
##'     reference below.
##'
##' In this routine, the binary tree search is implemented by
##'     recursive calls (that is, a dynamic programming algorithm).
##'     Although the worst case time complexity is exponential (recall
##'     that the Minimum Vertex Cover Problem is NP-hard), the
##'     approach is fairly efficient for a branch-and-bound technique.
##'
##' The tree node in which the algorithm is when it is called (by the
##'     user or by itself) is encoded in a sequence of vertex marks.
##'     Marks come in three flavors: "C" is assigned to "Covered"
##'     vertices, that is, already included in the partial cover.  "U"
##'     is asigned to "Uncovered" vertices, that is, those excluded
##'     from the partial cover.  Mark "F" is assigned to "Free"
##'     vertices, those not considered yet by the algorithm; one of
##'     them is considered in the actual function call, and the
##'     subtree under this vertex is explored before returning.  This
##'     mark sequence starts and ends with all vertices marked "F",
##'     and is used only by the algorithm, which modifies and passes
##'     it on to succesive calls to itself.
##'
##' When the verb argument is TRUE, the routine echoes to the console
##'     the newly found cover only if it is better than the last.
##'     This report includes the size, actual cover and number call of
##'     the routine.
##' 
##' The routine can drop the best cover found so far in a file so that
##'     the user can stop the run afterwards; this technique might be
##'     useful when the full run takes too much time to complete.
##' 
##' @title Branch-and-Bound algorithm for the Vertex-Cover problem
##' @param g Graph.
##' @param verb Boolean: Should echo each newly found cover to the
##'     console? Defaults to TRUE.
##' @param save.best.result Boolean: Should the algorithm save the
##'     result of the algorithm in a file?  It defaults to FALSE.
##'     When save.best.result = TRUE, a file is created with the
##'     variable "Xbest" being the best result achieved by the
##'     algorithm before its termination. 
##' @param filename.best.result Name of the file created when
##'     save.best.result = TRUE.  It defaults to
##'     "best_result_find_cover_BB.Rdata".
##' @param nu Size of the best cover currently found.
##' @param X Partial cover.
##' @param Xmin Best cover found so far.
##' @param marks Mark sequence storing the current state of the
##'     algorithm, see details.
##' @param call Number of recursive calls performed by the algorithm.
##' @return A list with five components: $set contains the subset of
##'     V(g) representing the cover and $size contains the number of
##'     vertices of the cover. Component $call is the number of calls
##'     the algorithm did on itself.  The remaining components are
##'     used to transfer the state of the algorithm in the search
##'     three from one call to the next; they are $partial, the
##'     partially constructed cover, and $marks, a sequence encoding
##'     the tree node in which the algorithm is when this function is
##'     called, see details.
##' @author Cesar Asensio
##' @seealso [is_cover] checks if a vertex subset is a vertex cover,
##'     [build_cover_greedy] builds a cover using a greedy heuristic,
##'     [build_cover_approx] builds a cover using a 2-approximation
##'     algorithm, [improve_cover_flip] improves a cover using local
##'     search, [search_cover_random] looks for a random cover of
##'     fixed size, [search_cover_ants] looks for a random cover using
##'     a version of the ant-colony optimization heuristic,
##'     [plot_cover] plots a cover.
##' @examples
##' set.seed(1)
##' g <- sample_gnp(25, p=0.25)        # Random graph
##' X7 <- find_cover_BB(g)
##' X7$size                            # Exact result: 16
##' X7$call                            # 108 recursive calls!
##' plot_cover(X7, g)
##'
##' ## Saving best result in a file (useful if the algorithm takes too
##' ## long and should be interrupted by the user)
##' ## It uses tempdir() to store the file
##' ## The variable is called "Xbest"
##' find_cover_BB(g, save.best.result = TRUE, filename.best.result = "BestResult_BB.Rdata")
##' 
##' @encoding UTF-8
##' @md 
##' @export 
find_cover_BB <- function(g, verb = TRUE,
			  save.best.result = FALSE,
			  filename.best.result = "best_result_find_cover_BB.Rdata",
			  nu    = gorder(g),
			  X     = c(),
			  Xmin  = c(),
			  marks = rep("F", gorder(g)),
			  call  = 0) {

    if (call == 0) {
	cat("Running branch-and-bound on a VCP with n =",
	    gorder(g), "vertices...")
    }

    call <- call + 1 # Call count
    J <- call

    ## Are there some remaining edges in the graph?  Otherwise,
    ## the graph is covered!
    q <- gsize(g)
    if (q == 0) {
	if (length(X) < nu) {
	    nu <- length(X)
	    Xmin <- X
	    if(verb) {
		cat("\nCover found:\n",
		    "  size = ", nu, "\n",
		    " cover = ", paste(X, collapse = ","), "\n",
		    "  call = ",  J, "\n",
		    sep="")
	    }
	    if (save.best.result)  {
		assign("Xbest", value = list(set = Xmin, size = nu))
		dirsep <- if(Sys.info()["sysname"] == "Windows") { "\\" } else { "/" }
		tfile <- paste(tempdir(), filename.best.result, sep = dirsep)
		save(Xbest, file = tfile)
		cat("\nSaved best result found in variable \"Xbest\" in file ", tfile, "\n", sep = "")
	    }
	}
	## if (plot) { plot_cover(list(set = X, size = nu), gini) }
	return(list(size = nu, set = Xmin, call = J, partial = X, marks = marks))
    }

    ## We compute bounds:
    ## F = Number of vertices to reach "nu" in X
    ## D = Upper bound of the number of uncovered edges
    F <- nu - length(X)
    ds <- degree(g)
    D <- sum_g(ds[marks == "F"], F)

    ## If uncovered edges outnumber the bound, covering in not
    ## possible anymore
    if (D < q) {
	return(list(size = nu, set = Xmin, call = J, partial = X, marks = marks)) 
    }

    ## We choose the "F" (free) vertex with maximum degree, as in the
    ## greedy heuristic
    V <- as.numeric(V(g))
    k <- which.max(ds[marks == "F"])
    v0 <- V[marks == "F"][k]

    ## We mark the chosen vertex as "C" (covered)  [subtree "v0 covered"]
    marks[v0] <- "C"
    X <- c(X,v0)   # ...end we add it to the subset X

    ## We remove the edges incident to v0
    ## Before, we record its neighbors to restore them later
    Nv0 <- neighbors(g, v0)
    g[v0,] <- FALSE

    ## Entering subtree "v0 covered" with updated "g" and "X"
    s <- find_cover_BB(g, verb, save.best.result, filename.best.result, nu, X, Xmin, marks, J)
    J <- s$call
    nu <- s$size
    X <- s$partial
    Xmin <- s$set
    marks <- s$marks

    ## At this point, subtree "v0 covered" has been explored; we
    ## restore the previously removed edges, and we remove v0 from X
    for (v in Nv0) { g <- g + edge(v0,v) }
    X <- X[X != v0]

    ## We check the second bound: Is F > d(v0)?  If it is, we mark
    ## with "C" all neighbors; if it is not, marking would lead to a
    ## suboptimal cover, so we just free v0.
    if (F > length(Nv0)) { # Subtree "v0 uncovered"
	marks[v0] <- "U"
	aris <- c()
	for (v in Nv0) {
	    marks[v] <- "C"
	    X <- c(X, v)
	    Nv <- neighbors(g, v)
	    for (u in Nv) { # We take note of edges to erase...
		aris <- c(aris, v, u)
	    }
	    g[v,] <- FALSE             # ...and we remove them
	}

	## Entering subtree "v0 uncovered"
	s <- find_cover_BB(g, verb, save.best.result, filename.best.result, nu, X, Xmin, marks, J)
	J <- s$call
	nu <- s$size
	X <- s$partial
	Xmin <- s$set
	marks <- s$marks

	## We free neighbors and restore the previously removed edges
	for (v in Nv0) {
	    X <- X[X!=v]
	    marks[v] <- "F"
	}
	g <- g + edges(aris)
    }

    ## Once checked both subtrees, we free v0 and we are done!
    marks[v0] <- "F"
    if (sum(marks == "F") == gorder(g)) cat("done\n")
    return(list(size = nu, set = Xmin, call = J, partial = X, marks = marks))
}

##' Sum of the higher terms of a list
##'
##' It sorts the list L in decreasing order and returns the sum of the
##'     first m components of the ordered list.
##' 
##' @title Sum of the higher terms of a list
##' @param L A numeric sequence
##' @param m A scalar
##' @return The sum of the m higher terms of the list L
##' @author Cesar Asensio
##' @examples 
##' sum_g(1:10, 3)  # 8 + 9 + 10 = 27
##' 
##' @encoding UTF-8
##' @md 
##' @export 
sum_g <- function(L, m) { sum(sort(L, decreasing = TRUE)[1:m]) }

##' Random cut generation on a graph.  This function generates a
##'     hopefully large cut on a graph by randomly selecting vertices;
##'     it does not attempt to maximize the cut size or weigth, so it
##'     is intended to be used as part of some smarter strategy.
##'
##' It selects a random subset of the vertex set of the graph,
##'     computing the associated cut, its size and its weigth,
##'     provided by the user as a weight matrix.  If the weight
##'     argument w is NA, the weights are taken as 1.
##' 
##' @title Random cut generation on a graph
##' @param G Graph
##' @param w Weight matrix (defaults to NA).  It should be zero for
##'     those edges not in G
##' @return A list with four components: $set contains the subset of
##'     V(g) representing the cut, $size contains the number of edges
##'     of the cut, $weight contains the weight of the cut (which
##'     coincides with $size if w is NA) and $cut contains the edges
##'     of the cut, joining vertices inside $set with vertices outside
##'     $set.
##' @importFrom stats runif
##' @author Cesar Asensio
##' @seealso [build_cut_greedy] builds a cut using a greedy algorithm,
##'     [compute_cut_weight] computes cut size, weight and edges,
##'     [improve_cut_flip] uses local search to improve a cut obtained
##'     by other methods, [plot_cut] plots a cut.
##' @examples 
##' ## Example with known maximum cut
##' K10 <- make_full_graph(10)   # Max cut of size 25
##' c0 <- build_cut_random(K10)
##' c0$size  # Different results: 24, 21, ...
##' plot_cut(c0, K10)
##'
##' ## Max-cut of a random graph
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)
##' c1 <- build_cut_random(g)   # Repeat as you like
##' c1$size  # Different results: 43, 34, 39, 46, 44, 48...
##' plot_cut(c1, g)
##'
##' @encoding UTF-8
##' @md 
##' @export 
build_cut_random <- function(G, w=NA) {
    n <- gorder(G)
    S <- as.numeric(V(G)[runif(n) < 0.5])
    eG <- as_edgelist(G)
    compute_cut_weight(S, n, eG, w, return.cut = TRUE)
}


##' Plot of a cut in a graph.
##'
##' It plots a graph, then superimposes a cut, drawing the associated
##'     vertex set in a different color.
##' 
##' @title Cut plotting
##' @param K Cut to be plotted; an output list returned by some
##'     cut-building function, see below.
##' @param G Graph on which to superimpose the cut.
##' @return This function is called for its side effect of plotting.
##' @importFrom graphics legend
##' @importFrom graphics text
##' @author Cesar Asensio
##' @seealso [build_cut_random] builds a random cut,
##'     [build_cut_greedy] builds a cut using a greedy algorithm,
##'     [improve_cut_flip] uses local search to improve a cut obtained
##'     by other methods, [compute_cut_weight] computes cut size,
##'     weight and edges.
##' @examples 
##' K10 <- make_full_graph(10)   # Max cut of size 25
##' c0 <- build_cut_random(K10)
##' plot_cut(c0, K10)
##'
##' @encoding UTF-8
##' @md 
##' @export 
plot_cut <- function(K, G) {
    n <- gorder(G)
    z <- layout_with_gem(G)
    plot(G, layout=z)
    gcut <- make_graph(t(K$cut), dir=FALSE)
    vcol <- rep("pink", n)
    lcol <- rep("black", n)
    vcol[K$set] <- "red4"
    lcol[K$set] <- "white"
    plot(gcut, layout=z, add=TRUE, edge.color="red3", edge.width=2,
	 vertex.color=vcol, vertex.label.color=lcol)
    legend(-1.2, 1.2, legend = c("Set", "Cut"),
	   lwd = c(NA,2), pch = c(21, NA), col = c("black", "red3"),
	   pt.bg = c("red4", NA), pt.cex = c(2.5, NA))
    text(0, 1.2,
	 labels = paste0("Size = ", K$size, ", Weight = ", K$weight))
}

##' Compute cut weight and size from its associated vertex set.  It
##'     can also return the edges in the cut.
##'
##' In a graph, a cut K is defined by means of a vertex subset S as
##'     the edges joining vertices inside S with vertices outside S.
##'     This routine computes these edges and their associated weight.
##' 
##' @title Compute cut weight and size
##' @param S Subset of the vertex set of the graph.
##' @param n Size of the graph.
##' @param eG Edgelist of the graph as returned by [as_edgelist], that
##'     is, a matrix with q rows and 2 columns.  Note that this is the
##'     graph format used by the routine.
##' @param w Weight matrix or NA if all edge weights are 1.  It should
##'     be zero for those edges not in G
##' @param return.cut Boolean.  Should the routine return the edges in
##'     the cut? It defaults to FALSE.  When TRUE, the routine also
##'     returns the input subset S, for easier cut plotting with
##'     [plot_cut].
##' @return A list with two components: $size is the number of edges
##'     in the cut, $weight is the weight of the cut, that is, the sum
##'     of the weights of the edges in the cut.  If w=NA these two
##'     numbers coincide.  When return.cut is TRUE, there are two
##'     additional components of the list: $cut, which contains the
##'     edges in the cut as rows of a two-column matrix, and $set,
##'     which contains the input set, as a convenience for plotting
##'     with [plot_cut].
##' @author Cesar Asensio
##' @seealso [build_cut_random] builds a random cut,
##'     [build_cut_greedy] builds a cut using a greedy algorithm,
##'     [improve_cut_flip] uses local search to improve a cut obtained
##'     by other methods, [plot_cut] plots a cut.
##' @examples 
##' K10 <- make_full_graph(10)
##' S <- c(1,4,7)
##' compute_cut_weight(S, gorder(K10), as_edgelist(K10))
##' cS <- compute_cut_weight(S, gorder(K10), as_edgelist(K10),
##'     return.cut = TRUE)
##' plot_cut(cS, K10)
##' 
##' @encoding UTF-8
##' @md 
##' @export 
compute_cut_weight <- function(S, n, eG, w=NA, return.cut = FALSE) {
    M <- 2*as.numeric(1:n %in% S) - 1
    q <- nrow(eG)
    k <- rep(FALSE, q)
    val <- 0
    for (e in 1:q) {
	u <- eG[e, 1]
	v <- eG[e, 2]
	if (prod(M[c(u,v)]) == -1) {
	    k[e] <- TRUE
	    if (is.na(w[1])) {
		val <- val + 1
	    } else {
		val <- val + w[u, v]
	    }
	}
    }
    if (return.cut) {
	list(set = S, size = sum(k), weight = val, cut = eG[k,])
    } else {
	list(size = sum(k), weight = val)
    }
}

##' This routine uses a greedy algorithm to build a cut with large
##'     weight.  This is a 2-approximation algorithm, which means that
##'     the weight of the cut returned by this algorithm is larger
##'     than half the maximum possible cut weight for a given graph.
##'
##' The algorithm builds a vertex subset S a step a a time.  It starts
##'     with S = c(v1), and with vertices v1 and v2 marked.  Then it
##'     iterates from vertex v3 to vn checking if the weight of the
##'     edges joining vi with marked vertices belonging to S is less
##'     than the weight of the edges joining vi with marked vertices
##'     not belonging to S.  If the former weight is less than the
##'     latter, then vi is adjoined to S.  At the end of each
##'     iteration, vertex vi is marked.  When all vertices are marked
##'     the algorithm ends and S is already built.
##'
##' @title Greedy algorithm aimed to build a large weight cut in a
##'     graph
##' @param G Graph
##' @param w Weight matrix (defaults to NA).  It should be zero for
##'     those edges not in G
##' @return A list with four components: $set contains the subset of
##'     V(g) representing the cut, $size contains the number of edges
##'     of the cut, $weight contains the weight of the cut (which
##'     coincides with $size if w is NA) and $cut contains the edges
##'     of the cut, joining vertices inside $set with vertices outside
##'     $set.
##' @author Cesar Asensio
##' @references Korte, Vygen \emph{Combinatorial Optimization. Theory
##'     and Algorithms.}
##' @seealso [build_cut_random] builds a random cut,
##'     [improve_cut_flip] uses local search to improve a cut obtained
##'     by other methods, [compute_cut_weight] computes cut size,
##'     weight and edges, [plot_cut] plots a cut.
##' @examples 
##' ## Example with known maximum cut
##' K10 <- make_full_graph(10)   # Max cut of size 25
##' c0 <- build_cut_greedy(K10)
##' c0$size  # 25
##' plot_cut(c0, K10)
##'
##' ## Max-cut of a random graph
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)
##' c2 <- build_cut_greedy(g)
##' c2$size   # 59
##' plot_cut(c2, g)
##'
##' @encoding UTF-8
##' @md 
##' @export 
build_cut_greedy <- function(G, w=NA) {
    n <- gorder(G)
    S <- c(1)
    R <- c(1,2)
    if (is.na(w[1])) {
	w <- as_adjacency_matrix(G)
    }
    for (i in 3:n) {
	RiS <- intersect(R,S)
	RsS <- setdiff(R,S)
	if (sum(w[i, RiS]) < sum(w[i, RsS])) {
	    S <- c(S, i)
	}
	R <- c(R, i)
    }
    eG <- as_edgelist(G)
    compute_cut_weight(S, n, eG, w, return.cut = TRUE)
}

##' Local search to improve a cut by using "neighboring" vertex
##'     subsets differing in just one element from the initial subset.
##' 
##' Given some cut specified by a vertex subset S in a graph, this
##'     routine scans the neighboring subsets obtained from S by
##'     adding/removing a vertex from S looking for a larger cut.  If
##'     such a cut is found, it replaces the starting cut and the
##'     search starts again.  This iterative procedure continues until
##'     no larger cut can be found.  Of course, the resulting cut is
##'     only a local maximum.
##'
##' @title Improving a cut with local search
##' @param G A graph
##' @param K A cut list with components $set, $size, $weight and $cut
##'     as returned by routines [build_cut_greedy], [build_cut_random]
##'     or [compute_cut_weight].  Only the $set and $weight components
##'     are used.  K represents the cut to be improved
##' @param w Weight matrix (defaults to NA).  It should be zero for
##'     those edges not in G
##' @param return.cut Boolean.  Should the routine return the cut?  It
##'     is passed on to [compute_cut_weight] on return.  It defaults
##'     to TRUE
##' @return A list with four components: $set contains the subset of
##'     V(g) representing the cut, $size contains the number of edges
##'     of the cut, $weight contains the weight of the cut (which
##'     coincides with $size if w is NA) and $cut contains the edges
##'     of the cut, joining vertices inside $set with vertices outside
##'     $set.  When return.cut is FALSE, components $set and $cut are
##'     omitted.
##' @author Cesar Asensio
##' @seealso [build_cut_random] builds a random cut,
##'     [build_cut_greedy] builds a cut using a greedy algorithm,
##'     [compute_cut_weight] computes cut size, weight and edges,
##'     [plot_cut] plots a cut.
##' @examples 
##' set.seed(1)
##' n <- 25
##' g <- sample_gnp(n, p=0.25)  # Random graph
##'
##' c1 <- build_cut_random(g)
##' c1$size    # 44
##' plot_cut(c1, g)
##'
##' c2 <- build_cut_greedy(g)
##' c2$size    # 59
##' plot_cut(c2, g)
##'
##' c3 <- improve_cut_flip(g, c1)
##' c3$size    # 65
##' plot_cut(c3,g)
##'
##' c4 <- improve_cut_flip(g, c2)
##' c4$size    # 60
##' plot_cut(c4,g)
##'
##' @encoding UTF-8
##' @md 
##' @export 
improve_cut_flip <- function(G, K, w=NA, return.cut = TRUE) {
    n <- gorder(G)
    vG <- 1:n
    eG <- as_edgelist(G)
    Smax <- S <- Sv <- vG %in% K$set
    wK <- wKmax <- wKmax.old <- K$weight
    while(TRUE) {
	for (i in vG) {
	    Sv <- S
	    Sv[i] <- !Sv[i]
	    cSv <- compute_cut_weight(vG[Sv], n, eG, w)
	    wK <- cSv$weight
	    if (wK > wKmax) {
		Smax <- Sv
		wKmax <- wK
	    }
	}
	if (wKmax > wKmax.old) {
	    wKmax.old <- wKmax
	    S <- Smax
	} else {
	    break
	}
    }
    compute_cut_weight(vG[Smax], n, eG, w, return.cut = return.cut)
}

##' Crossover sequence operation for use in the genetic cut-search algorithm.
##'
##' This operation takes two sequences of the same lenght "n" and
##'     splits them in two at a crossover point between 1 and "n-1".
##'     Then it produces two "offsprings" by interchanging the pieces
##'     and gluing them together.
##'
##' The crossover point can be specified in argument cpoint.  By
##'     providing NA (the default), cpoint is chosen randomly.
##'
##' Note that this crossover operation is the "classic" crossover
##'     included in the original genetic algorithm, and it is adequate
##'     when applied to binary sequences.  However, when applied to
##'     permutations, the result of this function can have repeated
##'     elements; hence, it is not adequate for the TSP.
##' 
##' @title Crossover of sequences
##' @param s1 Sequence
##' @param s2 Sequence of the same lenght as s1
##' @param cpoint Crossover point, an integer between 1 and
##'     length(s1)-1.  Defaults to NA, in which case it will be
##'     randomly chosen
##' @return A two-row matrix.  Rows are the offsprings produced by the
##'     crossover
##' @author Cesar Asensio
##' @seealso [search_cut_genetic] genetic algorithm cut-search,
##'     [mutate_binary_sequence] binary sequence mutation
##' @examples
##' set.seed(1)
##' s1 <- sample(0:1, 10, replace = TRUE)
##' s2 <- sample(0:1, 10, replace = TRUE)
##' crossover_sequences(s1, s2)
##' 
##' set.seed(1)
##' s1 <- sample(1:10, 10)
##' s2 <- sample(1:10, 10)
##' crossover_sequences(s1, s2, cpoint = 5)
##' 
##' @encoding UTF-8
##' @md 
##' @export 
crossover_sequences <- function(s1, s2, cpoint = NA) {
    n <- length(s1)
    if (is.na(cpoint)) cpoint <- sample(1:(n-1),1)
    s1.1 <- s1[1:cpoint]
    s1.2 <- s1[(cpoint+1):n]
    s2.1 <- s2[1:cpoint]
    s2.2 <- s2[(cpoint+1):n]
    rbind(c(s1.1, s2.2), c(s2.1,s1.2))
}


##' Mutation of binary sequences for use in the genetic algorithm
##'
##' This routine takes a binary sequence and it flips ("mutates") each
##'     bit with a fixed probability.  In the genetic algorithm
##'     context, this operation randomly explores regions of
##'     configuration space which are far away from the starting
##'     point, thus trying to avoid local optima.  The fitting
##'     function values of mutated individuals are generically very
##'     poor, and this behavior is to be expected.  Thus, mutation is
##'     not an optimization procedure per se.
##' 
##' @title Binary sequence mutation
##' @param s Sequence consisting of 0 and 1
##' @param p Mutation probability.  Defaults to 0.1
##' @return A mutated binary sequence 
##' @author Cesar Asensio
##' @importFrom stats runif
##' @seealso [search_cut_genetic] genetic cut-searching algorithm,
##'     [crossover_sequences] crossover operation
##' @examples
##' set.seed(1)
##' s <- sample(0:1, 10, replace = TRUE)  # 0 0 1 1 0 1 1 1 1 0
##' mutate_binary_sequence(s, p = 0.5)    # 1 1 1 0 0 0 1 1 0 0
##' mutate_binary_sequence(s, p = 1)      # 1 1 0 0 1 0 0 0 0 1
##' 
##' @encoding UTF-8
##' @md 
##' @export 
mutate_binary_sequence <- function(s, p = 0.1) {
    n <- length(s)
    r <- runif(n)
    m <- s[r < p]
    s[r < p] <- 1 - m
    s
}

##' Genetic algorithm for Max-Cut.  In addition to crossover and
##'     mutation, which are described below, the algorithm performs
##'     also local search on offsprings and mutants.
##'
##' This algorithm manipulates cuts by means of its associated binary
##'     sequences defined as follows.  Each cut K is defined by its
##'     associated vertex subset S of V(G): K contains all edges
##'     joining vertices inside S with vertices outside S.  If
##'     |V(G)|=n, we can construct a n-bit binary sequence b =
##'     (b1,b2,...,bn) with bi = 1 if vertex vi belongs to S, and 0
##'     otherwise.
##' 
##' The genetic algorithm consists of starting with a cut population,
##'     where each cut is represented by its corresponding binary
##'     sequence defined above, and thus the population is simply a
##'     binary matrix.  This initial cut population can be provided by
##'     the user or can be random.  The initial population can be the
##'     output of a previous run of the genetic algorithm, thus
##'     allowing a chained execution.  Then the routine sequentially
##'     perform over the cuts of the population the
##'     \strong{crossover}, \strong{mutation}, \strong{local search}
##'     and \strong{selection} operations.
##'
##' The \strong{crossover} operation takes two cuts as "parents" and
##'     forms two "offsprings" by cutting and interchanging the binary
##'     sequences of the parents; see [crossover_sequences] for more
##'     information.
##'
##' The \strong{mutation} operation performs a "small" perturbation of
##'     each cut trying to escape from local optima.  It uses a random
##'     flip on each bit of the binary sequence associated with the
##'     cut, see [mutate_binary_sequence] for more information.
##'
##' The \strong{local search} operation takes the cuts found by the
##'     crossover and mutation operations and improves them using some
##'     local search heuristic, in this case [improve_cut_flip].  This
##'     allows this algorithm to approach local maxima faster.
##'
##' The \strong{selection} operation is used when selecting pairs of
##'     parents for crossover and when selecting individuals to form
##'     the population for the next generation.  In both cases, it
##'     uses a probability exponential in the weight with rate
##'     parameter "beta", favouring the better fitted to be selected.
##'     Lower values of beta favours the inclusion of cuts with worse
##'     fitting function values.  When selecting the next population,
##'     the selection uses \emph{elitism}, which is to save the best
##'     fitted individuals to the next generation; this is controlled
##'     with parameter "elite".
##'
##'
##' The usefulness of the crossover and mutation operations stems from
##'     its abitily to escape from the local maxima.  Of course, more
##'     iterations (Ngen) and larger populations (Npop) might improve
##'     the result, but recall that no random algorithm can guarantee
##'     to find the optimum of a given Max-Cut instance.
##'
##' This algorithm calls many times the routines [compute_cut_weight],
##'     [crossover_sequences], [mutate_binary_sequence] and
##'     [improve_cut_flip]; therefore, it is not especially efficient
##'     when called on large problems or with high populations or many
##'     generations.  Please consider chaining the algorithm:  perform
##'     short runs, using the output of a run as the input of the
##'     next.
##'
##' @title Genetic Algorithm for Max-Cut
##' @param G Graph.
##' @param w Weight matrix.
##' @param Npop Population size.
##' @param Ngen Number of generations (iterations of the algorithm).
##' @param pmut Mutation probability.  It defaults to 0.1.
##' @param beta Control parameter of the crossing and selection
##'     probabilities.  It defaults to 1.
##' @param elite Number of better fitted individuals to pass on to the
##'     next generation.  It defaults to 2.
##' @param Pini Initial population.  If it is NA, a random initial
##'     population of Npop individuals is generated.  Otherwise, it
##'     should be a matrix; each row should be an individual (a
##'     permutation of the 1:n sequence) and then Npop is set to the
##'     number of rows of Pini.  This option allows to chain several
##'     runs of the genetic algorithms, which could be needed in the
##'     hardest cases.
##' @param verb Boolean to activate console echo.  It defaults to
##'     TRUE.
##' @param log Boolean to activate the recording of the weights of all
##'     cuts found by the algorithm.  It defaults to FALSE.
##' @return A list with several components: $set contains the subset
##'     of V(g) representing the cut, $size contains the number of
##'     edges of the cut, $weight contains the weight of the cut
##'     (which coincides with $size if w is NA) and $cut contains the
##'     edges of the cut, joining vertices inside $set with vertices
##'     outside $set; $generation contains the generation when the
##'     maximum was found and $population contains the final cut
##'     population.  When log=TRUE, the output includes several lists
##'     of weights of cuts found by the algorithm, separated by
##'     initial cuts, offsprings, mutants, local maxima and selected
##'     cuts.
##' @references Hromkovic \emph{Algorithms for hard problems} (2004),
##'     Hartmann, Weigt, \emph{Phase transitions in combinatorial
##'     optimization problems} (2005).
##' @author Cesar Asensio
##' @seealso [crossover_sequences] performs crossover operation,
##'     [mutate_binary_sequence] performs mutation operation,
##'     [build_cut_random] builds a random cut, [build_cut_greedy]
##'     builds a cut using a greedy algorithm, [improve_cut_flip]
##'     improves a cut by local search, [compute_cut_weight] computes
##'     cut size, weight and edges, [plot_cut] plots a cut.
##' @examples 
##' set.seed(1)
##' n <- 10
##' g <- sample_gnp(n, p=0.5)  # Random graph
##' c5 <- search_cut_genetic(g)
##' plot_cut(c5, g)
##' improve_cut_flip(g, c5)     # It does not improve
##' for (i in 1:5) {            # Weights of final population
##'    s5 <- which(c5$population[i,] == 1)
##'    cs5 <- compute_cut_weight(s5, gorder(g), as_edgelist(g))
##'    print(cs5$weight)
##' }
##'
##' \donttest{
##' ## Longer examples
##' c5 <- search_cut_genetic(g, Npop=10, Ngen=50, log = TRUE)
##' boxplot(c5$Wini, c5$Woff, c5$Wmut, c5$Wvec, c5$Wsel,
##'         names=c("Ini", "Off", "Mut", "Neigh", "Sel"))
##'
##' set.seed(1)
##' n <- 20
##' g <- sample_gnp(n, p=0.25)
##' Wg <- matrix(sample(1:3, n^2, replace=TRUE), nrow=n)
##' Wg <- Wg + t(Wg)
##' A <- as_adjacency_matrix(g)
##' Wg <- Wg * A
##' c6 <- search_cut_genetic(g, Wg, Ngen = 9)   # Size 38, weigth 147
##' plot_cut(c6, g)
##' }
##' 
##' @encoding UTF-8
##' @md 
##' @export 
search_cut_genetic <- function(G, w = NA, Npop = 5, Ngen = 20,
			       pmut = 0.1, beta = 1, elite = 2,
			       Pini = NA, verb = TRUE, log = FALSE) {
    n <- gorder(G)
    vG <- 1:n
    eG <- as_edgelist(G)

    ## Initial population
    if (is.na(Pini)) {
	welc <- "Generated"
	Pini <- matrix(sample(0:1, n*Npop, replace = TRUE),
		       nrow = Npop, ncol = n)
    } else {
	welc <- "Received"
	Npop <- nrow(Pini)
    }

    ## Initialization
    Wmax <- 0
    P <- Pini
    U  <- U0 <- matrix(0, ncol = n, nrow = Npop + Npop + 2*Npop + 4*Npop)
    U[1:Npop,] <- P
    Wu <- Wu0 <- rep(0, 8*Npop)
    for (i in 1:Npop) {
	Wu[i] <- compute_cut_weight(vG[P[i,] == 1], n, eG, w)$weight
    }
    K <- Npop + 1

    if (log) {
	Wini <- Wu[1:Npop]
	Woff <- Wmut <- Wvec <- Wsel <- c()
    }
    if (verb) {
	cat(welc, " initial population (Npop = ", Npop, ")\n",
	    "Evolving Ngen = ", Ngen, " generations:\n", sep="")
    }

    ## Main loop
    for (i in 1:Ngen) {
	if (verb) cat("[Gen = ",i,"] : ", sep="")

	if (verb) cat("Crossover...")                  # Begin crossover 
	Noff <- 0
	Wpar <- Wu[1:Npop]
	pcross <- exp(beta*Wpar/mean(Wpar))
	for (m in 1:Npop) {
	    coup <- sample(1:Npop, 2, prob = pcross)
	    offs <- crossover_sequences(P[coup[1],], P[coup[2],])
	    for (j in 1:2) {
		repe <- FALSE
		off <- offs[j,]
		for (ki in 1:(K-1)) {
		    dis <- sum(abs(off - U[ki,]))
		    eq.test <- (dis == 0)
		    co.test <- (dis == n)
		    if (eq.test | co.test) {
			repe <- TRUE
			break
		    }
		}
		if (!repe) {
		    U[K,] <- off
		    Wo <- compute_cut_weight(vG[off == 1], n, eG, w)
		    Wu[K] <- Wo$weight
		    if (log) { Woff <- c(Woff, Wo$weight) }
		    Noff <- Noff + 1
		    K <- K + 1
		}
		if (Noff == Npop) { break }
	    }
	    if (Noff == Npop) { break }
	}
	if (verb) {
	    if (Noff == 1) { sfin <- "" } else { sfin <- "s" }
	    cat("(", Noff, " offspring", sfin, ") ",sep="")
	}                                               # End crossover

	if (verb) cat("Mutation...")                    # Begin mutation 
	Ncan <- Npop + Noff
	Nmut <- 0
	for (m in 1:Ncan) {
	    mut <- mutate_binary_sequence(U[m, ], pmut)
	    repe <- FALSE
	    for (k in 1:(K-1)) {
		dis <- sum(abs(mut - U[ki,]))
		eq.test <- (dis == 0)
		co.test <- (dis == n)
		if (eq.test | co.test) {
		    repe <- TRUE
		    break
		}
	    }
	    if (!repe) {
		U[K,] <- mut
		Wo <- compute_cut_weight(vG[mut == 1], n, eG, w)
		Wu[K] <- Wo$weight
		if (log) { Wmut <- c(Wmut, Wo$weight) }
		Nmut <- Nmut + 1
		K <- K + 1
	    }
	}
	if (verb) {
	    if (Nmut == 1) { sfin <- "" } else { sfin <- "s" }
	    cat("(", Nmut, " mutant", sfin, ") ",sep="")
	}                                                # End mutation

	if (verb) cat("Local search...")                 # Begin local search
	Ncan <- Npop + Noff + Nmut
	Nvec <- 0
	for (m in 1:Ncan) {
	    Ko <- list(set = vG[U[m, ] == 1], weight = Wu[m])
	    if (verb & !is.na(w[1])) cat("i")
	    vecW <- improve_cut_flip(G, Ko, w)
	    vec <- rep(0,n)
	    vec[vG %in% vecW$set] <- 1
	    repe <- FALSE
	    for (ki in 1:(K-1)) {
		if (verb & !is.na(w[1])) cat("-")
		dis <- sum(abs(vec - U[ki,]))
		eq.test <- (dis == 0)
		co.test <- (dis == n)
		if (eq.test | co.test) {
		    repe <- TRUE
		    break
		}
	    }
	    if (!repe) {
		if (verb & !is.na(w[1])) cat("+")
		Nvec <- Nvec + 1
		U[K,] <- vec
		Wu[K] <- vecW$weight
		if (log) { Wvec <- c(Wvec, vecW$weight) }
		K <- K + 1
	    }
	}
	if (verb) {
	    if (Nvec == 1) { sfin <- "um" } else { sfin <- "a" }
	    cat("(", Nvec, " local maxim", sfin, ") ",sep="")
	}                                               # End local search

	if (verb) cat("Selection...")                    # Begin selection 
	Ncan <- Npop + Noff + Nmut + Nvec
	Wcan <- Wu[1:Ncan]
	Wcs <- sort(Wcan, decreasing = TRUE, index.return = TRUE)
	imax <- Wcs$ix[1]
	if (Wcan[imax] > Wmax) {
	    Wmax <- Wcan[imax]
	    Smax <- vG[U[imax,] == 1]
	    gmax <- i
	}
	P[1:elite,] <- U[Wcs$ix[1:elite],]
	psel <- exp(-beta*(Wmax - Wcan)/mean(Wcan))
	psel[Wcs$ix[1:elite]] <- 0
	isel <- sample(1:Ncan, Npop - elite, prob=psel)
	P[(elite+1):Npop,] <- U[isel,]
	if (verb) cat("done [Wmax = ", Wmax, "]\n",sep="") # End selection

	## Preparing data for next generation
	U <- U0
	U[1:Npop,] <- P
	Wu <- Wu0
	for (i in 1:Npop) {
	    Wo <- compute_cut_weight(vG[P[i,] == 1], n, eG, w)
	    Wu[i] <- Wo$weight
	    if (log) { Wsel <- c(Wsel, Wo$weight)}
	}
	K <- Npop + 1
    }
    Wo <- compute_cut_weight(Smax, n, eG, w, return.cut = TRUE)
    if (log) {
	return(list(set = Smax, size = Wo$size, weight = Wo$weight,
		    cut = Wo$cut, generation = gmax, population = P,
		    Wini = Wini, Woff = Woff, Wmut = Wmut,
		    Wvec = Wvec, Wsel = Wsel))
    } else {
	return(list(set = Smax, size = Wo$size, weight = Wo$weight,
		    cut = Wo$cut, generation = gmax, population = P))
    }
}

Try the gor package in your browser

Any scripts or data that you put into this service are public.

gor documentation built on May 4, 2023, 1:06 a.m.