-
Notifications
You must be signed in to change notification settings - Fork 5
/
LocalSearchAlg.pas
479 lines (423 loc) · 14.4 KB
/
LocalSearchAlg.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
{
Copyright (c) Peter Karpov 2010 - 2018.
Usage of the works is permitted provided that this instrument is retained with
the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
}
{$IFDEF FPC} {$MODE DELPHI} {$ENDIF}
unit LocalSearchAlg; ////////////////////////////////////////////////////////////////
{
>> Version: 0.7
>> Description
Implementation of local search supporting variable neighborhood search and several
search modes.
>> Author
Peter Karpov
Email : [email protected]
Homepage : inversed.ru
GitHub : inversed-ru
Twitter : @inversed_ru
>> Notes
Chain mode is typically the most efficient one. It requires slightly
more move evaluations, but greatly reduces move generation.
>> Usage
The user must supply MakeLSMoveList, PerformMove and UndoMove routines via the
problem definition module.
>> ToDo
- Experiment with combo mode to see if it deserves a comeback
>> Changelog
0.7 : 2019.10.01 + Multirun statistics collection
0.6 : 2019.05.21 ~ Renamed IsMinimize to Minimization
0.5 : 2018.09.16 - Combo mode
~ Freepascal compatibility
+ Improvement section moved from acceptance
0.4 : 2012.02.22 + Run statistics collection
+ Improvement procedure
0.3 : 2011.09.26 + Test version of Combo LS
0.2 : 2011.08.29 ~ Common interface for classical and smart local search
+ Variable neighborhood search
0.1 : 2011.08.15 + Smart local search
0.0 : 2011.05.09 + Initial version
Notation: + added, - removed, * fixed, ~ changed
}
{$MINENUMSIZE 4}
interface ///////////////////////////////////////////////////////////////////////////
uses
Common,
Messages,
Problem;
type
TLSMode = (lsmFirst = 0, lsmFull, lsmChain);
TLSParameters = TLSMode;
TLSStatus = TBasicStatus;
TImproveMode = (imLocal, imStochastic, imSpecific);
TImproveParams =
record
Mode : TImproveMode;
Iters : Integer;
LSMode : TLSMode;
end;
// Perform local search starting from Solution, update Stats and
// MultirunStats. The type of local search is determined by Params.
// In case of RandomInit, Solution is initialized randomly.
procedure LocalSearch(
var Solution : TSolution;
var Stats : TRunStats;
var MultirunStats : TMultirunStats;
const Params : TLSParameters;
const Status : TLSStatus;
RandomInit : Boolean);
// Apply local search with LSMode to Solution, disregard status and statistics
procedure LSImprovement(
var Solution : TSolution;
LSMode : TLSMode);
// Make Solution's neighbour, accept only in case of improvement. The result
// indicates the outcome.
function GreedyMove(
var Solution : TSolution
) : Boolean;
// Improve Solution according to ImproveParams
procedure Improve(
var Solution : TSolution;
const ImproveParams : TImproveParams);
implementation //////////////////////////////////////////////////////////////////////
uses
Math,
InvSys,
ExtraMath,
Arrays,
Sorting;
const
PathStatus = 'LS_Status.txt';
NameBest = 'LS_Best';
{-----------------------<< Local Search >>------------------------------------------}
// Search the MoveList for a move that improves the Solution and perform it upon
// finding. In case of FullSearch, the whole move list is scanned for the best move,
// otherwise the search stops at the first improving move. Trial moves are deleted
// from MoveList and NFE is updated in the process. The result indicates whether an
// improving move was found.
function MakeMove(
var Solution : TSolution;
var MoveList : TMoveList;
var NFE : Int64;
FullSearch : Boolean
) : Boolean;
var
i : Integer;
BestScore : TScore;
TrialMove,
BestMove : TMove;
Undo : TMoveUndo;
Found : Boolean;
begin
// Search for an improving move
Found := False;
BestScore := Solution.Score;
repeat
// Perform a random trial move
i := Random(MoveList.N);
TrialMove := MoveList.Moves[i];
DelMove(MoveList, i);
PerformMove(Solution, Undo, TrialMove);
Inc(NFE);
// New best move?
if (CompareScores(Solution.Score, BestScore) = scoreBetter) then
begin
BestScore := Solution.Score;
BestMove := TrialMove;
Found := True;
end;
// Restore initial state
UndoMove(Solution, Undo);
until (Found and not FullSearch) or (MoveList.N = 0);
// Perform improving move if it was found
if Found then
PerformMove(Solution, BestMove);
Result := Found;
end;
// Perform classic local search starting from Solution, update Stats and
// MultirunStats. In case of FullSearch, the best move is applied each iteration.
procedure ClassicLocalSearch(
var Solution : TSolution;
var Stats : TRunStats;
var MultirunStats : TMultirunStats;
FullSearch : Boolean;
const Status : TLSStatus);
var
MoveList : TMoveList;
Level : Integer;
Found : Boolean;
UseStatus : Boolean;
const
NStatsFields = 3;
// Create the header of MultirunStats
procedure WriteHeader(
var MultirunStats : TMultirunStats);
begin
with MultirunStats do
if NVars = 0 then
begin
InitMultirunStats(MultirunStats, NStatsFields);
SetLength(Header, NStatsFields);
Header[0] := 'Iter';
Header[1] := 'Score';
Header[2] := 'Level';
end;
end;
// Add the current search statistics to MultirunStats
procedure WriteStatus(
var MultirunStats : TMultirunStats;
const Best : TSolution;
Iters, Level : Integer);
var
Data : TRealArray;
begin
SetLength(Data, NStatsFields);
Data[0] := Iters;
Data[1] := Best.Score;
Data[2] := Level;
AddSample(MultirunStats, Data);
end;
begin
// Write the status header if necessary
UseStatus := Status.IterStatus <> 0;
if UseStatus then
begin
WriteHeader(MultirunStats);
PrepareNextRun(MultirunStats);
end;
// Perform the search
with Stats do
begin
repeat
// Try making an improving move
Inc(Stats.Iters);
Level := 0;
Found := False;
repeat
Inc(Level);
MakeLSMoveList(MoveList, Solution, Level);
if MoveList.N = 0 then
{<} break;
Found := MakeMove(Solution, MoveList, NFEpartial, FullSearch);
until Found;
// Write status
if Divisible(Iters, Status.IterStatus) then
WriteStatus(MultirunStats, Solution, Iters, Level);
until not Found;
// Run complete
ShowNewBestScore(Solution, Status.ShowMessage);
end;
end;
// #HACK huge, may need splitting
// #HACK rewrite using ExtMoveLists
// Perform chain local search starting from Solution, update Stats and
// MultirunStats
procedure ChainLocalSearch(
var Solution : TSolution;
var Stats : TRunStats;
var MultirunStats : TMultirunStats;
const Status : TLSStatus);
var
i : Integer;
MoveList,
Candidates : TMoveList;
Scores : TRealArray;
Ranking : TIntArray;
Order : TSortOrder;
OldScore : TScore;
Undo : TMoveUndo;
NFound : Integer;
Level : Integer;
UseStatus : Boolean;
const
NStatsFields = 5;
// Create the header of MultirunStats
procedure WriteHeader(
var MultirunStats : TMultirunStats);
begin
with MultirunStats do
if NVars = 0 then
begin
InitMultirunStats(MultirunStats, NStatsFields);
SetLength(Header, NStatsFields);
Header[0] := 'Iter';
Header[1] := 'Score';
Header[2] := 'Level';
Header[3] := 'NTested';
Header[4] := 'NFound';
end;
end;
// Add the current search statistics to MultirunStats
procedure WriteStatus(
var MultirunStats : TMultirunStats;
const Best : TSolution;
Iters,
Level,
NTested,
NFound : Integer);
var
Data : TRealArray;
begin
SetLength(Data, NStatsFields);
Data[0] := Iters;
Data[1] := Best.Score;
Data[2] := Level;
Data[3] := NTested;
Data[4] := NFound;
AddSample(MultirunStats, Data);
end;
begin
// Initialization
if Minimization then
Order := soAscending else
Order := soDescending;
// Write the status header if necessary
UseStatus := Status.IterStatus <> 0;
if UseStatus then
begin
WriteHeader(MultirunStats);
PrepareNextRun(MultirunStats);
end;
with Stats do
begin
// Search loop
repeat
Inc(Iters);
Level := 0;
repeat
// Make the move list
Inc(Level);
MakeLSMoveList(MoveList, Solution, Level);
if MoveList.N = 0 then
{<} break;
// Make a list of candidate moves
InitMoveList(Candidates);
SetLength(Scores, MoveList.N);
OldScore := Solution.Score;
for i := 0 to MoveList.N - 1 do
begin
PerformMove(Solution, Undo, MoveList.Moves[i]);
if CompareScores(Solution.Score, OldScore) = scoreBetter then
begin
AddMove(Candidates, MoveList.Moves[i]);
Scores[Candidates.N - 1] := Solution.Score - OldScore;
end;
UndoMove(Solution, Undo);
end;
SetLength(Scores, Candidates.N);
// #TODO Verify the equivalence
//SortOrder(Ranking, @Scores, RealArrayCompare, Candidates.N, Order); // old version
OrderRealArray(Ranking, Scores, Order);
Inc(NFEpartial, MoveList.N);
// Try all candidate moves
i := 0;
NFound := 0;
while (i < Candidates.N) and
(CompareScores(Scores[ Ranking[i] ], 0) = scoreBetter) do
begin
OldScore := Solution.Score;
PerformMove(Solution, Undo, Candidates.Moves[ Ranking[i] ]);
if CompareScores(Solution.Score, OldScore) = scoreBetter then
Inc(NFound)
else
UndoMove(Solution, Undo);
Inc(i);
Inc(NFEpartial);
end;
until NFound <> 0;
// Write status
if NFound <> 0 then
ShowNewBestScore(Solution, Status.ShowMessage);
if Divisible(Iters, Status.IterStatus) then
WriteStatus(MultirunStats, Solution, Iters, Level, {NTested:} i, NFound);
until NFound = 0;
end;
end;
// Perform local search starting from Solution, update Stats and
// MultirunStats. The type of local search is determined by Params.
// In case of RandomInit, Solution is initialized randomly.
procedure LocalSearch(
var Solution : TSolution;
var Stats : TRunStats;
var MultirunStats : TMultirunStats;
const Params : TLSParameters;
const Status : TLSStatus;
RandomInit : Boolean);
begin
// Initialization
if RandomInit then
NewSolution(Solution);
// Run classic or chain local search
case Params of
lsmFirst, lsmFull:
ClassicLocalSearch(
Solution,
Stats,
MultirunStats,
{FullSearch:} Params = lsmFull,
Status
);
lsmChain:
ChainLocalSearch(Solution, Stats, MultirunStats, Status);
else
Assert(False);
end;
// Run complete
if Status.SaveBest then
TrySaveSolution(NameBest, Solution, Status.ShowMessage);
end;
{-----------------------<< Improvement >>-------------------------------------------}
// Make Solution's neighbour, accept only in case of improvement. The result
// indicates the outcome.
function GreedyMove(
var Solution : TSolution
) : Boolean;
var
OldScore : TScore;
Undo : TSAUndo;
begin
OldScore := Solution.Score;
MakeNeighbour(Solution, Undo, {T:} 0);
if CompareScores(Solution.Score, OldScore) = scoreWorse then
begin
UndoSAMove(Solution, Undo);
Result := Fail;
end
else
Result := Success;
end;
// Apply local search with LSMode to Solution, disregard status and statistics
procedure LSImprovement(
var Solution : TSolution;
LSMode : TLSMode);
var
Stats : TRunStats;
Params : TLSParameters;
Dummy : TMultirunStats;
begin
Stats := EmptyStats;
Params := LSMode;
LocalSearch(Solution, Stats, Dummy, Params, NoStatus, {RandomInit:} False);
end;
// Improve Solution according to ImproveParams
procedure Improve(
var Solution : TSolution;
const ImproveParams : TImproveParams);
var
i : Integer;
begin
with ImproveParams do
case Mode of
imLocal:
LSImprovement(Solution, LSMode);
imStochastic:
for i := 1 to Iters do
GreedyMove(Solution);
imSpecific:
SpecialImprove(Solution);
else
Assert(False);
end;
end;
end.