|
| 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