OF: The objective function, to be minimised. Its first argument needs to be a solution; ... arguments are also passed.
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 override nS. The option is provided to makes it easier to compare and switch between functions LSopt, TAopt and SAopt.
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 when LSopt 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 solution x; it must return a changed solution.
printDetail: If TRUE (the default), information is printed. If an integer i greater then one, information is printed at very ith step.
printBar: If TRUE (the default), a txtProgressBar (from package utils) is printed). The progress bar is not shown if printDetail 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 matrix Fmat.
storeSolutions: default is FALSE. If TRUE, the solutions (ie, decision variables) in every generation are stored and returned in list xlist (see Value section below). To check, for instance, the current solution at the end of the ith generation, retrieve xlist[[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.
Returns
A list:
xbest: best solution found.
OFvalue: objective function value associated with best solution.
Fmat: a matrix with two columns. Fmat[ ,1L]
contains the proposed solution over all iterations; Fmat[ ,2L] contains the accepted solutions.
xlist: if algo$storeSolutions is TRUE, a list; else NA. Contains the neighbour solutions at a given iteration (xn) and the current solutions (xc). Example: Fmat[i, 2L] is the objective function value associated with xlist[[c(2L, i)]].
initial.state: the value of .Random.seed
when the function was called.
References
Gilli, M., Maringer, D. and Schumann, E. (2019) Numerical Methods and Optimization in Finance. 2nd edition. Elsevier. tools:::Rd_expr_doi("10.1016/C2017-0-01621-X")
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 setnc <-25L## number of columns in data setnr <-5L## number of rows in data sethowManyCols <-5L## length of true solutionX <- 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 onesum(y - rowSums(X[, xTRUE, drop =FALSE]))## should be 0sum(y - rowSums(X[, x0, drop =FALSE]))## a neighbourhood function: switch n elements in solutionneighbour <-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 functionneighbourG <-function(xc, Data){ of <-function(x) abs(sum(Data$y - rowSums(Data$X[,x, drop =FALSE]))) xbest <- xc
Fxbest <- of(xbest)for(i in1L: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 functionOF <-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 searchData <- 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 SEARCHalgo$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 <-10Lalgo$nS <- ceiling(algo$nS/algo$nT)algo$q <-0.99solTA <- 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 solutionall <- 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 resultspar(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)