Skip to content

Commit

Permalink
Implement model copy
Browse files Browse the repository at this point in the history
  • Loading branch information
blegat committed Aug 25, 2018
1 parent e7bc047 commit f863416
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 36 deletions.
4 changes: 2 additions & 2 deletions src/JuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ function direct_model(backend::MOI.ModelLike)
Dict{Symbol, Any}())
end


if VERSION >= v"0.7-"
Base.broadcastable(model::Model) = Ref(model)
end
Expand Down Expand Up @@ -331,7 +332,6 @@ function objectivesense(model::Model)
end

# TODO(IainNZ): Document these too.
# TODO(#1381): Implement Base.copy for Model.
object_dictionary(model::Model) = model.objdict
terminationstatus(m::Model) = MOI.get(m, MOI.TerminationStatus())
primalstatus(m::Model) = MOI.get(m, MOI.PrimalStatus())
Expand Down Expand Up @@ -727,13 +727,13 @@ struct NonlinearParameter <: AbstractJuMPScalar
end

##########################################################################
include("copy.jl")
include("containers.jl")
include("operators.jl")
include("macros.jl")
include("optimizerinterface.jl")
include("nlp.jl")
include("print.jl")


##########################################################################
end
8 changes: 4 additions & 4 deletions src/affexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Base.zero(::Type{GenericAffExpr{C,V}}) where {C,V} = GenericAffExpr{C,V}(zero(C)
Base.one(::Type{GenericAffExpr{C,V}}) where {C,V} = GenericAffExpr{C,V}(one(C), OrderedDict{V,C}())
Base.zero(a::GenericAffExpr) = zero(typeof(a))
Base.one( a::GenericAffExpr) = one(typeof(a))
Base.copy(a::GenericAffExpr) = GenericAffExpr(copy(a.constant), copy(a.terms))
Base.copy(a::GenericAffExpr) = GenericAffExpr(Base.copy(a.constant), Base.copy(a.terms))
if VERSION >= v"0.7-"
Base.broadcastable(a::GenericAffExpr) = Ref(a)
end
Expand All @@ -92,7 +92,7 @@ function map_coefficients_inplace!(f::Function, a::GenericAffExpr)
end

function map_coefficients(f::Function, a::GenericAffExpr)
return map_coefficients_inplace!(f, copy(a))
return map_coefficients_inplace!(f, Base.copy(a))
end

Base.sizehint!(a::GenericAffExpr, n::Int) = sizehint!(a.terms, n)
Expand Down Expand Up @@ -192,7 +192,7 @@ end
Base.hash(aff::GenericAffExpr, h::UInt) = hash(aff.constant, hash(aff.terms, h))

function Compat.SparseArrays.dropzeros(aff::GenericAffExpr)
result = copy(aff)
result = Base.copy(aff)
for (coef, var) in linearterms(aff)
if iszero(coef)
delete!(result.terms, var)
Expand Down Expand Up @@ -309,7 +309,7 @@ end
function Base.copy(a::GenericAffExpr, new_model::Model)
result = zero(a)
for (coef, var) in linearterms(a)
add_to_expression!(result, coef, copy(var, new_model))
add_to_expression!(result, coef, Base.copy(var, new_model))
end
result.constant = a.constant
return result
Expand Down
126 changes: 126 additions & 0 deletions src/copy.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# 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_variablewise_constraints(dest::Dict{MOIVAR,
MOICON{MOI.SingleVariable, S}},
src::Dict{MOIVAR,
MOICON{MOI.SingleVariable, S}},
index_map) where S
Copy the variablewise constraint indices of `src` into `dest` mapping variable
and constraint indices using `index_map`.
"""
function copy_variablewise_constraints(dest::Dict{MOIVAR,
MOICON{MOI.SingleVariable, S}},
src::Dict{MOIVAR,
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

"""
copy(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.
## 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.
## 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)
x_new = reference_map[x]
cref_new = reference_map[cref]
```
"""
function copy(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.moibackend 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.moibackend` and the indices of `new_model.moibackend`.
index_map = MOI.copy!(new_model.moibackend, model.moibackend,
copynames = true)

copy_variablewise_constraints(new_model.variabletolowerbound,
model.variabletolowerbound, index_map)
copy_variablewise_constraints(new_model.variabletoupperbound,
model.variabletoupperbound, index_map)
copy_variablewise_constraints(new_model.variabletofix,
model.variabletofix, index_map)
copy_variablewise_constraints(new_model.variabletointegrality,
model.variabletointegrality, index_map)
copy_variablewise_constraints(new_model.variabletozeroone,
model.variabletozeroone, index_map)

new_model.optimizehook = model.optimizehook

# TODO copy NLP data
@assert model.nlpdata === nothing

# TODO copy objdict

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

return new_model, ReferenceMap(new_model, index_map)
end
4 changes: 2 additions & 2 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ end
include("parseexpr.jl")

function buildrefsets(expr::Expr, cname)
c = copy(expr)
c = Base.copy(expr)
idxvars = Any[]
idxsets = Any[]
# Creating an indexed set of refs
Expand Down Expand Up @@ -138,7 +138,7 @@ function getloopedcode(varname, code, condition, idxvars, idxsets, sym, requeste
@assert !hasdependentsets(idxvars, idxsets)

i, j = esc(idxvars[1]), esc(idxvars[2])
expr = copy(code)
expr = Base.copy(code)
vname = expr.args[1].args[1]
tmp = gensym()
expr.args[1] = tmp
Expand Down
38 changes: 19 additions & 19 deletions src/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Base.:-(lhs::Number, rhs::AbstractVariableRef) = GenericAffExpr(convert(Float64,
Base.:*(lhs::Number, rhs::AbstractVariableRef) = GenericAffExpr(0.0, rhs => convert(Float64,lhs))
# Number--GenericAffExpr
function Base.:+(lhs::Number, rhs::GenericAffExpr)
result = copy(rhs)
result = Base.copy(rhs)
result.constant += lhs
return result
end
Expand All @@ -37,7 +37,7 @@ function Base.:-(lhs::Number, rhs::GenericAffExpr)
end
Base.:*(lhs::Number, rhs::GenericAffExpr) = map_coefficients(c -> lhs * c, rhs)
# Number--QuadExpr
Base.:+(lhs::Number, rhs::GenericQuadExpr) = GenericQuadExpr(lhs+rhs.aff, copy(rhs.terms))
Base.:+(lhs::Number, rhs::GenericQuadExpr) = GenericQuadExpr(lhs+rhs.aff, Base.copy(rhs.terms))
function Base.:-(lhs::Number, rhs::GenericQuadExpr)
result = -rhs
result.aff.constant += lhs
Expand Down Expand Up @@ -99,7 +99,7 @@ function Base.:*(lhs::V, rhs::GenericAffExpr{C,V}) where {C, V <: AbstractVariab
end
Base.:/(lhs::AbstractVariableRef, rhs::GenericAffExpr) = error("Cannot divide a variable by an affine expression")
# AbstractVariableRef--GenericQuadExpr
Base.:+(v::AbstractVariableRef, q::GenericQuadExpr) = GenericQuadExpr(v+q.aff, copy(q.terms))
Base.:+(v::AbstractVariableRef, q::GenericQuadExpr) = GenericQuadExpr(v+q.aff, Base.copy(q.terms))
function Base.:-(v::AbstractVariableRef, q::GenericQuadExpr)
result = -q
# This makes an unnecessary copy of aff, but it's important for v to appear
Expand Down Expand Up @@ -130,10 +130,10 @@ end
Base.:^(lhs::Union{AbstractVariableRef,GenericAffExpr}, rhs::Number) = error("Only exponents of 0, 1, or 2 are currently supported. Are you trying to build a nonlinear problem? Make sure you use @NLconstraint/@NLobjective.")
# GenericAffExpr--AbstractVariableRef
function Base.:+(lhs::GenericAffExpr{C,V}, rhs::V) where {C, V <: AbstractVariableRef}
return add_to_expression!(copy(lhs), one(C), rhs)
return add_to_expression!(Base.copy(lhs), one(C), rhs)
end
function Base.:-(lhs::GenericAffExpr{C,V}, rhs::V) where {C, V <: AbstractVariableRef}
return add_to_expression!(copy(lhs), -one(C), rhs)
return add_to_expression!(Base.copy(lhs), -one(C), rhs)
end
# Don't fall back on AbstractVariableRef*GenericAffExpr to preserve lhs/rhs
# consistency (appears in printing).
Expand All @@ -156,15 +156,15 @@ function Base.:+(lhs::GenericAffExpr{C,V}, rhs::GenericAffExpr{C,V}) where {C,V<
operator_warn(owner_model(first(linearterms(lhs))[2]))
end
end
result_terms = copy(lhs.terms)
result_terms = Base.copy(lhs.terms)
# merge() returns a Dict(), so we need to call merge!() instead.
# Note: merge!() doesn't appear to call sizehint!(). Is this important?
merge!(+, result_terms, rhs.terms)
return GenericAffExpr(lhs.constant + rhs.constant, result_terms)
end

function Base.:-(lhs::GenericAffExpr{C,V}, rhs::GenericAffExpr{C,V}) where {C,V<:JuMPTypes}
result = copy(lhs)
result = Base.copy(lhs)
result.constant -= rhs.constant
sizehint!(result, length(linearterms(lhs)) + length(linearterms(rhs)))
for (coef, var) in linearterms(rhs)
Expand Down Expand Up @@ -216,7 +216,7 @@ function Base.:*(lhs::GenericAffExpr{C,V}, rhs::GenericAffExpr{C,V}) where {C,V<
return result
end
# GenericAffExpr--GenericQuadExpr
Base.:+(a::GenericAffExpr, q::GenericQuadExpr) = GenericQuadExpr(a+q.aff, copy(q.terms))
Base.:+(a::GenericAffExpr, q::GenericQuadExpr) = GenericQuadExpr(a+q.aff, Base.copy(q.terms))
function Base.:-(a::GenericAffExpr, q::GenericQuadExpr)
result = -q
# This makes an unnecessary copy of aff, but it's important for a to appear
Expand All @@ -234,18 +234,18 @@ Base.:-(lhs::GenericQuadExpr, rhs::Number) = (+)(-rhs,lhs)
Base.:*(lhs::GenericQuadExpr, rhs::Number) = (*)(rhs,lhs)
Base.:/(lhs::GenericQuadExpr, rhs::Number) = (*)(inv(rhs),lhs)
# GenericQuadExpr--AbstractVariableRef
Base.:+(q::GenericQuadExpr, v::AbstractVariableRef) = GenericQuadExpr(q.aff+v, copy(q.terms))
Base.:-(q::GenericQuadExpr, v::AbstractVariableRef) = GenericQuadExpr(q.aff-v, copy(q.terms))
Base.:+(q::GenericQuadExpr, v::AbstractVariableRef) = GenericQuadExpr(q.aff+v, Base.copy(q.terms))
Base.:-(q::GenericQuadExpr, v::AbstractVariableRef) = GenericQuadExpr(q.aff-v, Base.copy(q.terms))
Base.:*(q::GenericQuadExpr, v::AbstractVariableRef) = error("Cannot multiply a quadratic expression by a variable")
Base.:/(q::GenericQuadExpr, v::AbstractVariableRef) = error("Cannot divide a quadratic expression by a variable")
# GenericQuadExpr--GenericAffExpr
Base.:+(q::GenericQuadExpr, a::GenericAffExpr) = GenericQuadExpr(q.aff+a, copy(q.terms))
Base.:-(q::GenericQuadExpr, a::GenericAffExpr) = GenericQuadExpr(q.aff-a, copy(q.terms))
Base.:+(q::GenericQuadExpr, a::GenericAffExpr) = GenericQuadExpr(q.aff+a, Base.copy(q.terms))
Base.:-(q::GenericQuadExpr, a::GenericAffExpr) = GenericQuadExpr(q.aff-a, Base.copy(q.terms))
Base.:*(q::GenericQuadExpr, a::GenericAffExpr) = error("Cannot multiply a quadratic expression by an aff. expression")
Base.:/(q::GenericQuadExpr, a::GenericAffExpr) = error("Cannot divide a quadratic expression by an aff. expression")
# GenericQuadExpr--GenericQuadExpr
function Base.:+(q1::GenericQuadExpr, q2::GenericQuadExpr)
result = copy(q1)
result = Base.copy(q1)
for (coef, var1, var2) in quadterms(q2)
add_to_expression!(result, coef, var1, var2)
end
Expand All @@ -256,7 +256,7 @@ function Base.:+(q1::GenericQuadExpr, q2::GenericQuadExpr)
return result
end
function Base.:-(q1::GenericQuadExpr, q2::GenericQuadExpr)
result = copy(q1)
result = Base.copy(q1)
for (coef, var1, var2) in quadterms(q2)
add_to_expression!(result, -coef, var1, var2)
end
Expand Down Expand Up @@ -659,15 +659,15 @@ end

# Special-case sparse matrix scalar multiplication/division
Base.:*(lhs::Number, rhs::SparseMatrixCSC{T}) where {T<:JuMPTypes} =
SparseMatrixCSC(rhs.m, rhs.n, copy(rhs.colptr), copy(rhs.rowval), lhs .* rhs.nzval)
SparseMatrixCSC(rhs.m, rhs.n, Base.copy(rhs.colptr), Base.copy(rhs.rowval), lhs .* rhs.nzval)
Base.:*(lhs::JuMPTypes, rhs::SparseMatrixCSC) =
SparseMatrixCSC(rhs.m, rhs.n, copy(rhs.colptr), copy(rhs.rowval), lhs .* rhs.nzval)
SparseMatrixCSC(rhs.m, rhs.n, Base.copy(rhs.colptr), Base.copy(rhs.rowval), lhs .* rhs.nzval)
Base.:*(lhs::SparseMatrixCSC{T}, rhs::Number) where {T<:JuMPTypes} =
SparseMatrixCSC(lhs.m, lhs.n, copy(lhs.colptr), copy(lhs.rowval), lhs.nzval .* rhs)
SparseMatrixCSC(lhs.m, lhs.n, Base.copy(lhs.colptr), Base.copy(lhs.rowval), lhs.nzval .* rhs)
Base.:*(lhs::SparseMatrixCSC, rhs::JuMPTypes) =
SparseMatrixCSC(lhs.m, lhs.n, copy(lhs.colptr), copy(lhs.rowval), lhs.nzval .* rhs)
SparseMatrixCSC(lhs.m, lhs.n, Base.copy(lhs.colptr), Base.copy(lhs.rowval), lhs.nzval .* rhs)
Base.:/(lhs::SparseMatrixCSC{T}, rhs::Number) where {T<:JuMPTypes} =
SparseMatrixCSC(lhs.m, lhs.n, copy(lhs.colptr), copy(lhs.rowval), lhs.nzval ./ rhs)
SparseMatrixCSC(lhs.m, lhs.n, Base.copy(lhs.colptr), Base.copy(lhs.rowval), lhs.nzval ./ rhs)


for (op,opsymbol) in [(+,:+), (-,:-), (*,:*), (/,:/)]
Expand Down
8 changes: 4 additions & 4 deletions src/parseexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,11 @@ destructive_add!(ex, c, x) = ex .+ c * x

destructive_add_with_reorder!(ex, arg) = destructive_add!(ex, 1.0, arg)
# Special case because "Val{false}()" is used as the default empty expression.
destructive_add_with_reorder!(ex::Val{false}, arg) = copy(arg)
# Calling `copy` on the matrix will not copy the entries
destructive_add_with_reorder!(ex::Val{false}, arg::AbstractArray) = copy.(arg)
destructive_add_with_reorder!(ex::Val{false}, arg) = Base.copy(arg)
# Calling `Base.copy` on the matrix will not copy the entries
destructive_add_with_reorder!(ex::Val{false}, arg::AbstractArray) = Base.copy.(arg)
function destructive_add_with_reorder!(ex::Val{false}, arg::Symmetric)
Symmetric(copy.(arg))
Symmetric(Base.copy.(arg))
end
destructive_add_with_reorder!(ex::Val{false}, args...) = (*)(args...)

Expand Down
10 changes: 5 additions & 5 deletions src/quadexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function Base.one(::Type{GenericQuadExpr{C,V}}) where {C,V}
end
Base.zero(q::GenericQuadExpr) = zero(typeof(q))
Base.one(q::GenericQuadExpr) = one(typeof(q))
Base.copy(q::GenericQuadExpr) = GenericQuadExpr(copy(q.aff), copy(q.terms))
Base.copy(q::GenericQuadExpr) = GenericQuadExpr(Base.copy(q.aff), Base.copy(q.terms))
if VERSION >= v"0.7-"
Base.broadcastable(q::GenericQuadExpr) = Ref(q)
end
Expand All @@ -73,7 +73,7 @@ function map_coefficients_inplace!(f::Function, q::GenericQuadExpr)
end

function map_coefficients(f::Function, q::GenericQuadExpr)
return map_coefficients_inplace!(f, copy(q))
return map_coefficients_inplace!(f, Base.copy(q))
end

"""
Expand Down Expand Up @@ -154,7 +154,7 @@ end
Base.hash(quad::GenericQuadExpr, h::UInt) = hash(quad.aff, hash(quad.terms, h))

function Compat.SparseArrays.dropzeros(quad::GenericQuadExpr)
quad_terms = copy(quad.terms)
quad_terms = Base.copy(quad.terms)
for (key, value) in quad.terms
if iszero(value)
delete!(quad_terms, key)
Expand Down Expand Up @@ -229,8 +229,8 @@ end
# Copy a quadratic expression to a new model by converting all the
# 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))
GenericQuadExpr(Base.copy(q.qvars1, new_model), Base.copy(q.qvars2, new_model),
Base.copy(q.qcoeffs), Base.copy(q.aff, new_model))
end

# TODO: resultvalue for QuadExpr
Expand Down
Loading

0 comments on commit f863416

Please sign in to comment.