Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
197 changes: 137 additions & 60 deletions src/Bridges/Constraint/vectorize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,82 @@

Transforms a constraint `G`-in-`scalar_set_type(S, T)` where
`S <: VectorLinearSet` to `F`-in-`S`.

## Examples

The constraint `SingleVariable`-in-`LessThan{Float64}` becomes
`VectorAffineFunction{Float64}`-in-`Nonpositives`, where `T = Float64`,
`F = VectorAffineFunction{Float64}`, `S = Nonpositives`, and
`G = SingleVariable`.
"""
mutable struct VectorizeBridge{T, F, S, G} <: AbstractBridge
vector_constraint::CI{F, S}
set_constant::T # constant in scalar set
end
function bridge_constraint(::Type{VectorizeBridge{T, F, S, G}},
model::MOI.ModelLike, g::G,
set::MOIU.ScalarLinearSet{T}) where {T, F, S, G}
set_constant = MOI.constant(set)
h = MOIU.operate(-, T, g, set_constant)
if -set_constant != MOI.constant(h)[1]
# This means the constant in `f` was not zero
constant = MOI.constant(h)[1] + set_constant
throw(MOI.ScalarFunctionConstantNotZero{typeof(constant), G,
typeof(set)}(constant))

function bridge_constraint(
::Type{VectorizeBridge{T, F, S, G}},
model::MOI.ModelLike,
scalar_f::G,
set::MOIU.ScalarLinearSet{T}
) where {T, F, S, G}
scalar_const = MOI.constant(scalar_f, T)
if !iszero(scalar_const)
throw(
MOI.ScalarFunctionConstantNotZero{
typeof(scalar_const), G, typeof(set)
}(scalar_const)
)
end
f = MOIU.operate(vcat, T, h)
vector_constraint = MOI.add_constraint(model, f, S(1))
return VectorizeBridge{T, F, S, G}(vector_constraint, set_constant)
vector_f = convert(F, scalar_f)
set_const = MOI.constant(set)
MOIU.operate_output_index!(-, T, 1, vector_f, set_const)
vector_constraint = MOI.add_constraint(model, vector_f, S(1))
return VectorizeBridge{T, F, S, G}(vector_constraint, set_const)
end

function MOI.supports_constraint(::Type{VectorizeBridge{T}},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOIU.ScalarLinearSet{T}}) where T
function MOI.supports_constraint(
::Type{VectorizeBridge{T}},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOIU.ScalarLinearSet{T}},
) where {T}
return true
end
MOIB.added_constrained_variable_types(::Type{<:VectorizeBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{<:VectorizeBridge{T, F, S}}) where {T, F, S}

function MOIB.added_constrained_variable_types(::Type{<:VectorizeBridge})
return Tuple{DataType}[]
end

function MOIB.added_constraint_types(
::Type{<:VectorizeBridge{T, F, S}}
) where {T, F, S}
return [(F, S)]
end
function concrete_bridge_type(::Type{<:VectorizeBridge{T}},
G::Type{<:MOI.AbstractScalarFunction},
S::Type{<:MOIU.ScalarLinearSet{T}}) where T

function concrete_bridge_type(
::Type{<:VectorizeBridge{T}},
G::Type{<:MOI.AbstractScalarFunction},
S::Type{<:MOIU.ScalarLinearSet{T}},
) where {T}
H = MOIU.promote_operation(-, T, G, T)
F = MOIU.promote_operation(vcat, T, H)
return VectorizeBridge{T, F, MOIU.vector_set_type(S), G}
end

# Attributes, Bridge acting as a model
function MOI.get(::VectorizeBridge{T, F, S},
::MOI.NumberOfConstraints{F, S}) where {T, F, S}

function MOI.get(
::VectorizeBridge{T, F, S}, ::MOI.NumberOfConstraints{F, S}
) where {T, F, S}
return 1
end
function MOI.get(bridge::VectorizeBridge{T, F, S},
::MOI.ListOfConstraintIndices{F, S}) where {T, F, S}

function MOI.get(
bridge::VectorizeBridge{T, F, S}, ::MOI.ListOfConstraintIndices{F, S}
) where {T, F, S}
return [bridge.vector_constraint]
end

# References
function MOI.delete(model::MOI.ModelLike, bridge::VectorizeBridge)
MOI.delete(model, bridge.vector_constraint)
end
Expand All @@ -61,62 +88,112 @@ end
function MOI.supports(
::MOI.ModelLike,
::Union{MOI.ConstraintPrimalStart, MOI.ConstraintDualStart},
::Type{<:VectorizeBridge})

::Type{<:VectorizeBridge},
)
return true
end
function MOI.get(model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart},
bridge::VectorizeBridge)

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
bridge::VectorizeBridge,
)
x = MOI.get(model, attr, bridge.vector_constraint)
@assert length(x) == 1
y = x[1]
if !(attr isa MOI.ConstraintPrimal &&
MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N))))
return x[1] + bridge.set_constant
end

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimal,
bridge::VectorizeBridge,
)
x = MOI.get(model, attr, bridge.vector_constraint)
@assert length(x) == 1
if MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N)))
# If it is an infeasibility certificate, it is a ray and satisfies the
# homogenized problem, see https://github.com/JuliaOpt/MathOptInterface.jl/issues/433
return x[1]
else
# Otherwise, we need to add the set constant since the ConstraintPrimal
# is defined as the value of the function and the set_constant was
# removed from the original function
y += bridge.set_constant
end
return y
return x[1] + bridge.set_constant
end
end
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart,
bridge::VectorizeBridge, value)
MOI.set(model, attr, bridge.vector_constraint, [value - bridge.set_constant])

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
bridge::VectorizeBridge,
value,
)
MOI.set(
model, attr, bridge.vector_constraint, [value - bridge.set_constant]
)
return
end
function MOI.get(model::MOI.ModelLike,
attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart},
bridge::VectorizeBridge)

function MOI.get(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart},
bridge::VectorizeBridge,
)
x = MOI.get(model, attr, bridge.vector_constraint)
@assert length(x) == 1
return x[1]
end
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintDualStart,
bridge::VectorizeBridge, value)

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
bridge::VectorizeBridge,
value,
)
MOI.set(model, attr, bridge.vector_constraint, [value])
return
end
function MOI.modify(model::MOI.ModelLike, bridge::VectorizeBridge,
change::MOI.ScalarCoefficientChange)
MOI.modify(model, bridge.vector_constraint,
MOI.MultirowChange(change.variable,
[(1, change.new_coefficient)]))

function MOI.modify(
model::MOI.ModelLike,
bridge::VectorizeBridge,
change::MOI.ScalarCoefficientChange,
)
MOI.modify(
model,
bridge.vector_constraint,
MOI.MultirowChange(change.variable, [(1, change.new_coefficient)]),
)
return
end
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::VectorizeBridge, new_set::MOIU.ScalarLinearSet)

function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintSet,
bridge::VectorizeBridge,
new_set::MOIU.ScalarLinearSet,
)
bridge.set_constant = MOI.constant(new_set)
MOI.modify(model, bridge.vector_constraint,
MOI.VectorConstantChange([-bridge.set_constant]))
MOI.modify(
model,
bridge.vector_constraint,
MOI.VectorConstantChange([-bridge.set_constant]),
)
return
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
bridge::VectorizeBridge{T, F, S, G}) where {T, F, S, G}

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintFunction,
bridge::VectorizeBridge{T, F, S, G}
) where {T, F, S, G}
f = MOIU.scalarize(MOI.get(model, attr, bridge.vector_constraint), true)
@assert isone(length(f))
# If `G` is `MOI.SingleVariable`, `f` will be `MOI.ScalarAffineFunction`.
@assert length(f) == 1
return convert(G, f[1])
end
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::VectorizeBridge{T, F, S}) where {T, F, S}

function MOI.get(
model::MOI.ModelLike, ::MOI.ConstraintSet, bridge::VectorizeBridge{T, F, S}
) where {T, F, S}
return MOIU.scalar_set_type(S, T)(bridge.set_constant)
end
119 changes: 100 additions & 19 deletions src/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -428,42 +428,123 @@ end
# Conversion between scalar functions
# Conversion to SingleVariable
function Base.convert(::Type{SingleVariable}, f::ScalarAffineFunction)
if !iszero(f.constant) || !isone(length(f.terms)) || !isone(f.terms[1].coefficient)
if (
!iszero(f.constant) ||
!isone(length(f.terms)) ||
!isone(f.terms[1].coefficient)
)
throw(InexactError(:convert, SingleVariable, f))
end
return SingleVariable(f.terms[1].variable_index)
end
function Base.convert(::Type{SingleVariable},
f::ScalarQuadraticFunction{T}) where T

function Base.convert(
::Type{SingleVariable}, f::ScalarQuadraticFunction{T}
) where {T}
return convert(SingleVariable, convert(ScalarAffineFunction{T}, f))
end

# Conversion to ScalarAffineFunction
function Base.convert(::Type{ScalarAffineFunction{T}}, α::T) where T
function Base.convert(::Type{ScalarAffineFunction{T}}, α::T) where {T}
return ScalarAffineFunction{T}(ScalarAffineTerm{T}[], α)
end
function Base.convert(::Type{ScalarAffineFunction{T}},
f::SingleVariable) where T

function Base.convert(
::Type{ScalarAffineFunction{T}}, f::SingleVariable
) where {T}
return ScalarAffineFunction{T}(f)
end
function Base.convert(::Type{ScalarAffineFunction{T}},
f::ScalarQuadraticFunction{T}) where T

function Base.convert(
::Type{ScalarAffineFunction{T}}, f::ScalarQuadraticFunction{T}
) where {T}
if !Base.isempty(f.quadratic_terms)
throw(InexactError(:convert, ScalarAffineFunction{T}, f))
end
return ScalarAffineFunction{T}(f.affine_terms, f.constant)
end

# Conversion to ScalarQuadraticFunction
function Base.convert(::Type{ScalarQuadraticFunction{T}}, α::T) where T
return ScalarQuadraticFunction{T}(ScalarAffineTerm{T}[], ScalarQuadraticTerm{T}[], α)
end
function Base.convert(::Type{ScalarQuadraticFunction{T}},
f::SingleVariable) where T
convert(ScalarQuadraticFunction{T}, convert(ScalarAffineFunction{T}, f))
end
function Base.convert(::Type{ScalarQuadraticFunction{T}},
f::ScalarAffineFunction{T}) where T
return ScalarQuadraticFunction{T}(f.terms, ScalarQuadraticTerm{T}[],
f.constant)
function Base.convert(
::Type{ScalarQuadraticFunction{T}}, α::T
) where {T}
return ScalarQuadraticFunction{T}(
ScalarAffineTerm{T}[], ScalarQuadraticTerm{T}[], α
)
end

function Base.convert(
::Type{ScalarQuadraticFunction{T}}, f::SingleVariable
) where {T}
return convert(
ScalarQuadraticFunction{T}, convert(ScalarAffineFunction{T}, f)
)
end

function Base.convert(
::Type{ScalarQuadraticFunction{T}}, f::ScalarAffineFunction{T}
) where {T}
return ScalarQuadraticFunction{T}(
f.terms, ScalarQuadraticTerm{T}[], f.constant
)
end

function Base.convert(::Type{VectorOfVariables}, g::SingleVariable)
return VectorOfVariables([g.variable])
end

function Base.convert(
::Type{VectorAffineFunction{T}}, g::SingleVariable
) where {T}
return VectorAffineFunction{T}(
[VectorAffineTerm(1, ScalarAffineTerm(one(T), g.variable))],
[zero(T)],
)
end

function Base.convert(
::Type{VectorQuadraticFunction{T}}, g::SingleVariable
) where {T}
return VectorQuadraticFunction{T}(
[VectorAffineTerm(1, ScalarAffineTerm(one(T), g.variable))],
VectorQuadraticTerm{T}[],
[zero(T)],
)
end

function Base.convert(
::Type{VectorAffineFunction{T}}, g::ScalarAffineFunction
) where {T}
return VectorAffineFunction{T}(
VectorAffineTerm{T}[
VectorAffineTerm(1, term) for term in g.terms
],
[g.constant],
)
end

function Base.convert(
::Type{VectorQuadraticFunction{T}}, g::ScalarAffineFunction
) where {T}
return VectorQuadraticFunction{T}(
VectorAffineTerm{T}[
VectorAffineTerm(1, term) for term in g.terms
],
VectorQuadraticTerm{T}[],
[g.constant],
)
end

function Base.convert(
::Type{VectorQuadraticFunction{T}}, g::ScalarQuadraticFunction
) where {T}
return VectorQuadraticFunction{T}(
VectorAffineTerm{T}[
VectorAffineTerm(1, term) for term in g.affine_terms
],
VectorQuadraticTerm{T}[
VectorQuadraticTerm(1, term) for term in g.quadratic_terms
],
[g.constant],
)
end
Loading