|
| 1 | +# `Highs::run()` |
| 2 | + |
| 3 | +`Highs::run()` has evolved a great deal since it was first created to |
| 4 | +"solve" the LP in the `HighsLp` instance `Highs::lp_`. As well as |
| 5 | +solving more problem classes, and using more solvers, features have |
| 6 | +been added inelegantly. The simplicity of having a single |
| 7 | +`Highs::run()` method, with different features obscured within it will |
| 8 | +be replaced by multiple, nested, "solve" methods which are explicit in |
| 9 | +their features and very much simpler. |
| 10 | + |
| 11 | +The only refactoring came when the multiple objective code was added: |
| 12 | +`Highs::optimizeModel()` inherited the content of `Highs::run()` so |
| 13 | +that a single call to`Highs::run()` could perform multiple |
| 14 | +optimizations. |
| 15 | + |
| 16 | +Other developments that have been implemented inelegantly are |
| 17 | + |
| 18 | +### Actioning executable run-time options via `Highs::run()` |
| 19 | + |
| 20 | +As [#2269](https://github.com/ERGO-Code/HiGHS/issues/2269) |
| 21 | +highlighted, users of `cvxpy` can only execute `Highs::run()`, so the |
| 22 | +following actions that were previously in `app/RunHighs.cpp`, are now |
| 23 | +performed in `Highs::run()` |
| 24 | + |
| 25 | +- Read from a solution and/or basis file |
| 26 | +- Write out the model |
| 27 | +- Write out the IIS model |
| 28 | +- Write out the solution and/or basis file. |
| 29 | + |
| 30 | +There is still one action in`app/RunHighs.cpp` that should be performed in `Highs::run()` |
| 31 | + |
| 32 | +- Write out the presolved model |
| 33 | + |
| 34 | +These "HiGHS files" actions must only be performed at the "top level" |
| 35 | +of `Highs::run()`, and this is acheived by caching the file options in |
| 36 | +the `Highs` class and clearing them from options_ so that they aren't |
| 37 | +applied at lower level calls to `Highs::run()`. They are then restored |
| 38 | +before returning from `Highs::run()`. |
| 39 | + |
| 40 | +### Performing user scaling |
| 41 | + |
| 42 | +User objective and/or bound scaling is performed before assessing |
| 43 | +whether there is excessive problem data and suggesting user objective |
| 44 | +and bound scaling. These user scaling actions must only be performed |
| 45 | +at the "top level" of `Highs::run()`, and this is acheived by caching |
| 46 | +the user scaling options in the `Highs` class and clearing them from |
| 47 | +options_ so that they aren't applied at lower level calls to |
| 48 | +`Highs::run()`. If user scaling has been applied in a call to |
| 49 | +`Highs::run()`, it is unapplied and the option values restored before |
| 50 | +returning from `Highs::run()`. |
| 51 | + |
| 52 | +### Applying "mods" |
| 53 | + |
| 54 | +The `HighsLp` class contains data values and structures that cannot be handled explicitly by the solvers. |
| 55 | + |
| 56 | +- If a variable has an excessivly large objective cost, this is |
| 57 | + interpreted as being infinte, and handled in |
| 58 | + `Highs::handleInfCost()` by fixing the variable at its lower or |
| 59 | + upper bound (when finite) according to the sign of the cost and the |
| 60 | + sense of the optimization, and zeroing the cost. After solving the |
| 61 | + problem, the cost and bounds must be restored. |
| 62 | + |
| 63 | +- If a variable is of type `HighsVarType::kSemiContinuous` or |
| 64 | + `HighsVarType::kSemiInteger`, it is assessed in |
| 65 | + `assessSemiVariables` (in `HighsLpUtils.cpp`) before reformulation |
| 66 | + in `withoutSemiVariables` (in `HighsLpUtils.cpp`) |
| 67 | + |
| 68 | + - If it is not strictly "semi" it is set to`HighsVarType::kContinuous` or `HighsVarType::kInteger` |
| 69 | + - If its lower bound is not positive, it is deemed to be illegal |
| 70 | + - If its upper bound is larger than `kMaxSemiVariableUpper` then, |
| 71 | + depending on the lower bound, if it is possible to reformulate it the |
| 72 | + upper bound is set to `kMaxSemiVariableUpper` (it is said to be "tightened". |
| 73 | + Otherwise, it is deemed to be illegal |
| 74 | + |
| 75 | +These modifications are currently performed in `Highs::run()`, with |
| 76 | +very careful code to ensure that they are removed before returning |
| 77 | +from `Highs::run()`. |
| 78 | + |
| 79 | +With the plan to allow indicator constraints and SOS as generalised |
| 80 | +disjunctive forms that will be reformulated, the handling of "mods" |
| 81 | +needs to be refactored! |
| 82 | + |
| 83 | +## Refactoring |
| 84 | + |
| 85 | +The inelegance of `Highs::run()` (and `Highs::optimizeModel()`) was |
| 86 | +exposed by |
| 87 | +[\#2635](https://github.com/ERGO-Code/HiGHS/issues/2635). Both methods |
| 88 | +need to be refactored. Firstly, `Highs::run()` must be refactored into |
| 89 | +the following set of nested methods. By calling the appropriate |
| 90 | +method, there is no need to "hide" option settings by caching and then |
| 91 | +clearing their value. |
| 92 | + |
| 93 | +Refactoring `Highs::optimizeModel()` is trickier. There needs to be a |
| 94 | +method where any "mods" are made, so that at the level below the |
| 95 | +problem defined by the `HighsModel` class (without semi-variables) is |
| 96 | +solved. |
| 97 | + |
| 98 | +### `Highs::run` |
| 99 | + |
| 100 | +This "outer" layer can contain the "HiGHS files" actions that were |
| 101 | +previously in `app/RunHighs.cpp`, and user scaling |
| 102 | + |
| 103 | +### `Highs::optimizeHighs()` |
| 104 | + |
| 105 | +The next layer applies any "mods" to the `HighsModel` class, and calls |
| 106 | +`Highs::optimizeModel()` or `Highs::multiobjectiveSolve()` if there |
| 107 | +are multiple objectives. |
| 108 | + |
| 109 | +### `Highs::optimizeModel()` |
| 110 | + |
| 111 | +The next layer should just optimize what's in the `HighsModel` (without semi-variables) |
| 112 | + |
| 113 | +## Observations |
| 114 | + |
| 115 | +- Refactoring `Highs::optimizeModel()` is tricky, so is is temporarily |
| 116 | + renamed `Highs::calledOptimizeModel()`, and `Highs::optimizeModel()` |
| 117 | + is a temporary intermediate method to facilitate this is. |
| 118 | + |
| 119 | +- The most obvious place where `Highs::run()` was called at a "lower |
| 120 | + level" is in the MIP solver. Since there are no "upper level" |
| 121 | + actions to be performed, it can call `Highs::optimizeModel()`. To |
| 122 | + emphasise that just an LP is being solved, `Highs::optimizeLp()` has |
| 123 | + been created. This is currently a call to `Highs::optimizeModel()` |
| 124 | + |
| 125 | +## ToDo |
| 126 | + |
| 127 | +- Move the code to write out the presolved model from `app/RunHighs.cpp` to `Highs::run()` |
| 128 | + |
| 129 | +- Move the "mods" to `Highs::optimizeHighs()` |
| 130 | + |
| 131 | +- For problems with multiple objectives |
| 132 | + |
| 133 | + - Apply user scaling to the multiple objectives |
| 134 | + |
| 135 | + - Assess the multiple objectives for extreme values |
| 136 | + |
| 137 | + - Accumulate subsystem solve times |
| 138 | + |
| 139 | +- In IIS calculations, accumulate subsystem solve times |
| 140 | + |
| 141 | + |
| 142 | + |
| 143 | + |
0 commit comments