Skip to content

Commit 97f402b

Browse files
alexlobodaSpace
authored and
Space
committed
Relax and cut solver for sgmwcs problem, documentation, licenses, vignettes.
1 parent 54d3e5d commit 97f402b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+3463
-853
lines changed

.Rbuildignore

+3
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
^\.travis\.yml$
44
^data-raw$
55
^.idea$
6+
^README.Rmd$
7+
^src/.idea$
8+
^src/.+\.o$
69
^lib$
710
^vignettes/tutorial_cache$

DESCRIPTION

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
Package: mwcsr
2-
Title: Solvers for Various Maximum Weight Connected Subgraph Problems
2+
Title: Solvers for Maximum Weight Connected Subgraph Problem and its variants
33
Version: 0.1.0
44
Authors@R: c(person("Alexander", "Loboda", email = "[email protected]", role = "aut"),
55
person("Nikolay", "Poperechnyi", email = "[email protected]", role = "aut"),
66
person("Eduardo", "Alvarez-Miranda", email = "[email protected]", role = "aut"),
77
person("Markus", "Sinnl", email = "[email protected]", role = "aut"),
8-
person("Alexey", "Sergushichev", email = "[email protected]", role = c("aut", "cre")))
9-
Description: The package implements algorithms for solving various Maximum
8+
person("Alexey", "Sergushichev", email = "[email protected]", role = c("aut", "cre")),
9+
person("Paul", "Hosler Jr.", role = "cph", comment = "Bundled JOpt Simple package"),
10+
person("www.hamcrest.org", role = "cph", comment = "Bundled hamcrest package"),
11+
person("Barak Naveh and Contributors", role = "cph", comment = "Bundled JGraphT package"),
12+
person("The Apache Software Foundation", role = "cph", comment = "Bundled Apache Commons Math package"))
13+
Description: Algorithms for solving various Maximum
1014
Weight Connected Subgraph Problems, including variants with
11-
budget constraints, cardinallity constraints, weighted edges.
15+
budget constraints, cardinallity constraints and weighted edges.
1216
Depends: R (>= 3.5)
1317
Imports:
1418
methods,
@@ -17,13 +21,16 @@ Imports:
1721
Suggests:
1822
knitr,
1923
rmarkdown,
24+
mathjaxr,
2025
testthat,
2126
BioNet,
27+
roxygen2,
2228
DLBCL
2329
License: MIT + file LICENSE
2430
Encoding: UTF-8
2531
LazyData: true
26-
RoxygenNote: 7.1.1
32+
RoxygenNote: 7.1.2
33+
Roxygen: list(markdown = TRUE)
2734
VignetteBuilder: knitr
2835
URL: https://github.com/ctlab/mwcsr
2936
BugReports: https://github.com/ctlab/mwcsr/issues

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
YEAR: 2017
2-
COPYRIGHT HOLDER: Alexey Sergushichev
2+
COPYRIGHT HOLDER: Alexey Sergushichev, Alexander Loboda, Nikolay Poperechnyi, Markus Sinnl, Eduardo Alvarez-Miranda

LICENSE.note

+849
Large diffs are not rendered by default.

NAMESPACE

+3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ S3method("[",mwcs_solver)
44
S3method("[<-",mwcs_solver)
55
S3method(parameters,default)
66
S3method(parameters,rmwcs_solver)
7+
S3method(parameters,rnc_solver)
78
S3method(parameters,simulated_annealing_solver)
89
S3method(parameters,virgo_solver)
910
S3method(print,mwcs_solver)
1011
S3method(print,mwcs_solver_params)
1112
S3method(solve_mwcsp,rmwcs_solver)
13+
S3method(solve_mwcsp,rnc_solver)
1214
S3method(solve_mwcsp,simulated_annealing_solver)
1315
S3method(solve_mwcsp,virgo_solver)
1416
export("timelimit<-")
@@ -18,6 +20,7 @@ export(get_weight)
1820
export(normalize_sgmwcs_instance)
1921
export(parameters)
2022
export(rmwcs_solver)
23+
export(rnc_solver)
2124
export(set_parameters)
2225
export(solve_mwcsp)
2326
export(virgo_solver)

