|
| 1 | +# Frequently Asked Questions |
| 2 | + |
| 3 | +## The Solver Seems to Violate Constraints During the Optimization, Causing `DomainError`s, What Can I Do About That? |
| 4 | + |
| 5 | +During the optimization, optimizers use slack variables to relax the solution to the constraints. Because of this, |
| 6 | +there is no guarentee that for an arbitrary optimizer the steps will all satisfy the constraints during the |
| 7 | +optimization. In many cases, this can cause one's objective function code throw a `DomainError` if it is evaluated |
| 8 | +outside of its acceptable zone. For example, `log(-1)` gives: |
| 9 | + |
| 10 | +``` |
| 11 | +julia> log(-1) |
| 12 | +ERROR: DomainError with -1.0: |
| 13 | +log will only return a complex result if called with a complex argument. Try log(Complex(x)). |
| 14 | +``` |
| 15 | + |
| 16 | +To handle this, one should not assume that the variables will always satisfy the constraints on each step. There |
| 17 | +are three general ways to handle this better: |
| 18 | + |
| 19 | +1. Use NaNMath.jl |
| 20 | +2. Process variables before domain-restricted calls |
| 21 | +3. Use a domain transformation |
| 22 | + |
| 23 | +NaNMath.jl gives alternative implementations of standard math functions like `log` and `sqrt` in forms that do not |
| 24 | +throw `DomainError`s but rather return `NaN`s. The optimizers will be able to handle the NaNs gracefully and recover, |
| 25 | +allowing for many of these cases to be solved without further modification. Note that this is done [internally in |
| 26 | +JuMP.jl, and thus if a case is working with JuMP and not Optimization.jl |
| 27 | +](https://discourse.julialang.org/t/optimizationmoi-ipopt-violating-inequality-constraint/92608/) this may be the |
| 28 | +reason for the difference. |
| 29 | + |
| 30 | +Alternatively, one can pre-process the values directly. For example, `log(abs(x))` is guaranteed to work. If one does |
| 31 | +this, there are two things to make note of. One is that the solution will not be transformed, and thus the transformation |
| 32 | +should be applied on `sol.u` as well. I.e., the solution could fine an optima for `x = -2`, and one should manually |
| 33 | +change this to `x = 2` if the `abs` version is used within the objective function. Note that many functions for this will |
| 34 | +introduce a disocontinuity in the derivative which can effect the optimization process. |
| 35 | + |
| 36 | +Finally and relatedly, one can write the optimization with domain transformations in order to allow the optimization to |
| 37 | +take place in the full real set. For example, instead of optimizing `x in [0,Inf]`, one can optimize `exp(x) in [0,Inf]` |
| 38 | +and thus `x in [-Inf, Inf]` is allowed without any bounds. To do this, you would simply add the transformations to the |
| 39 | +top of the objective function: |
| 40 | + |
| 41 | +```julia |
| 42 | +function my_objective(u) |
| 43 | + x = exp(u[1]) |
| 44 | + # ... use x |
| 45 | +end |
| 46 | +``` |
| 47 | + |
| 48 | +When the optimization is done, `sol.u[1]` will be `exp(x)` and thus `log(sol.u[1])` will be the optimal value for `x`. |
| 49 | +There exist packages in the Julia ecosystem which make it easier to keep track of these domain transformations and their |
| 50 | +inverses for more general domains. See [TransformVariables.jl](https://github.com/tpapp/TransformVariables.jl) and |
| 51 | +[Bijectors.jl](https://github.com/TuringLang/Bijectors.jl) for high level interfaces for this. |
| 52 | + |
| 53 | +While this can allow an optimization with constraints to be rewritten as one without constraints, note that this can change |
| 54 | +the numerical properties of the solve which can either improve or decrease the numerical stability in a case-by-case |
| 55 | +basis. Thus while a solution, one should be aware that it could make the optimization more difficult in some cases. |
| 56 | + |
| 57 | +## What are the advantages and disadvantages of using the ModelingToolkit.jl or other symbolic interfaces (JuMP)? |
| 58 | + |
| 59 | +The purely numerical function interfaces of Optimization.jl has its pros and cons. The major pro of the direct |
| 60 | +Optimization.jl interface is that it can take arbitrary Julia programs. If you have an optimization defined over a |
| 61 | +program, like a Neural ODE or something that calls out to web servers, then these advanced setups rarely work within |
| 62 | +specialized symbolic environments for optimization. Direct usage of Optimization.jl is thus the preferred route for |
| 63 | +this kind of problem, and is the popular choice in the Julia ecosystem for these cases due to the simplicity of use. |
| 64 | + |
| 65 | +However, symbolic interfaces are smart, and they may know more than you for how to make this optimization faster. |
| 66 | +And symbolic interfaces are willing to do "tedious work" in order to make the optimization more efficient. For |
| 67 | +example, the ModelingToolkit integration with Optimization.jl will do many simplifications when `structural_simplify` |
| 68 | +is called. One of them is tearing on the constraints. To understand the tearing process, assume that we had |
| 69 | +nonlinear constraints of the form: |
| 70 | + |
| 71 | +```julia |
| 72 | + 0 ~ u1 - sin(u5) * h, |
| 73 | + 0 ~ u2 - cos(u1), |
| 74 | + 0 ~ u3 - hypot(u1, u2), |
| 75 | + 0 ~ u4 - hypot(u2, u3), |
| 76 | +``` |
| 77 | + |
| 78 | +If these were the constraints, one can write `u1 = sin(u5) * h` and substitute `u1` for this value in the objective |
| 79 | +function. If this is done, then `u1` does not need to be solved for, the optimization has one less state variable and |
| 80 | +one less constraint. One can continue this process all the way to a bunch of functions: |
| 81 | + |
| 82 | +```julia |
| 83 | +u1 = f1(u5) |
| 84 | +u2 = f2(u1) |
| 85 | +u3 = f3(u1, u2) |
| 86 | +u4 = f4(u2, u3) |
| 87 | +``` |
| 88 | + |
| 89 | +and thus if the objective function was the function of these 5 variables and 4 constraints, ModelingToolkit.jl will |
| 90 | +transform it into system of 1 variable with no constraints, allowing unconstrained optimization on a smaller system. |
| 91 | +This will both be faster and numerically easier. |
| 92 | + |
| 93 | +[JuMP.jl](https://jump.dev/JuMP.jl/stable/) is another symbolic interface. While it does not include these tearing |
| 94 | +and symbolic simplification passes, it does include the ability to specialize the solution process. For example, |
| 95 | +it can treat linear optimization problems, quadratic optimization problem, convex optimization problems, etc. |
| 96 | +in specific ways that are more efficient than a general nonlinear interface. For more information on the types of |
| 97 | +special solves that are allowed with JuMP, see [this page](https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers). |
0 commit comments