Optimization

A guide for optimizing (solving) InfiniteOpt models. See the respective technical manual for more details.

Overview

Fundamentally, we seek to optimize a given infinite optimization model that we have defined and this is the very reason why InfiniteOpt was created. Thus, InfiniteOpt offers a general and intuitive platform to do just this. This is made up of transforming the InfiniteModel into a standard optimization problem stored as a JuMP.Model (referred to as the optimizer_model) that is then optimized via a compatible optimizer. By default, this is done via a TranscriptionModel as described on the previous page. However, user-defined reformulation strategies can readily be implemented as described in the Optimizer Models section on the extensions page.

Basic Usage

For most users, optimize! is the only method required to optimize an InfiniteModel. This is exactly analogous to that of any JuMP.Model and is designed to provide a similar user experience. Let's first define an InfiniteModel with an appropriate optimizer:

julia> using InfiniteOpt, Ipopt;

julia> model = InfiniteModel(Ipopt.Optimizer);

julia> set_optimizer_attribute(model, "print_level", 0);

julia> @infinite_parameter(model, t in [0, 10], num_supports = 10);

julia> @variable(model, y >= 0, Infinite(t));

julia> @variable(model, z >= 0);

julia> @objective(model, Min, 2z);

julia> @constraint(model, c1, z >= y);

julia> @constraint(model, c2, y(0) == 42);

julia> print(model)
Min 2 z
Subject to
 y(t) ≥ 0, ∀ t ∈ [0, 10]
 z ≥ 0
 c1 : z - y(t) ≥ 0, ∀ t ∈ [0, 10]
 y(0) ≥ 0
 c2 : y(0) = 42

Now we optimize the model using optimize!:

julia> optimize!(model);

julia> termination_status(model)
LOCALLY_SOLVED::TerminationStatusCode = 4

Now our model has been solved and we can query the solution. How to query the solution is explained on the Results page.

If no optimizer has been specified for the InfiniteModel, one can be provided via set_optimizer:

julia> set_optimizer(model, Ipopt.Optimizer)

A number of methods also exist to adjust the optimizer settings such as suppressing output. This is explained below in the Optimizer Settings section.

Optimizer Models

As discussed previously, InfiniteModels contain an optimizer_model field which stores a transformed finite version of the model in a JuMP.Model that contains a data object (that stores a mapping between the transformed model and the infinite model) in the Model.ext dictionary with an associated key. By default a JuMP.Model using TranscriptionData stored under the key :TransData is used and is referred to as a TranscriptionModel. The optimizer model is then what is used to optimize the infinite model, and it provides the information exacted by solution queries mapped back to the infinite model using the mapping data structure.

The process for optimizing an InfiniteModel is summarized in the following steps:

  1. fully define the InfiniteModel
  2. build the optimizer model via build_optimizer_model!
  3. optimize the optimizer_model via optimize!.

Here build_optimizer_model! creates a reformulated finite version of the InfiniteModel, stores it in InfiniteModel.optimizer_model via set_optimizer_model, and indicates that the optimizer model is ready via set_optimizer_model_ready. These steps are all automated when optimize! is invoked on the InfiniteModel.

The optimizer_model can be queried/extracted at any time from an InfiniteModel via optimizer_model. For example, let's extract the optimizer model from the example above in the basic usage section:

