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
4 changes: 4 additions & 0 deletions src/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ include("map.jl")
# Bridge optimizer bridging a specific variable bridge
include("single_bridge_optimizer.jl")

# Variable bridges
include("zeros.jl")
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}

end
68 changes: 68 additions & 0 deletions src/Bridges/Variable/zeros.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
ZerosBridge{T} <: Bridges.Variable.AbstractBridge

Transforms constrained variables in [`MathOptInterface.Zeros`](@ref) to zeros,
which ends up creating no variables in the underlying model.
The bridged variables are therefore similar to parameters with zero values.
Parameters with non-zero value can be created with constrained variables in
[`MOI.EqualTo`](@ref) by combining a [`VectorizeBridge`](@ref) and this bridge.
The functions cannot be unbridged, given a function, we cannot determine, if
the bridged variables were used.
The dual values cannot be determined by the bridge but they can be determined
by the bridged optimizer using [`MathOptInterface.Utilities.get_fallback`](@ref)
if a `CachingOptimizer` is used (since `ConstraintFunction` cannot be got
as functions cannot be unbridged).
"""
struct ZerosBridge{T} <: AbstractBridge
n::Int # Number of variables
end
function bridge_constrained_variable(::Type{ZerosBridge{T}},
model::MOI.ModelLike,
set::MOI.Zeros) where T
return ZerosBridge{T}(MOI.dimension(set))
end

function supports_constrained_variable(
::Type{<:ZerosBridge}, ::Type{MOI.Zeros})
return true
end
function MOIB.added_constrained_variable_types(::Type{<:ZerosBridge})
return Tuple{DataType}[]
end
function MOIB.added_constraint_types(::Type{<:ZerosBridge})
return Tuple{DataType, DataType}[]
end

# Attributes, Bridge acting as a model
MOI.get(bridge::ZerosBridge, ::MOI.NumberOfVariables) = 0
function MOI.get(bridge::ZerosBridge, ::MOI.ListOfVariableIndices)
return MOI.VariableIndex[]
end

# References
function MOI.delete(::MOI.ModelLike, ::ZerosBridge) end

# Attributes, Bridge acting as a constraint

function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::ZerosBridge)
return MOI.Zeros(bridge.n)
end

function MOI.get(::MOI.ModelLike, ::MOI.ConstraintPrimal,
bridge::ZerosBridge{T}) where T
return zeros(T, bridge.n)
end

function MOI.get(::MOI.ModelLike, ::MOI.VariablePrimal,
::ZerosBridge{T}, ::IndexInVector) where T
return zero(T)
end

function MOIB.bridged_function(::ZerosBridge{T}, ::IndexInVector) where T
return zero(MOI.ScalarAffineFunction{T})
end
function unbridged_map(::ZerosBridge, ::MOI.VariableIndex,
::IndexInVector)
return nothing
end
11 changes: 11 additions & 0 deletions src/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,12 @@ function bridged_function(b::AbstractBridgeOptimizer,
end
return func
end
# Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))`
function bridge_function(
::AbstractBridgeOptimizer, value::MOIU.ObjectOrArrayWithoutIndex)
return value
end


"""
unbridged_variable_function(b::AbstractBridgeOptimizer,
Expand Down Expand Up @@ -975,6 +981,11 @@ function unbridged_function(bridge::AbstractBridgeOptimizer,
func::Union{MOI.SingleVariable, MOI.VectorOfVariables})
return func # bridged variables are not allowed in non-bridged constraints
end
# Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))`
function unbridged_function(
::AbstractBridgeOptimizer, value::MOIU.ObjectOrArrayWithoutIndex)
return value
end

"""
unbridged_constraint_function(
Expand Down
8 changes: 3 additions & 5 deletions src/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,10 @@ or submittable value.
"""
function substitute_variables end

function substitute_variables(
::Function, x::Union{Number, Enum, AbstractArray{<:Union{Number, Enum}}})
return x
end
const ObjectWithoutIndex = Union{Number, Enum, MOI.AnyAttribute, MOI.AbstractSet}
const ObjectOrArrayWithoutIndex = Union{ObjectWithoutIndex, AbstractArray{<:ObjectWithoutIndex}}

substitute_variables(::Function, set::MOI.AbstractSet) = set
substitute_variables(::Function, x::ObjectOrArrayWithoutIndex) = x

function substitute_variables(variable_map::Function,
term::MOI.ScalarQuadraticTerm{T}) where T
Expand Down
2 changes: 2 additions & 0 deletions src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Abstract supertype for attribute objects that can be used to set or get attribut
"""
abstract type AbstractConstraintAttribute end

# Attributes should not contain any `VariableIndex` or `ConstraintIndex` as the
# set is passed unmodifed during `copy_to`.
const AnyAttribute = Union{AbstractOptimizerAttribute, AbstractModelAttribute, AbstractVariableAttribute, AbstractConstraintAttribute}

# This allows to use attributes in broadcast calls without the need to
Expand Down
3 changes: 3 additions & 0 deletions test/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ end
@testset "LazyBridgeOptimizer" begin
include("lazy_bridge_optimizer.jl")
end
@testset "Variable bridges" begin
include("Variable/Variable.jl")
end
@testset "Constraint bridges" begin
include("Constraint/Constraint.jl")
end
4 changes: 4 additions & 0 deletions test/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using Test
@testset "Map" begin
include("map.jl")
end
@testset "Zeros" begin
include("zeros.jl")
end
155 changes: 155 additions & 0 deletions test/Bridges/Variable/zeros.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges

include("../utilities.jl")

mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
config = MOIT.TestConfig()

bridged_mock = MOIB.Variable.Zeros{Float64}(mock)

x, cx = MOI.add_constrained_variable(bridged_mock, MOI.GreaterThan(0.0))
MOI.set(bridged_mock, MOI.VariableName(), x, "x")
MOI.set(bridged_mock, MOI.ConstraintName(), cx, "cx")
yz, cyz = MOI.add_constrained_variables(bridged_mock, MOI.Zeros(2))
MOI.set(bridged_mock, MOI.VariableName(), yz, ["y", "z"])
MOI.set(bridged_mock, MOI.ConstraintName(), cyz, "cyz")
y, z = yz
fx = MOI.SingleVariable(x)
fy = MOI.SingleVariable(y)
fz = MOI.SingleVariable(z)

@testset "SingleVariable objective" begin
err = ErrorException("Using bridged variable in `SingleVariable` function.")
@test_throws err MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fy)}(), fy)
MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MIN_SENSE)
MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}(), fx)
@test MOI.get(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}()) == fx
end

# Test before adding affine constraints are affine expressions cannot be
# unbridged when `Variable.ZerosBridge` is used.
@testset "Test bridged model" begin
s = """
variables: x, y, z
cx: x >= 0.0
cyz: [y, z] in MathOptInterface.Zeros(2)
minobjective: x
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, ["x", "y", "z"], ["cx", "cyz"])
end

