Skip to content

Commit

Permalink
Merge f53158e into f5a9fe9
Browse files Browse the repository at this point in the history
  • Loading branch information
blegat committed Aug 30, 2018
2 parents f5a9fe9 + f53158e commit 1346c0b
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/JuMP.jl
Expand Up @@ -331,7 +331,6 @@ function objective_sense(model::Model)
end

# TODO(IainNZ): Document these too.
# TODO(#1381): Implement Base.copy for Model.
object_dictionary(model::Model) = model.obj_dict
termination_status(model::Model) = MOI.get(model, MOI.TerminationStatus())
primal_status(model::Model) = MOI.get(model, MOI.PrimalStatus())
Expand Down Expand Up @@ -748,6 +747,7 @@ struct NonlinearParameter <: AbstractJuMPScalar
end

##########################################################################
include("copy.jl")
include("containers.jl")
include("operators.jl")
include("macros.jl")
Expand Down
180 changes: 180 additions & 0 deletions src/copy.jl
@@ -0,0 +1,180 @@
# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
copy_extension_data(data, new_model::AbstractModel, model::AbstractModel)
Return a copy of the extension data `data` of the model `model` to the extension
data of the new model `new_model`. A method should be added for any JuMP
extension storing data in the `ext` field.
"""
function copy_extension_data end

"""
copy_single_variable_constraints(dest::Dict{MOI.VariableIndex,
MOICON{MOI.SingleVariable, S}},
src::Dict{MOI.VariableIndex,
MOICON{MOI.SingleVariable, S}},
index_map) where S
Copy the single variable constraint indices of `src` into `dest` mapping
variable and constraint indices using `index_map`.
"""
function copy_single_variable_constraints(dest::Dict{MOI.VariableIndex,
MOICON{MOI.SingleVariable,
S}},
src::Dict{MOI.VariableIndex,
MOICON{MOI.SingleVariable,
S}},
index_map) where S
for (variable_index, constraint_index) in src
dest[index_map[variable_index]] = index_map[constraint_index]
end
end

"""
ReferenceMap
Mapping between variable and constraint reference of a model and its copy. The
reference of the copied model can be obtained by indexing the map with the
reference of the corresponding reference of the original model.
"""
struct ReferenceMap
model::Model
index_map::MOIU.IndexMap
end
function Base.getindex(reference_map::ReferenceMap, vref::VariableRef)
return VariableRef(reference_map.model,
reference_map.index_map[index(vref)])
end
function Base.getindex(reference_map::ReferenceMap, cref::ConstraintRef)
return ConstraintRef(reference_map.model,
reference_map.index_map[index(cref)],
cref.shape)
end
if VERSION >= v"0.7-"
Base.broadcastable(reference_map::ReferenceMap) = Ref(reference_map)
end


"""
copy_model(model::Model)
Return a copy of the model `model` and a [`ReferenceMap`](@ref) that can be used
to obtain the variable and constraint reference of the new model corresponding
to a given `model`'s reference. A [`Base.copy(::AbstractModel)`](@ref) method
has also been implemented, it is similar to `copy_model` but does not return
the reference map.
## Note
Model copy is not supported in Direct mode, i.e. when a model is constructed
using the [`direct_model`](@ref) constructor instead of the [`Model`](@ref)
constructor. Moreover, independently on whether an optimizer was provided at
model construction, the new model will have no optimizer, i.e., an optimizer
will have to be provided to the new model in the [`optimize!`](@ref) call.
## Examples
In the following example, a model `model` is constructed with a variable `x` and
a constraint `cref`. It is then copied into a model `new_model` with the new
references assigned to `x_new` and `cref_new`.
```julia
model = Model()
@variable(model, x)
@constraint(model, cref, x == 2)
new_model, reference_map = JuMP.copy_model(model)
x_new = reference_map[x]
cref_new = reference_map[cref]
```
"""
function copy_model(model::Model)
if mode(model) == Direct
error("Cannot copy a model in Direct mode. Use the `Model` constructor",
" instead of the `direct_model` constructor to be able to copy",
" the constructed model.")
end
caching_mode = caching_optimizer(model).mode
# TODO add bridges added to the bridge optimizer that are not part of the
# fullbridgeoptimizer
bridge_constraints = model.moi_backend isa MOI.Bridges.LazyBridgeOptimizer{<:MOIU.CachingOptimizer}
new_model = Model(caching_mode = caching_mode,
bridge_constraints = bridge_constraints)

# Copy the MOI backend, note that variable and constraint indices may have
# changed, the `index_map` gives the map between the indices of
# `model.moi_backend` and the indices of `new_model.moi_backend`.
index_map = MOI.copy!(new_model.moi_backend, model.moi_backend,
copynames = true)
# TODO copynames is needed because of https://github.com/JuliaOpt/MathOptInterface.jl/issues/494
# we can remove it when this is fixed and released

copy_single_variable_constraints(new_model.variable_to_lower_bound,
model.variable_to_lower_bound, index_map)
copy_single_variable_constraints(new_model.variable_to_upper_bound,
model.variable_to_upper_bound, index_map)
copy_single_variable_constraints(new_model.variable_to_fix,
model.variable_to_fix, index_map)
copy_single_variable_constraints(new_model.variable_to_integrality,
model.variable_to_integrality, index_map)
copy_single_variable_constraints(new_model.variable_to_zero_one,
model.variable_to_zero_one, index_map)

new_model.optimize_hook = model.optimize_hook

# TODO copy NLP data
if model.nlp_data !== nothing
error("copy is not supported yet for models with nonlinear constraints",
" and/or nonlinear objective function")
end

reference_map = ReferenceMap(new_model, index_map)

for (name, value) in object_dictionary(model)
new_model.obj_dict[name] = getindex.(reference_map, value)
end

for (key, data) in model.ext
new_model.ext[key] = copy_extension_data(data, new_model, model)
end

return new_model, reference_map
end

"""
copy(model::AbstractModel)
Return a copy of the model `model`. It is similar to [`copy_model`](@ref)
except that it does not return the mapping between the references of `model`
and its copy.
## Note
Model copy is not supported in Direct mode, i.e. when a model is constructed
using the [`direct_model`](@ref) constructor instead of the [`Model`](@ref)
constructor. Moreover, independently on whether an optimizer was provided at
model construction, the new model will have no optimizer, i.e., an optimizer
will have to be provided to the new model in the [`optimize!`](@ref) call.
## Examples
In the following example, a model `model` is constructed with a variable `x` and
a constraint `cref`. It is then copied into a model `new_model` with the new
references assigned to `x_new` and `cref_new`.
```julia
model = Model()
@variable(model, x)
@constraint(model, cref, x == 2)
new_model = copy(model)
x_new = model[:x]
cref_new = model[:cref]
```
"""
function Base.copy(model::AbstractModel)
new_model, _ = copy_model(model)
return new_model
end
2 changes: 1 addition & 1 deletion src/quadexpr.jl
Expand Up @@ -230,7 +230,7 @@ end
# variables to the new model's variables
function Base.copy(q::GenericQuadExpr, new_model::Model)
GenericQuadExpr(copy(q.qvars1, new_model), copy(q.qvars2, new_model),
copy(q.qcoeffs), copy(q.aff, new_model))
copy(q.qcoeffs), copy(q.aff, new_model))
end

# TODO: result_value for QuadExpr
Expand Down
75 changes: 75 additions & 0 deletions test/model.jl
Expand Up @@ -66,3 +66,78 @@ end
@test optimizer.a == 1
@test optimizer.b == 2
end

struct DummyExtensionData
model::JuMP.Model
end
function JuMP.copy_extension_data(data::DummyExtensionData,
new_model::JuMP.AbstractModel,
model::JuMP.AbstractModel)
@test data.model === model
return DummyExtensionData(new_model)
end
function dummy_optimizer_hook(::JuMP.AbstractModel) end

@testset "Model copy" begin
for copy_model in (true, true)
@testset "Using $(copy_model ? "JuMP.copy_model" : "Base.copy")" begin
for caching_mode in (MOIU.Automatic, MOIU.Manual)
@testset "In $caching_mode mode" begin
for bridge_constraints in (false, true)
model = Model(caching_mode = caching_mode,
bridge_constraints = bridge_constraints)
model.optimize_hook = dummy_optimizer_hook
data = DummyExtensionData(model)
model.ext[:dummy] = data
@variable(model, x 0, Bin)
@variable(model, y 1, Int)
@variable(model, z == 0)
@constraint(model, cref, x + y == 1)

if copy_model
new_model, reference_map = JuMP.copy_model(model)
else
new_model = copy(model)
reference_map = Dict{Union{JuMP.VariableRef,
JuMP.ConstraintRef},
Union{JuMP.VariableRef,
JuMP.ConstraintRef}}()
reference_map[x] = new_model[:x]
reference_map[y] = new_model[:y]
reference_map[z] = new_model[:z]
reference_map[cref] = new_model[:cref]
end
@test MOIU.mode(JuMP.caching_optimizer(new_model)) == caching_mode
@test bridge_constraints == (new_model.moi_backend isa MOI.Bridges.LazyBridgeOptimizer)
@test new_model.optimize_hook === dummy_optimizer_hook
@test new_model.ext[:dummy].model === new_model
x_new = reference_map[x]
@test x_new.m === new_model
@test JuMP.name(x_new) == "x"
y_new = reference_map[y]
@test y_new.m === new_model
@test JuMP.name(y_new) == "y"
z_new = reference_map[z]
@test z_new.m === new_model
@test JuMP.name(z_new) == "z"
if copy_model
@test JuMP.LowerBoundRef(x_new) == reference_map[JuMP.LowerBoundRef(x)]
@test JuMP.BinaryRef(x_new) == reference_map[JuMP.BinaryRef(x)]
@test JuMP.UpperBoundRef(y_new) == reference_map[JuMP.UpperBoundRef(y)]
@test JuMP.IntegerRef(y_new) == reference_map[JuMP.IntegerRef(y)]
@test JuMP.FixRef(z_new) == reference_map[JuMP.FixRef(z)]
end
cref_new = reference_map[cref]
@test cref_new.m === new_model
@test JuMP.name(cref_new) == "cref"
end
end
end
end
end
@testset "In Direct mode" begin
mock = MOIU.MockOptimizer(JuMP.JuMPMOIModel{Float64}())
model = JuMP.direct_model(mock)
@test_throws ErrorException JuMP.copy(model)
end
end

0 comments on commit 1346c0b

Please sign in to comment.