Description
Today I had a call with @sstroemer and @jd-lara, and we discussed the logistics of adding a Parameter{T} <: AbstractScalarSet
to MOI.
Motivation
The Parameter{T}
set is similar to the EqualTo{T}
set, but with the special case that solvers could support add_constrained_variable(model, ::Parameter)
and not add the index as a decision variable to the underlying solver object.
The goal is to support JuMP syntax like:
model = Model()
@variable(model, x)
@variable(model, p in MOI.Parameter(1.0))
# or
@variable(model, p == 1.0, Parameter)
@constraint(model, p * x <= p + 1)
@objective(model, Min, p * x + 3 * p)
optimize!(model)
fix(p, 2.0)
optimize!(model)
The benefits of an explicit parameter are that, compared to EqualTo
, they can reduce the problem size and allow solvers to interpret p * x
as an affine function instead of quadratic.
Backstory
We've tried quite a few ways to achieve this over the years...
EqualTo
The easiest approach is just to use fixed variables a parameters and let the solvers presolve them out. This works for additive parameters, but fails for p * x
if the solver does not support quadratic constraints.
Another downside is that it leads to more decision variables in the problem because p
is added as a decision variable.
Nonlinear
JuMP already has nonlinear parameters, which are added via @NLparameter
. If we add a NonlinearFunction
, #2059, then we'll need some way to add parameters to MOI anyway.
The current parameter implementation is very specialized inside MOI.Nonlinear.Model
.
ParameterJuMP
@joaquimg started ParameterJuMP.jl to support right-hand side parameters in JuMP.
The core premise is that it is a JuMP extension which defines a new ParameterRef <: AbstractVariableRef
, and then stores parameterized JuMP expressions as a double expression:
struct ParameterizedAffExpr
lhs::GenericAffExpr{V,VariableRef}
rhs::GenericAffExpr{V,ParameterRef}
end
There are two main downsides to this approach:
- Two expressions, coupled with Julia's compiler optimizations which can sometimes put an
AffExpr
on the stack instead of heap, means that memory overhead can be 2-3X larger than a JuMP model with theEqualTo
approach. - It doesn't support
p * x
; only additive terms are supported.
ParametricOptInterface
To work-around the two problems in ParameterJuMP, the PUC-Rio/PSR folks (@guilhermebodin, @rafabench, and @tomasfmg) have been developing https://github.com/jump-dev/ParametricOptInterface.jl.
This is a MOI solver layer that adds a ParametricOptInterface.Parameter
set, and then intercepts the model to replace parameters by their fixed constants.
The main downsides are the complexity of the implementation, and how much state is needed to be stored inside the optimizer to track where and when the parameters are stored.
Recent work
@sstroemer has been hard at work exploring this, also motivated by an energy systems model.
One idea is to use the recently added Bridges.final_touch
to rewrite quadratic constraints to affine:
#2092
Another is to add support at the JuMP-level to track parameters: jump-dev/JuMP.jl#3215.
Loosely, these correspond to the approaches in ParametricOptInterface and ParameterJuMP.
Proposal
The basic part of the proposal is to add a MOI.Parameter
set to MOI. We already know this is useful, because it is identical to what is added by ParametericOptInterface. In addition, we're going to need it for the nonlinear stuff.
The more complicated question is what should happen after that.
ParameterToEqualToBridge
We should add a bridge from Parameter
to EqualTo
. This would be a pretty trivial fallback that'll mean it should work across all solvers.
FixParametricVariablesBridge
We can update #2092 to use only Parameter
instead of EqualTo
. This should let p * x
be re-written to affine for solvers not supporting quadratic.
But for solvers supporting quadratic, and for additive parameters to be efficient, we'll need...
Solver support
Solvers will need to declare support for MOI.Parameter
, and then parse constraints as they're added to extract parameters into the constant terms. They'll also need to maintain a mapping so that updating a parameter updates all of constraints inside the solver.
Why not double down on ParametricOptInterface
This is a good question. At minimum, adding Parameter
will also help POI.
I don't have a good answer on the rest. One objection is that the POI codebase is complicated, and there is a lot of internal state to maintain. The bridge in #2092 is very simple in comparison. But perhaps the overhead of adding native parameter support to Gurobi.jl (for example) would be equally challenging.
The argument in favor of solver-specific implementations is that they could make their updates more efficient.