Skip to content

Commit ada5394

Browse files
odowmlubin
andauthored
[docs] start a tutorial on debugging JuMP models (#3043)
Co-authored-by: Miles Lubin <[email protected]>
1 parent 59083a9 commit ada5394

File tree

2 files changed

+312
-1
lines changed

2 files changed

+312
-1
lines changed

Diff for: docs/make.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ const _PAGES = [
104104
"tutorials/getting_started/getting_started_with_JuMP.md",
105105
"tutorials/getting_started/getting_started_with_sets_and_indexing.md",
106106
"tutorials/getting_started/getting_started_with_data_and_plotting.md",
107-
"tutorials/getting_started/performance_tips.md",
107+
"tutorials/getting_started/debugging.md",
108108
"tutorials/getting_started/design_patterns_for_larger_models.md",
109+
"tutorials/getting_started/performance_tips.md",
109110
],
110111
"Linear programs" => [
111112
"tutorials/linear/introduction.md",

Diff for: docs/src/tutorials/getting_started/debugging.jl

+310
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# Copyright (c) 2021 Oscar Dowson and contributors #src
2+
# #src
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy #src
4+
# of this software and associated documentation files (the "Software"), to deal #src
5+
# in the Software without restriction, including without limitation the rights #src
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #src
7+
# copies of the Software, and to permit persons to whom the Software is #src
8+
# furnished to do so, subject to the following conditions: #src
9+
# #src
10+
# The above copyright notice and this permission notice shall be included in all #src
11+
# copies or substantial portions of the Software. #src
12+
# #src
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #src
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #src
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #src
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #src
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #src
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #src
19+
# SOFTWARE. #src
20+
21+
# # Debugging
22+
23+
# Dealing with bugs is an unavoidable part of coding optimization models in
24+
# any framework, including JuMP. Sources of bugs include not only generic coding
25+
# errors (method errors, typos, off-by-one issues), but also semantic mistakes
26+
# in the formulation of an optimization problem and the incorrect use of a
27+
# solver.
28+
29+
# This tutorial explains some common sources of bugs and modeling issues that
30+
# you might encounter when writing models in JuMP, and it suggests a variety of
31+
# strategies to deal with them.
32+
33+
# !!! tip
34+
# This tutorial is more advanced than the other "Getting started" tutorials.
35+
# It's in the "Getting started" section to give you an early preview of how
36+
# to debug JuMP models. However, if you are new to JuMP, you may want to
37+
# briefly skim the tutorial, and come back to it once you have written a few
38+
# JuMP models.
39+
40+
using JuMP
41+
import HiGHS
42+
43+
# ## Getting help
44+
45+
# Debugging can be a frustrating part of modeling, particularly if you're new to
46+
# optimization and programming. If you're stuck, join the
47+
# [community forum](https://discourse.julialang.org/c/domain/opt/13) to search
48+
# for answers to commonly asked questions.
49+
50+
# Before asking a new question, make sure to read the post
51+
# [Make it easier to help you](https://discourse.julialang.org/t/please-read-make-it-easier-to-help-you/14757),
52+
# which contains a number of tips on how to ask a good question.
53+
54+
# Above all else, take time to simplify your code as much as possible. The
55+
# fewer lines of code you can post that reproduces the same issue, the faster
56+
# someone can answer your question.
57+
58+
# ## Debugging Julia code
59+
60+
# Read the [Debugging chapter](https://benlauwens.github.io/ThinkJulia.jl/latest/book.html#chap21)
61+
# in the book [ThinkJulia.jl](https://benlauwens.github.io/ThinkJulia.jl/latest/book.html).
62+
# It has a number of great tips and tricks for debugging Julia code.
63+
64+
# ## Solve failures
65+
66+
# When a solver experiences an issue that prevents it from finding an optimal
67+
# solution (or proving that one does not exist), JuMP may return one of a number
68+
# of [`termination_status`](@ref)es.
69+
70+
# For example, if the solver found a solution, but experienced numerical
71+
# imprecision, it may return a status such as `ALMOST_OPTIMAL` or
72+
# `ALMOST_LOCALLY_SOLVED` indicating that the problem was solved to a relaxed
73+
# set of tolerances. Alternatively, the solver may return a problematic status
74+
# such as `NUMERICAL_ERROR`, `SLOW_PROGRESS`, or `OTHER_ERROR`, indicating that
75+
# it could not find a solution to the problem.
76+
77+
# Most solvers can experience numerical imprecision because they use
78+
# [floating-point arithmetic](https://en.wikipedia.org/wiki/Floating-point_arithmetic)
79+
# to perform operations such as addition, subtraction, and multiplication. These
80+
# operations aren't exact, and small errors can accrue between the theoretical
81+
# value and the value that the computer computes. For example:
82+
83+
0.1 * 3 == 0.3
84+
85+
# !!! tip
86+
# Read the [Guidlines for numerical issues](https://www.gurobi.com/documentation/9.5/refman/guidelines_for_numerical_i.html)
87+
# section of the Gurobi documentation, along with the
88+
# [Debugging numerical problems](https://yalmip.github.io/inside/debuggingnumerics/)
89+
# section of the YALMIP documentation.
90+
91+
# ### Common sources
92+
93+
# Common sources of solve failures are:
94+
#
95+
# * Very large numbers and very small numbers as problem coefficients. Exactly
96+
# what "large" is depends on the solver and the problem, but in general,
97+
# values above 1e6 or smaller than 1e-6 cause problems.
98+
# * Nonlinear problems with functions that are not defined in parts of their
99+
# domain. For example, minimizing `log(x)` where `x >= 0` is undefined when
100+
# `x = 0` (a common starting value).
101+
102+
# ### Strategies
103+
104+
# Strategies to debug sources of solve failures include:
105+
#
106+
# * Rescale variables in the problem and their associated coefficients to
107+
# make the magnitudes of all coefficients in the 1e-4 to 1e4 range. For
108+
# example, that might mean rescaling a variable from measuring distance in
109+
# centimeters to kilometers.
110+
# * Try a different solver. Some solvers might be more robust than others for a
111+
# particular problem.
112+
# * Read the documentation of your solver, and try settings that encourage
113+
# numerical robustness.
114+
# * Set bounds or add constraints so that all nonlinear functions are defined
115+
# across all of the feasible region. This particularly applies for functions
116+
# like `1 / x` and `log(x)` which are not defined for `x = 0`.
117+
118+
# ## Incorrect results
119+
120+
# Sometimes, you might find that the solver returns an "optimal" solution that
121+
# is incorrect according to the model you are trying to solve (perhaps the
122+
# solution is suboptimal, or it doesn't satisfy some of the constraints).
123+
124+
# Incorrect results can be hard to detect and debug, because the solver gives no
125+
# hints that there is a problem. Indeed, the [`termination_status`](@ref) will
126+
# likely be `OPTIMAL` and a solution will be available.
127+
128+
# ### Common sources
129+
130+
# Common sources of incorrect results are:
131+
#
132+
# * A modeling error, so that your JuMP model does not match the formulation
133+
# you have on paper
134+
# * Not accounting for the tolerances that solvers use (for example, if `x` is
135+
# binary, a value like `x = 1.0000001` may still be considered feasible)
136+
# * A bug in JuMP or the solver.
137+
138+
# The probability of the issue being a bug in JuMP or the solver is much smaller
139+
# than a modeling error. When in doubt, first assume there is a bug in your code
140+
# before assuming that there is a bug in JuMP.
141+
142+
# ### Strategies
143+
144+
# Strategies to debug sources of incorrect results include:
145+
146+
# * Print your JuMP model to see if it matches the formulation you have on
147+
# paper. Look out for incorrect signs `+` instead of `-`, and off-by-one
148+
# errors such as `x[t]` instead of `x[t-1]`.
149+
# * Check that you are not using exact comparisons like `value(x) == 1.0`;
150+
# always use `isapprox(value(x), 1.0; atol = 1e-6)` where you manually
151+
# specify the comparison tolerance.
152+
# * Try a different solver. If one solver succeeds where another doesn't this
153+
# is a sign that the problem is a numerical issue or a bug in the solver.
154+
155+
# ## Debugging an infeasible model
156+
157+
# A model is infeasible if there is no primal solution that satisfies all of
158+
# the constraints. In general, an infeasible model means one of two things:
159+
#
160+
# * Your problem really has no feasible solution
161+
# * There is a mistake in your model.
162+
163+
# ### Example
164+
165+
# A simple example of an infeasible model is:
166+
167+
model = Model(HiGHS.Optimizer)
168+
set_silent(model)
169+
@variable(model, x >= 0)
170+
@objective(model, Max, 2x + 1)
171+
@constraint(model, 2x - 1 <= -2)
172+
173+
# because the bound says that `x >= 0`, but we can rewrite the constraint to be
174+
# `x <= -1/2`. When the problem is infeasible, JuMP may return one of a number
175+
# of statuses. The most common is `INFEASIBLE`:
176+
177+
optimize!(model)
178+
termination_status(model)
179+
180+
# Depending on the solver, you may also receive `INFEASIBLE_OR_UNBOUNDED` or
181+
# `LOCALLY_INFEASIBLE`.
182+
183+
# A termination status of `INFEASIBLE_OR_UNBOUNDED` means that the solver could
184+
# not prove if the solver was infeasible or unbounded, only that the model does
185+
# not have a finite feasible optimal solution.
186+
187+
# Nonlinear optimizers such as Ipopt may return the status `LOCALLY_INFEASIBLE`.
188+
# This does not mean that the solver _proved_ no feasible solution exists, only
189+
# that it could not find one. If you know a primal feasible point, try providing
190+
# it as a starting point using [`set_start_value`](@ref) and re-optimize.
191+
192+
# ### Common sources
193+
194+
# Common sources of infeasibility are:
195+
#
196+
# * Incorrect units, for example, using a lower bound of megawatts and an upper
197+
# bound of kilowatts
198+
# * Using `+` instead of `-` in a constraint
199+
# * Off-by-one and related errors, for example, using `x[t]` instead of
200+
# `x[t-1]` in part of a constraint
201+
# * Otherwise invalid mathematical formulations
202+
203+
# ### Strategies
204+
205+
# Strategies to debug sources of infeasibility include:
206+
207+
# * Iteratively comment out a constraint (or block of constraints) and re-solve
208+
# the problem. When you find a constraint that makes the problem infeasible
209+
# when added, check the constraint carefully for errors.
210+
# * If the problem is still infeasible with all constraints commented out,
211+
# check all variable bounds. Do they use the right data?
212+
# * If you have a known feasible solution, use [`primal_feasibility_report`](@ref)
213+
# to evaluate the constraints and check for violations. You'll probably find
214+
# that you have a typo in one of the constraints.
215+
# * Try a different solver. Sometimes, solvers have bugs, and they can
216+
# incorrectly report a problem as infeasible when it isn't. If you find such
217+
# a case where one solver reports the problem is infeasible and another can
218+
# find an optimal solution, please report it by opening an issue on the
219+
# GitHub repository of the solver that reports infeasibility.
220+
221+
# !!! tip
222+
# Some solvers also have specialized support for debugging sources of
223+
# infeasibility via an [irreducible infeasible subsystem](@ref Conflicts).
224+
# To see if your solver has support, try calling [`compute_conflict!`](@ref):
225+
# ```julia
226+
# julia> compute_conflict!(model)
227+
# ERROR: ArgumentError: The optimizer HiGHS.Optimizer does not support `compute_conflict!`
228+
# ```
229+
# In this case, HiGHS does not support computign conflicts, but other
230+
# solvers such as Gurobi and CPLEX do. If the solver does support computing
231+
# conflicts, read [Conflicts](@ref) for more details.
232+
233+
# ## Debugging an unbounded model
234+
235+
# A model is unbounded if there is no limit on how good the objective value can
236+
# get. Most often, an unbounded model means that you have an error in your
237+
# modeling, because all physical systems have limits. (You cannot make an
238+
# infinite amount of profit.)
239+
240+
# ### Example
241+
242+
# A simple example of an unbounded model is:
243+
244+
model = Model(HiGHS.Optimizer)
245+
set_silent(model)
246+
@variable(model, x >= 0)
247+
@objective(model, Max, 2x + 1)
248+
249+
# because we can increase `x` without limit, and the objective value `2x + 1`
250+
# gets better as `x` increases.
251+
252+
# When the problem is unbounded, JuMP may return one of a number of statuses.
253+
# The most common is `DUAL_INFEASIBLE`:
254+
255+
optimize!(model)
256+
termination_status(model)
257+
258+
# Depending on the solver, you may also receive `INFEASIBLE_OR_UNBOUNDED` or
259+
# an error code like `NORM_LIMIT`.
260+
261+
# ### Common sources
262+
263+
# Common sources of unboundedness are:
264+
#
265+
# * Using `Max` instead of `Min`
266+
# * Omitting variable bounds, such as `0 <= x <= 1`
267+
# * Using `+` instead of `-` in a term of the objective function.
268+
269+
# ### Strategies
270+
271+
# Strategies to debug sources of unboundedness include:
272+
273+
# * Double check whether you intended `Min` or `Max` in the [`@objective`](@ref)
274+
# line.
275+
# * Print the objective function with `print(objective_function(model))` and
276+
# verify that the value and sign of each coefficient is as you expect.
277+
# * Add large bounds to all variables that are free or have one-sided bounds,
278+
# then re-solve the problem. Because all variables are now bounded, the
279+
# problem will have a finite optimal solution. Look at the value of each
280+
# variable in the optimal solution to see if it is at one of the new bounds.
281+
# If it is, you either need to specify a better bound for that variable, or
282+
# there might be a mistake in the objective function associated with that
283+
# variable (for example, a `+` instead of a `-`).
284+
285+
# If there are too many variables to add bounds to, or there are too many terms
286+
# to examine by hand, another strategy is to create a new variable with a large
287+
# upper bound (if maximizing, lower bound if minimizing) and a constraint that
288+
# the variable must be less-than or equal to the expression of the objective
289+
# function. For example:
290+
291+
model = Model(HiGHS.Optimizer)
292+
set_silent(model)
293+
@variable(model, x >= 0)
294+
## @objective(model, Max, 2x + 1)
295+
@variable(model, objective <= 10_000)
296+
@constraint(model, objective <= 2x + 1)
297+
@objective(model, Max, objective)
298+
299+
# This new model has a finite optimal solution, so we can solve it and then look
300+
# for variables with large positive or negative values in the optimal solution.
301+
302+
optimize!(model)
303+
for var in all_variables(model)
304+
if var == objective
305+
continue
306+
end
307+
if abs(value(var)) > 1e3
308+
println("Variable `$(name(var))` may be unbounded")
309+
end
310+
end

0 commit comments

Comments
 (0)