Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deleting constraint in order to modify it not possible due to name registered in model #1956

Closed
junglegobs opened this issue May 8, 2019 · 12 comments · Fixed by #2417
Closed
Assignees

Comments

@junglegobs
Copy link
Contributor

junglegobs commented May 8, 2019

I run an MILP several times with different parameter values. Instead of recreating the model every time, I would like to be able to just change the relevant constraints. From the documentation, I figured that I'd be able to do this by deleting the constraint and then reformulating it.

Here's a toy example:

using JuMP

model = Model(with_optimizer(Gurobi.Optimizer))
@variable(model, x[i=1:2,j=1:2])
@constraint(model, con[i=1:2,j=1:2], x[i,j] >= 1)
for i=1:2
    for j=1:2
        delete(model, con[i,j])
    end
end
@constraint(model, con[i=1:2,j=1:2], x[i,j] >= 2)

This produces the error:

ERROR: LoadError: An object of name con is already attached to this model. If this is intended, consider using the anonymous construction syntax, e.g., x = @variable(model, [1:N], ...) where the name of the object does not appear inside the macro.

One workaround I can see is to delete the name con in model, but I don't know how / cannot do this. Ideally I would like a simpler way of modifying constraints, for example as:

model = Model(with_optimizer(Gurobi.Optimizer))
@variable(model, x[i=1:2,j=1:2])
@constraint(model, con[i=1:2,j=1:2], x[i,j] >= 1)
@constraint(model, con[i=1:2,j=1:2], x[i,j] >= 2)

However it would appear that this is quite difficult judging from this issue: #1241

@odow
Copy link
Member

odow commented May 8, 2019

JuMP has functions to modify constraints directly. See set_lower_bound and set_upper_bound (http://www.juliaopt.org/JuMP.jl/v0.19.0/variables/#Variable-bounds-1) as well as constraint modification.

That said, it looks like you have found a bug. Once all of the constraints have been deleted, the name should probably be deleted as well.

@gsoleilhac
Copy link
Contributor

gsoleilhac commented May 12, 2019

It also seems to break copy :

model = Model()
@variable(model, x)
@constraint(model, constr, x >= 1)
delete(model, constr)
copy(model)

produces the error :


ERROR: KeyError: key MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.GreaterThan{Float64}}(1) not found
Stacktrace:
 [1] getindex at .\dict.jl:477 [inlined]
 [2] getindex at C:\Users\Gauthier\.julia\packages\MathOptInterface\C3lip\src\Utilities\copy.jl:74 [inlined]
 [3] getindex at C:\Users\Gauthier\.julia\packages\JuMP\jnmGG\src\copy.jl:49 [inlined]
 [4] _broadcast_getindex_evalf at .\broadcast.jl:625 [inlined]
 [5] _broadcast_getindex at .\broadcast.jl:598 [inlined]
 [6] getindex at .\broadcast.jl:558 [inlined]
 [7] copy at .\broadcast.jl:808 [inlined]
 [8] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{0},Nothing,typeof(getindex),Tuple{Base.RefValue{ReferenceMap},Base.RefValue{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.GreaterThan{Float64}},ScalarShape}}}}) at .\broadcast.jl:798
 [9] copy_model(::Model) at C:\Users\Gauthier\.julia\packages\JuMP\jnmGG\src\copy.jl:128
 [10] copy(::Model) at C:\Users\Gauthier\.julia\packages\JuMP\jnmGG\src\copy.jl:169
 [11] top-level scope at REPL[250]:1

@odow odow added the Type: Bug label May 12, 2019
@junglegobs
Copy link
Contributor Author

