Changing upper bounds and parameters repeatedly: reconstructed constraints vs. mutable parameters

781 views
Skip to first unread message

Cord Kaldemeyer

unread,
Nov 23, 2018, 5:35:23 AM11/23/18
to pyomo...@googlegroups.com
Hi everybody,

I am repeatedly changing upper bounds and other parameters on a model whereas the upper bounds and parameters are used in various constraints.

Now I am asking myself whether it is computationally faster and more elegant to work with either reconstructed constraints or mutable parameters. The first seems to be the more "official" way and allows to define "classic" bounds for variables without an extra constraint. The latter allows to define upper bounds as a single constraint and later the model does not have to be reconstructed.

Here's a minimal example that illiustrates both ways:

from pyomo.environ import (ConcreteModel, Set, Param, Var, Constraint,
                           
Objective, maximize)
from pyomo.opt import SolverFactory


# Data
T
= [1, 2]
C_el
= [20, 20]
P_inst
= [100, 100]
P_inst_new
= [200, 200]

# Model
m
= ConcreteModel()

# Sets
m
.T = Set(initialize=T)

# Variables
m
.P = Var(m.T)

# Params
m
.C_el = Param(m.T, initialize=dict(zip(T, C_el)))
m
.P_inst = Param(m.T, initialize=dict(zip(T, P_inst)), mutable=True)


def inst_rule(model, t):
   
return(m.P[t] <= m.P_inst[t])
m
.inst = Constraint(m.T, rule=inst_rule)


# Objective rules
def obj_rule(model):
   
return(sum(m.C_el[t]*m.P[t] for t in m.T))
m
.profit = Objective(sense=maximize, rule=obj_rule)


# # ### FIRST WAY: Change upper bound as "real" upper bound
#
# # Change bound
# # This could of course be realized when instantiating the variable
# # but it is realized here manually to compare both ways
# for t in range(0, max(T)):
#     m.P[t+1].setlb(0)
#     m.P[t+1].setub(P_inst[t])
# m.inst.reconstruct()
#
# # Print and write model
# m.pprint()
# m.write('model.lp')
#
# # Change bound
# for t in range(0, max(T)):
#     m.P[t+1].setub(P_inst_new[t])
# m.inst.reconstruct()
#
# # Print and write new model
# m.pprint()
# m.write('model_new.lp')

### SECOND WAY: Change upper bound as mutable parameter

# Print and write model
m
.pprint()
m
.write('model.lp')

# Change bound
for t in range(0, max(T)):
    m
.P_inst[t+1] = P_inst_new[t]

# Print and write new model
m
.pprint()
m
.write('model_new.lp')

First time measurements on my small example show that the first way is faster. But assuming a much bigger model, I presume the second option to be faster and also like to have less code due to the obsolete constraint reconstruction.

What would be the best way to do this? Does anyone have experiences in this direction or can even provide a smarter way to achieve what I want to do?

Thanks in advance!
Cord

Cord Kaldemeyer

unread,
Nov 23, 2018, 7:29:22 AM11/23/18
to pyomo...@googlegroups.com
I forgot to mention that my question might be a bit misleading since the example only focuses on setting bounds. The more important thing is that constraints in which these bounds are used as a parameter are updated e.g. everything in the model that depends on the bounds has to be changed as well.

Thanks in advance!

Michael Bynum

unread,
Nov 23, 2018, 8:48:44 AM11/23/18
to Pyomo Forum
  1. I don't completely understand your example because changing the bounds on a variable will not update any constraints.
  2. I would simply use a mutable param anywhere it is needed and update the value of that mutable param. I don't think there is a faster way to do this.
  3. I put together a minimal example that does what I think you are trying to do.
  4. If you are really concerned about performance with repeated solves (and you are using either gurobi or cplex), you may want to consider the persistent solver interfaces (https://pyomo.readthedocs.io/en/latest/advanced_topics/persistent_solvers.html). These do add complexity, but they can significantly improve performance.

import pyomo.environ as pe


m = pe.ConcreteModel()

m.x = pe.Var()

m.y = pe.Var()

m.z = pe.Var()


m.xl = pe.Param(initialize=-1.0, mutable=True)

m.xu = pe.Param(initialize=1.0, mutable=True)

m.yl = pe.Param(initialize=-1.0, mutable=True)

m.yu = pe.Param(initialize=1.0, mutable=True)


m.x.setlb(m.xl)

m.x.setub(m.xu)

m.y.setlb(m.yl)

m.y.setub(m.yu)


m.objective = pe.Objective(expr=m.z)

m.c1 = pe.Constraint(expr=m.z >= m.xl * m.y + m.x * m.yl - m.xl * m.yl)

m.c2 = pe.Constraint(expr=m.z >= m.xu * m.y + m.x * m.yu - m.xu * m.yu)


opt = pe.SolverFactory('gurobi')

res = opt.solve(m)

print(pe.value(m.z))  # -1                                                                                                            


m.xl.value = -0.5

m.xu.value = 0.5

m.yl.value = 0.5

m.yu.value = 1.0


res = opt.solve(m)

print(pe.value(m.z))  # -0.5          


I realize that I did not explicitly show that the constraints and variable bounds are being updated properly, but they are. Feel free to play around with the example to verify for yourself. 

Michael

Cord Kaldemeyer

unread,
Nov 23, 2018, 9:15:46 AM11/23/18
to Pyomo Forum
Hi Michael,

thanks for your answer!
1. I don't completely understand your example because changing the bounds on a variable will not update any constraints.
That was my fault because I did not make clear that I want to "reference" the upper bound in some constraint e.g. by saying "m.some_var <= m.some_other_var.ub". If I set the upper bound for "m.some_other_var", build the constraint and set another bound for "m.some_other_var" the constraint has to be reconstructed in order to use the new upper bound. This is basically what I was trying to explain with my example which obviously has failed..
2. I would simply use a mutable param anywhere it is needed and update the value of that mutable param. I don't think there is a faster way to do this.
That was also my assumption. Thanks!
3. I put together a minimal example that does what I think you are trying to do.
 This makes things quite clear!
4. If you are really concerned about performance with repeated solves (and you are using either gurobi or cplex), you may want to consider the persistent solver interfaces (https://pyomo.readthedocs.io/en/latest/advanced_topics/persistent_solvers.html). These do add complexity, but they can significantly improve performance.
I have tested these but in my case could not state a significant increase in speed. But anyway, I want to remain solver-independent and thus use the lp-interface.

Thanks a lot for your help and have a nice weekend!

Cheers,
Cord
Reply all
Reply to author
Forward
0 new messages