Skip to content

Commit 3ae2ebb

Browse files
authored
Add support for starting values (#208)
* Add support for starting values * Add manual mode * Fixes * Fix format * fix * Continue test * Simplify * Add comments * Fixes * More uniform * Merge getters * Fix format * tmp * Rely on explicit vectorization layer * Fix format * Fix * Cleanup * Add docstrings * Fix correctness of ConstraintPrimal instead of hardcoding zero * Add test * Simplify MOI.set * Fixes * Fixes * Fix format * Fixes * Fix format * Add docstrings * Fix format * Improve code coverage * Fix format * Fix * fixes * Fixes * Improve code coverage * fix * fix * Fix * Fix format * Fix * Fix * Fix
1 parent 650fe1d commit 3ae2ebb

12 files changed

Lines changed: 1060 additions & 267 deletions

docs/src/manual.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,22 @@ Dualize the model
202202
dual_model = dualize(model)
203203
print(dual_model)
204204
```
205+
206+
## Advanced: add support for new attributes
207+
208+
You might feel that the Dualization layer is getting in your way to get information from an inner layer.
209+
If you ever see yourself typing `model.dual_problem.dual_model`, it is a sign that you need to define new attributes,
210+
especially if these information are related to specific variables and constraints.
211+
The mapping between variables and constraints before and after dualization is quite complex.
212+
It depends on whether constraints corresponded to variables constrained at creation or not and
213+
whether the corresponding set is an equality (i.e., `MOI.EqualTo` or `MOI.Zeros`).
214+
215+
When define MOI attributes to communicate information in MOI, layers like Dualization can transfer
216+
these attributes to the right variables or constraints before or after their transformation.
217+
So the best practice is to define such variable or constraint attributes and attempt to `MOI.get`
218+
or `MOI.set` them. They is an API that you need to define for these attributes that contains the
219+
functions are `dual_attribute`, `dual_attribute_value`,
220+
`constrained_variable_dual_attribute`, `fixed_variable_value`, `fixed_constrained_variables_get`,
221+
`equality_constraint_get`. That's a lot of functions, some of which may never be useful for
222+
your specific attributes (e.g., if it is never used on an equality constraint) so no need
223+
to implement the whole API for all attributes.

docs/src/reference.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ Dualization.PrimalConstraintData
7070
```@docs
7171
Dualization.PrimalDualMap
7272
```
73+
74+
## Attributes
75+
76+
```@docs
77+
Dualization.dual_attribute
78+
Dualization.dual_attribute_value_get
79+
Dualization.dual_attribute_value_set
80+
Dualization.constrained_variable_dual_attribute
81+
Dualization.fixed_variable_value
82+
Dualization.fixed_constrained_variables_get
83+
Dualization.equality_constraint_get
84+
```

src/Dualization.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ include("dual_model_variables.jl")
2020
include("dual_equality_constraints.jl")
2121
include("dualize.jl")
2222
include("MOI_wrapper.jl")
23+
include("vectorize_emulator.jl")
24+
include("attributes.jl")
2325

2426
export dualize
2527
export dual_optimizer

src/MOI_wrapper.jl

Lines changed: 16 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ end
207207

208208
function MOI.supports_add_constrained_variables(
209209
optimizer::DualOptimizer{T},
210-
S::Type{MOI.Reals},
210+
::Type{MOI.Reals},
211211
) where {T}
212212
return MOI.supports_constraint(
213213
optimizer.dual_problem.dual_model,
@@ -234,21 +234,29 @@ function MOI.supports_add_constrained_variables(
234234
end
235235

236236
function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike)
237+
MOI.empty!(dest)
237238
dualize(
238239
src,
239240
dest.dual_problem,
240241
assume_min_if_feasibility = dest.assume_min_if_feasibility,
241242
)
242-
idx_map = MOI.Utilities.IndexMap()
243-
for vi in MOI.get(src, MOI.ListOfVariableIndices())
244-
setindex!(idx_map, vi, vi)
243+
# Copy attributes except names which have already been passed in `dualize`
244+
primal_without_names = MOI.Utilities.ModelFilter(src) do attr
245+
return !(attr isa Union{MOI.VariableName,MOI.ConstraintName})
245246
end
247+
index_map = MOI.Utilities.identity_index_map(src)
248+
vis = MOI.get(src, MOI.ListOfVariableIndices())
249+
MOI.Utilities.pass_attributes(dest, primal_without_names, index_map, vis)
246250
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
247-
for con in MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
248-
setindex!(idx_map, con, con)
249-
end
251+
cis = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
252+
MOI.Utilities.pass_attributes(
253+
dest,
254+
primal_without_names,
255+
index_map,
256+
cis,
257+
)
250258
end
251-
return idx_map
259+
return index_map
252260
end
253261

254262
function MOI.optimize!(optimizer::DualOptimizer)
@@ -270,226 +278,3 @@ function MOI.get(optimizer::DualOptimizer, ::MOI.SolverName)
270278
name = MOI.get(optimizer.dual_problem.dual_model, MOI.SolverName())
271279
return "Dual model with $name attached"
272280
end
273-
274-
function MOI.get(
275-
optimizer::DualOptimizer{T},
276-
::MOI.VariablePrimal,
277-
vi::MOI.VariableIndex,
278-
)::T where {T}
279-
primal_dual_map = optimizer.dual_problem.primal_dual_map
280-
data = get(primal_dual_map.primal_variable_data, vi, nothing)
281-
if data === nothing
282-
# error
283-
elseif data.dual_constraint === nothing
284-
return zero(T)
285-
elseif data.primal_constrained_variable_constraint === nothing
286-
return -MOI.get(
287-
optimizer.dual_problem.dual_model,
288-
MOI.ConstraintDual(),
289-
data.dual_constraint,
290-
)
291-
elseif data.dual_constraint isa
292-
MOI.ConstraintIndex{<:MOI.AbstractVectorFunction}
293-
return MOI.get(
294-
optimizer.dual_problem.dual_model,
295-
MOI.ConstraintDual(),
296-
data.dual_constraint,
297-
)[data.primal_constrained_variable_index]
298-
else
299-
return MOI.get(
300-
optimizer.dual_problem.dual_model,
301-
MOI.ConstraintDual(),
302-
data.dual_constraint,
303-
)
304-
end
305-
end
306-
307-
function MOI.get(
308-
optimizer::DualOptimizer,
309-
::MOI.ConstraintDual,
310-
ci::MOI.ConstraintIndex{F,S},
311-
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
312-
primal_dual_map = optimizer.dual_problem.primal_dual_map
313-
if haskey(primal_dual_map.primal_constrained_variables, ci)
314-
vi = primal_dual_map.primal_constrained_variables[ci][]
315-
ci_dual = primal_dual_map.primal_variable_data[vi].dual_constraint
316-
if ci_dual === nothing
317-
return MOI.Utilities.eval_variables(
318-
primal_dual_map.primal_variable_data[vi].dual_function,
319-
) do inner_vi
320-
return MOI.get(
321-
optimizer.dual_problem.dual_model,
322-
MOI.VariablePrimal(),
323-
inner_vi,
324-
)
325-
end
326-
end
327-
set = MOI.get(
328-
optimizer.dual_problem.dual_model,
329-
MOI.ConstraintSet(),
330-
ci_dual,
331-
)
332-
return MOI.get(
333-
optimizer.dual_problem.dual_model,
334-
MOI.ConstraintPrimal(),
335-
ci_dual,
336-
) - MOI.constant(set)
337-
else
338-
return MOI.get(
339-
optimizer.dual_problem.dual_model,
340-
MOI.VariablePrimal(),
341-
primal_dual_map.primal_constraint_data[ci].dual_variables[],
342-
)
343-
end
344-
end
345-
346-
function MOI.get(
347-
optimizer::DualOptimizer,
348-
::MOI.ConstraintDual,
349-
ci::MOI.ConstraintIndex{F,S},
350-
) where {F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet}
351-
primal_dual_map = optimizer.dual_problem.primal_dual_map
352-
if !haskey(primal_dual_map.primal_constraint_data, ci)
353-
vis = primal_dual_map.primal_constrained_variables[ci]
354-
ci_dual = primal_dual_map.primal_variable_data[vis[1]].dual_constraint
355-
if ci_dual === nothing
356-
return [
357-
MOI.Utilities.eval_variables(
358-
primal_dual_map.primal_variable_data[vi].dual_function,
359-
) do inner_vi
360-
return MOI.get(
361-
optimizer.dual_problem.dual_model,
362-
MOI.VariablePrimal(),
363-
inner_vi,
364-
)
365-
end for vi in vis
366-
]
367-
end
368-
return MOI.get(
369-
optimizer.dual_problem.dual_model,
370-
MOI.ConstraintPrimal(),
371-
ci_dual,
372-
)
373-
else
374-
return MOI.get.(
375-
optimizer.dual_problem.dual_model,
376-
MOI.VariablePrimal(),
377-
primal_dual_map.primal_constraint_data[ci].dual_variables,
378-
)
379-
end
380-
end
381-
382-
function MOI.get(
383-
optimizer::DualOptimizer{T},
384-
::MOI.ConstraintPrimal,
385-
ci::MOI.ConstraintIndex{F,S},
386-
) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
387-
primal_dual_map = optimizer.dual_problem.primal_dual_map
388-
data = get(primal_dual_map.primal_constraint_data, ci, nothing)
389-
if data === nothing
390-
first_vi = primal_dual_map.primal_constrained_variables[ci][1]
391-
ci_dual = primal_dual_map.primal_variable_data[first_vi].dual_constraint
392-
if ci_dual === nothing
393-
return zero(T)
394-
else
395-
return MOI.get(
396-
optimizer.dual_problem.dual_model,
397-
MOI.ConstraintDual(),
398-
ci_dual,
399-
)
400-
end
401-
else
402-
primal_ci_constant = data.primal_set_constants[1]
403-
# If it has no key then there is no dual constraint
404-
ci_dual = data.dual_constrained_variable_constraint
405-
if ci_dual === nothing
406-
return -primal_ci_constant
407-
end
408-
return MOI.get(
409-
optimizer.dual_problem.dual_model,
410-
MOI.ConstraintDual(),
411-
ci_dual,
412-
) - primal_ci_constant
413-
end
414-
end
415-
416-
function MOI.get(
417-
optimizer::DualOptimizer{T},
418-
::MOI.ConstraintPrimal,
419-
ci::MOI.ConstraintIndex{F,S},
420-
) where {T,F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet}
421-
primal_dual_map = optimizer.dual_problem.primal_dual_map
422-
data = get(primal_dual_map.primal_constraint_data, ci, nothing)
423-
if data === nothing
424-
vis = primal_dual_map.primal_constrained_variables[ci]
425-
ci_dual = primal_dual_map.primal_variable_data[vis[1]].dual_constraint
426-
if ci_dual === nothing
427-
return zeros(T, length(vis))
428-
else
429-
return MOI.get(
430-
optimizer.dual_problem.dual_model,
431-
MOI.ConstraintDual(),
432-
ci_dual,
433-
)
434-
end
435-
else
436-
ci_dual = data.dual_constrained_variable_constraint
437-
# If it has no key then there is no dual constraint
438-
if ci_dual === nothing
439-
# The number of dual variable associated with the primal constraint is the ci dimension
440-
ci_dimension = length(data.dual_variables)
441-
return zeros(T, ci_dimension)
442-
end
443-
return MOI.get(
444-
optimizer.dual_problem.dual_model,
445-
MOI.ConstraintDual(),
446-
ci_dual,
447-
)
448-
end
449-
end
450-
451-
function MOI.get(optimizer::DualOptimizer, ::MOI.TerminationStatus)
452-
return _dual_status(
453-
MOI.get(optimizer.dual_problem.dual_model, MOI.TerminationStatus()),
454-
)
455-
end
456-
457-
function _dual_status(term::MOI.TerminationStatusCode)
458-
if term == MOI.INFEASIBLE
459-
return MOI.DUAL_INFEASIBLE
460-
elseif term == MOI.DUAL_INFEASIBLE
461-
return MOI.INFEASIBLE
462-
elseif term == MOI.ALMOST_INFEASIBLE
463-
return MOI.ALMOST_DUAL_INFEASIBLE
464-
elseif term == MOI.ALMOST_DUAL_INFEASIBLE
465-
return MOI.ALMOST_INFEASIBLE
466-
end
467-
return term
468-
end
469-
470-
function MOI.supports(
471-
optimizer::DualOptimizer,
472-
attr::MOI.AbstractOptimizerAttribute,
473-
)
474-
return MOI.supports(optimizer.dual_problem.dual_model, attr)
475-
end
476-
477-
function MOI.set(
478-
optimizer::DualOptimizer,
479-
attr::MOI.AbstractOptimizerAttribute,
480-
value,
481-
)
482-
return MOI.set(optimizer.dual_problem.dual_model, attr, value)
483-
end
484-
485-
function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractOptimizerAttribute)
486-
return MOI.get(optimizer.dual_problem.dual_model, attr)
487-
end
488-
489-
# For now we don't support setting arbitrary AbstractModelAttribute because
490-
# we don't know if they need to be modified via the dualization. One example
491-
# would be `MOI.set(model, MOI.ObjectiveFunction{F}(), f)`. We currently
492-
# don't support the incremental interface.
493-
function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractModelAttribute)
494-
return MOI.get(optimizer.dual_problem.dual_model, attr)
495-
end

0 commit comments

Comments
 (0)