julia> trans_model = optimizer_model(model)
A JuMP Model
Minimization problem with:
Variables: 11
Objective function type: AffExpr
`AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 1 constraint
`AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 10 constraints
`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 11 constraints
Model mode: AUTOMATIC
CachingOptimizer state: ATTACHED_OPTIMIZER
Solver name: Ipopt

The JuMP variable(s) stored in the optimizer model that correspond to a particular InfiniteOpt variable can be queried via optimizer_model_variable. Using a TranscriptionModel this equivalent to calling transcription_variable. Thus, using the going example we get:

julia> optimizer_model_variable(y) # infinite variable
10-element Vector{VariableRef}:
 y(support: 1)
 y(support: 2)
 y(support: 3)
 y(support: 4)
 y(support: 5)
 y(support: 6)
 y(support: 7)
 y(support: 8)
 y(support: 9)
 y(support: 10)

julia> optimizer_model_variable(z) # finite variable
z

In like manner, we get the JuMP constraints corresponding to a particular InfiniteOpt constraint via optimizer_model_constraint. Using a TranscriptionModel this equivalent to calling transcription_constraint. Thus, using going example we get:

julia> optimizer_model_constraint(c1) # infinite constraint
10-element Vector{ConstraintRef}:
 c1(support: 1) : z - y(support: 1) ≥ 0
 c1(support: 2) : z - y(support: 2) ≥ 0
 c1(support: 3) : z - y(support: 3) ≥ 0
 c1(support: 4) : z - y(support: 4) ≥ 0
 c1(support: 5) : z - y(support: 5) ≥ 0
 c1(support: 6) : z - y(support: 6) ≥ 0
 c1(support: 7) : z - y(support: 7) ≥ 0
 c1(support: 8) : z - y(support: 8) ≥ 0
 c1(support: 9) : z - y(support: 9) ≥ 0
 c1(support: 10) : z - y(support: 10) ≥ 0

We can also query the expressions via optimizer_model_expression:

julia> optimizer_model_expression(z - y^2 + 3) # infinite expression
10-element Vector{AbstractJuMPScalar}:
 -y(support: 1)² + z + 3
 -y(support: 2)² + z + 3
 -y(support: 3)² + z + 3
 -y(support: 4)² + z + 3
 -y(support: 5)² + z + 3
 -y(support: 6)² + z + 3
 -y(support: 7)² + z + 3
 -y(support: 8)² + z + 3
 -y(support: 9)² + z + 3
 -y(support: 10)² + z + 3
Note
  1. Like supports the optimizer_model_[obj] methods also employ the label::Type{AbstractSupportLabel} = PublicLabel keyword argument that by default will return variables/expressions/constraints associated with public supports. The full set (e.g., ones corresponding to internal collocation nodes) is obtained via label = All.
  2. These methods also employ the ndarray::Bool keyword argument that will cause the output to be formatted as a n-dimensional array where the dimensions correspond to the infinite parameter dependencies. For example, if we have an infinite variable y(t, ξ) and we invoke a query method with ndarray = true then we'll get a matrix whose dimensions correspond to the supports of t and ξ, respectively. Also, if ndarray = true then label correspond to the intersection of supports labels in contrast to its default of invoking the union of the labels.

The purpose of this optimizer_model abstraction is to readily enable user-defined reformulation extensions (e.g., using polynomial chaos expansion theory). However, this is all handled behind the scenes such that most users can interact with InfiniteModels like any JuMP.Model.

Optimizer Settings

A few optimizer settings can be set in a consistent way agnostic of particular solver keywords. One such setting is that of suppressing and unsuppressing optimizer verbose output. This is accomplished via set_silent and unset_silent. The syntax is exemplified below:

julia> set_silent(model)

julia> unset_silent(model)

We can also adjust the time limit in a solver independent fashion via set_time_limit_sec, unset_time_limit_sec, and time_limit_sec. These methods are illustrated below:

julia> set_time_limit_sec(model, 100)

julia> time_limit_sec(model)
100.0

julia> unset_time_limit_sec(model)

Other optimizer specific settings can be set via set_optimizer_attribute. For example, let's set the maximum CPU time for Ipopt:

julia> set_optimizer_attribute(model, "max_cpu_time", 60.)

Multiple settings can be specified via set_optimizer_attributes. For example, let's specify the tolerance and the maximum number of iterations:

julia> set_optimizer_attributes(model, "tol" => 1e-4, "max_iter" => 100)

Finally, we can query optimizer settings via get_optimizer_attribute. For example, let's query the maximum number of iterations:

julia> get_optimizer_attribute(model, "max_iter")
100

Note this only works if the attribute has been previously specified.