R/RcppExports.R

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ sa_solve <- function(instance, solver) {
55
.Call('_mwcsr_sa_solve', PACKAGE = 'mwcsr', instance, solver)
66
}
77

8+
sgmwcs_solve <- function(instance, solver_params) {
9+
.Call('_mwcsr_sgmwcs_solve', PACKAGE = 'mwcsr', instance, solver_params)
10+
}
11+
812
rmwcs_solve <- function(network, params) {
913
.Call('_mwcsr_rmwcs_solve', PACKAGE = 'mwcsr', network, params)
1014
}

R/annealing.R

+38-41
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ check_sa_solver <- function(solver) {
99

1010
#' @export
1111
parameters.simulated_annealing_solver <- function(solver) {
12-
params(parameter("normalization", type = "logical"),
13-
parameter("schedule", type = "mc", mc = schedules),
12+
params(parameter("schedule", type = "mc", mc = schedules),
1413
parameter("initial_temperature", type = "float", positive = TRUE),
1514
parameter("final_temperature", type = "float", positive = TRUE),
1615
parameter("verbose", type = "logical"))
@@ -22,72 +21,70 @@ parameters.simulated_annealing_solver <- function(solver) {
2221
#' Typically, it allows to find some good solution in a short time. This
2322
#' implementation doesn't compute any upper bound on solution, so there is no
2423
#' guarantee of optimality of solution provided.
25-
#' @param normalization adjust all weights so that all possible changes have
26-
#' mean square 1.0
24+
#'
25+
#' Algorithm maintains connected subgraph staring with empty subgraph.
26+
#' Each iteration one random action is considered. It may be a removal of a
27+
#' vertex or an edge which does not separate graph or addition of an extra vertex or
28+
#' an edge neighboring existing graph. If the subgraph is empty all vertices
29+
#' are considered as candidates to form a subgaph. After candidate is chosen two
30+
#' subgraph scores are considered: for a new subgraph and for an old one. Simulated
31+
#' annealing operates with a notion of temperature. The candidate action is
32+
#' accepted with probability: p(S'|S) = exp(-E / T), where E is weight difference
33+
#' between subgraphs and T is current temperature.
34+
#'
35+
#' Temperature is calculated in each iteration. in `mwcsr` there are two
36+
#' temperature schedules supported. So called Boltmann annealing uses the formula:
37+
#' T(k) = T0 / (ln(1 + k)), while in case of fast annealing this one is used:
38+
#' T(k) = T0 / k, where k is iteration number.
39+
#'
40+
#' To tune the algorithm it is useful to realize how typical changes in the goal
41+
#' function for single actions are distributed. Calculating the acceptance probabilities
42+
#' at initial temperature and final temperatures may help to choose schedule and
43+
#' temperatures.
44+
#'
45+
#' @seealso [rnc_solver] will probably be a better choice with minimal tuning necessary
2746
#' @param schedule boltzmann annealing or fast annealing
2847
#' @param initial_temperature initial value for the temperature
2948
#' @param final_temperature final value for the temperature
3049
#' @param verbose whether be verbose or not
3150
#' @export
32-
annealing_solver <- function(normalization = TRUE,
33-
schedule = c("fast", "boltzmann"),
51+
annealing_solver <- function(schedule = c("fast", "boltzmann"),
3452
initial_temperature = 1.0,
3553
final_temperature = 1e-6,
3654
verbose = FALSE) {
3755
solver_ctor(c(sa_class, mwcs_solver_class))
3856
}
3957

40-
normalize_weights <- function(instance) {
41-
weights <- c(V(instance)$weight, E(instance)$weight)
42-
support <- sum(weights[weights > 0]) / 2
43-
if (any(weights < -EPS)) {
44-
support <- min(support, -min(weights))
45-
}
46-
instance
47-
}
48-
49-
#' Solve generalized maximum weight subgraph problem using simulated annealing
50-
#'
51-
#' @inheritParams solve_mwcsp.virgo_solver
52-
#' @param solver An annealing solver object.
53-
#' @param instance An igraph instance to work with.
54-
#' @return An object of class mwcsp_solution.
58+
#' @rdname solve_mwcsp
59+
#' @order 5
60+
#' @param warm_start warm start solution, an object of the class mwcsp_solution.
5561
#' @export
56-
solve_mwcsp.simulated_annealing_solver <- function(solver, instance, ...) {
62+
solve_mwcsp.simulated_annealing_solver <- function(solver, instance, warm_start, ...) {
5763
if (!inherits(solver, sa_class)) {
5864
stop("Not a simulated annealing solver")
5965
}
6066

61-
inst_type <- get_instance_type(instance)
62-
63-
64-
if (inst_type$type == "SGMWCS" && inst_type$valid) {
65-
signal_instance <- instance
66-
} else if (inst_type$type == "GMWCS" && inst_type$valid) {
67-
signal_instance <- normalize_sgmwcs_instance(instance,
68-
nodes.group.by = NULL,
69-
edges.group.by = NULL)
70-
} else if (inst_type$type == "MWCS" && inst_type$valid) {
71-
inst2 <- instance
72-
E(inst2)$weight <- 0
73-
signal_instance <- normalize_sgmwcs_instance(inst2,
74-
nodes.group.by = NULL,
75-
edges.group.by = NULL)
76-
} else {
77-
stop("Not a valid MWCS, GMWCS or SGMWCS instance")
78-
}
67+
signal_instance <- to_signal_instance(instance)
7968

8069
inst_rep <- instance_from_graph(signal_instance)
8170
inst_rep[["vertex_signals"]] <- match(igraph::V(signal_instance)$signal, names(signal_instance$signals)) - 1
8271
inst_rep[["edge_signals"]] <- match(igraph::E(signal_instance)$signal, names(signal_instance$signals)) - 1
8372
inst_rep[["signal_weights"]] <- signal_instance$signals
8473

74+
if (!missing(warm_start)) {
75+
ws_sol <- warm_start$warm_start_solution
76+
inst_rep[["warm_start_vertices"]] <- ws_sol$vertices
77+
inst_rep[["warm_start_edges"]] <- ws_sol$edges
78+
inst_rep[["warm_start_weight"]] <- ws_sol$weight
79+
}
80+
8581
res <- sa_solve(inst_rep, solver)
8682
if (length(res$edges) == 0) {
8783
g <- igraph::induced_subgraph(instance, vids = res$vertices)
8884
} else {
8985
g <- igraph::subgraph.edges(instance, eids = res$edges)
9086
}
9187
weight <- get_weight(g)
92-
solution(g, weight, solved_to_optimality = FALSE)
88+
res$weight <- weight
89+
solution(g, weight, solved_to_optimality = FALSE, warm_start_solution = res)
9390
}

R/rmwcs.R

+58-19
Original file line numberDiff line numberDiff line change
@@ -28,42 +28,80 @@ parameters.rmwcs_solver <- function(solver) {
2828
parameter("verbose", type = "logical"))
2929
}
3030

31-
#' Generates a rmwcs solver with corresponding parameters
31+
#' Generate a rmwcs solver
32+
#'
33+
#' The method is based on relax-and-cut approach and allows to solve
34+
#' Maximum Weight Subgraph Probleam and its budget and cardinality variants.
35+
#' By constructing lagrangian
36+
#' relaxation of MWCS problem necessary graph connectivity constraints are introduced
37+
#' in the objective function giving upper bound on the weight of the optimal
38+
#' solution. On the other side, primal heuristic uses individul contribution
39+
#' of the variables to lagrangian relaxation to find possible solution of the initial
40+
#' problem. The relaxation is then optimized by using iterative subgradient method.
41+
#'
42+
#' One iteration of algorithm includes solving lagrangian relaxation and updating
43+
#' lagrange multipliers. It may also contain cuts (or connectivity constraints) separation process, run of
44+
#' heuristic method, variable fixing routine. The initial step size for
45+
#' subgradient method can be passed as `beta` argument. If there is no improvement in
46+
#' upper bound in consequtive `beta_iterations` iterations the step size is
47+
#' halved. There are three possible strategies for updating multipliers. See the references
48+
#' section for the article where differences are discussed.
49+
#'
50+
#' Some initial cuts are added at the start of the algorithm if `start_constraints`
51+
#' is set to \code{TRUE}. Other constraints are separated on the fly and are
52+
#' unaffected in the next `sep_iter_freeze` iterations of the subgradient mehod.
53+
#' Then the corresponding lagrange mutipliers are updated from iteration to iteration.
54+
#' Aging procedure for cuts is incorporated in the algorithm meaning constraint multipliers
55+
#' are updated for non-violated cuts for up to `max_age` iterations from
56+
#' the point where a cut was violated last time. There are two separation methods
57+
#' implemented: fast and strong, where tha latter is supposed to minimize number of
58+
#' variables used in generated constraint while in the former case there is no need to explore
59+
#' whole graph to construct a constraint.
60+
#'
61+
#' A variant of MST approximation of PCSTP is used as Primal Heuristic.
62+
#' See references for more details.
63+
#'
64+
#' The frequences
65+
#' of running separation process and heuristic are specified in
66+
#' `sep_iterations` and `heur_iterations`.
67+
#'
3268
#' @param timelimit Timelimit in seconds
33-
#' @param max_iterations Maximum number of subgradient iterations
69+
#' @param max_iterations Maximum number of iterations
70+
#' @param start_constraints Whether to add flow-conservation/degree constraints at start
71+
#' @param separation Separation: "strong" or "fast"
72+
#' @param sep_iterations Frequency of separating cuts (in iterations)
3473
#' @param beta_iterations Number of nonimproving iterations until beta is halved
35-
#' @param separation Separation: "strong" of "fast"
36-
#' @param sep_iterations Extending the life of non-violated inequalities
37-
#' @param start_constraints Whether to add flow-conservation/degree cons at start
38-
#' @param pegging Pegging (variable fixing)
39-
#' @param max_age extending the life of non-violated inequalities
40-
#' @param sep_iter_freeze After how many iterations we are checking added ineqs
41-
#' @param heur_iterations After how many iterations we are doing heuristics
74+
#' @param pegging variable fixing
75+
#' @param max_age number of iterations in aging procedure for non-violated cuts
76+
#' @param sep_iter_freeze Number of iterations when a newly separated cut is anaffected by subgradient algorithm.
77+
#' @param heur_iterations Frequency of calling heuristic method (in iterations)
4278
#' @param subgradient Subgradient: "classic", "average", "cft"
43-
#' @param beta Beta for subgradient
44-
#' @param verbose Whether to print solving progress
79+
#' @param beta Initial step size of subgradient algorithm
80+
#' @param verbose Should the solving progress and stats be printed?
81+
#' @references Álvarez-Miranda, E., & Sinnl, M. (2017). A Relax-and-Cut framework
82+
#' for large-scale maximum weight connected subgraph problems. Computers & Operations Research, 87, 63-82.
4583
#' @export
4684
#' @import igraph
4785
rmwcs_solver <- function(timelimit = 1800L,
4886
max_iterations = 1000L,
49-
beta_iterations = 50L,
87+
beta_iterations = 5L,
5088
separation = "strong",
5189
start_constraints = TRUE,
5290
pegging = TRUE,
53-
max_age = 3,
54-
sep_iterations= 1L,
55-
sep_iter_freeze = 1L,
56-
heur_iterations = 1L,
91+
max_age = 10,
92+
sep_iterations= 10L,
93+
sep_iter_freeze = 50L,
94+
heur_iterations = 10L,
5795
subgradient = "classic",
5896
beta = 2.0,
5997
verbose = FALSE) {
6098
solver_ctor(c(rmwcs_class, mwcs_solver_class))
6199
}
62100

63-
#' Solves MWCS problem using relax-and-cut approach
64-
#' @inheritParams solve_mwcsp
101+
#' @rdname solve_mwcsp
65102
#' @param max_cardinality integer maximum number of vertices in solution.
66103
#' @param budget numeric maximum budget of solution.
104+
#' @order 3
67105
#' @export
68106
solve_mwcsp.rmwcs_solver <- function(solver, instance, max_cardinality = NULL,
69107
budget = NULL, ...) {
@@ -103,5 +141,6 @@ solve_mwcsp.rmwcs_solver <- function(solver, instance, max_cardinality = NULL,
103141
subgraph <- igraph::induced_subgraph(instance, vs$graph)
104142
weight <- sum(instance_rep$vertex_weights[vs$graph])
105143
opt <- isTRUE(all.equal(weight, vs$ub))
106-
solution(subgraph, weight, opt, upper_bound = vs$ub)
144+
solution(subgraph, weight, opt, upper_bound = vs$ub, incumbent = vs$lb,
145+
solved_to_optimality = abs(vs$lb - vs$ub) < EPS)
107146
}

R/rnc_sgmwcs.R

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
rnc_class <- "rnc_solver"
2+
3+
check_rnc_solver <- function(solver) {
4+
if (!inherits(solver, rnc_class)) {
5+
stop("Invalid relax&cut sgmwcs solver provided.")
6+
}
7+
}
8+
9+
#' @export
10+
parameters.rnc_solver <- function(solver) {
11+
params(parameter("max_iterations", type = "integer", positive = TRUE),
12+
parameter("beta_iterations", type = "integer", positive = TRUE),
13+
parameter("sep_iterations", type = "integer", positive = TRUE),
14+
parameter("heur_iterations", type = "integer", positive = TRUE),
15+
parameter("max_age", type = "integer", positive = TRUE),
16+
parameter("verbose", type = "logical"))
17+
}
18+
19+
#' Construct relax-and-cut SGMWCS solver
20+
#'
21+
#' The solver is based on the same approach as rmwcs solver. Modifications to
22+
#' the original scheme are introduced to tackle problems arising with introduction
23+
#' of edge weights and signals. It is recommended to use rmwcs solver to solve MWCS
24+
#' problems, while due to differences in primal heuristic it may be a good pratice
25+
#' to run both solvers on the same problem.
26+
#' @inheritParams rmwcs_solver
27+
#' @seealso [rmwcs_solver]
28+
#' @export
29+
rnc_solver <- function(max_iterations = 1000L,
30+
beta_iterations = 50L,
31+
heur_iterations = 10L,
32+
sep_iterations = 10L,
33+
verbose = FALSE) {
34+
solver_ctor(c(rnc_class, mwcs_solver_class))
35+
}
36+
37+
#' @rdname solve_mwcsp
38+
#' @order 4
39+
#' @export
40+
solve_mwcsp.rnc_solver <- function(solver, instance, ...) {
41+
check_rnc_solver(solver)
42+
43+
signal_instance <- to_signal_instance(instance)
44+
45+
inst_rep <- instance_from_graph(signal_instance)
46+
inst_rep[["vertex_signals"]] <- match(igraph::V(signal_instance)$signal, names(signal_instance$signals)) - 1
47+
inst_rep[["edge_signals"]] <- match(igraph::E(signal_instance)$signal, names(signal_instance$signals)) - 1
48+
inst_rep[["signal_weights"]] <- signal_instance$signals
49+
50+
res <- sgmwcs_solve(inst_rep, solver)
51+
52+
if (length(res$edges) == 0) {
53+
g <- igraph::induced_subgraph(instance, vids = res$vertices)
54+
} else {
55+
g <- igraph::subgraph.edges(instance, eids = res$edges)
56+
}
57+
58+
weight <- get_weight(g)
59+
60+
stopifnot(abs(weight - res$lb) < EPS)
61+
62+
solution(g, weight, solved_to_optimality = abs(res$lb - res$ub) < EPS,
63+
upper_bound = res$ub)
64+
}

0 commit comments

Comments
 (0)