c1, c2 = MOI.add_constraints(
bridged_mock, [1.0fy + 1.0fz, 1.0fx + 1.0fy + 1.0fz],
[MOI.EqualTo(0.0), MOI.GreaterThan(1.0)]
)
MOI.set(bridged_mock, MOI.ConstraintName(), c1, "con1")
MOI.set(bridged_mock, MOI.ConstraintName(), c2, "con2")
MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MIN_SENSE)
obj = 1.0fx - 1.0fy - 1.0fz
MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(obj)}(), obj)

@test MOIB.Variable.unbridged_map(MOIB.bridge(bridged_mock, y), y, MOIB.Variable.IndexInVector(1)) === nothing
@test MOIB.Variable.unbridged_map(MOIB.bridge(bridged_mock, z), z, MOIB.Variable.IndexInVector(2)) === nothing

err = ErrorException(
"Cannot delete constraint index of bridged constrained variables. Delete" *
" the scalar variable or the vector of variables instead."
)
@test_throws err MOI.delete(bridged_mock, cyz)

err = ErrorException(
"Cannot add two `VectorOfVariables`-in-`MathOptInterface.Zeros` on the" *
" same first variable MathOptInterface.VariableIndex(-1)."
)
@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables(yz), MOI.Zeros(2))

err = ErrorException(
"Cannot `VectorOfVariables`-in-`MathOptInterface.Zeros` for" *
" which some variables are bridged but not the first one" *
" `MathOptInterface.VariableIndex(12345679)`."
)
@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables([x, y]), MOI.Zeros(2))

