Skip to content

Commit 2cbf75a

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #76 from staadecker/no-crossover
Disable crossover, 14x performance improvement
2 parents f73d455 + abcdfc7 commit 2cbf75a

File tree

5 files changed

+336
-290
lines changed

5 files changed

+336
-290
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ SWITCH. This will build HTML documentation files from python doc strings which
1818
will include descriptions of each module, their intentions, model
1919
components they define, and what input files they expect.
2020

21-
To learn about **numerical solvers** read [`docs/Numerical Solvers.md`](/docs/Numerical%20Solvers.md)
21+
To learn about **numerical solvers** read [`docs/Numerical Solvers.md`](/docs/Numerical%20Solvers.md)
22+
23+
To learn about **numerical issues** read [`docs/Numerical Issues.md`](/docs/Numerical%20Issues.md)

docs/Numerical Issues.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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

Comments
 (0)