| 1 | // Copyright (C) 2006, International Business Machines |
| 2 | // Corporation and others. All Rights Reserved. |
| 3 | // This code is licensed under the terms of the Eclipse Public License (EPL). |
| 4 | |
| 5 | #ifndef OsiChooseVariable_H |
| 6 | #define OsiChooseVariable_H |
| 7 | |
| 8 | #include <string> |
| 9 | #include <vector> |
| 10 | |
| 11 | #include "CoinWarmStartBasis.hpp" |
| 12 | #include "OsiBranchingObject.hpp" |
| 13 | |
| 14 | class OsiSolverInterface; |
| 15 | class OsiHotInfo; |
| 16 | |
| 17 | /** This class chooses a variable to branch on |
| 18 | |
| 19 | The base class just chooses the variable and direction without strong branching but it |
| 20 | has information which would normally be used by strong branching e.g. to re-enter |
| 21 | having fixed a variable but using same candidates for strong branching. |
| 22 | |
| 23 | The flow is : |
| 24 | a) initialize the process. This decides on strong branching list |
| 25 | and stores indices of all infeasible objects |
| 26 | b) do strong branching on list. If list is empty then just |
| 27 | choose one candidate and return without strong branching. If not empty then |
| 28 | go through list and return best. However we may find that the node is infeasible |
| 29 | or that we can fix a variable. If so we return and it is up to user to call |
| 30 | again (after fixing a variable). |
| 31 | */ |
| 32 | |
| 33 | class OsiChooseVariable { |
| 34 | |
| 35 | public: |
| 36 | |
| 37 | /// Default Constructor |
| 38 | OsiChooseVariable (); |
| 39 | |
| 40 | /// Constructor from solver (so we can set up arrays etc) |
| 41 | OsiChooseVariable (const OsiSolverInterface * solver); |
| 42 | |
| 43 | /// Copy constructor |
| 44 | OsiChooseVariable (const OsiChooseVariable &); |
| 45 | |
| 46 | /// Assignment operator |
| 47 | OsiChooseVariable & operator= (const OsiChooseVariable& rhs); |
| 48 | |
| 49 | /// Clone |
| 50 | virtual OsiChooseVariable * clone() const; |
| 51 | |
| 52 | /// Destructor |
| 53 | virtual ~OsiChooseVariable (); |
| 54 | |
| 55 | /** Sets up strong list and clears all if initialize is true. |
| 56 | Returns number of infeasibilities. |
| 57 | If returns -1 then has worked out node is infeasible! |
| 58 | */ |
| 59 | virtual int setupList ( OsiBranchingInformation *info, bool initialize); |
| 60 | /** Choose a variable |
| 61 | Returns - |
| 62 | -1 Node is infeasible |
| 63 | 0 Normal termination - we have a candidate |
| 64 | 1 All looks satisfied - no candidate |
| 65 | 2 We can change the bound on a variable - but we also have a strong branching candidate |
| 66 | 3 We can change the bound on a variable - but we have a non-strong branching candidate |
| 67 | 4 We can change the bound on a variable - no other candidates |
| 68 | We can pick up branch from bestObjectIndex() and bestWhichWay() |
| 69 | We can pick up a forced branch (can change bound) from firstForcedObjectIndex() and firstForcedWhichWay() |
| 70 | If we have a solution then we can pick up from goodObjectiveValue() and goodSolution() |
| 71 | If fixVariables is true then 2,3,4 are all really same as problem changed |
| 72 | */ |
| 73 | virtual int chooseVariable( OsiSolverInterface * solver, OsiBranchingInformation *info, bool fixVariables); |
| 74 | /// Returns true if solution looks feasible against given objects |
| 75 | virtual bool feasibleSolution(const OsiBranchingInformation * info, |
| 76 | const double * solution, |
| 77 | int numberObjects, |
| 78 | const OsiObject ** objects); |
| 79 | /// Saves a good solution |
| 80 | void saveSolution(const OsiSolverInterface * solver); |
| 81 | /// Clears out good solution after use |
| 82 | void clearGoodSolution(); |
| 83 | /// Given a candidate fill in useful information e.g. estimates |
| 84 | virtual void updateInformation( const OsiBranchingInformation *info, |
| 85 | int branch, OsiHotInfo * hotInfo); |
| 86 | #if 1 |
| 87 | /// Given a branch fill in useful information e.g. estimates |
| 88 | virtual void updateInformation( int whichObject, int branch, |
| 89 | double changeInObjective, double changeInValue, |
| 90 | int status); |
| 91 | #endif |
| 92 | /// Objective value for feasible solution |
| 93 | inline double goodObjectiveValue() const |
| 94 | { return goodObjectiveValue_;} |
| 95 | /// Estimate of up change or change on chosen if n-way |
| 96 | inline double upChange() const |
| 97 | { return upChange_;} |
| 98 | /// Estimate of down change or max change on other possibilities if n-way |
| 99 | inline double downChange() const |
| 100 | { return downChange_;} |
| 101 | /// Good solution - deleted by finalize |
| 102 | inline const double * goodSolution() const |
| 103 | { return goodSolution_;} |
| 104 | /// Index of chosen object |
| 105 | inline int bestObjectIndex() const |
| 106 | { return bestObjectIndex_;} |
| 107 | /// Set index of chosen object |
| 108 | inline void setBestObjectIndex(int value) |
| 109 | { bestObjectIndex_ = value;} |
| 110 | /// Preferred way of chosen object |
| 111 | inline int bestWhichWay() const |
| 112 | { return bestWhichWay_;} |
| 113 | /// Set preferred way of chosen object |
| 114 | inline void setBestWhichWay(int value) |
| 115 | { bestWhichWay_ = value;} |
| 116 | /// Index of forced object |
| 117 | inline int firstForcedObjectIndex() const |
| 118 | { return firstForcedObjectIndex_;} |
| 119 | /// Set index of forced object |
| 120 | inline void setFirstForcedObjectIndex(int value) |
| 121 | { firstForcedObjectIndex_ = value;} |
| 122 | /// Preferred way of forced object |
| 123 | inline int firstForcedWhichWay() const |
| 124 | { return firstForcedWhichWay_;} |
| 125 | /// Set preferred way of forced object |
| 126 | inline void setFirstForcedWhichWay(int value) |
| 127 | { firstForcedWhichWay_ = value;} |
| 128 | /// Get the number of objects unsatisfied at this node - accurate on first pass |
| 129 | inline int numberUnsatisfied() const |
| 130 | {return numberUnsatisfied_;} |
| 131 | /// Number of objects to choose for strong branching |
| 132 | inline int numberStrong() const |
| 133 | { return numberStrong_;} |
| 134 | /// Set number of objects to choose for strong branching |
| 135 | inline void setNumberStrong(int value) |
| 136 | { numberStrong_ = value;} |
| 137 | /// Number left on strong list |
| 138 | inline int numberOnList() const |
| 139 | { return numberOnList_;} |
| 140 | /// Number of strong branches actually done |
| 141 | inline int numberStrongDone() const |
| 142 | { return numberStrongDone_;} |
| 143 | /// Number of strong iterations actually done |
| 144 | inline int numberStrongIterations() const |
| 145 | { return numberStrongIterations_;} |
| 146 | /// Number of strong branches which changed bounds |
| 147 | inline int numberStrongFixed() const |
| 148 | { return numberStrongFixed_;} |
| 149 | /// List of candidates |
| 150 | inline const int * candidates() const |
| 151 | { return list_;} |
| 152 | /// Trust results from strong branching for changing bounds |
| 153 | inline bool trustStrongForBound() const |
| 154 | { return trustStrongForBound_;} |
| 155 | /// Set trust results from strong branching for changing bounds |
| 156 | inline void setTrustStrongForBound(bool yesNo) |
| 157 | { trustStrongForBound_ = yesNo;} |
| 158 | /// Trust results from strong branching for valid solution |
| 159 | inline bool trustStrongForSolution() const |
| 160 | { return trustStrongForSolution_;} |
| 161 | /// Set trust results from strong branching for valid solution |
| 162 | inline void setTrustStrongForSolution(bool yesNo) |
| 163 | { trustStrongForSolution_ = yesNo;} |
| 164 | /// Set solver and redo arrays |
| 165 | void setSolver (const OsiSolverInterface * solver); |
| 166 | /** Return status - |
| 167 | -1 Node is infeasible |
| 168 | 0 Normal termination - we have a candidate |
| 169 | 1 All looks satisfied - no candidate |
| 170 | 2 We can change the bound on a variable - but we also have a strong branching candidate |
| 171 | 3 We can change the bound on a variable - but we have a non-strong branching candidate |
| 172 | 4 We can change the bound on a variable - no other candidates |
| 173 | We can pick up branch from bestObjectIndex() and bestWhichWay() |
| 174 | We can pick up a forced branch (can change bound) from firstForcedObjectIndex() and firstForcedWhichWay() |
| 175 | If we have a solution then we can pick up from goodObjectiveValue() and goodSolution() |
| 176 | */ |
| 177 | inline int status() const |
| 178 | { return status_;} |
| 179 | inline void setStatus(int value) |
| 180 | { status_ = value;} |
| 181 | |
| 182 | |
| 183 | protected: |
| 184 | // Data |
| 185 | /// Objective value for feasible solution |
| 186 | double goodObjectiveValue_; |
| 187 | /// Estimate of up change or change on chosen if n-way |
| 188 | double upChange_; |
| 189 | /// Estimate of down change or max change on other possibilities if n-way |
| 190 | double downChange_; |
| 191 | /// Good solution - deleted by finalize |
| 192 | double * goodSolution_; |
| 193 | /// List of candidates |
| 194 | int * list_; |
| 195 | /// Useful array (for sorting etc) |
| 196 | double * useful_; |
| 197 | /// Pointer to solver |
| 198 | const OsiSolverInterface * solver_; |
| 199 | /* Status - |
| 200 | -1 Node is infeasible |
| 201 | 0 Normal termination - we have a candidate |
| 202 | 1 All looks satisfied - no candidate |
| 203 | 2 We can change the bound on a variable - but we also have a strong branching candidate |
| 204 | 3 We can change the bound on a variable - but we have a non-strong branching candidate |
| 205 | 4 We can change the bound on a variable - no other candidates |
| 206 | */ |
| 207 | int status_; |
| 208 | /// Index of chosen object |
| 209 | int bestObjectIndex_; |
| 210 | /// Preferred way of chosen object |
| 211 | int bestWhichWay_; |
| 212 | /// Index of forced object |
| 213 | int firstForcedObjectIndex_; |
| 214 | /// Preferred way of forced object |
| 215 | int firstForcedWhichWay_; |
| 216 | /// The number of objects unsatisfied at this node. |
| 217 | int numberUnsatisfied_; |
| 218 | /// Number of objects to choose for strong branching |
| 219 | int numberStrong_; |
| 220 | /// Number left on strong list |
| 221 | int numberOnList_; |
| 222 | /// Number of strong branches actually done |
| 223 | int numberStrongDone_; |
| 224 | /// Number of strong iterations actually done |
| 225 | int numberStrongIterations_; |
| 226 | /// Number of bound changes due to strong branching |
| 227 | int numberStrongFixed_; |
| 228 | /// List of unsatisfied objects - first numberOnList_ for strong branching |
| 229 | /// Trust results from strong branching for changing bounds |
| 230 | bool trustStrongForBound_; |
| 231 | /// Trust results from strong branching for valid solution |
| 232 | bool trustStrongForSolution_; |
| 233 | }; |
| 234 | |
| 235 | /** This class is the placeholder for the pseudocosts used by OsiChooseStrong. |
| 236 | It can also be used by any other pseudocost based strong branching |
| 237 | algorithm. |
| 238 | */ |
| 239 | |
| 240 | class OsiPseudoCosts { |
| 241 | protected: |
| 242 | // Data |
| 243 | /// Total of all changes up |
| 244 | double * upTotalChange_; |
| 245 | /// Total of all changes down |
| 246 | double * downTotalChange_; |
| 247 | /// Number of times up |
| 248 | int * upNumber_; |
| 249 | /// Number of times down |
| 250 | int * downNumber_; |
| 251 | /// Number of objects (could be found from solver) |
| 252 | int numberObjects_; |
| 253 | /// Number before we trust |
| 254 | int numberBeforeTrusted_; |
| 255 | |
| 256 | private: |
| 257 | void gutsOfDelete(); |
| 258 | void gutsOfCopy(const OsiPseudoCosts& rhs); |
| 259 | |
| 260 | public: |
| 261 | OsiPseudoCosts(); |
| 262 | virtual ~OsiPseudoCosts(); |
| 263 | OsiPseudoCosts(const OsiPseudoCosts& rhs); |
| 264 | OsiPseudoCosts& operator=(const OsiPseudoCosts& rhs); |
| 265 | |
| 266 | /// Number of times before trusted |
| 267 | inline int numberBeforeTrusted() const |
| 268 | { return numberBeforeTrusted_; } |
| 269 | /// Set number of times before trusted |
| 270 | inline void setNumberBeforeTrusted(int value) |
| 271 | { numberBeforeTrusted_ = value; } |
| 272 | /// Initialize the pseudocosts with n entries |
| 273 | void initialize(int n); |
| 274 | /// Give the number of objects for which pseudo costs are stored |
| 275 | inline int numberObjects() const |
| 276 | { return numberObjects_; } |
| 277 | |
| 278 | /** @name Accessor methods to pseudo costs data */ |
| 279 | //@{ |
| 280 | inline double* upTotalChange() { return upTotalChange_; } |
| 281 | inline const double* upTotalChange() const { return upTotalChange_; } |
| 282 | |
| 283 | inline double* downTotalChange() { return downTotalChange_; } |
| 284 | inline const double* downTotalChange() const { return downTotalChange_; } |
| 285 | |
| 286 | inline int* upNumber() { return upNumber_; } |
| 287 | inline const int* upNumber() const { return upNumber_; } |
| 288 | |
| 289 | inline int* downNumber() { return downNumber_; } |
| 290 | inline const int* downNumber() const { return downNumber_; } |
| 291 | //@} |
| 292 | |
| 293 | /// Given a candidate fill in useful information e.g. estimates |
| 294 | virtual void updateInformation(const OsiBranchingInformation *info, |
| 295 | int branch, OsiHotInfo * hotInfo); |
| 296 | #if 1 |
| 297 | /// Given a branch fill in useful information e.g. estimates |
| 298 | virtual void updateInformation( int whichObject, int branch, |
| 299 | double changeInObjective, double changeInValue, |
| 300 | int status); |
| 301 | #endif |
| 302 | }; |
| 303 | |
| 304 | /** This class chooses a variable to branch on |
| 305 | |
| 306 | This chooses the variable and direction with reliability strong branching. |
| 307 | |
| 308 | The flow is : |
| 309 | a) initialize the process. This decides on strong branching list |
| 310 | and stores indices of all infeasible objects |
| 311 | b) do strong branching on list. If list is empty then just |
| 312 | choose one candidate and return without strong branching. If not empty then |
| 313 | go through list and return best. However we may find that the node is infeasible |
| 314 | or that we can fix a variable. If so we return and it is up to user to call |
| 315 | again (after fixing a variable). |
| 316 | */ |
| 317 | |
| 318 | class OsiChooseStrong : public OsiChooseVariable { |
| 319 | |
| 320 | public: |
| 321 | |
| 322 | /// Default Constructor |
| 323 | OsiChooseStrong (); |
| 324 | |
| 325 | /// Constructor from solver (so we can set up arrays etc) |
| 326 | OsiChooseStrong (const OsiSolverInterface * solver); |
| 327 | |
| 328 | /// Copy constructor |
| 329 | OsiChooseStrong (const OsiChooseStrong &); |
| 330 | |
| 331 | /// Assignment operator |
| 332 | OsiChooseStrong & operator= (const OsiChooseStrong& rhs); |
| 333 | |
| 334 | /// Clone |
| 335 | virtual OsiChooseVariable * clone() const override; |
| 336 | |
| 337 | /// Destructor |
| 338 | virtual ~OsiChooseStrong (); |
| 339 | |
| 340 | /** Sets up strong list and clears all if initialize is true. |
| 341 | Returns number of infeasibilities. |
| 342 | If returns -1 then has worked out node is infeasible! |
| 343 | */ |
| 344 | virtual int setupList ( OsiBranchingInformation *info, bool initialize) override; |
| 345 | /** Choose a variable |
| 346 | Returns - |
| 347 | -1 Node is infeasible |
| 348 | 0 Normal termination - we have a candidate |
| 349 | 1 All looks satisfied - no candidate |
| 350 | 2 We can change the bound on a variable - but we also have a strong branching candidate |
| 351 | 3 We can change the bound on a variable - but we have a non-strong branching candidate |
| 352 | 4 We can change the bound on a variable - no other candidates |
| 353 | We can pick up branch from bestObjectIndex() and bestWhichWay() |
| 354 | We can pick up a forced branch (can change bound) from firstForcedObjectIndex() and firstForcedWhichWay() |
| 355 | If we have a solution then we can pick up from goodObjectiveValue() and goodSolution() |
| 356 | If fixVariables is true then 2,3,4 are all really same as problem changed |
| 357 | */ |
| 358 | virtual int chooseVariable( OsiSolverInterface * solver, OsiBranchingInformation *info, bool fixVariables) override; |
| 359 | |
| 360 | /** Pseudo Shadow Price mode |
| 361 | 0 - off |
| 362 | 1 - use if no strong info |
| 363 | 2 - use if strong not trusted |
| 364 | 3 - use even if trusted |
| 365 | */ |
| 366 | inline int shadowPriceMode() const |
| 367 | { return shadowPriceMode_;} |
| 368 | /// Set Shadow price mode |
| 369 | inline void setShadowPriceMode(int value) |
| 370 | { shadowPriceMode_ = value;} |
| 371 | |
| 372 | /** Accessor method to pseudo cost object*/ |
| 373 | const OsiPseudoCosts& pseudoCosts() const |
| 374 | { return pseudoCosts_; } |
| 375 | |
| 376 | /** Accessor method to pseudo cost object*/ |
| 377 | OsiPseudoCosts& pseudoCosts() |
| 378 | { return pseudoCosts_; } |
| 379 | |
| 380 | /** A feww pass-through methods to access members of pseudoCosts_ as if they |
| 381 | were members of OsiChooseStrong object */ |
| 382 | inline int numberBeforeTrusted() const { |
| 383 | return pseudoCosts_.numberBeforeTrusted(); } |
| 384 | inline void setNumberBeforeTrusted(int value) { |
| 385 | pseudoCosts_.setNumberBeforeTrusted(value); } |
| 386 | inline int numberObjects() const { |
| 387 | return pseudoCosts_.numberObjects(); } |
| 388 | |
| 389 | protected: |
| 390 | |
| 391 | /** This is a utility function which does strong branching on |
| 392 | a list of objects and stores the results in OsiHotInfo.objects. |
| 393 | On entry the object sequence is stored in the OsiHotInfo object |
| 394 | and maybe more. |
| 395 | It returns - |
| 396 | -1 - one branch was infeasible both ways |
| 397 | 0 - all inspected - nothing can be fixed |
| 398 | 1 - all inspected - some can be fixed (returnCriterion==0) |
| 399 | 2 - may be returning early - one can be fixed (last one done) (returnCriterion==1) |
| 400 | 3 - returning because max time |
| 401 | |
| 402 | */ |
| 403 | int doStrongBranching( OsiSolverInterface * solver, |
| 404 | OsiBranchingInformation *info, |
| 405 | int numberToDo, int returnCriterion); |
| 406 | |
| 407 | /** Clear out the results array */ |
| 408 | void resetResults(int num); |
| 409 | |
| 410 | protected: |
| 411 | /** Pseudo Shadow Price mode |
| 412 | 0 - off |
| 413 | 1 - use and multiply by strong info |
| 414 | 2 - use |
| 415 | */ |
| 416 | int shadowPriceMode_; |
| 417 | |
| 418 | /** The pseudo costs for the chooser */ |
| 419 | OsiPseudoCosts pseudoCosts_; |
| 420 | |
| 421 | /** The results of the strong branching done on the candidates where the |
| 422 | pseudocosts were not sufficient */ |
| 423 | OsiHotInfo* results_; |
| 424 | /** The number of OsiHotInfo objetcs that contain information */ |
| 425 | int numResults_; |
| 426 | }; |
| 427 | |
| 428 | /** This class contains the result of strong branching on a variable |
| 429 | When created it stores enough information for strong branching |
| 430 | */ |
| 431 | |
| 432 | class OsiHotInfo { |
| 433 | |
| 434 | public: |
| 435 | |
| 436 | /// Default Constructor |
| 437 | OsiHotInfo (); |
| 438 | |
| 439 | /// Constructor from useful information |
| 440 | OsiHotInfo ( OsiSolverInterface * solver, |
| 441 | const OsiBranchingInformation *info, |
| 442 | const OsiObject * const * objects, |
| 443 | int whichObject); |
| 444 | |
| 445 | /// Copy constructor |
| 446 | OsiHotInfo (const OsiHotInfo &); |
| 447 | |
| 448 | /// Assignment operator |
| 449 | OsiHotInfo & operator= (const OsiHotInfo& rhs); |
| 450 | |
| 451 | /// Clone |
| 452 | virtual OsiHotInfo * clone() const; |
| 453 | |
| 454 | /// Destructor |
| 455 | virtual ~OsiHotInfo (); |
| 456 | |
| 457 | /** Fill in useful information after strong branch. |
| 458 | Return status |
| 459 | */ |
| 460 | int updateInformation( const OsiSolverInterface * solver, const OsiBranchingInformation * info, |
| 461 | OsiChooseVariable * choose); |
| 462 | /// Original objective value |
| 463 | inline double originalObjectiveValue() const |
| 464 | { return originalObjectiveValue_;} |
| 465 | /// Up change - invalid if n-way |
| 466 | inline double upChange() const |
| 467 | { assert (branchingObject_->numberBranches()==2); return changes_[1];} |
| 468 | /// Down change - invalid if n-way |
| 469 | inline double downChange() const |
| 470 | { assert (branchingObject_->numberBranches()==2); return changes_[0];} |
| 471 | /// Set up change - invalid if n-way |
| 472 | inline void setUpChange(double value) |
| 473 | { assert (branchingObject_->numberBranches()==2); changes_[1] = value;} |
| 474 | /// Set down change - invalid if n-way |
| 475 | inline void setDownChange(double value) |
| 476 | { assert (branchingObject_->numberBranches()==2); changes_[0] = value;} |
| 477 | /// Change on way k |
| 478 | inline double change(int k) const |
| 479 | { return changes_[k];} |
| 480 | |
| 481 | /// Up iteration count - invalid if n-way |
| 482 | inline int upIterationCount() const |
| 483 | { assert (branchingObject_->numberBranches()==2); return iterationCounts_[1];} |
| 484 | /// Down iteration count - invalid if n-way |
| 485 | inline int downIterationCount() const |
| 486 | { assert (branchingObject_->numberBranches()==2); return iterationCounts_[0];} |
| 487 | /// Iteration count on way k |
| 488 | inline int iterationCount(int k) const |
| 489 | { return iterationCounts_[k];} |
| 490 | |
| 491 | /// Up status - invalid if n-way |
| 492 | inline int upStatus() const |
| 493 | { assert (branchingObject_->numberBranches()==2); return statuses_[1];} |
| 494 | /// Down status - invalid if n-way |
| 495 | inline int downStatus() const |
| 496 | { assert (branchingObject_->numberBranches()==2); return statuses_[0];} |
| 497 | /// Set up status - invalid if n-way |
| 498 | inline void setUpStatus(int value) |
| 499 | { assert (branchingObject_->numberBranches()==2); statuses_[1] = value;} |
| 500 | /// Set down status - invalid if n-way |
| 501 | inline void setDownStatus(int value) |
| 502 | { assert (branchingObject_->numberBranches()==2); statuses_[0] = value;} |
| 503 | /// Status on way k |
| 504 | inline int status(int k) const |
| 505 | { return statuses_[k];} |
| 506 | /// Branching object |
| 507 | inline OsiBranchingObject * branchingObject() const |
| 508 | { return branchingObject_;} |
| 509 | inline int whichObject() const |
| 510 | { return whichObject_;} |
| 511 | |
| 512 | protected: |
| 513 | // Data |
| 514 | /// Original objective value |
| 515 | double originalObjectiveValue_; |
| 516 | /// Objective changes |
| 517 | double * changes_; |
| 518 | /// Iteration counts |
| 519 | int * iterationCounts_; |
| 520 | /** Status |
| 521 | -1 - not done |
| 522 | 0 - feasible and finished |
| 523 | 1 - infeasible |
| 524 | 2 - not finished |
| 525 | */ |
| 526 | int * statuses_; |
| 527 | /// Branching object |
| 528 | OsiBranchingObject * branchingObject_; |
| 529 | /// Which object on list |
| 530 | int whichObject_; |
| 531 | }; |
| 532 | |
| 533 | |
| 534 | #endif |
| 535 | |