|
| 1 | +## Numerical Issues while Using Solvers |
| 2 | + |
| 3 | +### What are numerical issues and why do they occur? |
| 4 | + |
| 5 | +All computers store numbers in binary, that is, all numbers are represented as a finite sequence of 0s and 1s. |
| 6 | +Therefore, not all numbers can be perfectly stored. For example, the fraction |
| 7 | +`1/3`, when stored on a computer, might actually be stored as `0.33333334...` since representing an infinite number of 3s |
| 8 | +is not possible with only a finite number of 0s and 1s. |
| 9 | + |
| 10 | +When solving linear programs, these small errors can accumulate and cause significant deviations from the |
| 11 | +'true' result. When this occurs, we say that we've encountered *numerical issues*. |
| 12 | + |
| 13 | +Numerical issues most often arise when our linear program contains numerical values of very small or very large |
| 14 | +magnitudes (e.g. 10<sup>-10</sup> or 10<sup>10</sup>). This is because—due to how numbers are stored in binary—very large or |
| 15 | +very small values are stored less accurately (and therefore with a greater error). |
| 16 | + |
| 17 | +For more details on why numerical issues occur, the curious can read |
| 18 | +about [floating-point arithmetic](https://en.wikipedia.org/wiki/Floating-point_arithmetic) |
| 19 | +(how arithmetic is done on computers) and the [IEEE 754 standard](https://en.wikipedia.org/wiki/IEEE_754) |
| 20 | +(the standard used by almost all computers today). For a deep dive into the topic, |
| 21 | +read [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://www.itu.dk/~sestoft/bachelor/IEEE754_article.pdf). |
| 22 | + |
| 23 | +### How to detect numerical issues in Gurobi? |
| 24 | + |
| 25 | +Most solvers, including Gurobi, will have tests and thresholds to detect when the numerical errors become |
| 26 | +significant. If the solver detects that we've exceeded its thresholds, it will display warnings or errors. Based |
| 27 | +on [Gurobi's documentation](https://www.gurobi.com/documentation/9.1/refman/does_my_model_have_numeric.html), here are |
| 28 | +some warnings or errors that Gurobi might display. |
| 29 | + |
| 30 | +``` |
| 31 | +Warning: Model contains large matrix coefficient range |
| 32 | +Consider reformulating model or setting NumericFocus parameter |
| 33 | +to avoid numerical issues. |
| 34 | +Warning: Markowitz tolerance tightened to 0.5 |
| 35 | +Warning: switch to quad precision |
| 36 | +Numeric error |
| 37 | +Numerical trouble encountered |
| 38 | +Restart crossover... |
| 39 | +Sub-optimal termination |
| 40 | +Warning: ... variables dropped from basis |
| 41 | +Warning: unscaled primal violation = ... and residual = ... |
| 42 | +Warning: unscaled dual violation = ... and residual = ... |
| 43 | +``` |
| 44 | + |
| 45 | +Many of these warnings indicate that Gurobi is trying to workaround the numerical issues. The following list gives |
| 46 | +examples of what Gurobi does internally to workaround numerical issues. |
| 47 | + |
| 48 | +- If Gurobi's normal barrier method fails due to numerical issues, Gurobi will switch to the slower but more reliable |
| 49 | + dual simplex method (often indicated by the message: `Numerical trouble encountered`). |
| 50 | + |
| 51 | + |
| 52 | +- If Gurobi's dual simplex method encounters numerical issues, Gurobi might switch to quadruple precision |
| 53 | + (indicated by the warning: `Warning: switch to quad precision`). This is 20 to |
| 54 | + 80 times slower (on my computer) but will represent numbers using 128-bits instead of the normal 64-bits, allowing |
| 55 | + much greater precision and avoiding numerical issues. |
| 56 | + |
| 57 | +Sometimes Gurobi's internal mechanisms to avoid numerical issues are insufficient or not desirable |
| 58 | +(e.g. too slow). In this case, we need to resolve the numerical issues ourselves. One way to do this is by scaling our |
| 59 | +model. |
| 60 | + |
| 61 | +### Scaling our model to resolve numerical issues |
| 62 | + |
| 63 | +#### Introduction, an example of scaling |
| 64 | + |
| 65 | +As mentioned, numerical issues occur when our linear program contains numerical values of very small or very large |
| 66 | +magnitude (e.g. 10<sup>-10</sup> or 10<sup>10</sup>). We can get rid of these very large or small values by scaling our model. Consider |
| 67 | +the following example of a linear program. |
| 68 | + |
| 69 | +``` |
| 70 | +Maximize |
| 71 | +3E17 * x + 2E10 * y |
| 72 | +With constraints |
| 73 | +500 * x + 1E-5 * y < 1E-5 |
| 74 | +``` |
| 75 | + |
| 76 | +This program contains many large and small coefficients that we wish to get rid of. If we multiply our objective |
| 77 | +function by 10<sup>-10</sup>, and the constraint by 10<sup>5</sup> we get: |
| 78 | + |
| 79 | +``` |
| 80 | +Maximize |
| 81 | +3E7 * x + 2 * y |
| 82 | +With constraints |
| 83 | +5E7 * x + y < 0 |
| 84 | +``` |
| 85 | + |
| 86 | +Then if we define a new variable `x'` as 10<sup>7</sup> times the value of `x` we get: |
| 87 | + |
| 88 | +``` |
| 89 | +Maximize |
| 90 | +3 * x' + 2 * y |
| 91 | +With constraints |
| 92 | +5 * x' + y < 0 |
| 93 | +``` |
| 94 | + |
| 95 | +This last model can be solved without numerical issues since the coefficients are neither too |
| 96 | +small or too large. Once we solve the model, |
| 97 | +all that's left to do is dividing `x'` by 10<sup>7</sup> to get `x`. |
| 98 | + |
| 99 | +This example, although not very realistic, gives an example |
| 100 | +of what it means to scale a model. Scaling is often the best solution to resolving numerical issues |
| 101 | +and can be easily done with Pyomo (see below). In some cases scaling is insufficient and other |
| 102 | +changes need to be made, this is explained in the section *Other techniques to resolve numerical issues*. |
| 103 | + |
| 104 | +#### Gurobi Guidelines for ranges of values |
| 105 | + |
| 106 | +An obvious question is, what is considered too small or too large? |
| 107 | +Gurobi provides some guidelines on what is a reasonable range |
| 108 | +for numerical values ([here](https://www.gurobi.com/documentation/9.1/refman/recommended_ranges_for_var.html) and [here](https://www.gurobi.com/documentation/9.1/refman/advanced_user_scaling.html)). |
| 109 | +Here's a summary: |
| 110 | + |
| 111 | +- Right-hand sides of inequalities and variable domains (i.e. variable bounds) should |
| 112 | + be on the order of 10<sup>4</sup> or less. |
| 113 | + |
| 114 | +- The objective function's optimal value (i.e. the solution) should be between 1 and 10<sup>5</sup>. |
| 115 | + |
| 116 | +- The matrix coefficients should span a range of six orders of magnitude |
| 117 | + or less and ideally between 10<sup>-3</sup> and 10<sup>6</sup>. |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | +#### Scaling our model with Pyomo |
| 123 | + |
| 124 | +Scaling an objective function or constraint is easy. |
| 125 | +Simply multiply the expression by the scaling factor. For example, |
| 126 | + |
| 127 | +```python |
| 128 | +# Objective function without scaling |
| 129 | +model.TotalCost = Objective(..., rule=lambda m, ...: some_expression) |
| 130 | +# Objective function ith scaling |
| 131 | +scaling_factor = 1e-7 |
| 132 | +model.TotalCost = Objective(..., rule=lambda m, ...: (some_expression) * scaling_factor) |
| 133 | + |
| 134 | +# Constraint without scaling |
| 135 | +model.SomeConstraint = Constraint(..., rule=lambda m, ...: left_hand_side < right_hand_side) |
| 136 | +# Constraint with scaling |
| 137 | +scaling_factor = 1e-2 |
| 138 | +model.SomeConstraint = Constraint( |
| 139 | + ..., |
| 140 | + rule=lambda m, ...: (left_hand_side) * scaling_factor < (right_hand_side) * scaling_factor |
| 141 | +) |
| 142 | +``` |
| 143 | + |
| 144 | +Scaling a variable is more of a challenge since the variable |
| 145 | +might be used in multiple places, and we don't want to need |
| 146 | +to change our code in multiple places. The trick is to define an expression that equals our unscaled variable. |
| 147 | +We can use this expression throughout our model, and Pyomo will still use the underlying |
| 148 | +scaled variable when solving. Here's what I mean. |
| 149 | + |
| 150 | +```python |
| 151 | +from pyomo.environ import Var, Expression |
| 152 | +... |
| 153 | +scaling_factor = 1e5 |
| 154 | +# Define the variable |
| 155 | +model.ScaledVariable = Var(...) |
| 156 | +# Define an expression that equals the variable but unscaled. This is what we use elsewhere in our model. |
| 157 | +model.UnscaledExpression = Expression(..., rule=lambda m, *args: m.ScaledVariable[args] / scaling_factor) |
| 158 | +... |
| 159 | +``` |
| 160 | + |
| 161 | +Thankfully, I've written the `ScaledVariable` class that will do this for us. |
| 162 | +When we add a `ScaledVariable` to our model, the actual scaled |
| 163 | +variable is created behind the scenes and what's returned is the unscaled expression that |
| 164 | +we can use elsewhere in our code. Here's how to use `ScaledVariable` in practice. |
| 165 | + |
| 166 | + |
| 167 | +```python |
| 168 | +# Without scaling |
| 169 | +from pyomo.environ import Var |
| 170 | +model.SomeVariable = Var(...) |
| 171 | + |
| 172 | +# With scaling |
| 173 | +from switch_model.utilities.scaling import ScaledVariable |
| 174 | +model.SomeVariable = ScaledVariable(..., scaling_factor=5) |
| 175 | +``` |
| 176 | + |
| 177 | +Here, we can use `SomeVariable` throughout our code just as before. |
| 178 | +Internally, Pyomo (and the solver) will be using a scaled version of `SomeVariable`. |
| 179 | +In this case the solver will use a variable that is 5 times greater |
| 180 | +than the value we reference in our code. This means the |
| 181 | +coefficients in front of `SomeVariable` will be 1/5th of the usual. |
| 182 | + |
| 183 | +#### How much to scale by ? |
| 184 | + |
| 185 | +Earlier we shared Gurobi's guidelines on the ideal range for our matrix coefficients, |
| 186 | +right-hand side values, variable bounds and objective function. Yet how do |
| 187 | +we know where our values currently lie to determine how much to scale them by? |
| 188 | + |
| 189 | +For large models, determining which variables to scale and by how much can be a challenging task. |
| 190 | + |
| 191 | +First, when solving with the flag `--stream-solver` and `-v`, |
| 192 | +Gurobi will print to console helpful information for preliminary analysis. |
| 193 | +Here's an example of what Gurobi's output might look like. It might also |
| 194 | +include warnings about ranges that are too wide. |
| 195 | + |
| 196 | +``` |
| 197 | +Coefficient statistics: |
| 198 | + Matrix range [2e-05, 1e+06] |
| 199 | + Objective range [2e-05, 6e+04] |
| 200 | + Bounds range [3e-04, 4e+04] |
| 201 | + RHS range [1e-16, 3e+05] |
| 202 | +``` |
| 203 | + |
| 204 | +Second, if we solved our model with the flags `--keepfiles`, `--tempdir` and `--symbolic-solver-labels`, then |
| 205 | +we can read the `.lp` file in the temporary folder that contains the entire model including the coefficients. |
| 206 | +This is the file Gurobi reads before solving and has all the information we might need. |
| 207 | +However, this file is very hard to analyze manually due its size. |
| 208 | + |
| 209 | +Third, I'm working on a tool to automatically analyze the `.lp` file and return information |
| 210 | +useful that would help resolve numerical issues. The tool is available [here](https://github.com/staadecker/lp-analyzer). |
| 211 | + |
| 212 | + |
| 213 | +### Other techniques to resolve numerical issues |
| 214 | + |
| 215 | +In some cases, scaling might not be sufficient to resolve numerical issues. |
| 216 | +For example, if variables within the same set have values that span too wide of a range, |
| 217 | +scaling will not be able to reduce the range since scaling affects all variables |
| 218 | +within a set equally. |
| 219 | + |
| 220 | +Other than scaling, some techniques to resolve numerical issues are: |
| 221 | + |
| 222 | +- Reformulating the model |
| 223 | + |
| 224 | +- Avoiding unnecessarily large penalty terms |
| 225 | + |
| 226 | +- Changing the solver's method |
| 227 | + |
| 228 | +- Loosening tolerances (at the risk of getting less accurate, or inaccurate results) |
| 229 | + |
| 230 | +One can learn more about these methods |
| 231 | +by reading [Gurobi's guidelines on Numerical Issues](https://www.gurobi.com/documentation/9.1/refman/guidelines_for_numerical_i.html) |
| 232 | +or a [shorter PDF](http://files.gurobi.com/Numerics.pdf) that Gurobi has released. |
| 233 | + |
| 234 | + |
| 235 | + |
0 commit comments