Compute dual variables with continous state variables and mixed control variables
Thiago-NovaesB opened this issue · comments
https://sddp.dev/stable/guides/add_integrality/
I'm interested in a problem that has continuous state variables, but continuous and binary control variables.
Reading the section on integrality of the documentation, it seems to me that we can have the dual the relaxed MIP (then LP) or Lagrangian Dual.
My problem actually has only 1 feasible point for the binary part, so it would be perfect if the dual could be calculated as follows:
- Solve the MIP
- Fix the binaries variables.
- Solve the LP
- Get dual.
It is possible? I wouldn't want to relax integrality.
So the way to handle this would be to write a new duality handler. I think it would be similar to:
SDDP.jl/src/plugins/duality_handlers.jl
Lines 86 to 148 in 26717bf
except that instead of relaxing the integrality, we'd need to use fix_discrete_variables
:
https://jump.dev/JuMP.jl/stable/tutorials/linear/mip_duality/#Use-fix_discrete_variables
Perhaps something like (untested)
struct FixedDiscreteDuality <: SDDP.AbstractDualityHandler end
function get_dual_solution(node::SDDP.Node, ::FixedDiscreteDuality)
undo = JuMP.fix_discrete_variables(node.subproblem)
JuMP.optimize!(node.subproblem)
if JuMP.dual_status(node.subproblem) != JuMP.MOI.FEASIBLE_POINT
SDDP.write_subproblem_to_file(
node,
"subproblem.mof.json",
throw_error = true,
)
end
dual_sign = JuMP.objective_sense(node.subproblem) == MOI.MIN_SENSE ? 1 : -1
λ = Dict{Symbol,Float64}(
name => dual_sign * JuMP.dual(JuMP.FixRef(state.in)) for
(name, state) in node.states
)
V = JuMP.objective_value(node.subproblem)
undo()
return V, λ
end
SDDP.prepare_backward_pass(::Node, ::FixedDiscreteDuality, ::Options) = nothing
SDDP.duality_log_key(::FixedDiscreteDuality) = "F"
Perfect! I'll test this, thanks!
Did you end up testing this? I'm hesitant to add this as an option to SDDP.jl. It feels like it'd work only in very specific situations, and that it'd be too easy to use it when the model doesn't suit.
I haven't had time to test this yet, I should do it this weekend. If you want, you can close the issue and I will reopen it if something goes wrong. But I'm almost certain that your example should work or a small variation of it.
Regarding adding it to SDDP.jl, I was going to suggest that, but thinking about it further I'm also unsure whether it's a good idea. For me reasons not to add:
- From a theoretical point of view, this does not seem to me to be (by definition) a subgradient (Warning this in the documentation?).
- I think that if we have interger as state variables, the code will break (add an if in this function to check this?)
- In a general case, it will generate cuts that may say that the cost in certain states is very high, without knowing that with other interger values this cost would be lower.
Reasons to add:
- Relaxing interger variables seems to me to generate suboptimal solutions, as it is optimistic in relation to the future cost. This would be an option to be pessimistic regarding the future cost, that is, to generate the optimal policy for a subset of the original possible choices.
- In practice, this seems to be widely used in Brazil, for marginal cost. I remember being surprised when I saw that SDDP.jl relaxed instead of fixed (but I understand that it makes more sense given the points above.).
- Still on the previous point, after training the model, during the simulation it is possible to fix all integers, even state variables (here we will not generate cuts, so there is no problem). And then get the dual variables.
For hydrothermal dispatch, the integer state variables are usually whether a generator is turned on or not. There is not much practical interest in this "dual", the most important thing is the cost of water.
For me reasons not to add:
Yes, precisely.
Reasons to add:
- Correct. But only for the dual on the backward pass. We still do the forward pass with the integer variables. You could try
StrengthenedConicDuality()
as an improvement overConicDuality()
without having the solve the full Lagrangian. - Yeah, we have a tutorial about this inn JuMP: https://jump.dev/JuMP.jl/stable/tutorials/linear/mip_duality/.
- I've thought about this, but I don't think it makes much sense. I'd prefer that people realize MIPs do not have dual solutions.
If you want, you can close the issue and I will reopen it if something goes wrong. But I'm almost certain that your example should work or a small variation of it.
I think this sums it up. It seems like we're in agreement here on the pros and cons, and that it isn't obvious which decision is the best. For the code code, I'd prefer to default no, rather than default yes.
You're welcome to develop this in an external package, and if you need any internal parts of SDDP.jl changed to help then we can do that.
Closing this as won't implement. (But happy to make any changes that you need to implement this as an external plug-in.)