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
2 changes: 2 additions & 0 deletions src/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ include("single_bridge_optimizer.jl")
# Variable bridges
include("zeros.jl")
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
include("free.jl")
const Free{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{FreeBridge{T}, OT}
include("flip_sign.jl")
const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT}
include("vectorize.jl")
Expand Down
117 changes: 117 additions & 0 deletions src/Bridges/Variable/free.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
FreeBridge{T} <: Bridges.Variable.AbstractBridge

Transforms constrained variables in [`MOI.Reals`](@ref) to the difference of
constrained variables in [`MOI.Nonnegatives`](@ref).
"""
struct FreeBridge{T} <: AbstractBridge
variables::Vector{MOI.VariableIndex}
constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonnegatives}
end
function bridge_constrained_variable(::Type{FreeBridge{T}},
model::MOI.ModelLike,
set::MOI.Reals) where T
variables, constraint = MOI.add_constrained_variables(
model, MOI.Nonnegatives(2MOI.dimension(set)))
return FreeBridge{T}(variables, constraint)
end

function supports_constrained_variable(
::Type{<:FreeBridge}, ::Type{MOI.Reals})
return true
end
function MOIB.added_constrained_variable_types(::Type{<:FreeBridge})
return [(MOI.Nonnegatives,)]
end
function MOIB.added_constraint_types(::Type{FreeBridge{T}}) where T
return Tuple{DataType, DataType}[]
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::FreeBridge, ::MOI.NumberOfVariables)
return length(bridge.variables)
end
function MOI.get(bridge::FreeBridge, ::MOI.ListOfVariableIndices)
return vcat(bridge.variables)
end
function MOI.get(bridge::FreeBridge,
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
MOI.Nonnegatives})
return 1
end
function MOI.get(bridge::FreeBridge,
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
MOI.Nonnegatives})
return [bridge.constraint]
end

# References
function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge)
MOI.delete(model, bridge.variables)
end

function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge, i::IndexInVector)
n = div(length(bridge.variables), 2)
MOI.delete(model, bridge.variables[i.value])
MOI.delete(model, bridge.variables[n + i.value])
deleteat!(bridge.variables, [i.value, n + i.value])
end


# Attributes, Bridge acting as a constraint

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
bridge::FreeBridge{T}) where T
n = div(length(bridge.variables), 2)
primal = MOI.get(model, attr, bridge.constraint)
return primal[1:n] - primal[n .+ (1:n)]
end
# The transformation is x_free = [I -I] * x
# so the transformation of the dual is
# y = [I; -I] * y_free
# that is
# y[1:n] = -y[n .+ (1:n)] = y_free
# We can therefore compute `y_free` from either of them, let's take `y[1:n]`.
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
bridge::FreeBridge{T}) where T
n = div(length(bridge.variables), 2)
return MOI.get(model, attr, bridge.constraint)[1:n]
end

function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
bridge::FreeBridge{T}, i::IndexInVector) where T
n = div(length(bridge.variables), 2)
return MOI.get(model, attr, bridge.variables[i.value]) -
MOI.get(model, attr, bridge.variables[n + i.value])
end

function MOIB.bridged_function(bridge::FreeBridge{T}, i::IndexInVector) where T
n = div(length(bridge.variables), 2)
return MOIU.operate(-, T, MOI.SingleVariable(bridge.variables[i.value]),
MOI.SingleVariable(bridge.variables[n + i.value]))
end
# x_free has been replaced by x[i] - x[n + i].
# To undo it we replace x[i] by x_free and x[n + i] by 0.
function unbridged_map(bridge::FreeBridge{T}, vi::MOI.VariableIndex,
i::IndexInVector) where T
sv = MOI.SingleVariable(vi)
# `unbridged_map` is required to return a `MOI.ScalarAffineFunction`.
func = convert(MOI.ScalarAffineFunction{T}, sv)
n = div(length(bridge.variables), 2)
return bridge.variables[i.value] => func,
bridge.variables[n + i.value] => zero(MOI.ScalarAffineFunction{T})
end

function MOI.set(model::MOI.ModelLike, attr::MOI.VariablePrimalStart,
bridge::FreeBridge, value, i::IndexInVector)
if value < 0
nonneg = zero(value)
nonpos = -value
else
nonneg = value
nonpos = zero(value)
end
n = div(length(bridge.variables), 2)
MOI.set(model, attr, bridge.variables[i.value], nonneg)
MOI.set(model, attr, bridge.variables[n + i.value], nonpos)
end
3 changes: 3 additions & 0 deletions test/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ end
@testset "Zeros" begin
include("zeros.jl")
end
@testset "Free" begin
include("free.jl")
end
@testset "FlipSign" begin
include("flip_sign.jl")
end
Expand Down
187 changes: 187 additions & 0 deletions test/Bridges/Variable/free.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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.UniversalFallback(MOIU.Model{Float64}()))
config = MOIT.TestConfig()

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

@testset "solve_multirow_vectoraffine_nonpos" begin
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5, 0.0])
),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.25, 0.0])
)
)
MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config)

MOI.set(bridged_mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}()),
["c"])

@testset "Mock model" begin
var_names = ["xpos", "xneg"]
MOI.set(mock, MOI.VariableName(),
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
MOI.set(bridged_mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.Nonnegatives}()),
["nonneg"])
s = """
variables: xpos, xneg
nonneg: [xpos, xneg] in MathOptInterface.Nonnegatives(2)
c: [4.0xpos + -4.0xneg + -1.0, 3.0xpos + -3.0xneg + -1.0] in MathOptInterface.Nonpositives(2)
maxobjective: xpos + -1.0xneg
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, var_names, ["nonneg", "c"])
end
@testset "Bridged model" begin
s = """
variables: x
c: [4.0x + -1.0, 3.0x + -1.0] in MathOptInterface.Nonpositives(2)
maxobjective: 1.0x
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, ["x"], ["c"])
end
end

@testset "Linear6" begin
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0],
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0]
))
MOIT.linear6test(bridged_mock, config)

loc = MOI.get(bridged_mock, MOI.ListOfConstraints())
@test length(loc) == 2
@test !((MOI.VectorOfVariables, MOI.Reals) in loc)
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in loc
@test (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) in loc
@test MOI.get(mock, MOI.NumberOfVariables()) == 4
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 2
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
@test vis == MOI.VariableIndex.([-1, -2])

cx = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(vis[1].value)
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cx) == [100.0]
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == [0.0]
cy = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(vis[2].value)
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cy) == [-100.0]
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cy) == [0.0]

test_delete_bridged_variable(bridged_mock, vis[1], MOI.Reals, 2, (
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
))
test_delete_bridged_variable(bridged_mock, vis[2], MOI.Reals, 1, (
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
))
end

@testset "Linear7" begin
function set_mock_optimize_linear7Test!(mock)
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[1.0]],
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1.0]]
))
end
set_mock_optimize_linear7Test!(mock)
MOIT.linear7test(bridged_mock, config)

x, y = MOI.get(bridged_mock, MOI.ListOfVariableIndices())

cx = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(x.value)
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cx) == [100.0]
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == [0.0]
cy = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(y.value)
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cy) == [-100.0]
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cy) == [0.0]

@test MOI.supports(bridged_mock, MOI.VariablePrimalStart(), MOI.VariableIndex)
MOI.set(bridged_mock, MOI.VariablePrimalStart(), [x, y], [1.0, -1.0])
xa, xb, ya, yb = MOI.get(mock, MOI.ListOfVariableIndices())
@test MOI.get(mock, MOI.VariablePrimalStart(), [xa, xb, ya, yb]) == [1.0, 0.0, 0.0, 1.0]
end

@testset "Linear11" begin
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 0.0, 0.0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5, 0.0, 0.0]))
MOIT.linear11test(bridged_mock, config)

vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
@test vis == MOI.VariableIndex.([-1, -2])

MOI.set(bridged_mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()),
["c1"])
MOI.set(bridged_mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()),
["c2"])

@testset "Mock model" begin
var_names = ["v1pos", "v2pos", "v1neg", "v2neg"]
MOI.set(mock, MOI.VariableName(),
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
MOI.set(bridged_mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.Nonnegatives}()),
["nonneg"])
s = """
variables: v1pos, v2pos, v1neg, v2neg
nonneg: [v1pos, v2pos, v1neg, v2neg] in MathOptInterface.Nonnegatives(4)
c1: v1pos + -1.0v1neg + v2pos + -1.0v2neg >= 1.0
c2: v1pos + -1.0v1neg + v2pos + -1.0v2neg <= 2.0
minobjective: v1pos + -1.0v1neg + v2pos + -1.0v2neg
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, var_names, ["nonneg", "c1", "c2"])
end
@testset "Bridged model" begin
var_names = ["v1", "v2"]
MOI.set(bridged_mock, MOI.VariableName(),
vis, var_names)
s = """
variables: v1, v2
c1: v1 + v2 >= 1.0
c2: v1 + v2 <= 2.0
minobjective: v1 + v2
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, var_names, ["c1", "c2"])
end

test_delete_bridged_variable(bridged_mock, vis[1], MOI.Reals, 2, (
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
), used_bridges = 0, used_constraints = 0)
test_delete_bridged_variable(bridged_mock, vis[2], MOI.Reals, 1, (
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
))
end