Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a880cb8
Add support for starting values
blegat Mar 11, 2026
462f03d
Add manual mode
blegat Mar 18, 2026
9422059
Fixes
blegat Mar 18, 2026
cc4ede7
Fix format
blegat Mar 18, 2026
16f1866
fix
blegat Mar 18, 2026
3de770a
Continue test
blegat Apr 7, 2026
50fd1b5
Simplify
blegat Apr 8, 2026
895184a
Add comments
blegat Apr 8, 2026
77422de
Fixes
blegat Apr 8, 2026
6e5a572
More uniform
blegat Apr 8, 2026
1a97c61
Merge getters
blegat Apr 8, 2026
b2cf44e
Fix format
blegat Apr 8, 2026
1b77f3e
tmp
blegat Apr 8, 2026
da86856
Rely on explicit vectorization layer
blegat Apr 8, 2026
d34dea8
Fix format
blegat Apr 8, 2026
ad4c37c
Fix
blegat Apr 8, 2026
9622d0a
Cleanup
blegat Apr 8, 2026
f368fe0
Add docstrings
blegat Apr 8, 2026
7f0cd04
Fix correctness of ConstraintPrimal instead of hardcoding zero
blegat Apr 8, 2026
1c3fe15
Add test
blegat Apr 8, 2026
1d194de
Simplify MOI.set
blegat Apr 8, 2026
800d355
Fixes
blegat Apr 8, 2026
c681326
Fixes
blegat Apr 8, 2026
4f956d8
Fix format
blegat Apr 8, 2026
74a8cc0
Fixes
blegat Apr 12, 2026
54cbbf5
Fix format
blegat Apr 13, 2026
1e0696a
Add docstrings
blegat Apr 13, 2026
eb7e03f
Fix format
blegat Apr 13, 2026
1d39dd7
Improve code coverage
blegat Apr 13, 2026
9e82d61
Fix format
blegat Apr 13, 2026
7e85cf5
Fix
blegat Apr 13, 2026
76a7b44
fixes
blegat Apr 13, 2026
ad72a47
Fixes
blegat Apr 14, 2026
bcbeff2
Improve code coverage
blegat Apr 14, 2026
1390e9c
fix
blegat Apr 14, 2026
1ca341e
fix
blegat Apr 14, 2026
b92c47c
Fix
blegat Apr 14, 2026
6c1fffb
Fix format
blegat Apr 14, 2026
2ca2322
Fix
blegat Apr 14, 2026
abf943e
Fix
blegat Apr 14, 2026
7788a18
Fix
blegat Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/src/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,22 @@ Dualize the model
dual_model = dualize(model)
print(dual_model)
```

## Advanced: add support for new attributes

You might feel that the Dualization layer is getting in your way to get information from an inner layer.
If you ever see yourself typing `model.dual_problem.dual_model`, it is a sign that you need to define new attributes,
especially if these information are related to specific variables and constraints.
The mapping between variables and constraints before and after dualization is quite complex.
It depends on whether constraints corresponded to variables constrained at creation or not and
whether the corresponding set is an equality (i.e., `MOI.EqualTo` or `MOI.Zeros`).

When define MOI attributes to communicate information in MOI, layers like Dualization can transfer
these attributes to the right variables or constraints before or after their transformation.
So the best practice is to define such variable or constraint attributes and attempt to `MOI.get`
or `MOI.set` them. They is an API that you need to define for these attributes that contains the
functions are `dual_attribute`, `dual_attribute_value`,
`constrained_variable_dual_attribute`, `fixed_variable_value`, `fixed_constrained_variables_get`,
`equality_constraint_get`. That's a lot of functions, some of which may never be useful for
your specific attributes (e.g., if it is never used on an equality constraint) so no need
to implement the whole API for all attributes.
12 changes: 12 additions & 0 deletions docs/src/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,15 @@ Dualization.PrimalConstraintData
```@docs
Dualization.PrimalDualMap
```

## Attributes

```@docs
Dualization.dual_attribute
Dualization.dual_attribute_value_get
Dualization.dual_attribute_value_set
Dualization.constrained_variable_dual_attribute
Dualization.fixed_variable_value
Dualization.fixed_constrained_variables_get
Dualization.equality_constraint_get
```
2 changes: 2 additions & 0 deletions src/Dualization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ include("dual_model_variables.jl")
include("dual_equality_constraints.jl")
include("dualize.jl")
include("MOI_wrapper.jl")
include("vectorize_emulator.jl")
include("attributes.jl")

export dualize
export dual_optimizer
Expand Down
247 changes: 16 additions & 231 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ end

function MOI.supports_add_constrained_variables(
optimizer::DualOptimizer{T},
S::Type{MOI.Reals},
::Type{MOI.Reals},
) where {T}
return MOI.supports_constraint(
optimizer.dual_problem.dual_model,
Expand All @@ -234,21 +234,29 @@ function MOI.supports_add_constrained_variables(
end

function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike)
MOI.empty!(dest)
dualize(
src,
dest.dual_problem,
assume_min_if_feasibility = dest.assume_min_if_feasibility,
)
idx_map = MOI.Utilities.IndexMap()
for vi in MOI.get(src, MOI.ListOfVariableIndices())
setindex!(idx_map, vi, vi)
# Copy attributes except names which have already been passed in `dualize`
primal_without_names = MOI.Utilities.ModelFilter(src) do attr
return !(attr isa Union{MOI.VariableName,MOI.ConstraintName})
end
index_map = MOI.Utilities.identity_index_map(src)
vis = MOI.get(src, MOI.ListOfVariableIndices())
MOI.Utilities.pass_attributes(dest, primal_without_names, index_map, vis)
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
for con in MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
setindex!(idx_map, con, con)
end
cis = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
MOI.Utilities.pass_attributes(
dest,
primal_without_names,
index_map,
cis,
)
end
return idx_map
return index_map
end

function MOI.optimize!(optimizer::DualOptimizer)
Expand All @@ -270,226 +278,3 @@ function MOI.get(optimizer::DualOptimizer, ::MOI.SolverName)
name = MOI.get(optimizer.dual_problem.dual_model, MOI.SolverName())
return "Dual model with $name attached"
end

function MOI.get(
optimizer::DualOptimizer{T},
::MOI.VariablePrimal,
vi::MOI.VariableIndex,
)::T where {T}
primal_dual_map = optimizer.dual_problem.primal_dual_map
data = get(primal_dual_map.primal_variable_data, vi, nothing)
if data === nothing
# error
elseif data.dual_constraint === nothing
return zero(T)
elseif data.primal_constrained_variable_constraint === nothing
return -MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
data.dual_constraint,
)
elseif data.dual_constraint isa
MOI.ConstraintIndex{<:MOI.AbstractVectorFunction}
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
data.dual_constraint,
)[data.primal_constrained_variable_index]
else
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
data.dual_constraint,
)
end
end

function MOI.get(
optimizer::DualOptimizer,
::MOI.ConstraintDual,
ci::MOI.ConstraintIndex{F,S},
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
primal_dual_map = optimizer.dual_problem.primal_dual_map
if haskey(primal_dual_map.primal_constrained_variables, ci)
vi = primal_dual_map.primal_constrained_variables[ci][]
ci_dual = primal_dual_map.primal_variable_data[vi].dual_constraint
if ci_dual === nothing
return MOI.Utilities.eval_variables(
primal_dual_map.primal_variable_data[vi].dual_function,
) do inner_vi
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.VariablePrimal(),
inner_vi,
)
end
end
set = MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintSet(),
ci_dual,
)
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintPrimal(),
ci_dual,
) - MOI.constant(set)
else
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.VariablePrimal(),
primal_dual_map.primal_constraint_data[ci].dual_variables[],
)
end
end

function MOI.get(
optimizer::DualOptimizer,
::MOI.ConstraintDual,
ci::MOI.ConstraintIndex{F,S},
) where {F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet}
primal_dual_map = optimizer.dual_problem.primal_dual_map
if !haskey(primal_dual_map.primal_constraint_data, ci)
vis = primal_dual_map.primal_constrained_variables[ci]
ci_dual = primal_dual_map.primal_variable_data[vis[1]].dual_constraint
if ci_dual === nothing
return [
MOI.Utilities.eval_variables(
primal_dual_map.primal_variable_data[vi].dual_function,
) do inner_vi
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.VariablePrimal(),
inner_vi,
)
end for vi in vis
]
end
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintPrimal(),
ci_dual,
)
else
return MOI.get.(
optimizer.dual_problem.dual_model,
MOI.VariablePrimal(),
primal_dual_map.primal_constraint_data[ci].dual_variables,
)
end
end

function MOI.get(
optimizer::DualOptimizer{T},
::MOI.ConstraintPrimal,
ci::MOI.ConstraintIndex{F,S},
) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
primal_dual_map = optimizer.dual_problem.primal_dual_map
data = get(primal_dual_map.primal_constraint_data, ci, nothing)
if data === nothing
first_vi = primal_dual_map.primal_constrained_variables[ci][1]
ci_dual = primal_dual_map.primal_variable_data[first_vi].dual_constraint
if ci_dual === nothing
return zero(T)
else
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
ci_dual,
)
end
else
primal_ci_constant = data.primal_set_constants[1]
# If it has no key then there is no dual constraint
ci_dual = data.dual_constrained_variable_constraint
if ci_dual === nothing
return -primal_ci_constant
end
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
ci_dual,
) - primal_ci_constant
end
end

function MOI.get(
optimizer::DualOptimizer{T},
::MOI.ConstraintPrimal,
ci::MOI.ConstraintIndex{F,S},
) where {T,F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet}
primal_dual_map = optimizer.dual_problem.primal_dual_map
data = get(primal_dual_map.primal_constraint_data, ci, nothing)
if data === nothing
vis = primal_dual_map.primal_constrained_variables[ci]
ci_dual = primal_dual_map.primal_variable_data[vis[1]].dual_constraint
if ci_dual === nothing
return zeros(T, length(vis))
else
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
ci_dual,
)
end
else
ci_dual = data.dual_constrained_variable_constraint
# If it has no key then there is no dual constraint
if ci_dual === nothing
# The number of dual variable associated with the primal constraint is the ci dimension
ci_dimension = length(data.dual_variables)
return zeros(T, ci_dimension)
end
return MOI.get(
optimizer.dual_problem.dual_model,
MOI.ConstraintDual(),
ci_dual,
)
end
end

function MOI.get(optimizer::DualOptimizer, ::MOI.TerminationStatus)
return _dual_status(
MOI.get(optimizer.dual_problem.dual_model, MOI.TerminationStatus()),
)
end

function _dual_status(term::MOI.TerminationStatusCode)
if term == MOI.INFEASIBLE
return MOI.DUAL_INFEASIBLE
elseif term == MOI.DUAL_INFEASIBLE
return MOI.INFEASIBLE
elseif term == MOI.ALMOST_INFEASIBLE
return MOI.ALMOST_DUAL_INFEASIBLE
elseif term == MOI.ALMOST_DUAL_INFEASIBLE
return MOI.ALMOST_INFEASIBLE
end
return term
end

function MOI.supports(
optimizer::DualOptimizer,
attr::MOI.AbstractOptimizerAttribute,
)
return MOI.supports(optimizer.dual_problem.dual_model, attr)
end

function MOI.set(
optimizer::DualOptimizer,
attr::MOI.AbstractOptimizerAttribute,
value,
)
return MOI.set(optimizer.dual_problem.dual_model, attr, value)
end

function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractOptimizerAttribute)
return MOI.get(optimizer.dual_problem.dual_model, attr)
end

# For now we don't support setting arbitrary AbstractModelAttribute because
# we don't know if they need to be modified via the dualization. One example
# would be `MOI.set(model, MOI.ObjectiveFunction{F}(), f)`. We currently
# don't support the incremental interface.
function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractModelAttribute)
return MOI.get(optimizer.dual_problem.dual_model, attr)
end
Loading
Loading