1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2 /* */ 3 /* This file is part of the program and library */ 4 /* SCIP --- Solving Constraint Integer Programs */ 5 /* */ 6 /* Copyright (C) 2002-2021 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 scipopt.org. */ 13 /* */ 14 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 15 16 /**@file cons_nonlinear.h 17 * @ingroup CONSHDLRS 18 * @brief constraint handler for nonlinear constraints \f$\textrm{lhs} \leq \sum_{i=1}^n a_ix_i + \sum_{j=1}^m c_jf_j(x) \leq \textrm{rhs}\f$ 19 * @author Stefan Vigerske 20 * 21 */ 22 23 /*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/ 24 25 #ifndef __SCIP_CONS_NONLINEAR_H__ 26 #define __SCIP_CONS_NONLINEAR_H__ 27 28 #include "scip/def.h" 29 #include "scip/type_cons.h" 30 #include "nlpi/type_expr.h" 31 #include "scip/type_nlp.h" 32 #include "scip/type_retcode.h" 33 #include "scip/type_scip.h" 34 #include "scip/type_sol.h" 35 #include "scip/type_var.h" 36 37 #ifdef __cplusplus 38 extern "C" { 39 #endif 40 41 /** upgrading method for nonlinear constraints into more specific constraints 42 * 43 * the method might upgrade a nonlinear constraint into a set of upgrade constraints 44 * the caller provided an array upgdconss to store upgrade constraints 45 * the length of upgdconss is given by upgdconsssize 46 * if an upgrade is not possible, set *nupgdconss to zero 47 * if more than upgdconsssize many constraints shall replace cons, the function 48 * should return the required number as negated value in *nupgdconss 49 * i.e., if cons should be replaced by 3 constraints, the function should set 50 * *nupgdconss to -3 and return with SCIP_OKAY 51 * 52 * input: 53 * - scip : SCIP main data structure 54 * - cons : the nonlinear constraint to upgrade 55 * - nupgdconss : pointer to store number of constraints that replace this constraint 56 * - upgdconss : array to store constraints that replace this constraint 57 * - upgdconsssize : length of the provided upgdconss array 58 */ 59 #define SCIP_DECL_NONLINCONSUPGD(x) SCIP_RETCODE x (SCIP* scip, SCIP_CONS* cons, \ 60 int* nupgdconss, SCIP_CONS** upgdconss, int upgdconsssize) 61 62 /** reformulation method for expression graph nodes 63 * 64 * The method might reformulate a node in an expression graph by adding 65 * auxiliary constraints and/or variables. 66 * The caller provided an expression graph node which is to be reformulated. 67 * If the method takes action, it has to return the node that should replace 68 * the given node in *reformnode. The caller will then ensure that all parents of 69 * node will use *reformnode, so node may be freed. 70 * If the method does not do any reformulation, it shall return NULL in *reformnode. 71 * The counter naddcons can be used to setup the names of added variables/constraints. 72 * The method should increase this counter by the number of added constraints. 73 * The method has to ensure that the reformulated node, if still valid, 74 * has valid bound and curvature information. 75 * 76 * input: 77 * - scip : SCIP main data structure 78 * - exprgraph : the expression graph which node to reformulate 79 * - node : the expression graph node to reformulate 80 * - naddcons : counter on number of added constraints so far 81 * 82 * output: 83 * - naddcons : to be increased by number of additionally added constraints 84 * - reformnode : reformulated node to replace node with, or NULL if no reformulation 85 */ 86 #define SCIP_DECL_EXPRGRAPHNODEREFORM(x) SCIP_RETCODE x (SCIP* scip, \ 87 SCIP_EXPRGRAPH* exprgraph, SCIP_EXPRGRAPHNODE* node, \ 88 int* naddcons, SCIP_EXPRGRAPHNODE** reformnode) 89 90 /** creates the handler for nonlinear constraints and includes it in SCIP 91 * 92 * @ingroup ConshdlrIncludes 93 * */ 94 SCIP_EXPORT 95 SCIP_RETCODE SCIPincludeConshdlrNonlinear( 96 SCIP* scip /**< SCIP data structure */ 97 ); 98 99 /**@addtogroup CONSHDLRS 100 * 101 * @{ 102 * 103 * @name Nonlinear Constraints 104 * 105 * @{ 106 * 107 * This constraint handler handles constraints of the form 108 * \f[ 109 * \textrm{lhs} \leq \sum_{i=1}^n a_ix_i + \sum_{j=1}^m c_jf_j(x) \leq \textrm{rhs}, 110 * \f] 111 * where \f$a_i\f$ and \f$c_j\f$ are coefficients and 112 * \f$f_j(x)\f$ are nonlinear functions (given as expression tree). 113 * 114 * Constraints are enforced by separation, domain propagation, and spatial branching. 115 * 116 * For convex or concave \f$f_j(x)\f$, cuts that separate on the convex hull of the function graph are implemented. 117 * For \f$f_j(x)\f$ that are not known to be convex or concave, a simple variant of linear estimation based on interval gradients is implemented. 118 * 119 * Branching is performed for variables in nonconvex terms, if the relaxation solution cannot be separated. 120 * 121 * This header offers the upgrade functionality to upgrade a general nonlinear constraint into a more specific constraint 122 * via SCIP_DECL_NONLINCONSUPGD(). 123 * 124 * Furthermore, the definition of callbacks used to reformulate an expression graph is offered by 125 * SCIP_DECL_EXPRGRAPHNODEREFORM(). 126 * 127 * Further, the function representation is stored in an expression graph, which allows to propagate variable domains 128 * and constraint sides and offers a simple convexity check. 129 * During presolve, the expression graph is reformulated, whereby new variables and constraints are created 130 * such that for the remaining nonlinear constraints the functions \f$f_j(x)\f$ are known to be convex or concave. 131 * See also 132 * 133 * @par 134 * Stefan Vigerske@n 135 * Decomposition of Multistage Stochastic Programs and a Constraint Integer Programming Approach to Mixed-Integer Nonlinear Programming@n 136 * PhD Thesis, Humboldt-University Berlin, 2012, submitted. 137 */ 138 139 /** includes a nonlinear constraint upgrade method into the nonlinear constraint handler */ 140 SCIP_EXPORT 141 SCIP_RETCODE SCIPincludeNonlinconsUpgrade( 142 SCIP* scip, /**< SCIP data structure */ 143 SCIP_DECL_NONLINCONSUPGD((*nonlinconsupgd)),/**< method to call for upgrading nonlinear constraint, or NULL */ 144 SCIP_DECL_EXPRGRAPHNODEREFORM((*nodereform)),/**< method to call for reformulating expression graph node, or NULL */ 145 int priority, /**< priority of upgrading method */ 146 SCIP_Bool active, /**< should the upgrading method by active by default? */ 147 const char* conshdlrname /**< name of the constraint handler */ 148 ); 149 150 /** creates and captures a nonlinear constraint 151 * this variant takes expression trees as input 152 * 153 * @note the constraint gets captured, hence at one point you have to release it using the method SCIPreleaseCons() 154 */ 155 SCIP_EXPORT 156 SCIP_RETCODE SCIPcreateConsNonlinear( 157 SCIP* scip, /**< SCIP data structure */ 158 SCIP_CONS** cons, /**< pointer to hold the created constraint */ 159 const char* name, /**< name of constraint */ 160 int nlinvars, /**< number of linear variables in the constraint */ 161 SCIP_VAR** linvars, /**< array with linear variables of constraint entries */ 162 SCIP_Real* lincoefs, /**< array with coefficients of constraint linear entries */ 163 int nexprtrees, /**< number of expression trees for nonlinear part of constraint */ 164 SCIP_EXPRTREE** exprtrees, /**< expression trees for nonlinear part of constraint */ 165 SCIP_Real* nonlincoefs, /**< coefficients for expression trees for nonlinear part, or NULL if all 1.0 */ 166 SCIP_Real lhs, /**< left hand side of constraint */ 167 SCIP_Real rhs, /**< right hand side of constraint */ 168 SCIP_Bool initial, /**< should the LP relaxation of constraint be in the initial LP? 169 * Usually set to TRUE. Set to FALSE for 'lazy constraints'. */ 170 SCIP_Bool separate, /**< should the constraint be separated during LP processing? 171 * Usually set to TRUE. */ 172 SCIP_Bool enforce, /**< should the constraint be enforced during node processing? 173 * TRUE for model constraints, FALSE for additional, redundant constraints. */ 174 SCIP_Bool check, /**< should the constraint be checked for feasibility? 175 * TRUE for model constraints, FALSE for additional, redundant constraints. */ 176 SCIP_Bool propagate, /**< should the constraint be propagated during node processing? 177 * Usually set to TRUE. */ 178 SCIP_Bool local, /**< is constraint only valid locally? 179 * Usually set to FALSE. Has to be set to TRUE, e.g., for branching constraints. */ 180 SCIP_Bool modifiable, /**< is constraint modifiable (subject to column generation)? 181 * Usually set to FALSE. In column generation applications, set to TRUE if pricing 182 * adds coefficients to this constraint. */ 183 SCIP_Bool dynamic, /**< is constraint subject to aging? 184 * Usually set to FALSE. Set to TRUE for own cuts which 185 * are seperated as constraints. */ 186 SCIP_Bool removable, /**< should the relaxation be removed from the LP due to aging or cleanup? 187 * Usually set to FALSE. Set to TRUE for 'lazy constraints' and 'user cuts'. */ 188 SCIP_Bool stickingatnode /**< should the constraint always be kept at the node where it was added, even 189 * if it may be moved to a more global node? 190 * Usually set to FALSE. Set to TRUE to for constraints that represent node data. */ 191 ); 192 193 /** creates and captures a nonlinear constraint 194 * in its most basic version, i. e., all constraint flags are set to their basic value as explained for the 195 * method SCIPcreateConsNonlinear(); all flags can be set via SCIPsetConsFLAGNAME-methods in scip.h 196 * 197 * this variant takes expression trees as input 198 * 199 * @see SCIPcreateConsNonlinear() for information about the basic constraint flag configuration 200 * 201 * @note the constraint gets captured, hence at one point you have to release it using the method SCIPreleaseCons() 202 */ 203 SCIP_EXPORT 204 SCIP_RETCODE SCIPcreateConsBasicNonlinear( 205 SCIP* scip, /**< SCIP data structure */ 206 SCIP_CONS** cons, /**< pointer to hold the created constraint */ 207 const char* name, /**< name of constraint */ 208 int nlinvars, /**< number of linear variables in the constraint */ 209 SCIP_VAR** linvars, /**< array with linear variables of constraint entries */ 210 SCIP_Real* lincoefs, /**< array with coefficients of constraint linear entries */ 211 int nexprtrees, /**< number of expression trees for nonlinear part of constraint */ 212 SCIP_EXPRTREE** exprtrees, /**< expression trees for nonlinear part of constraint */ 213 SCIP_Real* nonlincoefs, /**< coefficients for expression trees for nonlinear part, or NULL if all 1.0 */ 214 SCIP_Real lhs, /**< left hand side of constraint */ 215 SCIP_Real rhs /**< right hand side of constraint */ 216 ); 217 218 /** creates and captures a nonlinear constraint 219 * this variant takes a node of the expression graph as input and can only be used during presolving 220 * it is assumed that the nonlinear constraint will be added to the transformed problem short after creation 221 * the given exprgraphnode is captured in this method 222 * 223 * @note the constraint gets captured, hence at one point you have to release it using the method SCIPreleaseCons() 224 */ 225 SCIP_EXPORT 226 SCIP_RETCODE SCIPcreateConsNonlinear2( 227 SCIP* scip, /**< SCIP data structure */ 228 SCIP_CONS** cons, /**< pointer to hold the created constraint */ 229 const char* name, /**< name of constraint */ 230 int nlinvars, /**< number of linear variables in the constraint */ 231 SCIP_VAR** linvars, /**< array with linear variables of constraint entries */ 232 SCIP_Real* lincoefs, /**< array with coefficients of constraint linear entries */ 233 SCIP_EXPRGRAPHNODE* exprgraphnode, /**< expression graph node associated to nonlinear expression */ 234 SCIP_Real lhs, /**< left hand side of constraint */ 235 SCIP_Real rhs, /**< right hand side of constraint */ 236 SCIP_Bool initial, /**< should the LP relaxation of constraint be in the initial LP? 237 * Usually set to TRUE. Set to FALSE for 'lazy constraints'. */ 238 SCIP_Bool separate, /**< should the constraint be separated during LP processing? 239 * Usually set to TRUE. */ 240 SCIP_Bool enforce, /**< should the constraint be enforced during node processing? 241 * TRUE for model constraints, FALSE for additional, redundant constraints. */ 242 SCIP_Bool check, /**< should the constraint be checked for feasibility? 243 * TRUE for model constraints, FALSE for additional, redundant constraints. */ 244 SCIP_Bool propagate, /**< should the constraint be propagated during node processing? 245 * Usually set to TRUE. */ 246 SCIP_Bool local, /**< is constraint only valid locally? 247 * Usually set to FALSE. Has to be set to TRUE, e.g., for branching constraints. */ 248 SCIP_Bool modifiable, /**< is constraint modifiable (subject to column generation)? 249 * Usually set to FALSE. In column generation applications, set to TRUE if pricing 250 * adds coefficients to this constraint. */ 251 SCIP_Bool dynamic, /**< is constraint subject to aging? 252 * Usually set to FALSE. Set to TRUE for own cuts which 253 * are seperated as constraints. */ 254 SCIP_Bool removable, /**< should the relaxation be removed from the LP due to aging or cleanup? 255 * Usually set to FALSE. Set to TRUE for 'lazy constraints' and 'user cuts'. */ 256 SCIP_Bool stickingatnode /**< should the constraint always be kept at the node where it was added, even 257 * if it may be moved to a more global node? 258 * Usually set to FALSE. Set to TRUE to for constraints that represent node data. */ 259 ); 260 261 /** creates and captures a nonlinear constraint 262 * in its most basic version, i. e., all constraint flags are set to their basic value as explained for the 263 * method SCIPcreateConsNonlinear2(); all flags can be set via SCIPsetConsFLAGNAME-methods in scip.h 264 * 265 * this variant takes a node of the expression graph as input and can only be used during presolving 266 * it is assumed that the nonlinear constraint will be added to the transformed problem short after creation 267 * the given exprgraphnode is captured in this method 268 * 269 * @see SCIPcreateConsNonlinear2() for information about the basic constraint flag configuration 270 * 271 * @note the constraint gets captured, hence at one point you have to release it using the method SCIPreleaseCons() 272 */ 273 SCIP_EXPORT 274 SCIP_RETCODE SCIPcreateConsBasicNonlinear2( 275 SCIP* scip, /**< SCIP data structure */ 276 SCIP_CONS** cons, /**< pointer to hold the created constraint */ 277 const char* name, /**< name of constraint */ 278 int nlinvars, /**< number of linear variables in the constraint */ 279 SCIP_VAR** linvars, /**< array with linear variables of constraint entries */ 280 SCIP_Real* lincoefs, /**< array with coefficients of constraint linear entries */ 281 SCIP_EXPRGRAPHNODE* exprgraphnode, /**< expression graph node associated to nonlinear expression */ 282 SCIP_Real lhs, /**< left hand side of constraint */ 283 SCIP_Real rhs /**< right hand side of constraint */ 284 ); 285 286 /** adds a linear variable with coefficient to a nonlinear constraint */ 287 SCIP_EXPORT 288 SCIP_RETCODE SCIPaddLinearVarNonlinear( 289 SCIP* scip, /**< SCIP data structure */ 290 SCIP_CONS* cons, /**< constraint */ 291 SCIP_VAR* var, /**< variable */ 292 SCIP_Real coef /**< coefficient of variable */ 293 ); 294 295 /** sets the expression trees in a nonlinear constraint 296 * constraint must not be active yet 297 */ 298 SCIP_EXPORT 299 SCIP_RETCODE SCIPsetExprtreesNonlinear( 300 SCIP* scip, /**< SCIP data structure */ 301 SCIP_CONS* cons, /**< constraint */ 302 int nexprtrees, /**< number of expression trees */ 303 SCIP_EXPRTREE** exprtrees, /**< new expression trees, or NULL if nexprtrees is 0 */ 304 SCIP_Real* coefs /**< coefficients of expression trees, or NULL if all 1.0 */ 305 ); 306 307 /** adds expression trees to a nonlinear constraint 308 * constraint must not be active yet 309 */ 310 SCIP_EXPORT 311 SCIP_RETCODE SCIPaddExprtreesNonlinear( 312 SCIP* scip, /**< SCIP data structure */ 313 SCIP_CONS* cons, /**< constraint */ 314 int nexprtrees, /**< number of expression trees */ 315 SCIP_EXPRTREE** exprtrees, /**< new expression trees, or NULL if nexprtrees is 0 */ 316 SCIP_Real* coefs /**< coefficients of expression trees, or NULL if all 1.0 */ 317 ); 318 319 /** gets the nonlinear constraint as a nonlinear row representation */ 320 SCIP_EXPORT 321 SCIP_RETCODE SCIPgetNlRowNonlinear( 322 SCIP* scip, /**< SCIP data structure */ 323 SCIP_CONS* cons, /**< constraint */ 324 SCIP_NLROW** nlrow /**< pointer to store nonlinear row */ 325 ); 326 327 /** gets the number of variables in the linear term of a nonlinear constraint */ 328 SCIP_EXPORT 329 int SCIPgetNLinearVarsNonlinear( 330 SCIP* scip, /**< SCIP data structure */ 331 SCIP_CONS* cons /**< constraint */ 332 ); 333 334 /** gets the variables in the linear part of a nonlinear constraint */ 335 SCIP_EXPORT 336 SCIP_VAR** SCIPgetLinearVarsNonlinear( 337 SCIP* scip, /**< SCIP data structure */ 338 SCIP_CONS* cons /**< constraint */ 339 ); 340 341 /** gets the coefficients in the linear part of a nonlinear constraint */ 342 SCIP_EXPORT 343 SCIP_Real* SCIPgetLinearCoefsNonlinear( 344 SCIP* scip, /**< SCIP data structure */ 345 SCIP_CONS* cons /**< constraint */ 346 ); 347 348 /** gets the number of expression trees of a nonlinear constraint */ 349 SCIP_EXPORT 350 int SCIPgetNExprtreesNonlinear( 351 SCIP* scip, /**< SCIP data structure */ 352 SCIP_CONS* cons /**< constraint */ 353 ); 354 355 /** gets the expression trees of a nonlinear constraint */ 356 SCIP_EXPORT 357 SCIP_EXPRTREE** SCIPgetExprtreesNonlinear( 358 SCIP* scip, /**< SCIP data structure */ 359 SCIP_CONS* cons /**< constraint */ 360 ); 361 362 /** gets the coefficients of the expression trees of a nonlinear constraint */ 363 SCIP_EXPORT 364 SCIP_Real* SCIPgetExprtreeCoefsNonlinear( 365 SCIP* scip, /**< SCIP data structure */ 366 SCIP_CONS* cons /**< constraint */ 367 ); 368 369 /** gets the expression graph node of a nonlinear constraint */ 370 SCIP_EXPORT 371 SCIP_EXPRGRAPHNODE* SCIPgetExprgraphNodeNonlinear( 372 SCIP* scip, /**< SCIP data structure */ 373 SCIP_CONS* cons /**< constraint */ 374 ); 375 376 /** gets the left hand side of a nonlinear constraint */ 377 SCIP_EXPORT 378 SCIP_Real SCIPgetLhsNonlinear( 379 SCIP* scip, /**< SCIP data structure */ 380 SCIP_CONS* cons /**< constraint */ 381 ); 382 383 /** gets the right hand side of a nonlinear constraint */ 384 SCIP_EXPORT 385 SCIP_Real SCIPgetRhsNonlinear( 386 SCIP* scip, /**< SCIP data structure */ 387 SCIP_CONS* cons /**< constraint */ 388 ); 389 390 /** check the function of a nonlinear constraint for convexity/concavity, if not done yet */ 391 SCIP_EXPORT 392 SCIP_RETCODE SCIPcheckCurvatureNonlinear( 393 SCIP* scip, /**< SCIP data structure */ 394 SCIP_CONS* cons /**< constraint */ 395 ); 396 397 /** gets the curvature of the nonlinear function of a nonlinear constraint 398 * 399 * The curvature is computed by summing up the curvature for each nonlinear summand. 400 * To get the curvature for single summands, use SCIPgetExprtreeCurvaturesNonlinear(). 401 */ 402 SCIP_EXPORT 403 SCIP_RETCODE SCIPgetCurvatureNonlinear( 404 SCIP* scip, /**< SCIP data structure */ 405 SCIP_CONS* cons, /**< constraint */ 406 SCIP_Bool checkcurv, /**< whether to check constraint curvature, if not checked before */ 407 SCIP_EXPRCURV* curvature /**< pointer to store curvature of constraint */ 408 ); 409 410 /** gets the curvature of the expression trees (multiplied by their coefficient) of a nonlinear constraint */ 411 SCIP_EXPORT 412 SCIP_RETCODE SCIPgetExprtreeCurvaturesNonlinear( 413 SCIP* scip, /**< SCIP data structure */ 414 SCIP_CONS* cons, /**< constraint */ 415 SCIP_Bool checkcurv, /**< whether to check constraint curvature, if not checked before */ 416 SCIP_EXPRCURV** curvatures /**< buffer to store curvatures of exprtrees */ 417 ); 418 419 /** computes the violation of a nonlinear constraint by a solution */ 420 SCIP_EXPORT 421 SCIP_RETCODE SCIPgetViolationNonlinear( 422 SCIP* scip, /**< SCIP data structure */ 423 SCIP_CONS* cons, /**< constraint */ 424 SCIP_SOL* sol, /**< solution which violation to calculate, or NULL for LP solution */ 425 SCIP_Real* violation /**< pointer to store violation of constraint */ 426 ); 427 428 /** get index of a linear variable of a nonlinear constraint that may be decreased without making any other constraint infeasible, or -1 if none */ 429 SCIP_EXPORT 430 int SCIPgetLinvarMayDecreaseNonlinear( 431 SCIP* scip, /**< SCIP data structure */ 432 SCIP_CONS* cons /**< constraint */ 433 ); 434 435 /** get index of a linear variable of a nonlinear constraint that may be increased without making any other constraint infeasible, or -1 if none */ 436 SCIP_EXPORT 437 int SCIPgetLinvarMayIncreaseNonlinear( 438 SCIP* scip, /**< SCIP data structure */ 439 SCIP_CONS* cons /**< constraint */ 440 ); 441 442 /** gets expression graph of nonlinear constraint handler */ 443 SCIP_EXPORT 444 SCIP_EXPRGRAPH* SCIPgetExprgraphNonlinear( 445 SCIP* scip, /**< SCIP data structure */ 446 SCIP_CONSHDLR* conshdlr /**< nonlinear constraint handler */ 447 ); 448 449 /** given three points, constructs coefficient of equation for hyperplane generated by these three points 450 * Three points a, b, and c are given. 451 * Computes coefficients alpha, beta, gamma, and delta, such that a, b, and c, satisfy 452 * alpha * x1 + beta * x2 + gamma * x3 = delta and gamma >= 0.0. 453 */ 454 SCIP_EXPORT 455 SCIP_RETCODE SCIPcomputeHyperplaneThreePoints( 456 SCIP* scip, /**< SCIP data structure */ 457 SCIP_Real a1, /**< first coordinate of a */ 458 SCIP_Real a2, /**< second coordinate of a */ 459 SCIP_Real a3, /**< third coordinate of a */ 460 SCIP_Real b1, /**< first coordinate of b */ 461 SCIP_Real b2, /**< second coordinate of b */ 462 SCIP_Real b3, /**< third coordinate of b */ 463 SCIP_Real c1, /**< first coordinate of c */ 464 SCIP_Real c2, /**< second coordinate of c */ 465 SCIP_Real c3, /**< third coordinate of c */ 466 SCIP_Real* alpha, /**< coefficient of first coordinate */ 467 SCIP_Real* beta, /**< coefficient of second coordinate */ 468 SCIP_Real* gamma_, /**< coefficient of third coordinate */ 469 SCIP_Real* delta /**< constant right-hand side */ 470 ); 471 472 /** @} */ 473 474 /** @} */ 475 476 #ifdef __cplusplus 477 } 478 #endif 479 480 #endif 481