LSopt {NMOF} | R Documentation |
Stochastic Local Search
Description
Performs a simple stochastic Local Search.
Usage
LSopt(OF, algo = list(), ...)
Arguments
OF |
The objective function, to be minimised. Its first argument needs to
be a solution; |
algo |
List of settings. See Details. |
... |
Other variables to be passed to the objective function and to the neighbourhood function. See Details. |
Details
Local Search (LS) changes an initial solution for a number
of times, accepting only such changes that lead to an improvement in
solution quality (as measured by the objective function OF
).
More specifically, in each iteration, a current solution xc
is
changed through a function algo$neighbour
. This function takes
xc
as an argument and returns a new solution xn
. If
xn
is not worse than xc
, ie, if
OF(xn,...)<=OF(xc,...)
, then xn
replaces xc
.
The list algo
contains the following items:
nS
The number of steps. The default is 1000; but this setting depends very much on the problem.
nI
-
Total number of iterations, with default
NULL
. If specified, it will overridenS
. The option is provided to makes it easier to compare and switch between functionsLSopt
,TAopt
andSAopt
. x0
The initial solution. This can be a function; it will then be called once without arguments to compute an initial solution, ie,
x0 <- algo$x0()
. This can be useful whenLSopt
is called in a loop of restarts and each restart is to have its own starting value.neighbour
The neighbourhood function, called as
neighbour(x, ...)
. Its first argument must be a solutionx
; it must return a changed solution.printDetail
If
TRUE
(the default), information is printed. If an integeri
greater then one, information is printed at veryi
th step.printBar
If
TRUE
(the default), atxtProgressBar
(from package utils) is printed). The progress bar is not shown ifprintDetail
is an integer greater than 1.storeF
if
TRUE
(the default), the objective function values for every solution in every generation are stored and returned as matrixFmat
.storeSolutions
default is
FALSE
. IfTRUE
, the solutions (ie, decision variables) in every generation are stored and returned in listxlist
(see Value section below). To check, for instance, the current solution at the end of thei
th generation, retrievexlist[[c(2L, i)]]
.OF.target
Numeric; when specified, the algorithm will stop when an objective-function value as low as
OF.target
(or lower) is achieved. This is useful when an optimal objective-function value is known: the algorithm will then stop and not waste time searching for a better solution.
At the minimum, algo
needs to contain an initial solution
x0
and a neighbour
function.
LS works on solutions through the functions neighbour
and OF
, which are specified by the user. Thus, a solution need
not be a numeric vector, but can be any other data structure as well
(eg, a list or a matrix).
To run silently (except for warnings and errors),
algo$printDetail
and algo$printBar
must be FALSE
.
Value
A list:
xbest |
best solution found. |
OFvalue |
objective function value associated with best solution. |
Fmat |
a matrix with two columns. |
xlist |
if |
initial.state |
the value of |
Author(s)
Enrico Schumann
References
Gilli, M., Maringer, D. and Schumann, E. (2019) Numerical Methods and Optimization in Finance. 2nd edition. Elsevier. doi:10.1016/C2017-0-01621-X
Schumann, E. (2023) Financial Optimisation with R (NMOF Manual). http://enricoschumann.net/NMOF.htm#NMOFmanual
See Also
TAopt
, restartOpt
.
Package neighbours (also on CRAN) offers helpers
for creating neighbourhood functions.
Examples
## Aim: find the columns of X that, when summed, give y
## random data set
nc <- 25L ## number of columns in data set
nr <- 5L ## number of rows in data set
howManyCols <- 5L ## length of true solution
X <- array(runif(nr*nc), dim = c(nr, nc))
xTRUE <- sample(1L:nc, howManyCols)
Xt <- X[ , xTRUE, drop = FALSE]
y <- rowSums(Xt)
## a random solution x0 ...
makeRandomSol <- function(nc) {
ii <- sample.int(nc, sample.int(nc, 1L))
x0 <- logical(nc); x0[ii] <- TRUE
x0
}
x0 <- makeRandomSol(nc)
## ... but probably not a good one
sum(y - rowSums(X[ , xTRUE, drop = FALSE])) ## should be 0
sum(y - rowSums(X[ , x0, drop = FALSE]))
## a neighbourhood function: switch n elements in solution
neighbour <- function(xc, Data) {
xn <- xc
p <- sample.int(Data$nc, Data$n)
xn[p] <- !xn[p]
if (sum(xn) < 1L)
xn <- xc
xn
}
## a greedy neighbourhood function
neighbourG <- function(xc, Data) {
of <- function(x)
abs(sum(Data$y - rowSums(Data$X[ ,x, drop = FALSE])))
xbest <- xc
Fxbest <- of(xbest)
for (i in 1L:Data$nc) {
xn <- xc; p <- i
xn[p] <- !xn[p]
if (sum(xn) >= 1L) {
Fxn <- of(xn)
if (Fxn < Fxbest) {
xbest <- xn
Fxbest <- Fxn
}
}
}
xbest
}
## an objective function
OF <- function(xn, Data)
abs(sum(Data$y - rowSums(Data$X[ ,xn, drop = FALSE])))
## (1) GREEDY SEARCH
## note: this could be done in a simpler fashion, but the
## redundancies/overhead here are small, and the example is to
## show how LSopt can be used for such a search
Data <- list(X = X, y = y, nc = nc, nr = nr, n = 1L)
algo <- list(nS = 500L, neighbour = neighbourG, x0 = x0,
printBar = FALSE, printDetail = FALSE)
solG <- LSopt(OF, algo = algo, Data = Data)
## after how many iterations did we stop?
iterG <- min(which(solG$Fmat[ ,2L] == solG$OFvalue))
solG$OFvalue ## the true solution has OF-value 0
## (2) LOCAL SEARCH
algo$neighbour <- neighbour
solLS <- LSopt(OF, algo = algo, Data = Data)
iterLS <- min(which(solLS$Fmat[ ,2L] == solLS$OFvalue))
solLS$OFvalue ## the true solution has OF-value 0
## (3) *Threshold Accepting*
algo$nT <- 10L
algo$nS <- ceiling(algo$nS/algo$nT)
algo$q <- 0.99
solTA <- TAopt(OF, algo = algo, Data = Data)
iterTA <- min(which(solTA$Fmat[ ,2L] == solTA$OFvalue))
solTA$OFvalue ## the true solution has OF-value 0
## look at the solution
all <- sort(unique(c(which(solTA$xbest),
which(solLS$xbest),
which(solG$xbest),
xTRUE)))
ta <- ls <- greedy <- true <- character(length(all))
true[ match(xTRUE, all)] <- "o"
greedy[match(which(solG$xbest), all)] <- "o"
ls[ match(which(solLS$xbest), all)] <- "o"
ta[ match(which(solTA$xbest), all)] <- "o"
data.frame(true = true, greedy = greedy, LS = ls , TA = ta,
row.names=all)
## plot results
par(ylog = TRUE, mar = c(5,5,1,6), las = 1)
plot(solTA$Fmat[seq_len(iterTA) ,2L],type = "l", log = "y",
ylim = c(1e-4,
max(pretty(c(solG$Fmat,solLS$Fmat,solTA$Fmat)))),
xlab = "iterations", ylab = "OF value", col = grey(0.5))
lines(cummin(solTA$Fmat[seq_len(iterTA), 2L]), type = "l")
lines(solG$Fmat[ seq_len(iterG), 2L], type = "p", col = "blue")
lines(solLS$Fmat[seq_len(iterLS), 2L], type = "l", col = "goldenrod3")
legend(x = "bottomleft",
legend = c("TA best solution", "TA current solution",
"Greedy", "LS current/best solution"),
lty = c(1,1,0,1),
col = c("black",grey(0.5),"blue","goldenrod2"),
pch = c(NA,NA,21,NA))
axis(4, at = c(solG$OFvalue, solLS$OFvalue, solTA$OFvalue),
labels = NULL, las = 1)
lines(x = c(iterG, par()$usr[2L]), y = rep(solG$OFvalue,2),
col = "blue", lty = 3)
lines(x = c(iterTA, par()$usr[2L]), y = rep(solTA$OFvalue,2),
col = "black", lty = 3)
lines(x = c(iterLS, par()$usr[2L]), y = rep(solLS$OFvalue,2),
col = "goldenrod3", lty = 3)