JuMP has functions to modify constraints directly. See set_lower_bound and set_upper_bound (http://www.juliaopt.org/JuMP.jl/v0.19.0/variables/#Variable-bounds-1) as well as constraint modification.

This doesn't quite achieve what I want, and setting a variable to be fixed turns my problem into a bilinear problem which is also an issue. My current solution is to just remake the entire model since speed is currently not an issue.

@odow
Copy link
Member

odow commented Jun 20, 2019

This doesn't quite achieve what I want

Why not? What part are you modifying?

@junglegobs
Copy link
Contributor Author

Why not? What part are you modifying?

I have power system expansion model (i.e. it tells you what technologies to invest in which meet electrical power demand while minimising operational and investment costs). I rerun my model every time with an increasing CO2 price, so I have a term similar to this:

costDueToCO2Emissions = generationInMW * fuelUseInMWhperMW * CO2EmissionsInTonnesPerMWH * CO2Price
OPEX = costDueToCO2Emissions + <other_terms>

If I want to change that CO2 price in a simpleish way, I could use the method showed here but you get a bilinear term.

I'm sure I could find a way around this, e.g. make costDueToCO2Emissions a JuMP variable (currently it's just an expression in the constraint which calculates my OPEX) and then use set_coefficient (though I'm not 100% sure that would work), but right now it's easier to just remake and solve my model since speed isn't an issue (it might be in the future though).

(Note that I use model and optimisation problem interchangeably)

@junglegobs
Copy link
Contributor Author

Would just like to bring this up again. I'm currently programming a progressive hedging algorithm and it would make my life easier if I could just redefine one constraint, reformulate my objective and then solve, instead of rebuilding my entire model every time.

@odow
Copy link
Member

odow commented Dec 12, 2019

There is really no need to keep rebuilding the model. This will kill the performance of you PH.

See https://www.juliaopt.org/JuMP.jl/v0.20.0/constraints/#Modifying-a-variable-coefficient-1

function build_model()
    CO2Price = 1
    model = Model()
    @variable(model, C02EmissionsInTonnesPerMWH >= 0)
    @variable(model, OPEX)
    @constraint(model, opex_constraint, OPEX == CO2Price * CO2EmissionsInTonnesPerMWH) 
    return model
end

function set_co2_price(model, price)
    # !! IMPORTANT !!
    # JuMP will normalize:
    #     OPEX == emissions * price 
    # to
    #     OPEX - price * emissions == 0,
    # moving all variables to the LHS, and all constants to the RHS.
    # set_normalized_coefficient sets the coefficient in the normalized constraint
    # so we need to set -price.
    set_normalized_coefficient(
        model[:opex_constraint], model[:CO2EmissionsInTonnesPerMWH], -price
    )
    return
end

model = build_model()
set_price(model, 2)
optimize!(model)
set_price(model, 3)
optimize!(model)

If you're implementing PH, see https://github.com/martinbiel/ProgressiveHedgingSolvers.jl. cc @martinbiel.

@mlubin
Copy link
Member

mlubin commented Dec 12, 2019

If you don't want to modify the coefficients for some reason, another workaround is to use anonymous constraints:

con = @constraint(model, [i=1:2,j=1:2], x[i,j] >= 1)
for i=1:2
    for j=1:2
        delete(model, con[i,j])
    end
end
con = @constraint(model, [i=1:2,j=1:2], x[i,j] >= 2)

Given the various workarounds, we haven't prioritized addressing this issue so far.

@junglegobs
Copy link
Contributor Author

junglegobs commented Dec 12, 2019

Given the various workarounds, we haven't prioritized addressing this issue so far.

Indeed there are a lot of workarounds, and I haven't implemented any (yet) due to sheer laziness. Still, I hope that eventually something similar to @NLparameter for linear problems will be introduced.

@timonviola
Copy link

timonviola commented Apr 29, 2020

Given the various workarounds, we haven't prioritized addressing this issue so far.

Indeed there are a lot of workarounds, and I haven't implemented any (yet) due to sheer laziness. Still, I hope that eventually something similar to @NLparameter for linear problems will be introduced.

I believe this bug is still present in v0.21.2
Thanks for the hint on @NLparameter. I iteratively change my constraints with set_value e.g.:

@NLparameter(pm.model,x_p[i = 1:N] == x_hat[i])                               # this is done only once
...
for i = 1:len
    x_hat = JuMP.lower_bound.(vars) + factor.*rand(rng, Float64, (N,1));     # new values
    JuMP.set_value.(x_p, x_hat) # iteratively update the values                    # update
    # optimize, etc.```

@davide-f
Copy link

davide-f commented Sep 25, 2020

Hello,

I wonder if there is any news or plan in addressing this issue.
Unlikely, the previous workarounds do not work in my case as my constraint is in the form like:
@constraint(model, cons, var <= parameter * (expression1 - constant))

however, the expression is too large and complex that setting all specific parameters to every variable would be so complex that it would be easier to rebuild the model.
On the other side, anonymous constraints do not apply, because I need to iteratively replace the constraint and to do so I would use functions, hence I loose the reference to the anonymous constraint.

Thank you,
Davide

@junglegobs
Copy link
Contributor Author

junglegobs commented Sep 26, 2020

You can still get around this. Good workflow tip: save your expressions, variables and constraints in model.ext, e.g.:

model.ext[:variables] = Dict()
model.ext[:expressions] = Dict()
model.ext[:constraints] = Dict()
model.ext[:constraints][:con1] = @constraint(model, ...)
delete!(model[:constraints][:con1])

Or something along those lines. That way if you pass your model to a function you can modify it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
7 participants