Scippy

SCIP

Solving Constraint Integer Programs

cutsel_ensemble.c
Go to the documentation of this file.
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2/* */
3/* This file is part of the program and library */
4/* SCIP --- Solving Constraint Integer Programs */
5/* */
6/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */
7/* */
8/* Licensed under the Apache License, Version 2.0 (the "License"); */
9/* you may not use this file except in compliance with the License. */
10/* You may obtain a copy of the License at */
11/* */
12/* http://www.apache.org/licenses/LICENSE-2.0 */
13/* */
14/* Unless required by applicable law or agreed to in writing, software */
15/* distributed under the License is distributed on an "AS IS" BASIS, */
16/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
17/* See the License for the specific language governing permissions and */
18/* limitations under the License. */
19/* */
20/* You should have received a copy of the Apache-2.0 license */
21/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */
22/* */
23/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24
25/**@file cutsel_ensemble.c
26 * @ingroup DEFPLUGINS_CUTSEL
27 * @brief ensemble cut selector
28 * @author Mark Turner
29 *
30 * @todo separator hard limit on density is inappropriate for MINLP. Need to relax hard limit in case of all cuts dense
31 * @todo penalising via parallelism is overly costly if many cuts. Hash cuts before and find appropriate groups?
32 */
33
34/*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/
35
36#include <assert.h>
37
38#include "scip/scip_cutsel.h"
39#include "scip/scip_cut.h"
40#include "scip/scip_lp.h"
43
44
45#define CUTSEL_NAME "ensemble"
46#define CUTSEL_DESC "weighted sum of many terms with optional filtering and penalties"
47#define CUTSEL_PRIORITY 7000
48
49#define RANDSEED 0x5EED
50
51#define DEFAULT_MINSCORE 0.0 /**< minimum score s.t. a cut can be selected */
52#define DEFAULT_EFFICACYWEIGHT 0.75 /**< weight of normed-efficacy in score calculation */
53#define DEFAULT_DIRCUTOFFDISTWEIGHT 0.0 /**< weight of normed-directed cutoff distance in score calculation */
54#define DEFAULT_OBJPARALWEIGHT 0.25 /**< weight of objective parallelism in score calculation */
55#define DEFAULT_INTSUPPORTWEIGHT 0.45 /**< weight of integral support in cut score calculation */
56#define DEFAULT_EXPIMPROVWEIGHT 0.1 /**< weight of normed-expected improvement in cut score calculation */
57#define DEFAULT_PSCOSTWEIGHT 0.75 /**< weight of normalised pseudo-costs in cut score calculation */
58#define DEFAULT_NLOCKSWEIGHT 0.25 /**< weight of normalised number of locks in cut score calculation */
59#define DEFAULT_MAXSPARSITYBONUS 0.5 /**< score given to a cut with complete sparsity */
60#define DEFAULT_SPARSITYENDBONUS 0.2 /**< the density at which a cut no longer receives additional score */
61#define DEFAULT_GOODNUMERICBONUS 0.0 /**< bonus provided for good numerics */
62#define DEFAULT_MAXCOEFRATIOBONUS 10000 /**< maximum coefficient ratio of cut for which numeric bonus is given */
63#define DEFAULT_PENALISELOCKS TRUE /**< whether having less locks should be rewarded instead of more */
64#define DEFAULT_PENALISEOBJPARAL TRUE /**< whether objective parallelism should be penalised not rewarded */
65#define DEFAULT_FILTERPARALCUTS FALSE /**< should cuts be filtered so no two parallel cuts are added */
66#define DEFAULT_MAXPARAL 0.95 /**< threshold for when two cuts are considered parallel to each other */
67#define DEFAULT_PENALISEPARALCUTS TRUE /**< should two parallel cuts be penalised instead of outright filtered */
68#define DEFAULT_PARALPENALTY 0.25 /**< penalty for weaker of two parallel cuts if penalising parallel cuts */
69#define DEFAULT_FILTERDENSECUTS TRUE /**< should cuts over a given density threshold be filtered */
70#define DEFAULT_MAXCUTDENSITY 0.425 /**< max allowed cut density if filtering dense cuts */
71#define DEFAULT_MAXNONZEROROOTROUND 4.5 /**< max nonzeros per round (root). Gets multiplied by num LP cols */
72#define DEFAULT_MAXNONZEROTREEROUND 9.5 /**< max nonzeros per round (tree). Gets multiplied by num LP cols */
73#define DEFAULT_MAXCUTS 200 /**< maximum number of cuts that can be considered by this cut selector */
74#define DEFAULT_MAXNUMVARS 50000 /**< maximum number of variables that a problem can have while calling this cut selector */
75
76/*
77 * Data structures
78 */
79
80/** cut selector data */
81struct SCIP_CutselData
82{
83 SCIP_RANDNUMGEN* randnumgen; /**< random generator for tie-breaking */
84 SCIP_Real minscore; /**< minimum score for a cut to be added to the LP */
85 SCIP_Real objparalweight; /**< weight of objective parallelism in cut score calculation */
86 SCIP_Real efficacyweight; /**< weight of normed-efficacy in cut score calculation */
87 SCIP_Real dircutoffdistweight;/**< weight of normed-directed cutoff distance in cut score calculation */
88 SCIP_Real expimprovweight; /**< weight of normed-expected improvement in cut score calculation */
89 SCIP_Real intsupportweight; /**< weight of integral support in cut score calculation */
90 SCIP_Real pscostweight; /**< weight of normalised pseudo-costs in cut score calculation */
91 SCIP_Real locksweight; /**< weight of normed-number of active locks in cut score calculation */
92 SCIP_Real maxsparsitybonus; /**< weight of maximum sparsity reward in cut score calculation */
93 SCIP_Real goodnumericsbonus; /**< weight of good numeric bonus in cut score calculation */
94 SCIP_Real endsparsitybonus; /**< max sparsity value for which a bonus is applied */
95 SCIP_Real maxparal; /**< threshold for when two cuts are considered parallel to each other */
96 SCIP_Real paralpenalty; /**< penalty for weaker of two parallel cuts if penalising parallel cuts */
97 SCIP_Real maxcutdensity; /**< max allowed cut density if filtering dense cuts */
98 SCIP_Real maxnonzerorootround;/**< max nonzeros per round (root). Gets multiplied by num LP cols */
99 SCIP_Real maxnonzerotreeround;/**< max nonzeros per round (tree). Gets multiplied by num LP cols */
100 SCIP_Bool filterparalcuts; /**< should cuts be filtered so no two parallel cuts are added */
101 SCIP_Bool penaliseparalcuts; /**< should two parallel cuts be penalised instead of outright filtered */
102 SCIP_Bool filterdensecuts; /**< should cuts over a given density threshold be filtered */
103 SCIP_Bool penaliselocks; /**< whether the number of locks should be penalised instead of rewarded */
104 SCIP_Bool penaliseobjparal; /**< whether objective parallelism should be penalised */
105 int maxcoefratiobonus; /**< maximum coefficient ratio for which numeric bonus is applied */
106 int maxcuts; /**< maximum number of cuts that can be considered by this cut selector */
107 int maxnumvars; /**< maximum number of variables that a problem can have while calling this cut selector */
108};
109
110
111/*
112 * Local methods
113 */
114
115/** returns the maximum score of cuts; if scores is not NULL, then stores the individual score of each cut in scores */
116static
118 SCIP* scip, /**< SCIP data structure */
119 SCIP_ROW** cuts, /**< array with cuts to score */
120 SCIP_CUTSELDATA* cutseldata, /**< cut selector data */
121 SCIP_Real* scores, /**< array to store the score of cuts or NULL */
122 SCIP_Bool root, /**< whether we are at the root node or not */
123 int ncuts /**< number of cuts in cuts array */
124 )
125{
126 SCIP_Real* effs;
127 SCIP_Real* dcds;
128 SCIP_Real* exps;
129 SCIP_Real* cutdensities;
130 SCIP_Real* cutlocks;
131 SCIP_Real* pscosts;
132 SCIP_SOL* sol;
133 SCIP_Real maxdcd = 0.0;
134 SCIP_Real maxeff = 0.0;
135 SCIP_Real maxexp = 0.0;
136 SCIP_Real maxpscost = 0.0;
137 SCIP_Real maxlocks = 0.0;
138 SCIP_Real ncols;
139
140 /* Get the solution that we use for directed cutoff distance calculations. Get the number of columns too */
141 sol = SCIPgetBestSol(scip);
142 ncols = SCIPgetNLPCols(scip);
143
144 /* Initialise all array information that we're going to use for scoring */
145 SCIP_CALL(SCIPallocBufferArray(scip, &effs, ncuts));
146 SCIP_CALL(SCIPallocBufferArray(scip, &dcds, ncuts));
147 SCIP_CALL(SCIPallocBufferArray(scip, &exps, ncuts));
148 SCIP_CALL(SCIPallocBufferArray(scip, &cutdensities, ncuts));
149 SCIP_CALL(SCIPallocBufferArray(scip, &cutlocks, ncuts));
150 SCIP_CALL(SCIPallocBufferArray(scip, &pscosts, ncuts));
151
152 /* Populate the number of cut locks, the pseudo-cost scores, and the cut densities */
153 for (int i = 0; i < ncuts; ++i )
154 {
155 SCIP_COL** cols;
156 SCIP_Real* cutvals;
157 SCIP_Real sqrcutnorm;
158 SCIP_Real ncutcols;
159 SCIP_Real cutalpha;
160
161 cols = SCIProwGetCols(cuts[i]);
162 cutvals = SCIProwGetVals(cuts[i]);
163 sqrcutnorm = MAX(SCIPsumepsilon(scip), SQR(SCIProwGetNorm(cuts[i]))); /*lint !e666*/
164 cutalpha = -SCIPgetRowFeasibility(scip, cuts[i]) / sqrcutnorm;
165 ncutcols = SCIProwGetNNonz(cuts[i]);
166 cutdensities[i] = ncutcols / ncols;
167 cutlocks[i] = 0;
168 pscosts[i] = 0;
169
170 for ( int j = 0; j < (int) ncutcols; ++j )
171 {
172 SCIP_VAR* colvar;
173 SCIP_Real colval;
174 SCIP_Real l1dist;
175
176 colval = SCIPcolGetPrimsol(cols[j]);
177 colvar = SCIPcolGetVar(cols[j]);
178 /* Get the number of active locks feature in the cut */
179 if( ! SCIPisInfinity(scip, SCIProwGetRhs(cuts[i])) && cutvals[j] > 0.0 )
180 cutlocks[i] += SCIPvarGetNLocksUp(colvar);
181 if( ! SCIPisInfinity(scip, -SCIProwGetLhs(cuts[i])) && cutvals[j] < 0.0 )
182 cutlocks[i] += SCIPvarGetNLocksUp(colvar);
183 if( ! SCIPisInfinity(scip, SCIProwGetRhs(cuts[i])) && cutvals[j] < 0.0 )
184 cutlocks[i] += SCIPvarGetNLocksDown(colvar);
185 if( ! SCIPisInfinity(scip, -SCIProwGetLhs(cuts[i])) && cutvals[j] > 0.0 )
186 cutlocks[i] += SCIPvarGetNLocksDown(colvar);
187
188 /* Get the L1 distance from the projection onto the cut and the LP solution in the variable direction */
189 l1dist = ABS(colval - (cutalpha * cutvals[j]));
190 pscosts[i] += SCIPgetVarPseudocostScore(scip, colvar, colval) * l1dist;
191 }
192 cutlocks[i] = cutlocks[i] / ncutcols; /*lint !e414*/
193
194 if( cutlocks[i] > maxlocks )
195 maxlocks = cutlocks[i];
196
197 if( pscosts[i] > maxpscost )
198 maxpscost = pscosts[i];
199 }
200
201 /* account for the case where maxlocks or maxpscost is 0 */
202 maxpscost = MAX(maxpscost, SCIPepsilon(scip)); /*lint !e666*/
203 maxlocks = MAX(maxlocks, 1);
204
205 for ( int i = 0; i < ncuts; i++ )
206 {
207 cutlocks[i] = cutlocks[i] / maxlocks; /*lint !e414*/
208 /* if locks are penalized, we complement the corresponding score */
209 if( cutseldata->penaliselocks )
210 cutlocks[i] = 1 - cutlocks[i];
211 pscosts[i] = pscosts[i] / maxpscost; /*lint !e414*/
212 }
213
214 /* Get the arrays / maximums of directed cutoff distance, efficacy, and expected improvement values. */
215 if ( sol != NULL && root )
216 {
217 for ( int i = 0; i < ncuts; i++ )
218 {
219 dcds[i] = SCIPgetCutLPSolCutoffDistance(scip, sol, cuts[i]);
220 maxdcd = MAX(maxdcd, dcds[i]);
221 }
222 }
223
224 for ( int i = 0; i < ncuts; ++i )
225 {
226 effs[i] = SCIPgetCutEfficacy(scip, NULL, cuts[i]);
227 exps[i] = effs[i] * SCIPgetRowObjParallelism(scip, cuts[i]);
228 maxeff = MAX(maxeff, effs[i]);
229 maxexp = MAX(maxexp, exps[i]);
230 }
231
232 /* Now score the cuts */
233 for ( int i = 0; i < ncuts; ++i )
234 {
235 SCIP_Real score;
236 SCIP_Real scaleddcd;
237 SCIP_Real scaledeff;
238 SCIP_Real scaledexp;
239 SCIP_Real objparallelism;
240 SCIP_Real intsupport;
242 SCIP_Real dynamism;
243 SCIP_Real mincutval;
244 SCIP_Real maxcutval;
245 SCIP_Real pscost;
246 SCIP_Real cutlock;
247
248 /* Get the integer support */
249 intsupport = SCIPgetRowNumIntCols(scip, cuts[i]) / (SCIP_Real) SCIProwGetNNonz(cuts[i]);
250 intsupport *= cutseldata->intsupportweight;
251
252 /* Get the objective parallelism and orthogonality */
253 if( ! cutseldata->penaliseobjparal )
254 objparallelism = cutseldata->objparalweight * SCIPgetRowObjParallelism(scip, cuts[i]);
255 else
256 objparallelism = cutseldata->objparalweight * (1 - SCIPgetRowObjParallelism(scip, cuts[i]));
257
258 /* Get the density score */
259 density = (cutseldata->maxsparsitybonus / cutseldata->endsparsitybonus) * -1 * cutdensities[i];
260 density += cutseldata->maxsparsitybonus;
261 density = MAX(density, 0.0);
262
263 /* Get the normalised pseudo-cost and number of locks score */
264 if( root )
265 pscost = 0.0;
266 else
267 pscost = cutseldata->pscostweight * pscosts[i];
268 cutlock = cutseldata->locksweight * cutlocks[i];
269
270 /* Get the dynamism (good numerics) score */
271 maxcutval = SCIPgetRowMaxCoef(scip, cuts[i]);
272 mincutval = SCIPgetRowMinCoef(scip, cuts[i]);
273 mincutval = mincutval > 0.0 ? mincutval : 1.0;
274 dynamism = cutseldata->maxcoefratiobonus >= maxcutval / mincutval ? cutseldata->goodnumericsbonus : 0.0;
275
276 /* Get the dcd / eff / exp score */
277 if ( sol != NULL && root )
278 {
279 if ( SCIPisSumLE(scip, dcds[i], 0.0))
280 scaleddcd = 0.0;
281 else
282 scaleddcd = cutseldata->dircutoffdistweight * SQR(LOG1P(dcds[i]) / LOG1P(maxdcd)); /*lint !e666*/
283 }
284 else
285 {
286 scaleddcd = 0.0;
287 }
288
289 if ( SCIPisSumLE(scip, exps[i], 0.0))
290 scaledexp = 0.0;
291 else
292 scaledexp = cutseldata->expimprovweight * SQR(LOG1P(exps[i]) / LOG1P(maxexp)); /*lint !e666*/
293
294 if ( SCIPisSumLE(scip, effs[i], 0.0))
295 {
296 scaledeff = 0.0;
297 }
298 else
299 {
300 if ( sol != NULL && root )
301 scaledeff = cutseldata->efficacyweight * SQR(LOG1P(effs[i]) / LOG1P(maxeff)); /*lint !e666*/
302 else
303 scaledeff = (cutseldata->efficacyweight + cutseldata->dircutoffdistweight) * SQR(LOG1P(effs[i]) / LOG1P(maxeff)); /*lint !e666*/
304 }
305
306 /* Combine all scores and introduce some minor randomness */
307 score = scaledeff + scaleddcd + scaledexp + objparallelism + intsupport + density + dynamism + pscost + cutlock;
308
309 score += SCIPrandomGetReal(cutseldata->randnumgen, 0.0, 1e-6);
310
311 scores[i] = score;
312 }
313
317 SCIPfreeBufferArray(scip, &cutdensities);
318 SCIPfreeBufferArray(scip, &cutlocks);
319 SCIPfreeBufferArray(scip, &pscosts);
320
321 return SCIP_OKAY;
322}
323
324
325/** move the cut with the highest score to the first position in the array; there must be at least one cut */
326static
328 SCIP_ROW** cuts, /**< array with cuts to perform selection algorithm */
329 SCIP_Real* scores, /**< array with scores of cuts to perform selection algorithm */
330 int ncuts /**< number of cuts in given array */
331 )
332{
333 int bestpos;
334 SCIP_Real bestscore;
335
336 assert(ncuts > 0);
337 assert(cuts != NULL);
338 assert(scores != NULL);
339
340 bestscore = scores[0];
341 bestpos = 0;
342
343 for( int i = 1; i < ncuts; ++i )
344 {
345 if( scores[i] > bestscore )
346 {
347 bestpos = i;
348 bestscore = scores[i];
349 }
350 }
351
352 SCIPswapPointers((void**) &cuts[bestpos], (void**) &cuts[0]);
353 SCIPswapReals(&scores[bestpos], &scores[0]);
354}
355
356/** filters the given array of cuts to enforce a maximum parallelism constraint
357 * w.r.t the given cut; moves filtered cuts to the end of the array and returns number of selected cuts */
358static
360 SCIP_ROW* cut, /**< cut to filter orthogonality with */
361 SCIP_ROW** cuts, /**< array with cuts to perform selection algorithm */
362 SCIP_Real* scores, /**< array with scores of cuts to perform selection algorithm */
363 int ncuts, /**< number of cuts in given array */
364 SCIP_Real maxparallel /**< maximal parallelism for all cuts that are not good */
365 )
366{
367 assert( cut != NULL );
368 assert( ncuts == 0 || cuts != NULL );
369 assert( ncuts == 0 || scores != NULL );
370
371 for( int i = ncuts - 1; i >= 0; --i )
372 {
373 SCIP_Real thisparallel;
374
375 thisparallel = SCIProwGetParallelism(cut, cuts[i], 'e');
376
377 if( thisparallel > maxparallel )
378 {
379 --ncuts;
380 SCIPswapPointers((void**) &cuts[i], (void**) &cuts[ncuts]);
381 SCIPswapReals(&scores[i], &scores[ncuts]);
382 }
383 }
384
385 return ncuts;
386}
387
388/** penalises any cut too parallel to cut by reducing the parallel cut's score. */
389static
391 SCIP* scip, /**< SCIP data structure */
392 SCIP_ROW* cut, /**< cut to filter orthogonality with */
393 SCIP_ROW** cuts, /**< array with cuts to perform selection algorithm */
394 SCIP_Real* scores, /**< array with scores of cuts to perform selection algorithm */
395 int ncuts, /**< number of cuts in given array */
396 SCIP_Real maxparallel, /**< maximal parallelism for all cuts that are not good */
397 SCIP_Real paralpenalty /**< penalty for weaker of two parallel cuts if penalising parallel cuts */
398 )
399{
400 assert( cut != NULL );
401 assert( ncuts == 0 || cuts != NULL );
402 assert( ncuts == 0 || scores != NULL );
403
404 for( int i = ncuts - 1; i >= 0; --i )
405 {
406 SCIP_Real thisparallel;
407
408 thisparallel = SCIProwGetParallelism(cut, cuts[i], 'e');
409
410 /* Filter cuts that are absolutely parallel still. Otherwise penalise if closely parallel */
411 if( thisparallel > 1 - SCIPsumepsilon(scip) )
412 {
413 --ncuts;
414 SCIPswapPointers((void**) &cuts[i], (void**) &cuts[ncuts]);
415 SCIPswapReals(&scores[i], &scores[ncuts]);
416 }
417 else if( thisparallel > maxparallel )
418 {
419 scores[i] -= paralpenalty;
420 }
421 }
422
423 return ncuts;
424}
425
426/** filters the given array of cuts to enforce a maximum density constraint,
427 * Moves filtered cuts to the end of the array and returns number of selected cuts */
428static
430 SCIP* scip, /**< SCIP data structure */
431 SCIP_ROW** cuts, /**< array with cuts to perform selection algorithm */
432 SCIP_Real maxdensity, /**< maximum density s.t. a cut is not filtered */
433 int ncuts /**< number of cuts in given array */
434 )
435{
436 SCIP_Real ncols;
437
438 assert( ncuts == 0 || cuts != NULL );
439
440 ncols = SCIPgetNLPCols(scip);
441
442 for( int i = ncuts - 1; i >= 0; --i )
443 {
444 SCIP_Real nvals;
445
446 nvals = SCIProwGetNNonz(cuts[i]);
447
448 if( maxdensity < nvals / ncols )
449 {
450 --ncuts;
451 SCIPswapPointers((void**) &cuts[i], (void**) &cuts[ncuts]);
452 }
453 }
454
455 return ncuts;
456}
457
458
459/*
460 * Callback methods of cut selector
461 */
462
463
464/** copy method for cut selector plugin (called when SCIP copies plugins) */
465static
466SCIP_DECL_CUTSELCOPY(cutselCopyEnsemble)
467{ /*lint --e{715}*/
468 assert(scip != NULL);
469 assert(cutsel != NULL);
470 assert(strcmp(SCIPcutselGetName(cutsel), CUTSEL_NAME) == 0);
471
472 /* call inclusion method of cut selector */
474
475 return SCIP_OKAY;
476}
477
478/** destructor of cut selector to free user data (called when SCIP is exiting) */
479/**! [SnippetCutselFreeEnsemble] */
480static
481SCIP_DECL_CUTSELFREE(cutselFreeEnsemble)
482{ /*lint --e{715}*/
483 SCIP_CUTSELDATA* cutseldata;
484
485 cutseldata = SCIPcutselGetData(cutsel);
486
487 SCIPfreeBlockMemory(scip, &cutseldata);
488
489 SCIPcutselSetData(cutsel, NULL);
490
491 return SCIP_OKAY;
492}
493/**! [SnippetCutselFreeEnsemble] */
494
495/** initialization method of cut selector (called after problem was transformed) */
496static
497SCIP_DECL_CUTSELINIT(cutselInitEnsemble)
498{ /*lint --e{715}*/
499 SCIP_CUTSELDATA* cutseldata;
500
501 cutseldata = SCIPcutselGetData(cutsel);
502 assert(cutseldata != NULL);
503
504 SCIP_CALL( SCIPcreateRandom(scip, &(cutseldata)->randnumgen, RANDSEED, TRUE) );
505
506 return SCIP_OKAY;
507}
508
509/** deinitialization method of cut selector (called before transformed problem is freed) */
510static
511SCIP_DECL_CUTSELEXIT(cutselExitEnsemble)
512{ /*lint --e{715}*/
513 SCIP_CUTSELDATA* cutseldata;
514
515 cutseldata = SCIPcutselGetData(cutsel);
516 assert(cutseldata != NULL);
517 assert(cutseldata->randnumgen != NULL);
518
519 SCIPfreeRandom(scip, &cutseldata->randnumgen);
520
521 return SCIP_OKAY;
522}
523
524/** cut selection method of cut selector */
525static
526SCIP_DECL_CUTSELSELECT(cutselSelectEnsemble)
527{ /*lint --e{715}*/
528 SCIP_CUTSELDATA* cutseldata;
529
530 assert(cutsel != NULL);
531 assert(result != NULL);
532
533 cutseldata = SCIPcutselGetData(cutsel);
534 assert(cutseldata != NULL);
535
536 if( ncuts > cutseldata->maxcuts || SCIPgetNVars(scip) > cutseldata->maxnumvars )
537 {
538 *result = SCIP_DIDNOTFIND;
539 return SCIP_OKAY;
540 }
541
542 *result = SCIP_SUCCESS;
543
544 SCIP_CALL( SCIPselectCutsEnsemble(scip, cuts, forcedcuts, cutseldata, root, ncuts, nforcedcuts,
545 maxnselectedcuts, nselectedcuts) );
546
547 return SCIP_OKAY;
548}
549
550
551/*
552 * cut selector specific interface methods
553 */
554
555/** creates the ensemble cut selector and includes it in SCIP */
557 SCIP* scip /**< SCIP data structure */
558 )
559{
560 SCIP_CUTSELDATA* cutseldata;
561 SCIP_CUTSEL* cutsel;
562
563 /* create ensemble cut selector data */
564 SCIP_CALL( SCIPallocBlockMemory(scip, &cutseldata) );
565 BMSclearMemory(cutseldata);
566
568 cutseldata) );
569
570 assert(cutsel != NULL);
571
572 /* set non fundamental callbacks via setter functions */
573 SCIP_CALL( SCIPsetCutselCopy(scip, cutsel, cutselCopyEnsemble) );
574
575 SCIP_CALL( SCIPsetCutselFree(scip, cutsel, cutselFreeEnsemble) );
576 SCIP_CALL( SCIPsetCutselInit(scip, cutsel, cutselInitEnsemble) );
577 SCIP_CALL( SCIPsetCutselExit(scip, cutsel, cutselExitEnsemble) );
578
579 /* add ensemble cut selector parameters */
580 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/efficacyweight", "weight of normed-efficacy in cut "
581 "score calculation", &cutseldata->efficacyweight, FALSE, DEFAULT_EFFICACYWEIGHT, 0.0, SCIP_INVALID/10.0, NULL,
582 NULL) );
583
584 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/dircutoffdistweight", "weight of normed-directed "
585 "cutoff distance in cut score calculation", &cutseldata->dircutoffdistweight, FALSE,
587
588 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/objparalweight", "weight of objective parallelism "
589 "in cut score calculation", &cutseldata->objparalweight, FALSE, DEFAULT_OBJPARALWEIGHT, 0.0, SCIP_INVALID/10.0,
590 NULL, NULL) );
591
592 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/intsupportweight", "weight of integral support in "
593 "cut score calculation", &cutseldata->intsupportweight, FALSE, DEFAULT_INTSUPPORTWEIGHT, 0.0,
594 SCIP_INVALID/10.0, NULL, NULL) );
595
596 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/expimprovweight", "weight of normed-expected obj "
597 "improvement in cut score calculation", &cutseldata->expimprovweight, FALSE, DEFAULT_EXPIMPROVWEIGHT, 0.0,
598 SCIP_INVALID/10.0, NULL, NULL) );
599
600 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/minscore", "minimum score s.t. a cut can be added",
601 &cutseldata->minscore, FALSE, DEFAULT_MINSCORE, -SCIP_INVALID/10.0, SCIP_INVALID/10.0, NULL, NULL) );
602
603 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/pscostweight", "weight of normed-pseudo-costs in "
604 "cut score calculation", &cutseldata->pscostweight, FALSE, DEFAULT_PSCOSTWEIGHT, 0.0, SCIP_INVALID/10.0, NULL,
605 NULL) );
606
607 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/locksweight", "weight of normed-num-locks in cut "
608 "score calculation", &cutseldata->locksweight, FALSE, DEFAULT_NLOCKSWEIGHT, 0.0, SCIP_INVALID/10.0, NULL,
609 NULL) );
610
611 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/maxsparsitybonus", "weight of maximum sparsity "
612 "reward in cut score calculation", &cutseldata->maxsparsitybonus, FALSE, DEFAULT_MAXSPARSITYBONUS, 0.0,
613 SCIP_INVALID/10.0, NULL, NULL) );
614
615 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/goodnumericsbonus", "weight of good numerics bonus "
616 "(ratio of coefficients) in cut score calculation", &cutseldata->goodnumericsbonus, FALSE,
618
619 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/endsparsitybonus", "max sparsity value for which a "
620 "bonus is applied in cut score calculation", &cutseldata->endsparsitybonus, FALSE, DEFAULT_SPARSITYENDBONUS,
621 0.0, SCIP_INVALID/10.0, NULL, NULL) );
622
623 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/maxparal", "threshold for when two cuts are "
624 "considered parallel to each other", &cutseldata->maxparal, FALSE, DEFAULT_MAXPARAL, 0.0, 1.0, NULL, NULL) );
625
626 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/paralpenalty", "penalty for weaker of two parallel "
627 "cuts if penalising parallel cuts", &cutseldata->paralpenalty, TRUE, DEFAULT_PARALPENALTY, 0.0,
628 SCIP_INVALID/10.0, NULL, NULL) );
629
630 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/maxcutdensity", "max allowed cut density if "
631 "filtering dense cuts", &cutseldata->maxcutdensity, TRUE, DEFAULT_MAXCUTDENSITY, 0.0, 1.0, NULL, NULL) );
632
633 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/maxnonzerorootround", "max non-zeros per round "
634 "applied cuts (root). multiple num LP cols.", &cutseldata->maxnonzerorootround, FALSE,
636
637 SCIP_CALL( SCIPaddRealParam(scip, "cutselection/" CUTSEL_NAME "/maxnonzerotreeround", "max non-zeros per round "
638 "applied cuts (tree). multiple num LP cols.", &cutseldata->maxnonzerotreeround, FALSE,
640
641 SCIP_CALL( SCIPaddBoolParam(scip, "cutselection/" CUTSEL_NAME "/filterparalcuts", "should cuts be filtered so no "
642 "two parallel cuts are added", &cutseldata->filterparalcuts, FALSE, DEFAULT_FILTERPARALCUTS, NULL, NULL) );
643
644 SCIP_CALL( SCIPaddBoolParam(scip, "cutselection/" CUTSEL_NAME "/penaliseparalcuts", "should two parallel cuts be "
645 "penalised instead of outright filtered", &cutseldata->penaliseparalcuts, TRUE, DEFAULT_PENALISEPARALCUTS,
646 NULL, NULL) );
647
648 SCIP_CALL( SCIPaddBoolParam(scip, "cutselection/" CUTSEL_NAME "/filterdensecuts", "should cuts over a given density "
649 "threshold be filtered", &cutseldata->filterdensecuts, TRUE, DEFAULT_FILTERDENSECUTS, NULL, NULL) );
650
651 SCIP_CALL( SCIPaddBoolParam(scip, "cutselection/" CUTSEL_NAME "/penaliselocks", "should the number of locks be "
652 "penalised instead of rewarded", &cutseldata->penaliselocks, TRUE, DEFAULT_PENALISELOCKS, NULL, NULL) );
653
654 SCIP_CALL( SCIPaddBoolParam(scip, "cutselection/" CUTSEL_NAME "/penaliseobjparal", "should objective parallelism "
655 "be penalised instead of rewarded", &cutseldata->penaliseobjparal, TRUE, DEFAULT_PENALISEOBJPARAL, NULL, NULL)
656 );
657
658 SCIP_CALL( SCIPaddIntParam(scip, "cutselection/" CUTSEL_NAME "/maxcoefratiobonus", "max coefficient ratio for "
659 "which numeric bonus is applied.", &cutseldata->maxcoefratiobonus, TRUE, DEFAULT_MAXCOEFRATIOBONUS, 1, 1000000,
660 NULL, NULL) );
661
662 SCIP_CALL( SCIPaddIntParam(scip, "cutselection/" CUTSEL_NAME "/maxcuts", "max number of cuts such that cut "
663 "selector is applied.", &cutseldata->maxcuts, TRUE, DEFAULT_MAXCUTS, 1, 1000000, NULL, NULL) );
664
665 SCIP_CALL( SCIPaddIntParam(scip, "cutselection/" CUTSEL_NAME "/maxnumvars", "max number of variables such that cut "
666 "selector is applied.", &cutseldata->maxnumvars, TRUE, DEFAULT_MAXNUMVARS, 1, 1000000, NULL, NULL) );
667
668 return SCIP_OKAY;
669}
670
671/** perform a cut selection algorithm for the given array of cuts
672 *
673 * This is the selection method of the ensemble cut selector. It uses a weighted sum of normalised efficacy,
674 * normalised directed cutoff distance, normalised expected improvements, objective parallelism,
675 * integer support, sparsity, dynamism, pseudo-costs, and variable locks.
676 * In addition to the weighted sum score, there are optionally parallelism-based filtering and penalties,
677 * and density filtering.
678 * There are also additional budget constraints on the number of cuts that should be added.
679 * The input cuts array gets re-sorted such that the selected cuts come first and the remaining ones are the end.
680 */
682 SCIP* scip, /**< SCIP data structure */
683 SCIP_ROW** cuts, /**< array with cuts to perform selection algorithm */
684 SCIP_ROW** forcedcuts, /**< array with forced cuts */
685 SCIP_CUTSELDATA* cutseldata, /**< cut selector data */
686 SCIP_Bool root, /**< whether we are at the root node or not */
687 int ncuts, /**< number of cuts in cuts array */
688 int nforcedcuts, /**< number of forced cuts */
689 int maxselectedcuts, /**< maximal number of cuts from cuts array to select */
690 int* nselectedcuts /**< pointer to return number of selected cuts from cuts array */
691 )
692{
693 SCIP_Real* scores;
694 SCIP_Real* origscoresptr;
695 SCIP_Real nonzerobudget;
696 SCIP_Real budgettaken = 0.0;
697 SCIP_Real ncols;
698
699 assert(cuts != NULL && ncuts > 0);
700 assert(forcedcuts != NULL || nforcedcuts == 0);
701 assert(nselectedcuts != NULL);
702
703 *nselectedcuts = 0;
704 ncols = SCIPgetNLPCols(scip);
705
706 /* filter dense cuts */
707 if( cutseldata->filterdensecuts )
708 {
709 ncuts = filterWithDensity(scip, cuts, cutseldata->maxcutdensity, ncuts);
710 if( ncuts == 0 )
711 return SCIP_OKAY;
712 }
713
714 /* Initialise the score array */
715 SCIP_CALL( SCIPallocBufferArray(scip, &scores, ncuts) );
716 origscoresptr = scores;
717
718 /* compute scores of cuts */
719 SCIP_CALL( scoring(scip, cuts, cutseldata, scores, root, ncuts) );
720
721 /* perform cut selection algorithm for the cuts */
722
723 /* forced cuts are going to be selected so use them to filter cuts */
724 for( int i = 0; i < nforcedcuts && ncuts > 0; ++i )
725 {
726 if( cutseldata->filterparalcuts )
727 ncuts = filterWithParallelism(forcedcuts[i], cuts, scores, ncuts, cutseldata->maxparal);
728 else if( cutseldata->penaliseparalcuts )
729 ncuts = penaliseWithParallelism(scip, forcedcuts[i], cuts, scores, ncuts, cutseldata->maxparal, cutseldata->paralpenalty);
730 }
731
732 /* Get the budget depending on if we are the root or not */
733 nonzerobudget = root ? cutseldata->maxnonzerorootround : cutseldata->maxnonzerotreeround;
734
735 /* now greedily select the remaining cuts */
736 while( ncuts > 0 )
737 {
738 SCIP_ROW* selectedcut;
739
740 selectBestCut(cuts, scores, ncuts);
741 selectedcut = cuts[0];
742
743 /* if the best cut of the remaining cuts is considered bad, we discard it and all remaining cuts */
744 if( scores[0] < cutseldata->minscore )
745 break;
746
747 ++(*nselectedcuts);
748
749 /* if the maximal number of cuts was selected, we can stop here */
750 if( *nselectedcuts == maxselectedcuts )
751 break;
752
753 /* increase the non-zero budget counter of added cuts */
754 budgettaken += SCIProwGetNNonz(cuts[0]) / ncols;
755
756 /* move the pointers to the next position and filter the remaining cuts to enforce the maximum parallelism constraint */
757 ++cuts;
758 ++scores;
759 --ncuts;
760
761 if( cutseldata->filterparalcuts && ncuts > 0)
762 ncuts = filterWithParallelism(selectedcut, cuts, scores, ncuts, cutseldata->maxparal);
763 else if( cutseldata->penaliseparalcuts && ncuts > 0 )
764 ncuts = penaliseWithParallelism(scip, selectedcut, cuts, scores, ncuts, cutseldata->maxparal, cutseldata->paralpenalty);
765
766 /* Filter out all remaining cuts that would go over the non-zero budget threshold */
767 if( nonzerobudget - budgettaken < 1 && ncuts > 0 )
768 ncuts = filterWithDensity(scip, cuts, nonzerobudget - budgettaken, ncuts);
769 }
770
771 SCIPfreeBufferArray(scip, &origscoresptr);
772
773 return SCIP_OKAY;
774}
#define DEFAULT_MAXCUTS
#define DEFAULT_SPARSITYENDBONUS
static SCIP_DECL_CUTSELEXIT(cutselExitEnsemble)
#define DEFAULT_EFFICACYWEIGHT
#define DEFAULT_MAXNONZEROTREEROUND
#define DEFAULT_OBJPARALWEIGHT
#define RANDSEED
#define DEFAULT_FILTERDENSECUTS
#define DEFAULT_MAXSPARSITYBONUS
#define DEFAULT_MINSCORE
#define DEFAULT_PARALPENALTY
#define DEFAULT_PENALISEPARALCUTS
#define DEFAULT_GOODNUMERICBONUS
#define DEFAULT_PENALISELOCKS
static SCIP_DECL_CUTSELCOPY(cutselCopyEnsemble)
static SCIP_DECL_CUTSELINIT(cutselInitEnsemble)
#define DEFAULT_NLOCKSWEIGHT
static SCIP_RETCODE scoring(SCIP *scip, SCIP_ROW **cuts, SCIP_CUTSELDATA *cutseldata, SCIP_Real *scores, SCIP_Bool root, int ncuts)
static void selectBestCut(SCIP_ROW **cuts, SCIP_Real *scores, int ncuts)
static int filterWithDensity(SCIP *scip, SCIP_ROW **cuts, SCIP_Real maxdensity, int ncuts)
static int penaliseWithParallelism(SCIP *scip, SCIP_ROW *cut, SCIP_ROW **cuts, SCIP_Real *scores, int ncuts, SCIP_Real maxparallel, SCIP_Real paralpenalty)
static SCIP_DECL_CUTSELSELECT(cutselSelectEnsemble)
#define CUTSEL_DESC
#define CUTSEL_PRIORITY
static int filterWithParallelism(SCIP_ROW *cut, SCIP_ROW **cuts, SCIP_Real *scores, int ncuts, SCIP_Real maxparallel)
#define DEFAULT_EXPIMPROVWEIGHT
#define DEFAULT_MAXCOEFRATIOBONUS
#define DEFAULT_DIRCUTOFFDISTWEIGHT
#define DEFAULT_MAXCUTDENSITY
#define DEFAULT_MAXNONZEROROOTROUND
static SCIP_DECL_CUTSELFREE(cutselFreeEnsemble)
#define CUTSEL_NAME
#define DEFAULT_PENALISEOBJPARAL
#define DEFAULT_MAXNUMVARS
#define DEFAULT_PSCOSTWEIGHT
#define DEFAULT_FILTERPARALCUTS
#define DEFAULT_MAXPARAL
#define DEFAULT_INTSUPPORTWEIGHT
ensemble cut selector
#define NULL
Definition: def.h:267
#define LOG1P(x)
Definition: def.h:222
#define SCIP_INVALID
Definition: def.h:193
#define SCIP_Bool
Definition: def.h:91
#define SCIP_Real
Definition: def.h:173
#define ABS(x)
Definition: def.h:235
#define SQR(x)
Definition: def.h:214
#define TRUE
Definition: def.h:93
#define FALSE
Definition: def.h:94
#define MAX(x, y)
Definition: def.h:239
#define SCIP_CALL(x)
Definition: def.h:374
static const SCIP_Real density
Definition: gastrans.c:145
SCIP_RETCODE SCIPselectCutsEnsemble(SCIP *scip, SCIP_ROW **cuts, SCIP_ROW **forcedcuts, SCIP_CUTSELDATA *cutseldata, SCIP_Bool root, int ncuts, int nforcedcuts, int maxselectedcuts, int *nselectedcuts)
SCIP_RETCODE SCIPincludeCutselEnsemble(SCIP *scip)
int SCIPgetNVars(SCIP *scip)
Definition: scip_prob.c:1992
SCIP_RETCODE SCIPaddIntParam(SCIP *scip, const char *name, const char *desc, int *valueptr, SCIP_Bool isadvanced, int defaultvalue, int minvalue, int maxvalue, SCIP_DECL_PARAMCHGD((*paramchgd)), SCIP_PARAMDATA *paramdata)
Definition: scip_param.c:83
SCIP_RETCODE SCIPaddRealParam(SCIP *scip, const char *name, const char *desc, SCIP_Real *valueptr, SCIP_Bool isadvanced, SCIP_Real defaultvalue, SCIP_Real minvalue, SCIP_Real maxvalue, SCIP_DECL_PARAMCHGD((*paramchgd)), SCIP_PARAMDATA *paramdata)
Definition: scip_param.c:139
SCIP_RETCODE SCIPaddBoolParam(SCIP *scip, const char *name, const char *desc, SCIP_Bool *valueptr, SCIP_Bool isadvanced, SCIP_Bool defaultvalue, SCIP_DECL_PARAMCHGD((*paramchgd)), SCIP_PARAMDATA *paramdata)
Definition: scip_param.c:57
void SCIPswapPointers(void **pointer1, void **pointer2)
Definition: misc.c:10396
void SCIPswapReals(SCIP_Real *value1, SCIP_Real *value2)
Definition: misc.c:10383
SCIP_VAR * SCIPcolGetVar(SCIP_COL *col)
Definition: lp.c:17042
SCIP_Real SCIPcolGetPrimsol(SCIP_COL *col)
Definition: lp.c:16996
SCIP_Real SCIPgetCutEfficacy(SCIP *scip, SCIP_SOL *sol, SCIP_ROW *cut)
Definition: scip_cut.c:94
SCIP_Real SCIPgetCutLPSolCutoffDistance(SCIP *scip, SCIP_SOL *sol, SCIP_ROW *cut)
Definition: scip_cut.c:72
SCIP_RETCODE SCIPsetCutselInit(SCIP *scip, SCIP_CUTSEL *cutsel, SCIP_DECL_CUTSELINIT((*cutselinit)))
Definition: scip_cutsel.c:157
SCIP_RETCODE SCIPincludeCutselBasic(SCIP *scip, SCIP_CUTSEL **cutsel, const char *name, const char *desc, int priority, SCIP_DECL_CUTSELSELECT((*cutselselect)), SCIP_CUTSELDATA *cutseldata)
Definition: scip_cutsel.c:92
SCIP_RETCODE SCIPsetCutselCopy(SCIP *scip, SCIP_CUTSEL *cutsel, SCIP_DECL_CUTSELCOPY((*cutselcopy)))
Definition: scip_cutsel.c:125
SCIP_CUTSELDATA * SCIPcutselGetData(SCIP_CUTSEL *cutsel)
Definition: cutsel.c:419
SCIP_RETCODE SCIPsetCutselFree(SCIP *scip, SCIP_CUTSEL *cutsel, SCIP_DECL_CUTSELFREE((*cutselfree)))
Definition: scip_cutsel.c:141
void SCIPcutselSetData(SCIP_CUTSEL *cutsel, SCIP_CUTSELDATA *cutseldata)
Definition: cutsel.c:429
const char * SCIPcutselGetName(SCIP_CUTSEL *cutsel)
Definition: cutsel.c:159
SCIP_RETCODE SCIPsetCutselExit(SCIP *scip, SCIP_CUTSEL *cutsel, SCIP_DECL_CUTSELEXIT((*cutselexit)))
Definition: scip_cutsel.c:173
int SCIPgetNLPCols(SCIP *scip)
Definition: scip_lp.c:527
#define SCIPallocBufferArray(scip, ptr, num)
Definition: scip_mem.h:124
#define SCIPfreeBufferArray(scip, ptr)
Definition: scip_mem.h:136
#define SCIPfreeBlockMemory(scip, ptr)
Definition: scip_mem.h:108
#define SCIPallocBlockMemory(scip, ptr)
Definition: scip_mem.h:89
SCIP_Real SCIPgetRowMaxCoef(SCIP *scip, SCIP_ROW *row)
Definition: scip_lp.c:1922
SCIP_Real SCIProwGetLhs(SCIP_ROW *row)
Definition: lp.c:17292
SCIP_Real SCIPgetRowMinCoef(SCIP *scip, SCIP_ROW *row)
Definition: scip_lp.c:1904
SCIP_Real SCIPgetRowFeasibility(SCIP *scip, SCIP_ROW *row)
Definition: scip_lp.c:2124
SCIP_Real SCIProwGetParallelism(SCIP_ROW *row1, SCIP_ROW *row2, char orthofunc)
Definition: lp.c:7724
int SCIProwGetNNonz(SCIP_ROW *row)
Definition: lp.c:17213
SCIP_COL ** SCIProwGetCols(SCIP_ROW *row)
Definition: lp.c:17238
SCIP_Real SCIProwGetRhs(SCIP_ROW *row)
Definition: lp.c:17302
SCIP_Real SCIProwGetNorm(SCIP_ROW *row)
Definition: lp.c:17268
SCIP_Real SCIPgetRowObjParallelism(SCIP *scip, SCIP_ROW *row)
Definition: scip_lp.c:2190
int SCIPgetRowNumIntCols(SCIP *scip, SCIP_ROW *row)
Definition: scip_lp.c:1886
SCIP_Real * SCIProwGetVals(SCIP_ROW *row)
Definition: lp.c:17248
SCIP_SOL * SCIPgetBestSol(SCIP *scip)
Definition: scip_sol.c:2169
SCIP_Bool SCIPisInfinity(SCIP *scip, SCIP_Real val)
SCIP_Real SCIPepsilon(SCIP *scip)
SCIP_Real SCIPsumepsilon(SCIP *scip)
SCIP_Bool SCIPisSumLE(SCIP *scip, SCIP_Real val1, SCIP_Real val2)
int SCIPvarGetNLocksDown(SCIP_VAR *var)
Definition: var.c:3416
int SCIPvarGetNLocksUp(SCIP_VAR *var)
Definition: var.c:3429
SCIP_Real SCIPgetVarPseudocostScore(SCIP *scip, SCIP_VAR *var, SCIP_Real solval)
Definition: scip_var.c:9103
void SCIPfreeRandom(SCIP *scip, SCIP_RANDNUMGEN **randnumgen)
SCIP_Real SCIPrandomGetReal(SCIP_RANDNUMGEN *randnumgen, SCIP_Real minrandval, SCIP_Real maxrandval)
Definition: misc.c:10130
SCIP_RETCODE SCIPcreateRandom(SCIP *scip, SCIP_RANDNUMGEN **randnumgen, unsigned int initialseed, SCIP_Bool useglobalseed)
#define BMSclearMemory(ptr)
Definition: memory.h:129
public methods for cuts and aggregation rows
public methods for cut selector plugins
public methods for the LP relaxation, rows and columns
public methods for random numbers
struct SCIP_CutselData SCIP_CUTSELDATA
Definition: type_cutsel.h:53
@ SCIP_DIDNOTFIND
Definition: type_result.h:44
@ SCIP_SUCCESS
Definition: type_result.h:58
@ SCIP_OKAY
Definition: type_retcode.h:42
enum SCIP_Retcode SCIP_RETCODE
Definition: type_retcode.h:63