err = ErrorException(
"Cannot unbridge function because some variables are bridged by" *
" variable bridges that do not support reverse mapping, e.g.," *
" `ZerosBridge`."
)
@test_throws err MOI.get(bridged_mock, MOI.ObjectiveFunction{typeof(obj)}())
# With `c1`, the function does not contain any variable so it tests that it
# also throws an error even if it never calls `variable_unbridged_function`.
@test_throws err MOI.get(bridged_mock, MOI.ConstraintFunction(), c1)
@test_throws err MOI.get(bridged_mock, MOI.ConstraintFunction(), c2)

err = ArgumentError(
"Variable bridge of type `MathOptInterface.Bridges.Variable.ZerosBridge{Float64}`" *
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
)
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)

@testset "Results" begin
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
mock, [1.0],
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => 0.0,
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => 1.0)
)
MOI.optimize!(bridged_mock)
@test MOI.get(bridged_mock, MOI.VariablePrimal(), x) == 1.0
@test MOI.get(bridged_mock, MOI.VariablePrimal(), y) == 0.0
@test MOI.get(bridged_mock, MOI.VariablePrimal(), z) == 0.0

@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cyz) == zeros(2)

@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == 0.0
@test MOI.get(bridged_mock, MOI.ConstraintDual(), c1) == 0.0
@test MOI.get(bridged_mock, MOI.ConstraintDual(), c2) == 1.0

err = ArgumentError(
"Bridge of type `MathOptInterface.Bridges.Variable.ZerosBridge{Float64}`" *
" does not support accessing the attribute" *
" `MathOptInterface.ConstraintDual(1)`."
)
@test_throws err MOI.get(bridged_mock, MOI.ConstraintDual(), cyz)
end

@testset "Query" begin
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), cyz).variables == yz
@test MOI.get(mock, MOI.NumberOfVariables()) == 1
@test MOI.get(mock, MOI.ListOfVariableIndices()) == [x]
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 3
@test MOI.get(bridged_mock, MOI.ListOfVariableIndices()) == [x, y, z]
@test MOI.get(mock, MOI.NumberOfConstraints{MOI.VectorOfVariables, MOI.Zeros}()) == 0
@test MOI.get(bridged_mock, MOI.NumberOfConstraints{MOI.VectorOfVariables, MOI.Zeros}()) == 1
@test MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MOI.Zeros}()) == [cyz]
end

@testset "Test mock model" begin
s = """
variables: x
cx: x >= 0.0
con1: x + 0.0 == 0.0
con2: x + 0.0 >= 1.0
minobjective: x
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, ["x"], ["cx", "con1", "con2"])
end

@testset "Delete" begin
test_delete_bridged_variables(bridged_mock, yz, MOI.Zeros, 3, (
(MOI.SingleVariable, MOI.GreaterThan{Float64}, 1),
))
@test MOI.is_valid(bridged_mock, x)
@test !MOI.is_valid(bridged_mock, y)
@test !MOI.is_valid(bridged_mock, z)
end
12 changes: 6 additions & 6 deletions test/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,25 @@ end
c1 = MOI.add_constraint(model, f1, MOI.Interval(-1, 1))

@test MOI.get(model, MOI.ListOfConstraints()) == [(MOI.ScalarAffineFunction{Int},MOI.Interval{Int})]
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 0)
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 0)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
@test (@inferred MOI.get(model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Int},MOI.Interval{Int}}())) == [c1]

f2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2, -1], [x, y]), 2)
c2 = MOI.add_constraint(model, f1, MOI.GreaterThan(-2))

@test MOI.get(model, MOI.ListOfConstraints()) == [(MOI.ScalarAffineFunction{Int},MOI.GreaterThan{Int}), (MOI.ScalarAffineFunction{Int},MOI.Interval{Int})]
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 1)
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 1)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
@test (@inferred MOI.get(model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Int},MOI.Interval{Int}}())) == [c1]
@test (@inferred MOI.get(model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Int},MOI.GreaterThan{Int}}())) == [c2]

@test MOI.is_valid(model, c2)
MOI.delete(model, c2)

@test MOI.get(model, MOI.ListOfConstraints()) == [(MOI.ScalarAffineFunction{Int},MOI.Interval{Int})]
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 0)
test_noc(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.GreaterThan{Int}, 0)
test_num_constraints(model, MOI.ScalarAffineFunction{Int}, MOI.Interval{Int}, 1)
@test (@inferred MOI.get(model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Int},MOI.Interval{Int}}())) == [c1]
end

Expand Down
Loading