1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2 /* */
3 /* This file is part of the program and library */
4 /* SCIP --- Solving Constraint Integer Programs */
5 /* */
6 /* Copyright (C) 2002-2020 Konrad-Zuse-Zentrum */
7 /* fuer Informationstechnik Berlin */
8 /* */
9 /* SCIP is distributed under the terms of the ZIB Academic License. */
10 /* */
11 /* You should have received a copy of the ZIB Academic License */
12 /* along with SCIP; see the file COPYING. If not visit scip.zib.de. */
13 /* */
14 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
15
16 /**@file nlhdlr_convex.c
17 * @ingroup DEFPLUGINS_NLHDLR
18 * @brief nonlinear handlers for convex and concave expressions
19 * @author Benjamin Mueller
20 * @author Stefan Vigerske
21 *
22 * TODO convex: perturb reference point if separation fails due to too large numbers
23 */
24
25 #include <string.h>
26
27 #include "scip/nlhdlr_convex.h"
28 #include "scip/pub_nlhdlr.h"
29 #include "scip/scip_expr.h"
30 #include "scip/cons_nonlinear.h"
31 #include "scip/expr_var.h"
32 #include "scip/pub_misc_rowprep.h"
33 #include "scip/dbldblarith.h"
34
35 /* fundamental nonlinear handler properties */
36 #define CONVEX_NLHDLR_NAME "convex"
37 #define CONVEX_NLHDLR_DESC "handler that identifies and estimates convex expressions"
38 #define CONVEX_NLHDLR_DETECTPRIORITY 50
39 #define CONVEX_NLHDLR_ENFOPRIORITY 50
40
41 #define CONCAVE_NLHDLR_NAME "concave"
42 #define CONCAVE_NLHDLR_DESC "handler that identifies and estimates concave expressions"
43 #define CONCAVE_NLHDLR_DETECTPRIORITY 40
44 #define CONCAVE_NLHDLR_ENFOPRIORITY 40
45
46 #define DEFAULT_DETECTSUM FALSE
47 #define DEFAULT_EXTENDEDFORM TRUE
48 #define DEFAULT_CVXQUADRATIC_CONVEX TRUE
49 #define DEFAULT_CVXQUADRATIC_CONCAVE FALSE
50 #define DEFAULT_CVXSIGNOMIAL TRUE
51 #define DEFAULT_CVXPRODCOMP TRUE
52 #define DEFAULT_HANDLETRIVIAL FALSE
53
54 #define INITLPMAXVARVAL 1000.0 /**< maximal absolute value of variable for still generating a linearization cut at that point in initlp */
55
56 /*lint -e440*/
57 /*lint -e441*/
58 /*lint -e666*/
59 /*lint -e777*/
60
61 /*
62 * Data structures
63 */
64
65 /** nonlinear handler expression data */
66 struct SCIP_NlhdlrExprData
67 {
68 SCIP_EXPR* nlexpr; /**< expression (copy) for which this nlhdlr estimates */
69 SCIP_HASHMAP* nlexpr2origexpr; /**< mapping of our copied expression to original expression */
70
71 int nleafs; /**< number of distinct leafs of nlexpr, i.e., number of distinct (auxiliary) variables handled */
72 SCIP_EXPR** leafexprs; /**< distinct leaf expressions (excluding value-expressions), thus variables */
73 };
74
75 /** nonlinear handler data */
76 struct SCIP_NlhdlrData
77 {
78 SCIP_Bool isnlhdlrconvex; /**< whether this data is used for the convex nlhdlr (TRUE) or the concave one (FALSE) */
79 SCIP_SOL* evalsol; /**< solution used for evaluating expression in a different point,
80 e.g., for facet computation of vertex-polyhedral function */
81
82 /* parameters */
83 SCIP_Bool detectsum; /**< whether to run detection when the root of an expression is a non-quadratic sum */
84 SCIP_Bool extendedform; /**< whether to create extended formulations instead of looking for maximal possible subexpression */
85
86 /* advanced parameters (maybe remove some day) */
87 SCIP_Bool cvxquadratic; /**< whether to use convexity check on quadratics */
88 SCIP_Bool cvxsignomial; /**< whether to use convexity check on signomials */
89 SCIP_Bool cvxprodcomp; /**< whether to use convexity check on product composition f(h)*h */
90 SCIP_Bool handletrivial; /**< whether to handle trivial expressions, i.e., those where all children are variables */
91 };
92
93 /** data struct to be be passed on to vertexpoly-evalfunction (see SCIPcomputeFacetVertexPolyhedralNonlinear) */
94 typedef struct
95 {
96 SCIP_NLHDLREXPRDATA* nlhdlrexprdata;
97 SCIP_SOL* evalsol;
98 SCIP* scip;
99 } VERTEXPOLYFUN_EVALDATA;
100
101 /** stack used in constructExpr to store expressions that need to be investigated ("to do list") */
102 typedef struct
103 {
104 SCIP_EXPR** stack; /**< stack elements */
105 int stacksize; /**< allocated space (in number of pointers) */
106 int stackpos; /**< position of top element of stack */
107 } EXPRSTACK;
108
109 #define DECL_CURVCHECK(x) SCIP_RETCODE x( \
110 SCIP* scip, /**< SCIP data structure */ \
111 SCIP_EXPR* nlexpr, /**< nlhdlr-expr to check */ \
112 SCIP_Bool isrootexpr, /**< whether nlexpr is the root from where detection has been started */ \
113 EXPRSTACK* stack, /**< stack where to add generated leafs */ \
114 SCIP_HASHMAP* nlexpr2origexpr, /**< mapping from our expression copy to original expression */ \
115 SCIP_NLHDLRDATA* nlhdlrdata, /**< data of nlhdlr */ \
116 SCIP_HASHMAP* assumevarfixed, /**< hashmap containing variables that should be assumed to be fixed, or NULL */ \
117 SCIP_Bool* success /**< whether we found something */ \
118 )
119
120 /*
121 * static methods
122 */
123
124 /** create nlhdlr-expression
125 *
126 * does not create children, i.e., assumes that this will be a leaf
127 */
128 static
129 SCIP_RETCODE nlhdlrExprCreate(
130 SCIP* scip, /**< SCIP data structure */
131 SCIP_HASHMAP* nlexpr2origexpr, /**< mapping from copied to original expression */
132 SCIP_EXPR** nlhdlrexpr, /**< buffer to store created expr */
133 SCIP_EXPR* origexpr, /**< original expression to be copied */
134 SCIP_EXPRCURV curv /**< curvature to achieve */
135 )
136 {
137 assert(scip != NULL);
138 assert(nlexpr2origexpr != NULL);
139 assert(nlhdlrexpr != NULL);
140 assert(origexpr != NULL);
141
142 if( SCIPexprGetNChildren(origexpr) == 0 )
143 {
144 /* for leaves, do not copy */
145 *nlhdlrexpr = origexpr;
146 SCIPcaptureExpr(*nlhdlrexpr);
147 if( !SCIPhashmapExists(nlexpr2origexpr, (void*)*nlhdlrexpr) )
148 {
149 SCIP_CALL( SCIPhashmapInsert(nlexpr2origexpr, (void*)*nlhdlrexpr, (void*)origexpr) );
150 }
151 return SCIP_OKAY;
152 }
153
154 /* create copy of expression, but without children */
155 SCIP_CALL( SCIPduplicateExprShallow(scip, origexpr, nlhdlrexpr, NULL, NULL) );
156 assert(*nlhdlrexpr != NULL); /* copies within the same SCIP must always work */
157
158 /* store the curvature we want to get in the curvature flag of the copied expression
159 * it's a bit of a misuse, but once we are done with everything, this is actually correct
160 */
161 SCIPexprSetCurvature(*nlhdlrexpr, curv);
162
163 /* remember which the original expression was */
164 SCIP_CALL( SCIPhashmapInsert(nlexpr2origexpr, (void*)*nlhdlrexpr, (void*)origexpr) );
165
166 return SCIP_OKAY;
167 }
168
169 /** expand nlhdlr-expression by adding children according to original expression */
170 static
171 SCIP_RETCODE nlhdlrExprGrowChildren(
172 SCIP* scip, /**< SCIP data structure */
173 SCIP_HASHMAP* nlexpr2origexpr, /**< mapping from copied to original expression */
174 SCIP_EXPR* nlhdlrexpr, /**< expression for which to create children */
175 SCIP_EXPRCURV* childrencurv /**< curvature required for children, or NULL if to set to UNKNOWN */
176 )
177 {
178 SCIP_EXPR* origexpr;
179 SCIP_EXPR* child;
180 int nchildren;
181 int i;
182
183 assert(scip != NULL);
184 assert(nlhdlrexpr != NULL);
185 assert(SCIPexprGetNChildren(nlhdlrexpr) == 0);
186
187 origexpr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlhdlrexpr);
188
189 nchildren = SCIPexprGetNChildren(origexpr);
190 if( nchildren == 0 )
191 return SCIP_OKAY;
192
193 for( i = 0; i < nchildren; ++i )
194 {
195 SCIP_CALL( nlhdlrExprCreate(scip, nlexpr2origexpr, &child, SCIPexprGetChildren(origexpr)[i],
196 childrencurv != NULL ? childrencurv[i] : SCIP_EXPRCURV_UNKNOWN) );
197 SCIP_CALL( SCIPappendExprChild(scip, nlhdlrexpr, child) );
198 /* append captures child, so we can release the capture from nlhdlrExprCreate */
199 SCIP_CALL( SCIPreleaseExpr(scip, &child) );
200 }
201
202 assert(SCIPexprGetNChildren(nlhdlrexpr) == SCIPexprGetNChildren(origexpr));
203
204 return SCIP_OKAY;
205 }
206
207 /** evaluate expression at solution w.r.t. auxiliary variables */
208 static
209 SCIP_DECL_VERTEXPOLYFUN(nlhdlrExprEvalConcave)
210 {
211 VERTEXPOLYFUN_EVALDATA* evaldata = (VERTEXPOLYFUN_EVALDATA*)funcdata;
212 int i;
213
214 assert(args != NULL);
215 assert(nargs == evaldata->nlhdlrexprdata->nleafs);
216 assert(evaldata != NULL);
217
218 #ifdef SCIP_MORE_DEBUG
219 SCIPdebugMsg(evaldata->scip, "eval vertexpolyfun at\n");
220 #endif
221 for( i = 0; i < nargs; ++i )
222 {
223 #ifdef SCIP_MORE_DEBUG
224 SCIPdebugMsg(evaldata->scip, " <%s> = %g\n",
225 SCIPvarGetName(SCIPgetVarExprVar(evaldata->nlhdlrexprdata->leafexprs[i])), args[i]);
226 #endif
227 SCIP_CALL_ABORT( SCIPsetSolVal(evaldata->scip, evaldata->evalsol,
228 SCIPgetVarExprVar(evaldata->nlhdlrexprdata->leafexprs[i]), args[i]) );
229 }
230
231 SCIP_CALL_ABORT( SCIPevalExpr(evaldata->scip, evaldata->nlhdlrexprdata->nlexpr, evaldata->evalsol, 0L) );
232
233 return SCIPexprGetEvalValue(evaldata->nlhdlrexprdata->nlexpr);
234 }
235
236 /** initialize expression stack */
237 static
238 SCIP_RETCODE exprstackInit(
239 SCIP* scip, /**< SCIP data structure */
240 EXPRSTACK* exprstack, /**< stack to initialize */
241 int initsize /**< initial size */
242 )
243 {
244 assert(scip != NULL);
245 assert(exprstack != NULL);
246 assert(initsize > 0);
247
248 SCIP_CALL( SCIPallocBufferArray(scip, &exprstack->stack, initsize) );
249 exprstack->stacksize = initsize;
250 exprstack->stackpos = -1;
251
252 return SCIP_OKAY;
253 }
254
255 /** free expression stack */
256 static
257 void exprstackFree(
258 SCIP* scip, /**< SCIP data structure */
259 EXPRSTACK* exprstack /**< free expression stack */
260 )
261 {
262 assert(scip != NULL);
263 assert(exprstack != NULL);
264
265 SCIPfreeBufferArray(scip, &exprstack->stack);
266 }
267
268 /** add expressions to expression stack */
269 static
270 SCIP_RETCODE exprstackPush(
271 SCIP* scip, /**< SCIP data structure */
272 EXPRSTACK* exprstack, /**< expression stack */
273 int nexprs, /**< number of expressions to push */
274 SCIP_EXPR** exprs /**< expressions to push */
275 )
276 {
277 assert(scip != NULL);
278 assert(exprstack != NULL);
279
280 if( nexprs == 0 )
281 return SCIP_OKAY;
282
283 assert(exprs != NULL);
284
285 if( exprstack->stackpos+1 + nexprs > exprstack->stacksize ) /*lint !e644*/
286 {
287 exprstack->stacksize = SCIPcalcMemGrowSize(scip, exprstack->stackpos+1 + nexprs); /*lint !e644*/
288 SCIP_CALL( SCIPreallocBufferArray(scip, &exprstack->stack, exprstack->stacksize) );
289 }
290
291 memcpy(exprstack->stack + (exprstack->stackpos+1), exprs, nexprs * sizeof(SCIP_EXPR*)); /*lint !e679*/ /*lint !e737*/
292 exprstack->stackpos += nexprs;
293
294 return SCIP_OKAY;
295 }
296
297 /** gives expression from top of expression stack and removes it from stack */
298 static
299 SCIP_EXPR* exprstackPop(
300 EXPRSTACK* exprstack /**< expression stack */
301 )
302 {
303 assert(exprstack != NULL);
304 assert(exprstack->stackpos >= 0);
305
306 return exprstack->stack[exprstack->stackpos--];
307 }
308
309 /** indicate whether expression stack is empty */
310 static
311 SCIP_Bool exprstackIsEmpty(
312 EXPRSTACK* exprstack /**< expression stack */
313 )
314 {
315 assert(exprstack != NULL);
316
317 return exprstack->stackpos < 0;
318 }
319
320 /** looks whether given expression is (proper) quadratic and has a given curvature
321 *
322 * If having a given curvature, currently require all arguments of quadratic to be linear.
323 * Hence, not using this for a simple square term, as curvCheckExprhdlr may provide a better condition on argument curvature then.
324 * Also we wouldn't do anything useful for a single bilinear term.
325 * Thus, run on sum's only.
326 */
327 static
328 DECL_CURVCHECK(curvCheckQuadratic)
329 { /*lint --e{715}*/
330 SCIP_EXPR* expr;
331 SCIP_EXPRCURV presentcurv;
332 SCIP_EXPRCURV wantedcurv;
333 SCIP_HASHSET* lonelysquares = NULL;
334 SCIP_Bool isquadratic;
335 int nbilinexprs;
336 int nquadexprs;
337 int i;
338
339 assert(nlexpr != NULL);
340 assert(stack != NULL);
341 assert(nlexpr2origexpr != NULL);
342 assert(success != NULL);
343
344 *success = FALSE;
345
346 if( !nlhdlrdata->cvxquadratic )
347 return SCIP_OKAY;
348
349 if( !SCIPisExprSum(scip, nlexpr) )
350 return SCIP_OKAY;
351
352 wantedcurv = SCIPexprGetCurvature(nlexpr);
353 if( wantedcurv == SCIP_EXPRCURV_LINEAR )
354 return SCIP_OKAY;
355 assert(wantedcurv == SCIP_EXPRCURV_CONVEX || wantedcurv == SCIP_EXPRCURV_CONCAVE);
356
357 expr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr);
358 assert(expr != NULL);
359
360 /* check whether quadratic */
361 SCIP_CALL( SCIPcheckExprQuadratic(scip, expr, &isquadratic) );
362
363 /* if not quadratic, then give up here */
364 if( !isquadratic )
365 return SCIP_OKAY;
366
367 SCIPexprGetQuadraticData(expr, NULL, NULL, NULL, NULL, &nquadexprs, &nbilinexprs, NULL, NULL);
368
369 /* if only single square term (+linear), then give up here (let curvCheckExprhdlr handle this) */
370 if( nquadexprs <= 1 )
371 return SCIP_OKAY;
372
373 /* if root expression is only sum of squares (+linear) and detectsum is disabled, then give up here, too */
374 if( isrootexpr && !nlhdlrdata->detectsum && nbilinexprs == 0 )
375 return SCIP_OKAY;
376
377 /* get curvature of quadratic
378 * TODO as we know what curvature we want, we could first do some simple checks like computing xQx for a random x
379 */
380 SCIP_CALL( SCIPcomputeExprQuadraticCurvature(scip, expr, &presentcurv, assumevarfixed, FALSE) );
381
382 /* if not having desired curvature, return */
383 if( presentcurv != wantedcurv )
384 return SCIP_OKAY;
385
386 *success = TRUE;
387
388 if( !nlhdlrdata->detectsum )
389 {
390 /* first step towards block-decomposition of quadratic term:
391 * collect all square-expressions (in original expr) which have no adjacent bilinear term
392 * we will treat these x^2 as linear, i.e., add an auxvar for them, so x^2 maybe linearized
393 * more efficiently (in particular if x is discrete)
394 */
395 SCIP_CALL( SCIPhashsetCreate(&lonelysquares, SCIPblkmem(scip), nquadexprs) );
396 for( i = 0; i < nquadexprs; ++i )
397 {
398 int nadjbilin;
399 SCIP_EXPR* sqrexpr;
400
401 SCIPexprGetQuadraticQuadTerm(expr, i, NULL, NULL, NULL, &nadjbilin, NULL, &sqrexpr);
402 if( nadjbilin == 0 )
403 {
404 assert(sqrexpr != NULL);
405 SCIP_CALL( SCIPhashsetInsert(lonelysquares, SCIPblkmem(scip), (void*)sqrexpr) );
406 }
407 }
408 }
409
410 /* add immediate children to nlexpr */
411 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, nlexpr, NULL) );
412 assert(SCIPexprGetNChildren(nlexpr) == SCIPexprGetNChildren(expr));
413
414 /* put children that are not square or product on stack
415 * grow child for children that are square or product and put this child on stack
416 * require all children to be linear
417 */
418 for( i = 0; i < SCIPexprGetNChildren(nlexpr); ++i )
419 {
420 SCIP_EXPR* child;
421 SCIP_EXPRCURV curvlinear[2] = { SCIP_EXPRCURV_LINEAR, SCIP_EXPRCURV_LINEAR };
422
423 child = SCIPexprGetChildren(nlexpr)[i];
424 assert(child != NULL);
425
426 assert(SCIPhashmapGetImage(nlexpr2origexpr, (void*)child) == SCIPexprGetChildren(expr)[i]);
427
428 if( SCIPisExprPower(scip, child) && SCIPgetExponentExprPow(child) == 2.0 &&
429 (lonelysquares == NULL || !SCIPhashsetExists(lonelysquares, SCIPexprGetChildren(expr)[i])) )
430 {
431 /* square term that isn't lonely, i.e., orig-version of child is a square-expr and nadjbilin>0 */
432 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, child, curvlinear) );
433 assert(SCIPexprGetNChildren(child) == 1);
434 SCIP_CALL( exprstackPush(scip, stack, 1, SCIPexprGetChildren(child)) );
435 }
436 else if( SCIPisExprProduct(scip, child) && SCIPexprGetNChildren(SCIPexprGetChildren(expr)[i]) == 2 )
437 /* using original version of child here as NChildren(child)==0 atm */
438 {
439 /* bilinear term */
440 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, child, curvlinear) );
441 assert(SCIPexprGetNChildren(child) == 2);
442 SCIP_CALL( exprstackPush(scip, stack, 2, SCIPexprGetChildren(child)) );
443 }
444 else
445 {
446 /* linear term (or term to be considered as linear) */
447 SCIPexprSetCurvature(child, SCIP_EXPRCURV_LINEAR);
448 SCIP_CALL( exprstackPush(scip, stack, 1, &child) );
449 }
450 }
451
452 if( lonelysquares != NULL )
453 SCIPhashsetFree(&lonelysquares, SCIPblkmem(scip));
454
455 return SCIP_OKAY;
456 }
457
458 /** looks whether top of given expression looks like a signomial that can have a given curvature
459 *
460 * e.g., sqrt(x)*sqrt(y) is convex if x,y >= 0 and x and y are convex
461 *
462 * unfortunately, doesn't work for tls, because i) it's originally sqrt(x*y), and ii) it is expanded into some sqrt(z*y+y);
463 * but works for cvxnonsep_nsig
464 */
465 static
466 DECL_CURVCHECK(curvCheckSignomial)
467 { /*lint --e{715}*/
468 SCIP_EXPR* expr;
469 SCIP_EXPR* child;
470 SCIP_Real* exponents;
471 SCIP_INTERVAL* bounds;
472 SCIP_EXPRCURV* curv;
473 int nfactors;
474 int i;
475
476 assert(nlexpr != NULL);
477 assert(stack != NULL);
478 assert(nlexpr2origexpr != NULL);
479 assert(success != NULL);
480
481 *success = FALSE;
482
483 if( !nlhdlrdata->cvxsignomial )
484 return SCIP_OKAY;
485
486 if( !SCIPisExprProduct(scip, nlexpr) )
487 return SCIP_OKAY;
488
489 expr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr);
490 assert(expr != NULL);
491
492 nfactors = SCIPexprGetNChildren(expr);
493 if( nfactors <= 1 ) /* boooring */
494 return SCIP_OKAY;
495
496 SCIP_CALL( SCIPallocBufferArray(scip, &exponents, nfactors) );
497 SCIP_CALL( SCIPallocBufferArray(scip, &bounds, nfactors) );
498 SCIP_CALL( SCIPallocBufferArray(scip, &curv, nfactors) );
499
500 for( i = 0; i < nfactors; ++i )
501 {
502 child = SCIPexprGetChildren(expr)[i];
503 assert(child != NULL);
504
505 if( !SCIPisExprPower(scip, child) )
506 {
507 exponents[i] = 1.0;
508 SCIP_CALL( SCIPevalExprActivity(scip, child) );
509 bounds[i] = SCIPexprGetActivity(child);
510 }
511 else
512 {
513 exponents[i] = SCIPgetExponentExprPow(child);
514 SCIP_CALL( SCIPevalExprActivity(scip, SCIPexprGetChildren(child)[0]) );
515 bounds[i] = SCIPexprGetActivity(SCIPexprGetChildren(child)[0]);
516 }
517 }
518
519 if( !SCIPexprcurvMonomialInv(SCIPexprcurvMultiply(SCIPgetCoefExprProduct(expr), SCIPexprGetCurvature(nlexpr)),
520 nfactors, exponents, bounds, curv) )
521 goto TERMINATE;
522
523 /* add immediate children to nlexpr
524 * some entries in curv actually apply to arguments of pow's, will correct this next
525 */
526 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, nlexpr, curv) );
527 assert(SCIPexprGetNChildren(nlexpr) == nfactors);
528
529 /* put children that are not power on stack
530 * grow child for children that are power and put this child on stack
531 * if extendedform, then require children to be linear
532 * unless they are linear, an auxvar will be introduced for them and thus they will be handled as var here
533 */
534 for( i = 0; i < nfactors; ++i )
535 {
536 child = SCIPexprGetChildren(nlexpr)[i];
537 assert(child != NULL);
538
539 if( SCIPisExprPower(scip, child) )
540 {
541 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, child, &curv[i]) );
542 assert(SCIPexprGetNChildren(child) == 1);
543 child = SCIPexprGetChildren(child)[0];
544 }
545 assert(SCIPexprGetNChildren(child) == 0);
546
547 if( nlhdlrdata->extendedform )
548 {
549 SCIPexprSetCurvature(child, SCIP_EXPRCURV_LINEAR);
550 #ifdef SCIP_DEBUG
551 SCIPinfoMessage(scip, NULL, "Extendedform: Require linearity for ");
552 SCIPprintExpr(scip, child, NULL);
553 SCIPinfoMessage(scip, NULL, "\n");
554 #endif
555 }
556
557 SCIP_CALL( exprstackPush(scip, stack, 1, &child) );
558 }
559
560 *success = TRUE;
561
562 TERMINATE:
563 SCIPfreeBufferArray(scip, &curv);
564 SCIPfreeBufferArray(scip, &bounds);
565 SCIPfreeBufferArray(scip, &exponents);
566
567 return SCIP_OKAY;
568 }
569
570 /** looks for \f$f(c h(x)+d) h(x) \cdot \text{constant}\f$ and tries to conclude conditions on curvature
571 *
572 * Assume \f$h\f$ is univariate:
573 * - First derivative is \f$f'(c h + d) c h' h + f(c h + d) h'\f$.
574 * - Second derivative is \f{align}{&f''(c h + d) c h' c h' h + f'(c h + d) (c h'' h + c h' h') + f'(c h + d) c h' h' + f(c h + d) h'' \\
575 * =& f''(c h + d) c^2 h'^2 h + f'(c h + d) c h'' h + 2 f'(c h + d) c h'^2 + f(c h + d) h''.\f}
576 * Remove always positive factors leaves \f[f''(c h + d) h,\quad f'(c h + d) c h'' h,\quad f'(c h + d) c,\quad f(c h + d) h''.\f]
577 * For convexity we want all these terms to be nonnegative. For concavity we want all of them to be nonpositive.
578 * Note, that in each term either both \f$f'(c h + d)\f$ and \f$c\f$ occur, or none of them.
579 * - Thus, \f$f(c h(x) + d)h(x)\f$ is convex if \f$cf\f$ is monotonically increasing \f$(c f' \geq 0)\f$ and either
580 * - \f$f\f$ is convex \f$(f'' \geq 0)\f$ and \f$h\f$ is nonnegative \f$(h \geq 0)\f$ and \f$h\f$ is convex \f$(h'' \geq 0)\f$ and [\f$f\f$ is nonnegative \f$(f \geq 0)\f$ or \f$h\f$ is linear \f$(h''=0)\f$], or
581 * - \f$f\f$ is concave \f$(f'' \leq 0)\f$ and \f$h\f$ is nonpositive \f$(h \leq 0)\f$ and \f$h\f$ is concave \f$(h'' \leq 0)\f$ and [\f$f\f$ is nonpositive \f$(f \leq 0)\f$ or \f$h\f$ is linear \f$(h''=0)\f$].
582 * - Further, \f$f(c h(x) + d)h(x)\f$ is concave if \f$cf\f$ is monotonically decreasing \f$(c f' \leq 0)\f$ and either
583 * - f is convex \f$(f'' \geq 0)\f$ and \f$h\f$ is nonpositive \f$(h \leq 0)\f$ and \f$h\f$ is concave \f$(h'' \leq 0)\f$ and [\f$f\f$ is nonnegative \f$(f \geq 0)\f$ or \f$h\f$ is linear \f$(h''=0)\f$], or
584 * - f is concave \f$(f'' \leq 0)\f$ and \f$h\f$ is nonnegative \f$(h >= 0)\f$ and \f$h\f$ is convex \f$(h'' \geq 0)\f$ and [\f$f\f$ is nonpositive \f$(f \leq 0)\f$ or \f$h\f$ is linear \f$(h''=0)\f$].
585 *
586 * This should hold also for multivariate and linear \f$h\f$, as things are invariant under linear transformations.
587 * Similar to signomial, I'll assume that this will also hold for other multivariate \f$h\f$ (someone has a formal proof?).
588 */
589 static
590 DECL_CURVCHECK(curvCheckProductComposite)
591 { /*lint --e{715}*/
592 SCIP_EXPR* expr;
593 SCIP_EXPR* f;
594 SCIP_EXPR* h = NULL;
595 SCIP_Real c = 0.0;
596 SCIP_EXPR* ch = NULL; /* c * h */
597 SCIP_INTERVAL fbounds;
598 SCIP_INTERVAL hbounds;
599 SCIP_MONOTONE fmonotonicity;
600 SCIP_EXPRCURV desiredcurv;
601 SCIP_EXPRCURV hcurv;
602 SCIP_EXPRCURV dummy;
603 int fidx;
604
605 assert(nlexpr != NULL);
606 assert(stack != NULL);
607 assert(nlexpr2origexpr != NULL);
608 assert(success != NULL);
609
610 *success = FALSE;
611
612 if( !nlhdlrdata->cvxprodcomp )
613 return SCIP_OKAY;
614
615 if( !SCIPisExprProduct(scip, nlexpr) )
616 return SCIP_OKAY;
617
618 expr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr);
619 assert(expr != NULL);
620
621 if( SCIPexprGetNChildren(expr) != 2 )
622 return SCIP_OKAY;
623
624 /* check whether we have f(c * h(x)) * h(x) or h(x) * f(c * h(x)) */
625 for( fidx = 0; fidx <= 1; ++fidx )
626 {
627 f = SCIPexprGetChildren(expr)[fidx];
628
629 if( SCIPexprGetNChildren(f) != 1 )
630 continue;
631
632 ch = SCIPexprGetChildren(f)[0];
633 c = 1.0;
634 h = ch;
635
636 /* check whether ch is of the form c*h(x), then switch h to child ch */
637 if( SCIPisExprSum(scip, ch) && SCIPexprGetNChildren(ch) == 1 )
638 {
639 c = SCIPgetCoefsExprSum(ch)[0];
640 h = SCIPexprGetChildren(ch)[0];
641 assert(c != 1.0 || SCIPgetConstantExprSum(ch) != 0.0); /* we could handle this, but it should have been simplified away */
642 }
643
644 #ifndef NLHDLR_CONVEX_UNITTEST
645 /* can assume that duplicate subexpressions have been identified and comparing pointer is sufficient */
646 if( SCIPexprGetChildren(expr)[1-fidx] == h )
647 #else
648 /* called from unittest -> duplicate subexpressions were not identified -> compare more expensively */
649 if( SCIPcompareExpr(scip, SCIPexprGetChildren(expr)[1-fidx], h) == 0 )
650 #endif
651 break;
652 }
653 if( fidx == 2 )
654 return SCIP_OKAY;
655
656 #ifdef SCIP_MORE_DEBUG
657 SCIPinfoMessage(scip, NULL, "f(c*h+d)*h with f = %s, c = %g, d = %g, h = ", SCIPexprhdlrGetName(SCIPexprGetHdlr(f)),
658 c, h != ch ? SCIPgetConstantExprSum(ch) : 0.0);
659 SCIPprintExpr(scip, h, NULL);
660 SCIPinfoMessage(scip, NULL, "\n");
661 #endif
662
663 assert(c != 0.0);
664
665 SCIP_CALL( SCIPevalExprActivity(scip, f) );
666 SCIP_CALL( SCIPevalExprActivity(scip, h) );
667 fbounds = SCIPexprGetActivity(f);
668 hbounds = SCIPexprGetActivity(h);
669
670 /* if h has mixed sign, then cannot conclude anything */
671 if( hbounds.inf < 0.0 && hbounds.sup > 0.0 )
672 return SCIP_OKAY;
673
674 SCIP_CALL( SCIPcallExprMonotonicity(scip, f, 0, &fmonotonicity) );
675
676 /* if f is not monotone, then cannot conclude anything */
677 if( fmonotonicity == SCIP_MONOTONE_UNKNOWN )
678 return SCIP_OKAY;
679
680 /* curvature we want to achieve (negate if product has negative coef) */
681 desiredcurv = SCIPexprcurvMultiply(SCIPgetCoefExprProduct(nlexpr), SCIPexprGetCurvature(nlexpr));
682
683 /* now check the conditions as stated above */
684 if( desiredcurv == SCIP_EXPRCURV_CONVEX )
685 {
686 /* f(c h(x)+d)h(x) is convex if c*f is monotonically increasing (c f' >= 0) and either
687 * - f is convex (f'' >= 0) and h is nonnegative (h >= 0) and h is convex (h'' >= 0) and [f is nonnegative (f >= 0) or h is linear (h''=0)], or
688 * - f is concave (f'' <= 0) and h is nonpositive (h <= 0) and h is concave (h'' <= 0) and [f is nonpositive (f <= 0) or h is linear (h''=0)]
689 * as the curvature requirements on f are on f only and not the composition f(h), we can ignore the requirements returned by SCIPcallExprCurvature (last arg)
690 */
691 if( (c > 0.0 && fmonotonicity != SCIP_MONOTONE_INC) || (c < 0.0 && fmonotonicity != SCIP_MONOTONE_DEC) )
692 return SCIP_OKAY;
693
694 /* check whether f can be convex (h>=0) or concave (h<=0), resp., and derive requirements for h */
695 if( hbounds.inf >= 0 )
696 {
697 SCIP_CALL( SCIPcallExprCurvature(scip, f, SCIP_EXPRCURV_CONVEX, success, &dummy) );
698
699 /* now h also needs to be convex; and if f < 0, then h actually needs to be linear */
700 if( fbounds.inf < 0.0 )
701 hcurv = SCIP_EXPRCURV_LINEAR;
702 else
703 hcurv = SCIP_EXPRCURV_CONVEX;
704 }
705 else
706 {
707 SCIP_CALL( SCIPcallExprCurvature(scip, f, SCIP_EXPRCURV_CONCAVE, success, &dummy) );
708
709 /* now h also needs to be concave; and if f > 0, then h actually needs to be linear */
710 if( fbounds.sup > 0.0 )
711 hcurv = SCIP_EXPRCURV_LINEAR;
712 else
713 hcurv = SCIP_EXPRCURV_CONCAVE;
714 }
715 }
716 else
717 {
718 /* f(c h(x)+d)*h(x) is concave if c*f is monotonically decreasing (c f' <= 0) and either
719 * - f is convex (f'' >= 0) and h is nonpositive (h <= 0) and h is concave (h'' <= 0) and [f is nonnegative (f >= 0) or h is linear (h''=0)], or
720 * - f is concave (f'' <= 0) and h is nonnegative (h >= 0) and h is convex (h'' >= 0) and [f is nonpositive (f <= 0) or h is linear (h''=0)]
721 * as the curvature requirements on f are on f only and not the composition f(h), we can ignore the requirements returned by SCIPcallExprCurvature (last arg)
722 */
723 if( (c > 0.0 && fmonotonicity != SCIP_MONOTONE_DEC) || (c < 0.0 && fmonotonicity != SCIP_MONOTONE_INC) )
724 return SCIP_OKAY;
725
726 /* check whether f can be convex (h<=0) or concave (h>=0), resp., and derive requirements for h */
727 if( hbounds.sup <= 0 )
728 {
729 SCIP_CALL( SCIPcallExprCurvature(scip, f, SCIP_EXPRCURV_CONVEX, success, &dummy) );
730
731 /* now h also needs to be concave; and if f < 0, then h actually needs to be linear */
732 if( fbounds.inf < 0.0 )
733 hcurv = SCIP_EXPRCURV_LINEAR;
734 else
735 hcurv = SCIP_EXPRCURV_CONCAVE;
736 }
737 else
738 {
739 SCIP_CALL( SCIPcallExprCurvature(scip, f, SCIP_EXPRCURV_CONCAVE, success, &dummy) );
740
741 /* now h also needs to be convex; and if f > 0, then h actually needs to be linear */
742 if( fbounds.sup > 0.0 )
743 hcurv = SCIP_EXPRCURV_LINEAR;
744 else
745 hcurv = SCIP_EXPRCURV_CONVEX;
746 }
747 }
748
749 if( !*success )
750 return SCIP_OKAY;
751
752 /* add immediate children (f and ch) to nlexpr; we set required curvature for h further below */
753 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, nlexpr, NULL) );
754 assert(SCIPexprGetNChildren(nlexpr) == 2);
755
756 /* copy of f (and h) should have same child position in nlexpr as f (and h) has on expr (resp) */
757 assert(SCIPhashmapGetImage(nlexpr2origexpr, (void*)SCIPexprGetChildren(nlexpr)[fidx]) == (void*)f);
758 #ifndef NLHDLR_CONVEX_UNITTEST
759 assert(SCIPhashmapGetImage(nlexpr2origexpr, (void*)SCIPexprGetChildren(nlexpr)[1-fidx]) == (void*)h);
760 #endif
761 /* push this h onto stack for further checking */
762 SCIP_CALL( exprstackPush(scip, stack, 1, &(SCIPexprGetChildren(nlexpr)[1-fidx])) );
763
764 /* if we prefer extended formulations, then we always want h() to be linear */
765 if( nlhdlrdata->extendedform )
766 hcurv = SCIP_EXPRCURV_LINEAR;
767
768 /* h-child of product should have curvature hcurv */
769 SCIPexprSetCurvature(SCIPexprGetChildren(nlexpr)[1-fidx], hcurv);
770
771 if( h != ch )
772 {
773 /* add copy of ch as child to copy of f */
774 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, SCIPexprGetChildren(nlexpr)[fidx], NULL) );
775 assert(SCIPexprGetNChildren(SCIPexprGetChildren(nlexpr)[fidx]) == 1);
776 assert(SCIPhashmapGetImage(nlexpr2origexpr, (void*)SCIPexprGetChildren(SCIPexprGetChildren(nlexpr)[fidx])[0]) == (void*)ch);
777
778 /* add copy of h (created above as child of product) as child in copy of ch */
779 SCIP_CALL( SCIPappendExprChild(scip,
780 SCIPexprGetChildren(SCIPexprGetChildren(nlexpr)[fidx])[0] /* copy of ch */,
781 SCIPexprGetChildren(nlexpr)[1-fidx] /* copy of h */) );
782 }
783 else
784 {
785 /* add copy of h (created above as child of product) as child in copy of f */
786 SCIP_CALL( SCIPappendExprChild(scip,
787 SCIPexprGetChildren(nlexpr)[fidx] /* copy of f */,
788 SCIPexprGetChildren(nlexpr)[1-fidx] /* copy of h */) );
789 }
790
791 return SCIP_OKAY;
792 }
793
794 /** use expression handlers curvature callback to check whether given curvature can be achieved */
795 static
796 DECL_CURVCHECK(curvCheckExprhdlr)
797 { /*lint --e{715}*/
798 SCIP_EXPR* origexpr;
799 int nchildren;
800 SCIP_EXPRCURV* childcurv;
801
802 assert(nlexpr != NULL);
803 assert(stack != NULL);
804 assert(nlexpr2origexpr != NULL);
805 assert(success != NULL);
806
807 origexpr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, nlexpr);
808 assert(origexpr != NULL);
809 nchildren = SCIPexprGetNChildren(origexpr);
810
811 if( nchildren == 0 )
812 {
813 /* if originally no children, then should be var or value, which should have every curvature,
814 * so should always be success
815 */
816 SCIP_CALL( SCIPcallExprCurvature(scip, origexpr, SCIPexprGetCurvature(nlexpr), success, NULL) );
817 assert(*success);
818
819 return SCIP_OKAY;
820 }
821
822 /* ignore sums if > 1 children
823 * NOTE: this means that for something like 1+f(x), even if f is a trivial convex expression, we would handle 1+f(x)
824 * with this nlhdlr, instead of formulating this as 1+z and handling z=f(x) with the default nlhdlr, i.e., the exprhdlr
825 * today, I prefer handling this here, as it avoids introducing an extra auxiliary variable
826 */
827 if( isrootexpr && !nlhdlrdata->detectsum && SCIPisExprSum(scip, nlexpr) && nchildren > 1 )
828 return SCIP_OKAY;
829
830 SCIP_CALL( SCIPallocBufferArray(scip, &childcurv, nchildren) );
831
832 /* check whether and under which conditions origexpr can have desired curvature */
833 SCIP_CALL( SCIPcallExprCurvature(scip, origexpr, SCIPexprGetCurvature(nlexpr), success, childcurv) );
834 #ifdef SCIP_MORE_DEBUG
835 SCIPprintExpr(scip, origexpr, NULL);
836 SCIPinfoMessage(scip, NULL, " is %s? %d\n", SCIPexprcurvGetName(SCIPexprGetCurvature(nlexpr)), *success);
837 #endif
838 if( !*success )
839 goto TERMINATE;
840
841 /* if origexpr can have curvature curv, then don't treat it as leaf, but include its children */
842 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, nlexpr, childcurv) );
843 assert(SCIPexprGetChildren(nlexpr) != NULL);
844 assert(SCIPexprGetNChildren(nlexpr) == nchildren);
845
846 /* If we prefer extended formulations, then require all children to be linear.
847 * Unless they are, auxvars will be introduced and they will be handles as variables, which can be an
848 * advantage in the context of extended formulations.
849 */
850 if( nlhdlrdata->extendedform )
851 {
852 int i;
853 for( i = 0; i < nchildren; ++i )
854 SCIPexprSetCurvature(SCIPexprGetChildren(nlexpr)[i], SCIP_EXPRCURV_LINEAR);
855 #ifdef SCIP_DEBUG
856 SCIPinfoMessage(scip, NULL, "require linearity for children of ");
857 SCIPprintExpr(scip, origexpr, NULL);
858 SCIPinfoMessage(scip, NULL, "\n");
859 #endif
860 }
861
862 /* add children expressions to to-do list (stack) */
863 SCIP_CALL( exprstackPush(scip, stack, nchildren, SCIPexprGetChildren(nlexpr)) );
864
865 TERMINATE:
866 SCIPfreeBufferArray(scip, &childcurv);
867
868 return SCIP_OKAY;
869 }
870
871 /** curvature check and expression-growing methods
872 *
873 * some day this could be plugins added by users at runtime, but for now we have a fixed list here
874 * @note curvCheckExprhdlr should be last
875 */
876 static DECL_CURVCHECK((*CURVCHECKS[])) = { curvCheckProductComposite, curvCheckSignomial, curvCheckQuadratic, curvCheckExprhdlr };
877 /** number of curvcheck methods */
878 static const int NCURVCHECKS = sizeof(CURVCHECKS) / sizeof(void*);
879
880 /** checks whether expression is a sum with more than one child and each child being a variable or going to be a variable if `expr` is a nlhdlr-specific copy
881 *
882 * Within constructExpr(), we can have an expression of any type which is a copy of an original expression,
883 * but without children. At the end of constructExpr() (after the loop with the stack), these expressions
884 * will remain as leafs and will eventually be turned into variables in collectLeafs(). Thus, we treat
885 * every child that has no children as if it were a variable. Theoretically, there is still the possibility
886 * that it could be a constant (value-expression), but simplify should have removed these.
887 */
888 static
889 SCIP_Bool exprIsMultivarLinear(
890 SCIP* scip, /**< SCIP data structure */
891 SCIP_EXPR* expr /**< expression to check */
892 )
893 {
894 int nchildren;
895 int c;
896
897 assert(expr != NULL);
898
899 if( !SCIPisExprSum(scip, expr) )
900 return FALSE;
901
902 nchildren = SCIPexprGetNChildren(expr);
903 if( nchildren <= 1 )
904 return FALSE;
905
906 for( c = 0; c < nchildren; ++c )
907 /*if( !SCIPisExprVar(scip, SCIPexprGetChildren(expr)[c]) ) */
908 if( SCIPexprGetNChildren(SCIPexprGetChildren(expr)[c]) > 0 )
909 return FALSE;
910
911 return TRUE;
912 }
913
914 /** constructs a subexpression (as nlhdlr-expression) of maximal size that has a given curvature
915 *
916 * If the curvature cannot be achieved for an expression in the original expression graph,
917 * then this expression becomes a leaf in the nlhdlr-expression.
918 *
919 * Sets `*rootnlexpr` to NULL if failed.
920 */
921 static
922 SCIP_RETCODE constructExpr(
923 SCIP* scip, /**< SCIP data structure */
924 SCIP_NLHDLRDATA* nlhdlrdata, /**< nonlinear handler data */
925 SCIP_EXPR** rootnlexpr, /**< buffer to store created expression */
926 SCIP_HASHMAP* nlexpr2origexpr, /**< mapping from our expression copy to original expression */
927 int* nleafs, /**< number of leafs in constructed expression */
928 SCIP_EXPR* rootexpr, /**< expression */
929 SCIP_EXPRCURV curv, /**< curvature to achieve */
930 SCIP_HASHMAP* assumevarfixed, /**< hashmap containing variables that should be assumed to be fixed, or NULL */
931 SCIP_Bool* curvsuccess /**< pointer to store whether the curvature could be achieved
932 w.r.t. the original variables (might be NULL) */
933 )
934 {
935 SCIP_EXPR* nlexpr;
936 EXPRSTACK stack; /* to do list: expressions where to check whether they can have the desired curvature when taking their children into account */
937 int oldstackpos;
938 SCIP_Bool isrootexpr = TRUE;
939
940 assert(scip != NULL);
941 assert(nlhdlrdata != NULL);
942 assert(rootnlexpr != NULL);
943 assert(nlexpr2origexpr != NULL);
944 assert(nleafs != NULL);
945 assert(rootexpr != NULL);
946 assert(curv == SCIP_EXPRCURV_CONVEX || curv == SCIP_EXPRCURV_CONCAVE);
947
948 /* create root expression */
949 SCIP_CALL( nlhdlrExprCreate(scip, nlexpr2origexpr, rootnlexpr, rootexpr, curv) );
950
951 *nleafs = 0;
952 if( curvsuccess != NULL )
953 *curvsuccess = TRUE;
954
955 SCIP_CALL( exprstackInit(scip, &stack, 20) );
956 SCIP_CALL( exprstackPush(scip, &stack, 1, rootnlexpr) );
957 while( !exprstackIsEmpty(&stack) )
958 {
959 /* take expression from stack */
960 nlexpr = exprstackPop(&stack);
961 assert(nlexpr != NULL);
962 assert(SCIPexprGetNChildren(nlexpr) == 0);
963
964 oldstackpos = stack.stackpos;
965 if( nlhdlrdata->isnlhdlrconvex && !SCIPexprhdlrHasBwdiff(SCIPexprGetHdlr(nlexpr)) )
966 {
967 /* if bwdiff is not implemented, then we could not generate cuts in the convex nlhdlr, so "stop" (treat nlexpr as variable) */
968 }
969 else if( !nlhdlrdata->isnlhdlrconvex && exprIsMultivarLinear(scip, (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr)) )
970 {
971 /* if we are in the concave handler, we would like to treat linear multivariate subexpressions by a new auxvar always,
972 * e.g., handle log(x+y) as log(z), z=x+y, because the estimation problem will be smaller then without making the estimator worse
973 * (cons_nonlinear does this, too)
974 * this check takes care of this when x and y are original variables
975 * however, it isn't unlikely that we will have sums that become linear after we add auxvars for some children
976 * this will be handled in a postprocessing below
977 * for now, the check is performed on the original expression since there is not enough information in nlexpr yet
978 */
979 #ifdef SCIP_MORE_DEBUG
980 SCIPprintExpr(scip, SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr), NULL);
981 SCIPinfoMessage(scip, NULL, "... is a multivariate linear sum that we'll treat as auxvar\n");
982 #endif
983 }
984 else if( SCIPexprGetCurvature(nlexpr) != SCIP_EXPRCURV_UNKNOWN )
985 {
986 /* if we are here, either convexity or concavity is required; try to check for this curvature */
987 SCIP_Bool success;
988 int method;
989
990 /* try through curvature check methods until one succeeds */
991 for( method = 0; method < NCURVCHECKS; ++method )
992 {
993 SCIP_CALL( CURVCHECKS[method](scip, nlexpr, isrootexpr, &stack, nlexpr2origexpr, nlhdlrdata, assumevarfixed, &success) );
994 if( success )
995 break;
996 }
997 }
998 else
999 {
1000 /* if we don't care about curvature in this subtree anymore (very unlikely),
1001 * then only continue iterating this subtree to assemble leaf expressions
1002 */
1003 SCIP_CALL( nlhdlrExprGrowChildren(scip, nlexpr2origexpr, nlexpr, NULL) );
1004
1005 /* add children expressions, if any, to to-do list (stack) */
1006 SCIP_CALL( exprstackPush(scip, &stack, SCIPexprGetNChildren(nlexpr), SCIPexprGetChildren(nlexpr)) );
1007 }
1008 assert(stack.stackpos >= oldstackpos); /* none of the methods above should have removed something from the stack */
1009
1010 isrootexpr = FALSE;
1011
1012 /* if nothing was added, then none of the successors of nlexpr were added to the stack
1013 * this is either because nlexpr was already a variable or value expressions, thus a leaf,
1014 * or because the desired curvature could not be achieved, so it will be handled as variables, thus a leaf
1015 */
1016 if( stack.stackpos == oldstackpos )
1017 {
1018 ++*nleafs;
1019
1020 /* check whether the new leaf is not an original variable (or constant) */
1021 if( curvsuccess != NULL && !SCIPisExprVar(scip, nlexpr) && !SCIPisExprValue(scip, nlexpr) )
1022 *curvsuccess = FALSE;
1023 }
1024 }
1025
1026 exprstackFree(scip, &stack);
1027
1028 if( !nlhdlrdata->isnlhdlrconvex && *rootnlexpr != NULL )
1029 {
1030 /* remove multivariate linear subexpressions, that is, change some f(z1+z2) into f(z3) (z3=z1+z2 will be done by nlhdlr_default)
1031 * this handles the case that was not covered by the above check, which could recognize f(x+y) for x, y original variables
1032 */
1033 SCIP_EXPRITER* it;
1034
1035 SCIP_CALL( SCIPcreateExpriter(scip, &it) );
1036 SCIP_CALL( SCIPexpriterInit(it, *rootnlexpr, SCIP_EXPRITER_DFS, FALSE) );
1037 SCIPexpriterSetStagesDFS(it, SCIP_EXPRITER_VISITINGCHILD);
1038
1039 while( !SCIPexpriterIsEnd(it) )
1040 {
1041 SCIP_EXPR* child;
1042
1043 child = SCIPexpriterGetChildExprDFS(it);
1044 assert(child != NULL);
1045
1046 /* We want to change some f(x+y+z) into just f(), where f is the expression the iterator points to
1047 * and x+y+z is child. A child of a child, e.g., z, may not be a variable yet (these are added in collectLeafs later),
1048 * but an expression of some nonlinear type without children.
1049 */
1050 if( exprIsMultivarLinear(scip, child) )
1051 {
1052 /* turn child (x+y+z) into a sum without children
1053 * collectLeafs() should then replace this by an auxvar
1054 */
1055 #ifdef SCIP_MORE_DEBUG
1056 SCIPprintExpr(scip, child, NULL);
1057 SCIPinfoMessage(scip, NULL, "... is a multivariate linear sum that we'll treat as auxvar instead (postprocess)\n");
1058 #endif
1059
1060 /* TODO remove children from nlexpr2origexpr ?
1061 * should also do this if they are not used somewhere else; we could check nuses for this
1062 * however, it shouldn't matter to have some stray entries in the hashmap either
1063 */
1064 SCIP_CALL( SCIPremoveExprChildren(scip, child) );
1065 assert(SCIPexprGetNChildren(child) == 0);
1066
1067 (void) SCIPexpriterSkipDFS(it);
1068 }
1069 else
1070 {
1071 (void) SCIPexpriterGetNext(it);
1072 }
1073 }
1074
1075 SCIPfreeExpriter(&it);
1076 }
1077
1078 if( *rootnlexpr != NULL )
1079 {
1080 SCIP_Bool istrivial = TRUE;
1081
1082 /* if handletrivial is enabled, then only require that rootnlexpr itself has required curvature (so has children; see below) and
1083 * that we are not a trivial sum (because the previous implementation of this nlhdlr didn't allow this, either)
1084 */
1085 if( !nlhdlrdata->handletrivial || SCIPisExprSum(scip, *rootnlexpr) )
1086 {
1087 /* if all children do not have children, i.e., are variables, or will be replaced by auxvars, then free
1088 * also if rootnlexpr has no children, then free
1089 */
1090 int i;
1091 for( i = 0; i < SCIPexprGetNChildren(*rootnlexpr); ++i )
1092 {
1093 if( SCIPexprGetNChildren(SCIPexprGetChildren(*rootnlexpr)[i]) > 0 )
1094 {
1095 istrivial = FALSE;
1096 break;
1097 }
1098 }
1099 }
1100 else if( SCIPexprGetNChildren(*rootnlexpr) > 0 ) /* if handletrivial, then just require children */
1101 istrivial = FALSE;
1102
1103 if( istrivial )
1104 {
1105 SCIP_CALL( SCIPreleaseExpr(scip, rootnlexpr) );
1106 }
1107 }
1108
1109 return SCIP_OKAY;
1110 }
1111
1112 /** collects (non-value) leaf expressions and ensure that they correspond to a variable (original or auxiliary)
1113 *
1114 * For children where we could not achieve the desired curvature, get the auxvar and replace the child by a
1115 * var-expression that points to this auxvar.
1116 * Collect all leaf expressions (if not a value-expression) and index them.
1117 */
1118 static
1119 SCIP_RETCODE collectLeafs(
1120 SCIP* scip, /**< SCIP data structure */
1121 SCIP_NLHDLREXPRDATA* nlhdlrexprdata /**< nlhdlr expression data */
1122 )
1123 {
1124 SCIP_EXPRITER* it;
1125 SCIP_EXPR* nlexpr;
1126 SCIP_HASHMAP* leaf2index;
1127 int i;
1128
1129 assert(nlhdlrexprdata != NULL);
1130 assert(nlhdlrexprdata->nlexpr != NULL);
1131 assert(nlhdlrexprdata->nlexpr2origexpr != NULL);
1132 /* nleafs should be the upper bound on the number of variables given by constructExpr
1133 * leafexprs should be NULL, as this is what we want to setup here
1134 */
1135 assert(nlhdlrexprdata->nleafs > 0);
1136 assert(nlhdlrexprdata->leafexprs == NULL);
1137
1138 /* collect all auxvars and collect all variables */
1139 SCIP_CALL( SCIPhashmapCreate(&leaf2index, SCIPblkmem(scip), nlhdlrexprdata->nleafs) );
1140 nlhdlrexprdata->nleafs = 0; /* we start a new count, this time skipping value-expressions */
1141
1142 SCIP_CALL( SCIPcreateExpriter(scip, &it) );
1143 SCIP_CALL( SCIPexpriterInit(it, nlhdlrexprdata->nlexpr, SCIP_EXPRITER_DFS, FALSE) );
1144 SCIPexpriterSetStagesDFS(it, SCIP_EXPRITER_VISITINGCHILD);
1145
1146 for( nlexpr = SCIPexpriterGetCurrent(it); !SCIPexpriterIsEnd(it); nlexpr = SCIPexpriterGetNext(it) )
1147 {
1148 SCIP_EXPR* child;
1149 SCIP_EXPR* origexpr;
1150
1151 assert(nlexpr != NULL);
1152
1153 child = SCIPexpriterGetChildExprDFS(it);
1154
1155 /* if the to-be-visited child has children, then it doesn't need to be replaced by a new expression (representing the auxvar) */
1156 if( SCIPexprGetNChildren(child) > 0 )
1157 continue;
1158
1159 origexpr = (SCIP_EXPR*)SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)child);
1160 assert(origexpr != NULL);
1161
1162 if( SCIPexprGetNChildren(origexpr) > 0 )
1163 {
1164 SCIP_EXPR* newchild;
1165 int childidx;
1166 SCIP_VAR* var;
1167
1168 /* having a child that had children in original but not in copy means that we could not achieve the desired curvature
1169 * thus, replace by a new child that points to the auxvar of the original expression
1170 * we registered in createNlhdlrExprData that we need an auxvar, so it should exist now
1171 */
1172 var = SCIPgetExprAuxVarNonlinear(origexpr);
1173 assert(var != NULL);
1174
1175 SCIP_CALL( SCIPcreateExprVar(scip, &newchild, var, NULL, NULL) ); /* this captures newchild once */
1176
1177 childidx = SCIPexpriterGetChildIdxDFS(it);
1178 SCIP_CALL( SCIPreplaceExprChild(scip, nlexpr, childidx, newchild) ); /* this captures newchild again */
1179
1180 /* do not remove child->origexpr from hashmap, as child may appear again due to common subexprs
1181 * (created by curvCheckProductComposite, for example)
1182 * if it doesn't reappear, though, but the memory address is reused, we need to make sure it
1183 * points to the right origexpr
1184 */
1185 /* SCIP_CALL( SCIPhashmapRemove(nlexpr2origexpr, (void*)child) ); */
1186 SCIP_CALL( SCIPhashmapSetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)newchild, (void*)origexpr) );
1187
1188 if( !SCIPhashmapExists(leaf2index, (void*)newchild) )
1189 {
1190 /* new leaf -> new index and remember in hashmap */
1191 SCIP_CALL( SCIPhashmapInsertInt(leaf2index, (void*)newchild, nlhdlrexprdata->nleafs++) );
1192 }
1193
1194 child = newchild;
1195 SCIP_CALL( SCIPreleaseExpr(scip, &newchild) ); /* because it was captured by both create and replace */
1196 }
1197 else if( SCIPisExprVar(scip, child) )
1198 {
1199 /* if variable, then add to hashmap, if not already there */
1200 if( !SCIPhashmapExists(leaf2index, (void*)child) )
1201 {
1202 SCIP_CALL( SCIPhashmapInsertInt(leaf2index, (void*)child, nlhdlrexprdata->nleafs++) );
1203 }
1204 }
1205 /* else: it's probably a value-expression, nothing to do */
1206
1207 /* update integrality flag for future leaf expressions: convex nlhdlr may use this information */
1208 SCIP_CALL( SCIPcomputeExprIntegrality(scip, child) );
1209 }
1210 assert(nlhdlrexprdata->nleafs > 0);
1211
1212 SCIPfreeExpriter(&it);
1213
1214 /* assemble auxvars array */
1215 SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(nlhdlrexprdata->leafexprs), nlhdlrexprdata->nleafs) );
1216 for( i = 0; i < SCIPhashmapGetNEntries(leaf2index); ++i )
1217 {
1218 SCIP_HASHMAPENTRY* entry;
1219 SCIP_EXPR* leaf;
1220 int idx;
1221
1222 entry = SCIPhashmapGetEntry(leaf2index, i);
1223 if( entry == NULL )
1224 continue;
1225
1226 leaf = (SCIP_EXPR*) SCIPhashmapEntryGetOrigin(entry);
1227 assert(leaf != NULL);
1228 assert(SCIPisExprVar(scip, leaf));
1229
1230 idx = SCIPhashmapEntryGetImageInt(entry);
1231 assert(idx >= 0);
1232 assert(idx < nlhdlrexprdata->nleafs);
1233
1234 nlhdlrexprdata->leafexprs[idx] = leaf;
1235
1236 SCIPdebugMsg(scip, "leaf %d: <%s>\n", idx, SCIPvarGetName(SCIPgetVarExprVar(leaf)));
1237 }
1238
1239 SCIPhashmapFree(&leaf2index);
1240
1241 return SCIP_OKAY;
1242 }
1243
1244 /** creates nonlinear handler expression data structure and registers expr usage */
1245 static
1246 SCIP_RETCODE createNlhdlrExprData(
1247 SCIP* scip, /**< SCIP data structure */
1248 SCIP_NLHDLRDATA* nlhdlrdata, /**< nlhdlr data */
1249 SCIP_NLHDLREXPRDATA** nlhdlrexprdata, /**< pointer to store nlhdlr expression data */
1250 SCIP_EXPR* expr, /**< original expression */
1251 SCIP_EXPR* nlexpr, /**< our copy of expression */
1252 SCIP_HASHMAP* nlexpr2origexpr, /**< mapping of expression copy to original */
1253 int nleafs, /**< number of leafs as counted by constructExpr */
1254 SCIP_NLHDLR_METHOD participating /**< the enfo methods in which we plan to participate */
1255 )
1256 {
1257 SCIP_EXPRITER* it;
1258 SCIP_Bool usingaux;
1259
1260 assert(scip != NULL);
1261 assert(expr != NULL);
1262 assert(nlhdlrexprdata != NULL);
1263 assert(*nlhdlrexprdata == NULL);
1264 assert(nlexpr != NULL);
1265 assert(nlexpr2origexpr != NULL);
1266
1267 assert(SCIPexprGetNChildren(nlexpr) > 0);
1268 assert(SCIPexprGetChildren(nlexpr) != NULL);
1269
1270 SCIP_CALL( SCIPallocClearBlockMemory(scip, nlhdlrexprdata) );
1271 (*nlhdlrexprdata)->nlexpr = nlexpr;
1272 (*nlhdlrexprdata)->nlexpr2origexpr = nlexpr2origexpr;
1273 (*nlhdlrexprdata)->nleafs = nleafs;
1274
1275 usingaux = FALSE;
1276
1277 SCIP_CALL( SCIPcreateExpriter(scip, &it) );
1278 SCIP_CALL( SCIPexpriterInit(it, nlexpr, SCIP_EXPRITER_DFS, FALSE) );
1279 SCIPexpriterSetStagesDFS(it, SCIP_EXPRITER_VISITINGCHILD);
1280
1281 for( ; !SCIPexpriterIsEnd(it); (void) SCIPexpriterGetNext(it) )
1282 {
1283 SCIP_EXPR* child;
1284 SCIP_EXPR* origexpr;
1285
1286 /* check whether to-be-visited child needs to be replaced by a new expression (representing the auxvar)
1287 * if child has children, then that is not the case
1288 * if child has no children, but also corresponding origexpr has no chilren, then this is also not the case
1289 */
1290 child = SCIPexpriterGetChildExprDFS(it);
1291 if( SCIPexprGetNChildren(child) > 0 )
1292 continue;
1293
1294 origexpr = (SCIP_EXPR*)SCIPhashmapGetImage(nlexpr2origexpr, (void*)child);
1295 assert(origexpr != NULL);
1296
1297 /* if child had children in original but not in copy means that we could not achieve the desired curvature
1298 * thus, we will later replace by a new child that points to the auxvar of the original expression
1299 * as we do not have the auxvar now, we will only register that we will need the auxvar later (if origexpr isn't a variable or constant)
1300 * if we are working for the concave nlhdlr, then we also indicate interest on the exprs activity for estimate (distinguish below or above)
1301 */
1302 SCIP_CALL( SCIPregisterExprUsageNonlinear(scip, origexpr,
1303 SCIPexprGetNChildren(origexpr) > 0, FALSE,
1304 !nlhdlrdata->isnlhdlrconvex && (participating & SCIP_NLHDLR_METHOD_SEPABELOW),
1305 !nlhdlrdata->isnlhdlrconvex && (participating & SCIP_NLHDLR_METHOD_SEPAABOVE)) );
1306
1307 /* remember that we use an auxvar */
1308 if( SCIPexprGetNChildren(origexpr) > 0 )
1309 usingaux = TRUE;
1310 }
1311
1312 SCIPfreeExpriter(&it);
1313
1314 #ifdef SCIP_DEBUG
1315 SCIPprintExpr(scip, nlexpr, NULL);
1316 SCIPinfoMessage(scip, NULL, " (%p) is handled as %s\n", SCIPhashmapGetImage(nlexpr2origexpr, (void*)nlexpr),
1317 SCIPexprcurvGetName(SCIPexprGetCurvature(nlexpr)));
1318 #endif
1319
1320 /* If we don't work on the extended formulation, then set curvature also in original expression
1321 * (in case someone wants to pick this up; this might be removed again).
1322 * This doesn't ensure that every convex or concave original expression is actually marked here.
1323 * Not only because our tests are incomprehensive, but also because we may not detect on sums,
1324 * prefer extended formulations (in nlhdlr_convex), or introduce auxvars for linear subexpressions
1325 * on purpose (in nlhdlr_concave).
1326 */
1327 if( !usingaux )
1328 SCIPexprSetCurvature(expr, SCIPexprGetCurvature(nlexpr));
1329
1330 return SCIP_OKAY;
1331 }
1332
1333 /** adds an estimator for a vertex-polyhedral (e.g., concave) function to a given rowprep
1334 *
1335 * Calls \ref SCIPcomputeFacetVertexPolyhedralNonlinear() for given function and
1336 * box set to local bounds of auxiliary variables.
1337 */
1338 static
1339 SCIP_RETCODE estimateVertexPolyhedral(
1340 SCIP* scip, /**< SCIP data structure */
1341 SCIP_CONSHDLR* conshdlr, /**< nonlinear constraint handler */
1342 SCIP_NLHDLR* nlhdlr, /**< nonlinear handler */
1343 SCIP_NLHDLREXPRDATA* nlhdlrexprdata, /**< nonlinear handler expression data */
1344 SCIP_SOL* sol, /**< solution to use, unless usemidpoint is TRUE */
1345 SCIP_Bool usemidpoint, /**< whether to use the midpoint of the domain instead of sol */
1346 SCIP_Bool overestimate, /**< whether over- or underestimating */
1347 SCIP_Real targetvalue, /**< a target value to achieve; if not reachable, then can give up early */
1348 SCIP_ROWPREP* rowprep, /**< rowprep where to store estimator */
1349 SCIP_Bool* success /**< buffer to store whether successful */
1350 )
1351 {
1352 SCIP_NLHDLRDATA* nlhdlrdata;
1353 VERTEXPOLYFUN_EVALDATA evaldata;
1354 SCIP_Real* xstar;
1355 SCIP_Real* box;
1356 SCIP_Real facetconstant;
1357 SCIP_VAR* var;
1358 int i;
1359 SCIP_Bool allfixed;
1360
1361 assert(scip != NULL);
1362 assert(nlhdlr != NULL);
1363 assert(nlhdlrexprdata != NULL);
1364 assert(rowprep != NULL);
1365 assert(success != NULL);
1366
1367 *success = FALSE;
1368
1369 /* caller is responsible to have checked whether we can estimate, i.e., expression curvature and overestimate flag match */
1370 assert( overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONCAVE); /* if underestimate, then must be concave */
1371 assert(!overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONVEX); /* if overestimate, then must be convex */
1372
1373 #ifdef SCIP_DEBUG
1374 SCIPinfoMessage(scip, NULL, "%sestimate expression ", overestimate ? "over" : "under");
1375 SCIPprintExpr(scip, nlhdlrexprdata->nlexpr, NULL);
1376 SCIPinfoMessage(scip, NULL, " at point\n");
1377 for( i = 0; i < nlhdlrexprdata->nleafs; ++i )
1378 {
1379 var = SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[i]);
1380 assert(var != NULL);
1381
1382 SCIPinfoMessage(scip, NULL, " <%s> = %g [%g,%g]\n", SCIPvarGetName(var),
1383 usemidpoint ? 0.5 * (SCIPvarGetLbLocal(var) + SCIPvarGetUbLocal(var)) : SCIPgetSolVal(scip, sol, var),
1384 SCIPvarGetLbLocal(var), SCIPvarGetUbLocal(var));
1385 }
1386 #endif
1387
1388 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
1389 assert(nlhdlrdata != NULL);
1390
1391 if( nlhdlrdata->evalsol == NULL )
1392 {
1393 SCIP_CALL( SCIPcreateSol(scip, &nlhdlrdata->evalsol, NULL) );
1394 }
1395
1396 evaldata.nlhdlrexprdata = nlhdlrexprdata;
1397 evaldata.evalsol = nlhdlrdata->evalsol;
1398 evaldata.scip = scip;
1399
1400 SCIP_CALL( SCIPallocBufferArray(scip, &xstar, nlhdlrexprdata->nleafs) );
1401 SCIP_CALL( SCIPallocBufferArray(scip, &box, 2*nlhdlrexprdata->nleafs) );
1402
1403 allfixed = TRUE;
1404 for( i = 0; i < nlhdlrexprdata->nleafs; ++i )
1405 {
1406 var = SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[i]);
1407 assert(var != NULL);
1408
1409 box[2*i] = SCIPvarGetLbLocal(var);
1410 if( SCIPisInfinity(scip, -box[2*i]) )
1411 {
1412 SCIPdebugMsg(scip, "lower bound at -infinity, no estimate possible\n");
1413 goto TERMINATE;
1414 }
1415
1416 box[2*i+1] = SCIPvarGetUbLocal(var);
1417 if( SCIPisInfinity(scip, box[2*i+1]) )
1418 {
1419 SCIPdebugMsg(scip, "upper bound at +infinity, no estimate possible\n");
1420 goto TERMINATE;
1421 }
1422
1423 if( !SCIPisRelEQ(scip, box[2*i], box[2*i+1]) )
1424 allfixed = FALSE;
1425
1426 if( usemidpoint )
1427 xstar[i] = 0.5 * (box[2*i] + box[2*i+1]);
1428 else
1429 xstar[i] = SCIPgetSolVal(scip, sol, var);
1430 assert(xstar[i] != SCIP_INVALID);
1431 }
1432
1433 if( allfixed )
1434 {
1435 /* SCIPcomputeFacetVertexPolyhedralNonlinear prints a warning and does not succeed if all is fixed */
1436 SCIPdebugMsg(scip, "all variables fixed, skip estimate\n");
1437 goto TERMINATE;
1438 }
1439
1440 SCIP_CALL( SCIPensureRowprepSize(scip, rowprep, nlhdlrexprdata->nleafs + 1) );
1441
1442 SCIP_CALL( SCIPcomputeFacetVertexPolyhedralNonlinear(scip, conshdlr, overestimate, nlhdlrExprEvalConcave, (void*)&evaldata,
1443 xstar, box, nlhdlrexprdata->nleafs, targetvalue, success, SCIProwprepGetCoefs(rowprep), &facetconstant) );
1444
1445 if( !*success )
1446 {
1447 SCIPdebugMsg(scip, "failed to compute facet of convex hull\n");
1448 goto TERMINATE;
1449 }
1450
1451 SCIProwprepSetLocal(rowprep, TRUE);
1452 SCIProwprepAddConstant(rowprep, facetconstant);
1453 for( i = 0; i < nlhdlrexprdata->nleafs; ++i )
1454 {
1455 SCIP_CALL( SCIPaddRowprepTerm(scip, rowprep, SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[i]), SCIProwprepGetCoefs(rowprep)[i]) );
1456 }
1457
1458 #ifdef SCIP_DEBUG
1459 SCIPinfoMessage(scip, NULL, "computed estimator: ");
1460 SCIPprintRowprep(scip, rowprep, NULL);
1461 #endif
1462
1463 TERMINATE:
1464 SCIPfreeBufferArray(scip, &box);
1465 SCIPfreeBufferArray(scip, &xstar);
1466
1467 return SCIP_OKAY;
1468 }
1469
1470 /** adds an estimator computed via a gradient to a given rowprep */
1471 static
1472 SCIP_RETCODE estimateGradient(
1473 SCIP* scip, /**< SCIP data structure */
1474 SCIP_NLHDLREXPRDATA* nlhdlrexprdata, /**< nonlinear handler expression data */
1475 SCIP_SOL* sol, /**< solution to use */
1476 SCIP_Real auxvalue, /**< value of nlexpr in sol - we may not be able to take this value
1477 from nlexpr if it was evaluated at a different sol recently */
1478 SCIP_ROWPREP* rowprep, /**< rowprep where to store estimator */
1479 SCIP_Bool* success /**< buffer to store whether successful */
1480 )
1481 {
1482 SCIP_EXPR* nlexpr;
1483 SCIP_Real QUAD(constant);
1484 int i;
1485
1486 assert(nlhdlrexprdata != NULL);
1487 assert(rowprep != NULL);
1488 assert(success != NULL);
1489
1490 nlexpr = nlhdlrexprdata->nlexpr;
1491 assert(nlexpr != NULL);
1492
1493 #ifdef SCIP_DEBUG
1494 SCIPinfoMessage(scip, NULL, "estimate expression ");
1495 SCIPprintExpr(scip, nlexpr, NULL);
1496 SCIPinfoMessage(scip, NULL, " by gradient\n");
1497 #endif
1498
1499 *success = FALSE;
1500
1501 /* evaluation error -> skip */
1502 if( auxvalue == SCIP_INVALID )
1503 {
1504 SCIPdebugMsg(scip, "evaluation error / too large value (%g) for %p\n", auxvalue, (void*)nlexpr);
1505 return SCIP_OKAY;
1506 }
1507
1508 /* compute gradient (TODO: this also re-evaluates (soltag=0), which shouldn't be necessary unless we tried ConvexSecant before) */
1509 SCIP_CALL( SCIPevalExprGradient(scip, nlexpr, sol, 0L) );
1510
1511 /* gradient evaluation error -> skip */
1512 if( SCIPexprGetDerivative(nlexpr) == SCIP_INVALID )
1513 {
1514 SCIPdebugMsg(scip, "gradient evaluation error for %p\n", (void*)nlexpr);
1515 return SCIP_OKAY;
1516 }
1517
1518 /* add gradient underestimator to rowprep: f(sol) + (x - sol) \nabla f(sol)
1519 * constant will store f(sol) - sol * \nabla f(sol)
1520 * to avoid some cancellation errors when linear variables take huge values (like 1e20),
1521 * we use double-double arithemtic here
1522 */
1523 QUAD_ASSIGN(constant, SCIPexprGetEvalValue(nlexpr)); /* f(sol) */
1524 for( i = 0; i < nlhdlrexprdata->nleafs; ++i )
1525 {
1526 SCIP_VAR* var;
1527 SCIP_Real deriv;
1528 SCIP_Real varval;
1529
1530 assert(SCIPexprGetDiffTag(nlhdlrexprdata->leafexprs[i]) == SCIPexprGetDiffTag(nlexpr));
1531 deriv = SCIPexprGetDerivative(nlhdlrexprdata->leafexprs[i]);
1532 if( deriv == SCIP_INVALID )
1533 {
1534 SCIPdebugMsg(scip, "gradient evaluation error for component %d of %p\n", i, (void*)nlexpr);
1535 return SCIP_OKAY;
1536 }
1537
1538 var = SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[i]);
1539 assert(var != NULL);
1540
1541 varval = SCIPgetSolVal(scip, sol, var);
1542
1543 SCIPdebugMsg(scip, "add %g * (<%s> - %g) to rowprep\n", deriv, SCIPvarGetName(var), varval);
1544
1545 /* add deriv * var to rowprep and deriv * (-varval) to constant */
1546 SCIP_CALL( SCIPaddRowprepTerm(scip, rowprep, var, deriv) );
1547 SCIPquadprecSumQD(constant, constant, -deriv * varval);
1548 }
1549
1550 SCIProwprepAddConstant(rowprep, QUAD_TO_DBL(constant));
1551 SCIProwprepSetLocal(rowprep, FALSE);
1552
1553 *success = TRUE;
1554
1555 return SCIP_OKAY;
1556 }
1557
1558 /** adds an estimator generated by putting a secant through the coordinates given by the two closest integer points */
1559 static
1560 SCIP_RETCODE estimateConvexSecant(
1561 SCIP* scip, /**< SCIP data structure */
1562 SCIP_NLHDLR* nlhdlr, /**< nonlinear handler */
1563 SCIP_NLHDLREXPRDATA* nlhdlrexprdata, /**< nonlinear handler expression data */
1564 SCIP_SOL* sol, /**< solution to use, unless usemidpoint is TRUE */
1565 SCIP_ROWPREP* rowprep, /**< rowprep where to store estimator */
1566 SCIP_Bool* success /**< buffer to store whether successful */
1567 )
1568 {
1569 SCIP_NLHDLRDATA* nlhdlrdata;
1570 SCIP_EXPR* nlexpr;
1571 SCIP_VAR* var;
1572 SCIP_Real x;
1573 SCIP_Real left, right;
1574 SCIP_Real fleft, fright;
1575
1576 assert(nlhdlrexprdata != NULL);
1577 assert(nlhdlrexprdata->nleafs == 1);
1578 assert(rowprep != NULL);
1579 assert(success != NULL);
1580
1581 nlexpr = nlhdlrexprdata->nlexpr;
1582 assert(nlexpr != NULL);
1583
1584 *success = FALSE;
1585
1586 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
1587 assert(nlhdlrdata != NULL);
1588
1589 var = SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[0]);
1590 assert(var != NULL);
1591
1592 x = SCIPgetSolVal(scip, sol, var);
1593
1594 #ifdef SCIP_DEBUG
1595 SCIPinfoMessage(scip, NULL, "estimate expression ");
1596 SCIPprintExpr(scip, nlexpr, NULL);
1597 SCIPinfoMessage(scip, NULL, " by secant\n");
1598 SCIPinfoMessage(scip, NULL, "integral variable <%s> = %g [%g,%g]\n", SCIPvarGetName(var),
1599 x, SCIPvarGetLbGlobal(var), SCIPvarGetUbGlobal(var));
1600 #endif
1601
1602 /* find out coordinates of var left and right to sol */
1603 if( SCIPisIntegral(scip, x) )
1604 {
1605 x = SCIPround(scip, x);
1606 if( SCIPisEQ(scip, x, SCIPvarGetLbGlobal(var)) )
1607 {
1608 left = x;
1609 right = left + 1.0;
1610 }
1611 else
1612 {
1613 right = x;
1614 left = right - 1.0;
1615 }
1616 }
1617 else
1618 {
1619 left = SCIPfloor(scip, x);
1620 right = SCIPceil(scip, x);
1621 }
1622 assert(left != right);
1623
1624 /* now evaluate at left and right */
1625 if( nlhdlrdata->evalsol == NULL )
1626 {
1627 SCIP_CALL( SCIPcreateSol(scip, &nlhdlrdata->evalsol, NULL) );
1628 }
1629
1630 SCIP_CALL( SCIPsetSolVal(scip, nlhdlrdata->evalsol, var, left) );
1631 SCIP_CALL( SCIPevalExpr(scip, nlexpr, nlhdlrdata->evalsol, 0L) );
1632
1633 /* evaluation error or a too large constant -> skip */
1634 fleft = SCIPexprGetEvalValue(nlexpr);
1635 if( SCIPisInfinity(scip, REALABS(fleft)) )
1636 {
1637 SCIPdebugMsg(scip, "evaluation error / too large value (%g) for %p\n", SCIPexprGetEvalValue(nlexpr), (void*)nlexpr);
1638 return SCIP_OKAY;
1639 }
1640
1641 SCIP_CALL( SCIPsetSolVal(scip, nlhdlrdata->evalsol, var, right) );
1642 SCIP_CALL( SCIPevalExpr(scip, nlexpr, nlhdlrdata->evalsol, 0L) );
1643
1644 /* evaluation error or a too large constant -> skip */
1645 fright = SCIPexprGetEvalValue(nlexpr);
1646 if( SCIPisInfinity(scip, REALABS(fright)) )
1647 {
1648 SCIPdebugMsg(scip, "evaluation error / too large value (%g) for %p\n", SCIPexprGetEvalValue(nlexpr), (void*)nlexpr);
1649 return SCIP_OKAY;
1650 }
1651
1652 SCIPdebugMsg(scip, "f(%g)=%g, f(%g)=%g\n", left, fleft, right, fright);
1653
1654 /* skip if too steep
1655 * for clay0204h, this resulted in a wrong cut from f(0)=1e12 f(1)=0.99998,
1656 * since due to limited precision, this was handled as if f(1)=1
1657 */
1658 if( (!SCIPisZero(scip, fleft) && REALABS(fright/fleft)*SCIPepsilon(scip) > 1.0) ||
1659 (!SCIPisZero(scip, fright) && REALABS(fleft/fright)*SCIPepsilon(scip) > 1.0) )
1660 {
1661 SCIPdebugMsg(scip, "function is too steep, abandoning\n");
1662 return SCIP_OKAY;
1663 }
1664
1665 /* now add f(left) + (f(right) - f(left)) * (x - left) as estimator to rowprep */
1666 SCIP_CALL( SCIPaddRowprepTerm(scip, rowprep, var, fright - fleft) );
1667 SCIProwprepAddConstant(rowprep, fleft - (fright - fleft) * left);
1668 SCIProwprepSetLocal(rowprep, FALSE);
1669
1670 *success = TRUE;
1671
1672 return SCIP_OKAY;
1673 }
1674
1675 /*
1676 * Callback methods of convex nonlinear handler
1677 */
1678
1679 /** free handler data of convex or concave nlhdlr */
1680 static
1681 SCIP_DECL_NLHDLRFREEHDLRDATA(nlhdlrfreeHdlrDataConvexConcave)
1682 { /*lint --e{715}*/
1683 assert(scip != NULL);
1684 assert(nlhdlrdata != NULL);
1685 assert(*nlhdlrdata != NULL);
1686 assert((*nlhdlrdata)->evalsol == NULL);
1687
1688 SCIPfreeBlockMemory(scip, nlhdlrdata);
1689
1690 return SCIP_OKAY;
1691 }
1692
1693 /** callback to free expression specific data */
1694 static
1695 SCIP_DECL_NLHDLRFREEEXPRDATA(nlhdlrfreeExprDataConvexConcave)
1696 { /*lint --e{715}*/
1697 assert(scip != NULL);
1698 assert(nlhdlrexprdata != NULL);
1699 assert(*nlhdlrexprdata != NULL);
1700
1701 SCIPfreeBlockMemoryArrayNull(scip, &(*nlhdlrexprdata)->leafexprs, (*nlhdlrexprdata)->nleafs);
1702 SCIP_CALL( SCIPreleaseExpr(scip, &(*nlhdlrexprdata)->nlexpr) );
1703 SCIPhashmapFree(&(*nlhdlrexprdata)->nlexpr2origexpr);
1704
1705 SCIPfreeBlockMemory(scip, nlhdlrexprdata);
1706
1707 return SCIP_OKAY;
1708 }
1709
1710 /** deinitialization of problem-specific data */
1711 static
1712 SCIP_DECL_NLHDLREXIT(nlhdlrExitConvex)
1713 {
1714 SCIP_NLHDLRDATA* nlhdlrdata;
1715
1716 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
1717 assert(nlhdlrdata != NULL);
1718
1719 if( nlhdlrdata->evalsol != NULL )
1720 {
1721 SCIP_CALL( SCIPfreeSol(scip, &nlhdlrdata->evalsol) );
1722 }
1723
1724 return SCIP_OKAY;
1725 }
1726
1727 /** checks whether expression (or -expression) is convex, possibly after introducing auxiliary variables */
1728 static
1729 SCIP_DECL_NLHDLRDETECT(nlhdlrDetectConvex)
1730 { /*lint --e{715}*/
1731 SCIP_NLHDLRDATA* nlhdlrdata;
1732 SCIP_EXPR* nlexpr = NULL;
1733 SCIP_HASHMAP* nlexpr2origexpr;
1734 int nleafs = 0;
1735
1736 assert(scip != NULL);
1737 assert(nlhdlr != NULL);
1738 assert(expr != NULL);
1739 assert(enforcing != NULL);
1740 assert(participating != NULL);
1741 assert(nlhdlrexprdata != NULL);
1742
1743 /* we currently do not participate if only activity computation is required */
1744 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPABOTH) == SCIP_NLHDLR_METHOD_SEPABOTH )
1745 return SCIP_OKAY;
1746
1747 /* ignore pure constants and variables */
1748 if( SCIPexprGetNChildren(expr) == 0 )
1749 return SCIP_OKAY;
1750
1751 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
1752 assert(nlhdlrdata != NULL);
1753 assert(nlhdlrdata->isnlhdlrconvex);
1754
1755 SCIPdebugMsg(scip, "nlhdlr_convex detect for expr %p\n", (void*)expr);
1756
1757 /* initialize mapping from copied expression to original one
1758 * 20 is not a bad estimate for the size of convex subexpressions that we can usually discover
1759 * when expressions will be allowed to store "user"data, we could get rid of this hashmap (TODO)
1760 */
1761 SCIP_CALL( SCIPhashmapCreate(&nlexpr2origexpr, SCIPblkmem(scip), 20) );
1762
1763 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPABELOW) == 0 ) /* if no separation below yet */
1764 {
1765 SCIP_CALL( constructExpr(scip, nlhdlrdata, &nlexpr, nlexpr2origexpr, &nleafs, expr,
1766 SCIP_EXPRCURV_CONVEX, NULL, NULL) );
1767 if( nlexpr != NULL )
1768 {
1769 assert(SCIPexprGetNChildren(nlexpr) > 0); /* should not be trivial */
1770
1771 *participating |= SCIP_NLHDLR_METHOD_SEPABELOW;
1772
1773 SCIPdebugMsg(scip, "detected expr %p to be convex -> can enforce expr <= auxvar\n", (void*)expr);
1774 }
1775 else
1776 {
1777 SCIP_CALL( SCIPhashmapRemoveAll(nlexpr2origexpr) );
1778 }
1779 }
1780
1781 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPAABOVE) == 0 && nlexpr == NULL ) /* if no separation above and not convex */
1782 {
1783 SCIP_CALL( constructExpr(scip, nlhdlrdata, &nlexpr, nlexpr2origexpr, &nleafs, expr,
1784 SCIP_EXPRCURV_CONCAVE, NULL, NULL) );
1785 if( nlexpr != NULL )
1786 {
1787 assert(SCIPexprGetNChildren(nlexpr) > 0); /* should not be trivial */
1788
1789 *participating |= SCIP_NLHDLR_METHOD_SEPAABOVE;
1790
1791 SCIPdebugMsg(scip, "detected expr %p to be concave -> can enforce expr >= auxvar\n", (void*)expr);
1792 }
1793 }
1794
1795 /* everything we participate in we also enforce */
1796 *enforcing |= *participating;
1797
1798 assert(*participating || nlexpr == NULL);
1799 if( !*participating )
1800 {
1801 SCIPhashmapFree(&nlexpr2origexpr);
1802 return SCIP_OKAY;
1803 }
1804
1805 /* create the expression data of the nonlinear handler
1806 * notify conshdlr about expr for which we will require auxiliary variables
1807 */
1808 SCIP_CALL( createNlhdlrExprData(scip, nlhdlrdata, nlhdlrexprdata, expr, nlexpr, nlexpr2origexpr, nleafs, *participating) );
1809
1810 return SCIP_OKAY;
1811 }
1812
1813 /** auxiliary evaluation callback */
1814 static
1815 SCIP_DECL_NLHDLREVALAUX(nlhdlrEvalAuxConvexConcave)
1816 { /*lint --e{715}*/
1817 assert(nlhdlrexprdata != NULL);
1818 assert(nlhdlrexprdata->nlexpr != NULL);
1819 assert(auxvalue != NULL);
1820
1821 SCIP_CALL( SCIPevalExpr(scip, nlhdlrexprdata->nlexpr, sol, 0L) );
1822 *auxvalue = SCIPexprGetEvalValue(nlhdlrexprdata->nlexpr);
1823
1824 return SCIP_OKAY;
1825 }
1826
1827 /** init sepa callback that initializes LP */
1828 static
1829 SCIP_DECL_NLHDLRINITSEPA(nlhdlrInitSepaConvex)
1830 { /*lint --e{715}*/
1831 SCIP_EXPR* nlexpr;
1832 SCIP_EXPRCURV curvature;
1833 SCIP_Bool success;
1834 SCIP_ROWPREP* rowprep = NULL;
1835 SCIP_ROW* row;
1836 SCIP_Real lb;
1837 SCIP_Real ub;
1838 SCIP_Real lambda;
1839 SCIP_SOL* sol;
1840 int k;
1841
1842 assert(scip != NULL);
1843 assert(expr != NULL);
1844 assert(nlhdlrexprdata != NULL);
1845
1846 /* setup nlhdlrexprdata->leafexprs */
1847 SCIP_CALL( collectLeafs(scip, nlhdlrexprdata) );
1848
1849 nlexpr = nlhdlrexprdata->nlexpr;
1850 assert(nlexpr != NULL);
1851 assert(SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)nlexpr) == expr);
1852
1853 curvature = SCIPexprGetCurvature(nlexpr);
1854 assert(curvature == SCIP_EXPRCURV_CONVEX || curvature == SCIP_EXPRCURV_CONCAVE);
1855
1856 /* we can only be estimating on the convex side */
1857 if( curvature == SCIP_EXPRCURV_CONVEX )
1858 overestimate = FALSE;
1859 else if( curvature == SCIP_EXPRCURV_CONCAVE )
1860 underestimate = FALSE;
1861 if( !overestimate && !underestimate )
1862 return SCIP_OKAY;
1863
1864 /* linearizes at 5 different points obtained as convex combination of the lower and upper bound of the variables
1865 * present in the convex expression; whether more weight is given to the lower or upper bound of a variable depends
1866 * on whether the fixing of the variable to that value is better for the objective function
1867 */
1868 SCIP_CALL( SCIPcreateSol(scip, &sol, NULL) );
1869
1870 *infeasible = FALSE;
1871
1872 for( k = 0; k < 5; ++k )
1873 {
1874 int i;
1875 lambda = 0.1 * (k+1); /* lambda = 0.1, 0.2, 0.3, 0.4, 0.5 */
1876
1877 for( i = 0; i < nlhdlrexprdata->nleafs; ++i )
1878 {
1879 SCIP_VAR* var;
1880
1881 var = SCIPgetVarExprVar(nlhdlrexprdata->leafexprs[i]);
1882
1883 lb = SCIPvarGetLbGlobal(var);
1884 ub = SCIPvarGetUbGlobal(var);
1885
1886 /* make sure the absolute values of bounds are not too large */
1887 if( ub > -INITLPMAXVARVAL )
1888 lb = MAX(lb, -INITLPMAXVARVAL);
1889 if( lb < INITLPMAXVARVAL )
1890 ub = MIN(ub, INITLPMAXVARVAL);
1891
1892 /* in the case when ub < -maxabsbnd or lb > maxabsbnd, we still want to at least make bounds finite */
1893 if( SCIPisInfinity(scip, -lb) )
1894 lb = MIN(-10.0, ub - 0.1*REALABS(ub));
1895 if( SCIPisInfinity(scip, ub) )
1896 ub = MAX( 10.0, lb + 0.1*REALABS(lb));
1897
1898 if( SCIPvarGetBestBoundType(var) == SCIP_BOUNDTYPE_LOWER )
1899 SCIP_CALL( SCIPsetSolVal(scip, sol, var, lambda * ub + (1.0 - lambda) * lb) );
1900 else
1901 SCIP_CALL( SCIPsetSolVal(scip, sol, var, lambda * lb + (1.0 - lambda) * ub) );
1902 }
1903
1904 SCIP_CALL( SCIPcreateRowprep(scip, &rowprep, overestimate ? SCIP_SIDETYPE_LEFT : SCIP_SIDETYPE_RIGHT, TRUE) );
1905 SCIP_CALL( estimateGradient(scip, nlhdlrexprdata, sol, 0.0, rowprep, &success) );
1906 if( !success )
1907 {
1908 SCIPdebugMsg(scip, "failed to linearize for k = %d\n", k);
1909 if( rowprep != NULL )
1910 SCIPfreeRowprep(scip, &rowprep);
1911 continue;
1912 }
1913
1914 /* add auxiliary variable */
1915 SCIP_CALL( SCIPaddRowprepTerm(scip, rowprep, SCIPgetExprAuxVarNonlinear(expr), -1.0) );
1916
1917 /* straighten out numerics */
1918 SCIP_CALL( SCIPcleanupRowprep2(scip, rowprep, NULL, SCIPgetHugeValue(scip), &success) );
1919 if( !success )
1920 {
1921 SCIPdebugMsg(scip, "failed to cleanup rowprep numerics for k = %d\n", k);
1922 if( rowprep != NULL )
1923 SCIPfreeRowprep(scip, &rowprep);
1924 continue;
1925 }
1926
1927 (void) SCIPsnprintf(SCIProwprepGetName(rowprep), SCIP_MAXSTRLEN, "%sestimate_gradient%p_initsepa_%d",
1928 overestimate ? "over" : "under", (void*)expr, k);
1929 SCIP_CALL( SCIPgetRowprepRowCons(scip, &row, rowprep, cons) );
1930 SCIPfreeRowprep(scip, &rowprep);
1931
1932 #ifdef SCIP_DEBUG
1933 SCIPinfoMessage(scip, NULL, "initsepa computed row: ");
1934 SCIPprintRow(scip, row, NULL);
1935 #endif
1936
1937 SCIP_CALL( SCIPaddRow(scip, row, FALSE, infeasible) );
1938 SCIP_CALL( SCIPreleaseRow(scip, &row) );
1939
1940 if( *infeasible )
1941 break;
1942 }
1943
1944 SCIP_CALL( SCIPfreeSol(scip, &sol) );
1945
1946 return SCIP_OKAY;
1947 }
1948
1949 /** estimator callback */
1950 static
1951 SCIP_DECL_NLHDLRESTIMATE(nlhdlrEstimateConvex)
1952 { /*lint --e{715}*/
1953 SCIP_ROWPREP* rowprep;
1954
1955 assert(scip != NULL);
1956 assert(expr != NULL);
1957 assert(nlhdlrexprdata != NULL);
1958 assert(nlhdlrexprdata->nlexpr != NULL);
1959 assert(rowpreps != NULL);
1960 assert(success != NULL);
1961
1962 assert(SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)nlhdlrexprdata->nlexpr) == expr);
1963
1964 /* we must be called only for the side that we indicated to participate in during DETECT */
1965 assert(SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONVEX
1966 || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONCAVE);
1967 assert(!overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONCAVE);
1968 assert( overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONVEX);
1969
1970 *success = FALSE;
1971 *addedbranchscores = FALSE;
1972
1973 /* we can skip eval as nlhdlrEvalAux should have been called for same solution before */
1974 /* SCIP_CALL( nlhdlrExprEval(scip, nlexpr, sol) ); */
1975 assert(auxvalue == SCIPexprGetEvalValue(nlhdlrexprdata->nlexpr)); /* given value (originally from
1976 nlhdlrEvalAuxConvexConcave) should coincide with the one stored in nlexpr */
1977
1978 SCIP_CALL( SCIPcreateRowprep(scip, &rowprep, overestimate ? SCIP_SIDETYPE_LEFT : SCIP_SIDETYPE_RIGHT, TRUE) );
1979
1980 if( nlhdlrexprdata->nleafs == 1 && SCIPexprIsIntegral(nlhdlrexprdata->leafexprs[0]) )
1981 {
1982 SCIP_CALL( estimateConvexSecant(scip, nlhdlr, nlhdlrexprdata, sol, rowprep, success) );
1983
1984 (void) SCIPsnprintf(SCIProwprepGetName(rowprep), SCIP_MAXSTRLEN, "%sestimate_convexsecant%p_%s%d",
1985 overestimate ? "over" : "under",
1986 (void*)expr,
1987 sol != NULL ? "sol" : "lp",
|
(1) Event invalid_type: |
Argument "(sol != NULL) ? sol->index : SCIPgetNLPs(scip)" to format specifier "%d" was expected to have type "int" but has type "long long". [details] |
1988 sol != NULL ? SCIPsolGetIndex(sol) : SCIPgetNLPs(scip));
1989 }
1990
1991 /* if secant method was not used or failed, then try with gradient */
1992 if( !*success )
1993 {
1994 SCIP_CALL( estimateGradient(scip, nlhdlrexprdata, sol, auxvalue, rowprep, success) );
1995
1996 (void) SCIPsnprintf(SCIProwprepGetName(rowprep), SCIP_MAXSTRLEN, "%sestimate_convexgradient%p_%s%d",
1997 overestimate ? "over" : "under",
1998 (void*)expr,
1999 sol != NULL ? "sol" : "lp",
2000 sol != NULL ? SCIPsolGetIndex(sol) : SCIPgetNLPs(scip));
2001 }
2002
2003 if( *success )
2004 {
2005 SCIP_CALL( SCIPsetPtrarrayVal(scip, rowpreps, 0, rowprep) );
2006 }
2007 else
2008 {
2009 SCIPfreeRowprep(scip, &rowprep);
2010 }
2011
2012 return SCIP_OKAY;
2013 }
2014
2015 /** include nlhdlr in another scip instance */
2016 static
2017 SCIP_DECL_NLHDLRCOPYHDLR(nlhdlrCopyhdlrConvex)
2018 { /*lint --e{715}*/
2019 assert(targetscip != NULL);
2020 assert(sourcenlhdlr != NULL);
2021 assert(strcmp(SCIPnlhdlrGetName(sourcenlhdlr), CONVEX_NLHDLR_NAME) == 0);
2022
2023 SCIP_CALL( SCIPincludeNlhdlrConvex(targetscip) );
2024
2025 return SCIP_OKAY;
2026 }
2027
2028 /** includes convex nonlinear handler in nonlinear constraint handler */
2029 SCIP_RETCODE SCIPincludeNlhdlrConvex(
2030 SCIP* scip /**< SCIP data structure */
2031 )
2032 {
2033 SCIP_NLHDLR* nlhdlr;
2034 SCIP_NLHDLRDATA* nlhdlrdata;
2035
2036 assert(scip != NULL);
2037
2038 SCIP_CALL( SCIPallocBlockMemory(scip, &nlhdlrdata) );
2039 nlhdlrdata->isnlhdlrconvex = TRUE;
2040 nlhdlrdata->evalsol = NULL;
2041
2042 SCIP_CALL( SCIPincludeNlhdlrNonlinear(scip, &nlhdlr, CONVEX_NLHDLR_NAME, CONVEX_NLHDLR_DESC,
2043 CONVEX_NLHDLR_DETECTPRIORITY, CONVEX_NLHDLR_ENFOPRIORITY, nlhdlrDetectConvex, nlhdlrEvalAuxConvexConcave, nlhdlrdata) );
2044 assert(nlhdlr != NULL);
2045
2046 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/detectsum",
2047 "whether to run convexity detection when the root of an expression is a non-quadratic sum",
2048 &nlhdlrdata->detectsum, FALSE, DEFAULT_DETECTSUM, NULL, NULL) );
2049
2050 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/extendedform",
2051 "whether to create extended formulations instead of looking for maximal convex expressions",
2052 &nlhdlrdata->extendedform, FALSE, DEFAULT_EXTENDEDFORM, NULL, NULL) );
2053
2054 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/cvxquadratic",
2055 "whether to use convexity check on quadratics",
2056 &nlhdlrdata->cvxquadratic, TRUE, DEFAULT_CVXQUADRATIC_CONVEX, NULL, NULL) );
2057
2058 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/cvxsignomial",
2059 "whether to use convexity check on signomials",
2060 &nlhdlrdata->cvxsignomial, TRUE, DEFAULT_CVXSIGNOMIAL, NULL, NULL) );
2061
2062 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/cvxprodcomp",
2063 "whether to use convexity check on product composition f(h)*h",
2064 &nlhdlrdata->cvxprodcomp, TRUE, DEFAULT_CVXPRODCOMP, NULL, NULL) );
2065
2066 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONVEX_NLHDLR_NAME "/handletrivial",
2067 "whether to also handle trivial convex expressions",
2068 &nlhdlrdata->handletrivial, TRUE, DEFAULT_HANDLETRIVIAL, NULL, NULL) );
2069
2070 SCIPnlhdlrSetFreeHdlrData(nlhdlr, nlhdlrfreeHdlrDataConvexConcave);
2071 SCIPnlhdlrSetCopyHdlr(nlhdlr, nlhdlrCopyhdlrConvex);
2072 SCIPnlhdlrSetFreeExprData(nlhdlr, nlhdlrfreeExprDataConvexConcave);
2073 SCIPnlhdlrSetSepa(nlhdlr, nlhdlrInitSepaConvex, NULL, nlhdlrEstimateConvex, NULL);
2074 SCIPnlhdlrSetInitExit(nlhdlr, NULL, nlhdlrExitConvex);
2075
2076 return SCIP_OKAY;
2077 }
2078
2079 /*
2080 * Callback methods of concave nonlinear handler
2081 */
2082
2083 /** deinitialization of problem-specific data */
2084 static
2085 SCIP_DECL_NLHDLREXIT(nlhdlrExitConcave)
2086 {
2087 SCIP_NLHDLRDATA* nlhdlrdata;
2088
2089 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
2090 assert(nlhdlrdata != NULL);
2091
2092 if( nlhdlrdata->evalsol != NULL )
2093 {
2094 SCIP_CALL( SCIPfreeSol(scip, &nlhdlrdata->evalsol) );
2095 }
2096
2097 return SCIP_OKAY;
2098 }
2099
2100 /** checks whether expression (or -expression) is concave, possibly after introducing auxiliary variables */
2101 static
2102 SCIP_DECL_NLHDLRDETECT(nlhdlrDetectConcave)
2103 { /*lint --e{715}*/
2104 SCIP_NLHDLRDATA* nlhdlrdata;
2105 SCIP_EXPR* nlexpr = NULL;
2106 SCIP_HASHMAP* nlexpr2origexpr;
2107 int nleafs = 0;
2108
2109 assert(scip != NULL);
2110 assert(nlhdlr != NULL);
2111 assert(expr != NULL);
2112 assert(enforcing != NULL);
2113 assert(participating != NULL);
2114 assert(nlhdlrexprdata != NULL);
2115
2116 /* we currently do not participate if only activity computation is required */
2117 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPABOTH) == SCIP_NLHDLR_METHOD_SEPABOTH )
2118 return SCIP_OKAY;
2119
2120 /* ignore pure constants and variables */
2121 if( SCIPexprGetNChildren(expr) == 0 )
2122 return SCIP_OKAY;
2123
2124 nlhdlrdata = SCIPnlhdlrGetData(nlhdlr);
2125 assert(nlhdlrdata != NULL);
2126 assert(!nlhdlrdata->isnlhdlrconvex);
2127
2128 SCIPdebugMsg(scip, "nlhdlr_concave detect for expr %p\n", (void*)expr);
2129
2130 /* initialize mapping from copied expression to original one
2131 * 20 is not a bad estimate for the size of concave subexpressions that we can usually discover
2132 * when expressions will be allowed to store "user"data, we could get rid of this hashmap (TODO)
2133 */
2134 SCIP_CALL( SCIPhashmapCreate(&nlexpr2origexpr, SCIPblkmem(scip), 20) );
2135
2136 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPABELOW) == 0 ) /* if no separation below yet */
2137 {
2138 SCIP_CALL( constructExpr(scip, nlhdlrdata, &nlexpr, nlexpr2origexpr, &nleafs, expr,
2139 SCIP_EXPRCURV_CONCAVE, NULL, NULL) );
2140
2141 if( nlexpr != NULL && nleafs > SCIP_MAXVERTEXPOLYDIM )
2142 {
2143 SCIPdebugMsg(scip, "Too many variables (%d) in constructed expression. Will not be able to estimate. Rejecting.\n", nleafs);
2144 SCIP_CALL( SCIPreleaseExpr(scip, &nlexpr) );
2145 }
2146
2147 if( nlexpr != NULL )
2148 {
2149 assert(SCIPexprGetNChildren(nlexpr) > 0); /* should not be trivial */
2150
2151 *participating |= SCIP_NLHDLR_METHOD_SEPABELOW;
2152
2153 SCIPdebugMsg(scip, "detected expr %p to be concave -> can enforce expr <= auxvar\n", (void*)expr);
2154 }
2155 else
2156 {
2157 SCIP_CALL( SCIPhashmapRemoveAll(nlexpr2origexpr) );
2158 }
2159 }
2160
2161 if( (*enforcing & SCIP_NLHDLR_METHOD_SEPAABOVE) == 0 && nlexpr == NULL ) /* if no separation above and not concave */
2162 {
2163 SCIP_CALL( constructExpr(scip, nlhdlrdata, &nlexpr, nlexpr2origexpr, &nleafs, expr,
2164 SCIP_EXPRCURV_CONVEX, NULL, NULL) );
2165
2166 if( nlexpr != NULL && nleafs > SCIP_MAXVERTEXPOLYDIM )
2167 {
2168 SCIPdebugMsg(scip, "Too many variables (%d) in constructed expression. Will not be able to estimate. Rejecting.\n", nleafs);
2169 SCIP_CALL( SCIPreleaseExpr(scip, &nlexpr) );
2170 }
2171
2172 if( nlexpr != NULL )
2173 {
2174 assert(SCIPexprGetNChildren(nlexpr) > 0); /* should not be trivial */
2175
2176 *participating |= SCIP_NLHDLR_METHOD_SEPAABOVE;
2177
2178 SCIPdebugMsg(scip, "detected expr %p to be convex -> can enforce expr >= auxvar\n", (void*)expr);
2179 }
2180 }
2181
2182 /* everything we participate in we also enforce (at the moment) */
2183 *enforcing |= *participating;
2184
2185 assert(*participating || nlexpr == NULL);
2186 if( !*participating )
2187 {
2188 SCIPhashmapFree(&nlexpr2origexpr);
2189 return SCIP_OKAY;
2190 }
2191
2192 /* create the expression data of the nonlinear handler
2193 * notify conshdlr about expr for which we will require auxiliary variables and use activity
2194 */
2195 SCIP_CALL( createNlhdlrExprData(scip, nlhdlrdata, nlhdlrexprdata, expr, nlexpr, nlexpr2origexpr, nleafs, *participating) );
2196
2197 return SCIP_OKAY;
2198 }
2199
2200 /** init sepa callback that initializes LP */
2201 static
2202 SCIP_DECL_NLHDLRINITSEPA(nlhdlrInitSepaConcave)
2203 {
2204 SCIP_EXPR* nlexpr;
2205 SCIP_EXPRCURV curvature;
2206 SCIP_Bool success;
2207 SCIP_ROWPREP* rowprep = NULL;
2208 SCIP_ROW* row;
2209
2210 assert(scip != NULL);
2211 assert(expr != NULL);
2212 assert(nlhdlrexprdata != NULL);
2213
2214 nlexpr = nlhdlrexprdata->nlexpr;
2215 assert(nlexpr != NULL);
2216 assert(SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)nlexpr) == expr);
2217
2218 /* setup nlhdlrexprdata->leafexprs */
2219 SCIP_CALL( collectLeafs(scip, nlhdlrexprdata) );
2220
2221 curvature = SCIPexprGetCurvature(nlexpr);
2222 assert(curvature == SCIP_EXPRCURV_CONVEX || curvature == SCIP_EXPRCURV_CONCAVE);
2223 /* we can only be estimating on non-convex side */
2224 if( curvature == SCIP_EXPRCURV_CONCAVE )
2225 overestimate = FALSE;
2226 else if( curvature == SCIP_EXPRCURV_CONVEX )
2227 underestimate = FALSE;
2228 if( !overestimate && !underestimate )
2229 return SCIP_OKAY;
2230
2231 /* compute estimator and store in rowprep */
2232 SCIP_CALL( SCIPcreateRowprep(scip, &rowprep, overestimate ? SCIP_SIDETYPE_LEFT : SCIP_SIDETYPE_RIGHT, TRUE) );
2233 SCIP_CALL( estimateVertexPolyhedral(scip, conshdlr, nlhdlr, nlhdlrexprdata, NULL, TRUE, overestimate,
2234 overestimate ? SCIPinfinity(scip) : -SCIPinfinity(scip), rowprep, &success) );
2235 if( !success )
2236 {
2237 SCIPdebugMsg(scip, "failed to compute facet of convex hull\n");
2238 goto TERMINATE;
2239 }
2240
2241 /* add auxiliary variable */
2242 SCIP_CALL( SCIPaddRowprepTerm(scip, rowprep, SCIPgetExprAuxVarNonlinear(expr), -1.0) );
2243
2244 /* straighten out numerics */
2245 SCIP_CALL( SCIPcleanupRowprep2(scip, rowprep, NULL, SCIPgetHugeValue(scip), &success) );
2246 if( !success )
2247 {
2248 SCIPdebugMsg(scip, "failed to cleanup rowprep numerics\n");
2249 goto TERMINATE;
2250 }
2251
2252 (void) SCIPsnprintf(SCIProwprepGetName(rowprep), SCIP_MAXSTRLEN, "%sestimate_concave%p_initsepa",
2253 overestimate ? "over" : "under", (void*)expr);
2254 SCIP_CALL( SCIPgetRowprepRowCons(scip, &row, rowprep, cons) );
2255
2256 #ifdef SCIP_DEBUG
2257 SCIPinfoMessage(scip, NULL, "initsepa computed row: ");
2258 SCIPprintRow(scip, row, NULL);
2259 #endif
2260
2261 SCIP_CALL( SCIPaddRow(scip, row, FALSE, infeasible) );
2262 SCIP_CALL( SCIPreleaseRow(scip, &row) );
2263
2264 TERMINATE:
2265 if( rowprep != NULL )
2266 SCIPfreeRowprep(scip, &rowprep);
2267
2268 return SCIP_OKAY;
2269 }
2270
2271 /** estimator callback */
2272 static
2273 SCIP_DECL_NLHDLRESTIMATE(nlhdlrEstimateConcave)
2274 { /*lint --e{715}*/
2275 SCIP_ROWPREP* rowprep;
2276
2277 assert(scip != NULL);
2278 assert(expr != NULL);
2279 assert(nlhdlrexprdata != NULL);
2280 assert(nlhdlrexprdata->nlexpr != NULL);
2281 assert(rowpreps != NULL);
2282 assert(success != NULL);
2283
2284 assert(SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, (void*)nlhdlrexprdata->nlexpr) == expr);
2285
2286 /* we must be called only for the side that we indicated to participate in during DETECT */
2287 assert(SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONVEX
2288 || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONCAVE);
2289 assert(!overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONVEX);
2290 assert( overestimate || SCIPexprGetCurvature(nlhdlrexprdata->nlexpr) == SCIP_EXPRCURV_CONCAVE);
2291
2292 *success = FALSE;
2293 *addedbranchscores = FALSE;
2294
2295 SCIP_CALL( SCIPcreateRowprep(scip, &rowprep, overestimate ? SCIP_SIDETYPE_LEFT : SCIP_SIDETYPE_RIGHT, TRUE) );
2296
2297 SCIP_CALL( estimateVertexPolyhedral(scip, conshdlr, nlhdlr, nlhdlrexprdata, sol, FALSE, overestimate, targetvalue, rowprep, success) );
2298
2299 if( *success )
2300 {
2301 SCIP_CALL( SCIPsetPtrarrayVal(scip, rowpreps, 0, rowprep) );
2302
2303 (void) SCIPsnprintf(SCIProwprepGetName(rowprep), SCIP_MAXSTRLEN, "%sestimate_concave%p_%s%d",
2304 overestimate ? "over" : "under",
2305 (void*)expr,
2306 sol != NULL ? "sol" : "lp",
2307 sol != NULL ? SCIPsolGetIndex(sol) : SCIPgetNLPs(scip));
2308 }
2309 else
2310 {
2311 SCIPfreeRowprep(scip, &rowprep);
2312 }
2313
2314 if( addbranchscores )
2315 {
2316 SCIP_Real violation;
2317
2318 /* check how much is the violation on the side that we estimate */
2319 if( auxvalue == SCIP_INVALID )
2320 {
2321 /* if cannot evaluate, then always branch */
2322 violation = SCIPinfinity(scip);
2323 }
2324 else
2325 {
2326 SCIP_Real auxval;
2327
2328 /* get value of auxiliary variable of this expression */
2329 assert(SCIPgetExprAuxVarNonlinear(expr) != NULL);
2330 auxval = SCIPgetSolVal(scip, sol, SCIPgetExprAuxVarNonlinear(expr));
2331
2332 /* compute the violation
2333 * if we underestimate, then we enforce expr <= auxval, so violation is (positive part of) auxvalue - auxval
2334 * if we overestimate, then we enforce expr >= auxval, so violation is (positive part of) auxval - auxvalue
2335 */
2336 if( !overestimate )
2337 violation = MAX(0.0, auxvalue - auxval);
2338 else
2339 violation = MAX(0.0, auxval - auxvalue);
2340 }
2341 assert(violation >= 0.0);
2342
2343 /* add violation as branching-score to expressions; the core will take care distributing this onto variables */
2344 if( nlhdlrexprdata->nleafs == 1 )
2345 {
2346 SCIP_EXPR* e;
2347 e = (SCIP_EXPR*)SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, nlhdlrexprdata->leafexprs[0]);
2348 SCIP_CALL( SCIPaddExprsViolScoreNonlinear(scip, &e, 1, violation, sol, addedbranchscores) );
2349 }
2350 else
2351 {
2352 SCIP_EXPR** exprs;
2353 int c;
2354
2355 /* map leaf expressions back to original expressions
2356 * TODO do this once at end of detect and store in nlhdlrexprdata
2357 */
2358 SCIP_CALL( SCIPallocBufferArray(scip, &exprs, nlhdlrexprdata->nleafs) );
2359 for( c = 0; c < nlhdlrexprdata->nleafs; ++c )
2360 exprs[c] = (SCIP_EXPR*)SCIPhashmapGetImage(nlhdlrexprdata->nlexpr2origexpr, nlhdlrexprdata->leafexprs[c]);
2361
2362 SCIP_CALL( SCIPaddExprsViolScoreNonlinear(scip, exprs, nlhdlrexprdata->nleafs, violation, sol, addedbranchscores) );
2363
2364 SCIPfreeBufferArray(scip, &exprs);
2365 }
2366 }
2367
2368 return SCIP_OKAY;
2369 }
2370
2371 /** includes nonlinear handler in another scip instance */
2372 static
2373 SCIP_DECL_NLHDLRCOPYHDLR(nlhdlrCopyhdlrConcave)
2374 { /*lint --e{715}*/
2375 assert(targetscip != NULL);
2376 assert(sourcenlhdlr != NULL);
2377 assert(strcmp(SCIPnlhdlrGetName(sourcenlhdlr), CONCAVE_NLHDLR_NAME) == 0);
2378
2379 SCIP_CALL( SCIPincludeNlhdlrConcave(targetscip) );
2380
2381 return SCIP_OKAY;
2382 }
2383
2384 /** includes concave nonlinear handler in nonlinear constraint handler */
2385 SCIP_EXPORT
2386 SCIP_RETCODE SCIPincludeNlhdlrConcave(
2387 SCIP* scip /**< SCIP data structure */
2388 )
2389 {
2390 SCIP_NLHDLR* nlhdlr;
2391 SCIP_NLHDLRDATA* nlhdlrdata;
2392
2393 assert(scip != NULL);
2394
2395 SCIP_CALL( SCIPallocBlockMemory(scip, &nlhdlrdata) );
2396 nlhdlrdata->isnlhdlrconvex = FALSE;
2397 nlhdlrdata->evalsol = NULL;
2398
2399 SCIP_CALL( SCIPincludeNlhdlrNonlinear(scip, &nlhdlr, CONCAVE_NLHDLR_NAME, CONCAVE_NLHDLR_DESC,
2400 CONCAVE_NLHDLR_DETECTPRIORITY, CONCAVE_NLHDLR_ENFOPRIORITY, nlhdlrDetectConcave, nlhdlrEvalAuxConvexConcave, nlhdlrdata) );
2401 assert(nlhdlr != NULL);
2402
2403 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONCAVE_NLHDLR_NAME "/detectsum",
2404 "whether to run convexity detection when the root of an expression is a sum",
2405 &nlhdlrdata->detectsum, FALSE, DEFAULT_DETECTSUM, NULL, NULL) );
2406
2407 /* "extended" formulations of a concave expressions can give worse estimators */
2408 nlhdlrdata->extendedform = FALSE;
2409
2410 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONCAVE_NLHDLR_NAME "/cvxquadratic",
2411 "whether to use convexity check on quadratics",
2412 &nlhdlrdata->cvxquadratic, TRUE, DEFAULT_CVXQUADRATIC_CONCAVE, NULL, NULL) );
2413
2414 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONCAVE_NLHDLR_NAME "/cvxsignomial",
2415 "whether to use convexity check on signomials",
2416 &nlhdlrdata->cvxsignomial, TRUE, DEFAULT_CVXSIGNOMIAL, NULL, NULL) );
2417
2418 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONCAVE_NLHDLR_NAME "/cvxprodcomp",
2419 "whether to use convexity check on product composition f(h)*h",
2420 &nlhdlrdata->cvxprodcomp, TRUE, DEFAULT_CVXPRODCOMP, NULL, NULL) );
2421
2422 SCIP_CALL( SCIPaddBoolParam(scip, "nlhdlr/" CONCAVE_NLHDLR_NAME "/handletrivial",
2423 "whether to also handle trivial convex expressions",
2424 &nlhdlrdata->handletrivial, TRUE, DEFAULT_HANDLETRIVIAL, NULL, NULL) );
2425
2426 SCIPnlhdlrSetFreeHdlrData(nlhdlr, nlhdlrfreeHdlrDataConvexConcave);
2427 SCIPnlhdlrSetCopyHdlr(nlhdlr, nlhdlrCopyhdlrConcave);
2428 SCIPnlhdlrSetFreeExprData(nlhdlr, nlhdlrfreeExprDataConvexConcave);
2429 SCIPnlhdlrSetSepa(nlhdlr, nlhdlrInitSepaConcave, NULL, nlhdlrEstimateConcave, NULL);
2430 SCIPnlhdlrSetInitExit(nlhdlr, NULL, nlhdlrExitConcave);
2431
2432 return SCIP_OKAY;
2433 }
2434
2435 /** checks whether a given expression is convex or concave w.r.t. the original variables
2436 *
2437 * This function uses the methods that are used in the detection algorithm of the convex nonlinear handler.
2438 */
2439 SCIP_RETCODE SCIPhasExprCurvature(
2440 SCIP* scip, /**< SCIP data structure */
2441 SCIP_EXPR* expr, /**< expression */
2442 SCIP_EXPRCURV curv, /**< curvature to check for */
2443 SCIP_Bool* success, /**< buffer to store whether expression has curvature curv (w.r.t. original variables) */
2444 SCIP_HASHMAP* assumevarfixed /**< hashmap containing variables that should be assumed to be fixed, or NULL */
2445 )
2446 {
2447 SCIP_NLHDLRDATA nlhdlrdata;
2448 SCIP_EXPR* rootnlexpr;
2449 SCIP_HASHMAP* nlexpr2origexpr;
2450 int nleafs;
2451
2452 assert(expr != NULL);
2453 assert(curv != SCIP_EXPRCURV_UNKNOWN);
2454 assert(success != NULL);
2455
2456 /* create temporary hashmap */
2457 SCIP_CALL( SCIPhashmapCreate(&nlexpr2origexpr, SCIPblkmem(scip), 20) );
2458
2459 /* prepare nonlinear handler data */
2460 nlhdlrdata.isnlhdlrconvex = TRUE;
2461 nlhdlrdata.evalsol = NULL;
2462 nlhdlrdata.detectsum = TRUE;
2463 nlhdlrdata.extendedform = FALSE;
2464 nlhdlrdata.cvxquadratic = TRUE;
2465 nlhdlrdata.cvxsignomial = TRUE;
2466 nlhdlrdata.cvxprodcomp = TRUE;
2467 nlhdlrdata.handletrivial = TRUE;
2468
2469 SCIP_CALL( constructExpr(scip, &nlhdlrdata, &rootnlexpr, nlexpr2origexpr, &nleafs, expr, curv, assumevarfixed, success) );
2470
2471 /* free created expression */
2472 if( rootnlexpr != NULL )
2473 {
2474 SCIP_CALL( SCIPreleaseExpr(scip, &rootnlexpr) );
2475 }
2476
2477 /* free hashmap */
2478 SCIPhashmapFree(&nlexpr2origexpr);
2479
2480 return SCIP_OKAY;
2481 }
2482