Mixed Integer Linear Programming with pyomo or PuLP - optimization

I am new to optimization programming using python and I have a problem with defining a variable in both pyomo and PuLP for a MILP problem. I am using gene expression data and I am confused about how to define a variable as a MXN matrix. I am trying to use either pyomo or PuLP. I know how to use MATLAB and define it so I am putting it make it more helpful to anyone to understand.
y = optimvar('y',M,N,'Type','integer','LowerBound',0,'UpperBound',1)
Also If you think there are better python package please give me your suggestion.
Thank you very much in advance !!

I'd pick pyomo. I think it is more expressive and easier to read/work with. You'll need to install a solver separately. If that is daunting, pulp comes with CBC built in.
This should get you started. There are a decent number of examples in the pyomo documentation to get you started.
import pyomo.environ as pe
m = pe.ConcreteModel('example')
m.M = pe.Set(initialize=[1, 2, 3], name='the M set')
m.N = pe.Set(initialize=['A', 'B', 'C'], name='the N set')
m.X = pe.Var(m.M, m.N, domain=pe.NonNegativeReals)
m.pprint()
Yields:
3 Set Declarations
M : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {1, 2, 3}
N : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {'A', 'B', 'C'}
X_index : Size=1, Index=None, Ordered=True
Key : Dimen : Domain : Size : Members
None : 2 : M*N : 9 : {(1, 'A'), (1, 'B'), (1, 'C'), (2, 'A'), (2, 'B'), (2, 'C'), (3, 'A'), (3, 'B'), (3, 'C')}
1 Var Declarations
X : Size=9, Index=X_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
(1, 'A') : 0 : None : None : False : True : NonNegativeReals
(1, 'B') : 0 : None : None : False : True : NonNegativeReals
(1, 'C') : 0 : None : None : False : True : NonNegativeReals
(2, 'A') : 0 : None : None : False : True : NonNegativeReals
(2, 'B') : 0 : None : None : False : True : NonNegativeReals
(2, 'C') : 0 : None : None : False : True : NonNegativeReals
(3, 'A') : 0 : None : None : False : True : NonNegativeReals
(3, 'B') : 0 : None : None : False : True : NonNegativeReals
(3, 'C') : 0 : None : None : False : True : NonNegativeReals
4 Declarations: M N X_index X
[Finished in 1.5s]
================
Edit: Making crossed sets
Some variation of below should work fine. You can either make a full x-set by initializing to the cross of two sets, or if your model is sparse, and it makes sense, I'd encourage you to use the within keyword to control the domain and only populated necessary pairs with initialize as shown.
import pyomo.environ as pe
m = pe.ConcreteModel('example')
m.M = pe.Set(initialize=[1, 2, 3], name='the M set')
m.N = pe.Set(initialize=['A', 'B', 'C'], name='the N set')
m.cross = pe.Set(within=m.M*m.N, initialize = [(1,'A'), (3,'B')])

Related

Dynamic lower/upper bound of Pyomo Variable for every iteration

I want to adjust the lower and upper bound of three pyo.Var() depending on the outcome of another variable.
the variable model.inverter_power should be in the i-th iteration bigger than model.inverter_power[i].lb + model.fcr_power[i] but also smaller than model.inverter_power[i].ub - model.fcr_power[i])
How can i implement this? Unfortuntately my idea is not working....
def fcr_inverter_reduction(model, i):
return (model.inverter_power[i] >= model.inverter_power[i].lb + model.fcr_power[i],
model.inverter_power[i] <= model.inverter_power[i].ub - model.fcr_power[i])
model.fcr_inverter_rule = pyo.Constraint(model.i, rule = fcr_inverter_reduction)
I tried various versions of this code, not only is this code not linear anymore, so I used ipopt as a solver but no solution can be found, i got this error message:
File "D:\.conda\envs\PythonEnviromentV2\lib\site-packages\pyomo\opt\base\solvers.py", line 596, in solve
raise ApplicationError(
pyomo.common.errors.ApplicationError: Solver (ipopt) did not exit normally
This is very doable if you reformulate just a bit. Also, I don't think it is possible to return a tuple of 2 constraints like you are doing with that function, so you should break it up... it is clearer as well.
You probably could access the upper/lower bound and use that within the constraint because they are fixed/constant with respect to the solver, but I think it is probably clearer to break out your min/max values as parameters. Some variation of this works.
(also, in the future, you are more likely to get better help/results if you post a fully minimal-reproducible example instead of just 1-line.)
Code:
import pyomo.environ as pyo
model = pyo.ConcreteModel()
model.I = pyo.Set(initialize=[1,2,3])
# power parameters...
model.min_inverter = pyo.Param(model.I, initialize={1:10, 2:15, 3:22})
model.max_inverter = pyo.Param(model.I, initialize={1:55, 2:45, 3:80})
# vars...
model.inverter_power = pyo.Var(model.I)
model.fcr_power = pyo.Var(model.I)
def fcr_inverter_min(model, i):
return model.inverter_power[i] >= model.min_inverter[i] + model.fcr_power[i]
model.fcr_inverter_rule_min = pyo.Constraint(model.I, rule=fcr_inverter_min)
def fcr_inverter_max(model, i):
return model.inverter_power[i] <= model.max_inverter[i] - model.fcr_power[i]
model.fcr_inverter_rule_max = pyo.Constraint(model.I, rule=fcr_inverter_max)
model.pprint()
Output:
1 Set Declarations
I : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {1, 2, 3}
2 Param Declarations
max_inverter : Size=3, Index=I, Domain=Any, Default=None, Mutable=False
Key : Value
1 : 55
2 : 45
3 : 80
min_inverter : Size=3, Index=I, Domain=Any, Default=None, Mutable=False
Key : Value
1 : 10
2 : 15
3 : 22
2 Var Declarations
fcr_power : Size=3, Index=I
Key : Lower : Value : Upper : Fixed : Stale : Domain
1 : None : None : None : False : True : Reals
2 : None : None : None : False : True : Reals
3 : None : None : None : False : True : Reals
inverter_power : Size=3, Index=I
Key : Lower : Value : Upper : Fixed : Stale : Domain
1 : None : None : None : False : True : Reals
2 : None : None : None : False : True : Reals
3 : None : None : None : False : True : Reals
2 Constraint Declarations
fcr_inverter_rule_max : Size=3, Index=I, Active=True
Key : Lower : Body : Upper : Active
1 : -Inf : inverter_power[1] - (55 - fcr_power[1]) : 0.0 : True
2 : -Inf : inverter_power[2] - (45 - fcr_power[2]) : 0.0 : True
3 : -Inf : inverter_power[3] - (80 - fcr_power[3]) : 0.0 : True
fcr_inverter_rule_min : Size=3, Index=I, Active=True
Key : Lower : Body : Upper : Active
1 : -Inf : 10 + fcr_power[1] - inverter_power[1] : 0.0 : True
2 : -Inf : 15 + fcr_power[2] - inverter_power[2] : 0.0 : True
3 : -Inf : 22 + fcr_power[3] - inverter_power[3] : 0.0 : True

How to extract variables that equal a certain value in pyomo?

I am building a linear optimization model and trying to extract key information from my decision variable values. instance.x.display() displays the following sample
x : Size=121, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
(1, 1) : 0 : None : 1 : False : True : Binary
(1, 2) : 0 : 0.0 : 1 : False : False : Binary
(1, 3) : 0 : 0.0 : 1 : False : False : Binary
(1, 4) : 0 : 1.0 : 1 : False : False : Binary
(1, 5) : 0 : 0.0 : 1 : False : False : Binary
(1, 6) : 0 : 0.0 : 1 : False : False : Binary
(1, 7) : 0 : 0.0 : 1 : False : False : Binary
(1, 8) : 0 : 0.0 : 1 : False : False : Binary
(1, 9) : 0 : 0.0 : 1 : False : False : Binary
(1, 10) : 0 : 0.0 : 1 : False : False : Binary
(1, 11) : 0 : 0.0 : 1 : False : False : Binary
(2, 1) : 0 : 0.0 : 1 : False : False : Binary
(2, 2) : 0 : None : 1 : False : True : Binary
(2, 3) : 0 : 0.0 : 1 : False : False : Binary
(2, 4) : 0 : 0.0 : 1 : False : False : Binary
(2, 5) : 0 : 0.0 : 1 : False : False : Binary
I want to extract the values that are equal to 1, such as x(1,4) which is equal to 1.
I have tried the following code: instance.x.display(value(model.x[i,j] == 1)) which gives me the error message ValueError: Error retrieving component x[11,11]: The component has not been constructed. I am thinking that this is because the value for this is 'None' just like x(1,1) and x(2,2) above.
Any ideas on how to code this to display something like this:
(1,4) -- 1
(2,3) -- 1
(3,5) -- 1
(4,2) -- 1
(5,4) -- 1
(6,7) -- 1
You can extract all values to a more user-friendly format by:
x_dic = x.get_values()
for i in x_dic.keys():
if r[i]==1:
print(i)
I am sure there is a nicer way but this should do the trick :)
Cheers

is pyomo constraint with multiple indexes of one variable possible?

Is there a way to set multiple indexes to a set value within one constraint without having to type out the same variable for each indexed time. I have provided an example, imagine that you want to optimize when to charge your electric vehicle but you don't want it to charge at certain hours in a day. The example below works to avoid charging at the 4th and 5th hour. However, what if I want it to not charge for 15 hours of the day but don't feel like writing m.EVcharge[0]+m.EVcharge[1]+... putting m.EVcharge[:15] == 0 will not work because the constraints don't handle slices that well in pyomo.
def time_rule(m):
return m.EVcharge[4]+m.EVcharge[5] == 0
m.time_rule = Constraint(time, rule=time_rule)
Yes. There are a variety of ways to do this. You could make a subset of m.time and only pass that subset in to the constraint rule, which would constrain them to zero, or sum across the subset and make constrain it to zero (both assume negative charging is not possible.)
Or, you could do it more cleanly with data or a parameter that holds the limit for any arbitrary time block and use that, which keeps the data separate from the model, which is generally a good idea...
import pyomo.environ as pyo
# some background data on limits...
use_limit = { 0:3, # limited juice avial
1:3,
2:0, # no juice avail. :)
3:0}
m = pyo.ConcreteModel('EV Charge')
m.T = pyo.Set(initialize=range(6))
m.EV_charge = pyo.Var(m.T, domain=pyo.NonNegativeReals)
# Constraints
def charge_limit(m, time):
return m.EV_charge[time] <= use_limit[time]
m.C1 = pyo.Constraint(use_limit.keys(), rule=charge_limit)
m.pprint()
Yields:
2 Set Declarations
C1_index : Size=1, Index=None, Ordered=False
Key : Dimen : Domain : Size : Members
None : 1 : Any : 4 : {0, 1, 2, 3}
T : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 6 : {0, 1, 2, 3, 4, 5}
1 Var Declarations
EV_charge : Size=6, Index=T
Key : Lower : Value : Upper : Fixed : Stale : Domain
0 : 0 : None : None : False : True : NonNegativeReals
1 : 0 : None : None : False : True : NonNegativeReals
2 : 0 : None : None : False : True : NonNegativeReals
3 : 0 : None : None : False : True : NonNegativeReals
4 : 0 : None : None : False : True : NonNegativeReals
5 : 0 : None : None : False : True : NonNegativeReals
1 Constraint Declarations
C1 : Size=4, Index=C1_index, Active=True
Key : Lower : Body : Upper : Active
0 : -Inf : EV_charge[0] : 3.0 : True
1 : -Inf : EV_charge[1] : 3.0 : True
2 : -Inf : EV_charge[2] : 0.0 : True
3 : -Inf : EV_charge[3] : 0.0 : True

Minimize cost based on purchased volume Pyomo

I'd like to find the optimal solution for buying goods from suppliers where the shipping cost is dependent on the cost of goods bought from given supplier. I'm using Pyomo. My code so far is:
model = ConcreteModel(name="(MN_2)")
# products
N = ['prod1', 'prod2', 'prod3']
# suppliers
M = ['A', 'B']
# price
p = {('prod1', 'A'): 10,
('prod2', 'A'): 9,
('prod3', 'A'): 50,
('prod1', 'B'): 16,
('prod2', 'B'): 20,
('prod3', 'B'): 35}
# user quantity contraint
q_u = {('prod1', 'A'): 2,
('prod2', 'A'): 1,
('prod3', 'A'): 1,
('prod1', 'B'): 1,
('prod2', 'B'): 1,
('prod3', 'B'): 1}
# seller quantity contraint
q_s = {('prod1', 'A'): 20,
('prod2', 'A'): 10,
('prod3', 'A'): 10,
('prod1', 'B'): 10,
('prod2', 'B'): 10,
('prod3', 'B'): 10}
# quantity of product n bough in shop m
model.x = Var(N, M, bounds=(0,10))
def obj_rule(model):
return sum(p[n,m]*model.x[n,m] for n in N for m in M)
model.obj = Objective(rule=obj_rule)
def user_quantity(model, n, m):
return model.x[n,m] >= q_u[n,m]
model.user_quantity = Constraint(N, M, rule=user_quantity)
def seller_quantity(model, n, m):
return model.x[n,m] <= q_s[n,m]
model.seller_quantity = Constraint(N, M, rule=seller_quantity)
solver = SolverFactory('glpk')
solver.solve(model)
model.x.pprint()
What I'm struggling with is how to include the shipping cost that is dependent on the cost of goods bought from given supplier. For example:
For supplier A: shipping cost is =
10 if the sum of costs of products bought from them is <= 100,
0 if the sum of costs of products bought from them is > 100
For supplier B: shipping cost is =
8 if the sum of costs of products bought from them is <= 150,
0 if the sum of costs of products bought from them is > 150
The constraints you're describing are an implementation of if-then condition which is described here. The quirk is that your conditions require the binary variable to be 1 if your procurement costs are less than or equal to some threshold rather than strictly less than the threshold. We can add a very small number (0.0001) to the threshold that doesn't affect adherence to the condition and allow us to use the new value in a strictly less than inequality.
To your initial model, you can add one new binary variable per seller (model.shipping_bin) and one constraint per binary variable that forces the binary variable to be 1 if the cost is less than the threshold and 0 otherwise. We can then multiply by the shipping cost in the objective function by these variables.
# add new binary variables to track costs per supplier
model.shipping_bin = Var(M,within = Binary)
shipping_costs = {'A':10,'B':8}
shipping_thresholds = {'A':100,'B':150} # threshold to meet to not incur shipping
# We need big M values to multiply the binaries to enforce the constraint without constraining the procurement cost incurred.
# We can set to the maximum amount we expect to procure from the seller
# The largest cost you could incur from each seller is
# the price times the max quantity
shipping_big_m = {seller: sum([p[(prod,seller)] * q_s[(prod,seller)] for prod in N])
for seller in M}
# add constraints
def shipping_bin_rule(model,seller):
# Sets shipping binary var to 1 if threshold not met
# Allows it to be 0 otherwise
# 790 * (model.shipping_bin['A']) >= 100.0001 + cost of products from seller 'A'
# if cost of products from 'A' < 100.0001 then binary variable = 1
# 710 * (model.shipping_bin['B']) >= 150.0001 + cost of products from seller 'B'
# if cost of products from 'B' < 150.0001 then binary variable = 1
epsilon = .0001 # to make sure case where cost == threshold is still accounted for
return(shipping_big_m[seller] * model.shipping_bin[seller] >= shipping_thresholds[seller] + epsilon - sum([p[(product,seller)] * model.x[product,seller]
for product in N]))
model.shipping_bin_con = Constraint(M,rule = shipping_bin_rule)
# new objective function adding the shipping cost
def obj_with_shipping_rule(model):
orig_cost = obj_rule(model) # call the original function, but can combine into one function if desired
# apply the shipping cost if cost of products is less than threshold (binary is 0)
shipping_cost = sum([shipping_costs[seller] * model.shipping_bin[seller]
for seller in M])
return(orig_cost + shipping_cost)
# deactivate the original objective to apply the new one
model.obj.deactivate()
model.obj_with_shipping = Objective(rule = obj_with_shipping_rule)
# solve the model with new obj
solver.solve(model)
model.x.pprint() # x values remain unchanged
# x : Size=6, Index=x_index
# Key : Lower : Value : Upper : Fixed : Stale : Domain
# ('prod1', 'A') : 0 : 2.0 : 10 : False : False : Reals
# ('prod1', 'B') : 0 : 1.0 : 10 : False : False : Reals
# ('prod2', 'A') : 0 : 1.0 : 10 : False : False : Reals
# ('prod2', 'B') : 0 : 1.0 : 10 : False : False : Reals
# ('prod3', 'A') : 0 : 1.0 : 10 : False : False : Reals
# ('prod3', 'B') : 0 : 1.0 : 10 : False : False : Reals
# cost from A = 2 * 10 + 1 * 9 + 1 * 50 = 79 < 100 so model.shipping_bin['A'] = 1
# cost from B = 1 * 16 + 1 * 20 + 1 * 35 = 71 < 150 so model.shipping_bin['B'] = 1
model.shipping_bin.pprint()
# shipping_bin : Size=2, Index=shipping_bin_index
# Key : Lower : Value : Upper : Fixed : Stale : Domain
# A : 0 : 1.0 : 1 : False : False : Binary
# B : 0 : 1.0 : 1 : False : False : Binary
value(model.obj_with_shipping) # 168 (18 units larger than original because of shipping)

Possibility of indexing decision variables with 2 indices using a set of tuples in Pyomo

I am currently attempting to solve a network problem that is not fully connected.Thus,I have attempted to do some preprocessing of data so as to form a set of tuples, e.g. {(a,b , (c,e)}..., i.e. from a to b, from c to e.
I am able to declare binary decision variables with keys such as (a,b), (c,e), via using the set of tuples for indexing.
However, when I tried to use rules to declare constraints, with decision variables such as x[i][j], errors are thrown stating that (a,b) is an invalid index.
Hence, I would like to ask if tuples can be used as indices for decision variables.
If not, is there a way to only declare the only decision variables that are needed, rather than declaring all, and then setting those unneeded to 0.
Thank you!
Welcome to the site.
You certainly CAN index variables with tuples of arbitrary dimensionality. The code I have below shows an example of indexing bad_X with a tuple of nodes. This makes sense when you have a variable that has a logical representation for all combinations of your indices or you control the indices somehow with smart set notation or you risk a whole bunch of nonsense variables as you can see in my example below with bad_X in the printout.
For your network, I would suggest just making a set of ARCS that are the valid combination of the NODES. This set would just contain the tuples of the valid connections. Note in my example below, the set of NODES could be blown away as it is not needed. You could just create ARCS directly. Sometimes it is handy to have both if you have some node-based data like supply/demand.
import pyomo.environ as pyo
mdl = pyo.ConcreteModel()
arc_data = { ('a', 'b'): 10,
('c', 'd'): 5,
('e', 'j'): 44,
('a', 'j'): 2,
('j', 'e'): 12}
# collapse the nodes into a set from the keys of the arc data
node_set = set()
for k in arc_data.keys():
node_set.update(*k)
# Sets
mdl.NODES = pyo.Set(initialize=node_set)
mdl.ARCS = pyo.Set(within=mdl.NODES * mdl.NODES, initialize=arc_data.keys())
# Params
mdl.cost = pyo.Param(mdl.NODES, mdl.NODES, initialize=arc_data)
# Selection Variable: poor choice...
mdl.bad_X = pyo.Var(mdl.NODES, mdl.NODES, domain=pyo.Binary)
# some examples of using this "bad X"
print('printing just to show how to access...this will produce a None')
print(mdl.bad_X['a', 'e'].value)
print(mdl.bad_X[('c', 'd')].value) # also valid
# Better selection variable
mdl.X = pyo.Var(mdl.ARCS, domain=pyo.Binary)
# toy constraint...ensure at least 3 are selected
mdl.c1 = pyo.Constraint(expr=sum(mdl.X[arc] for arc in mdl.ARCS) >= 3)
# Objective: Minimize cost of 3 selected arcs
mdl.OBJ = pyo.Objective(expr=sum(mdl.X[arc] * mdl.cost[arc] for arc in mdl.ARCS))
# to show the difference between the choices of variables
mdl.pprint()
# solve and show results
solver = pyo.SolverFactory('cbc')
results = solver.solve(mdl)
for arc in mdl.ARCS:
print(arc, mdl.X[arc].value)
Output:
printing just to show how to access...this will produce a None
None
None
5 Set Declarations
ARCS : Dim=0, Dimen=2, Size=5, Domain=ARCS_domain, Ordered=False, Bounds=None
[('a', 'b'), ('a', 'j'), ('c', 'd'), ('e', 'j'), ('j', 'e')]
ARCS_domain : Dim=0, Dimen=2, Size=36, Domain=None, Ordered=False, Bounds=None
Virtual
NODES : Dim=0, Dimen=1, Size=6, Domain=None, Ordered=False, Bounds=None
['a', 'b', 'c', 'd', 'e', 'j']
bad_X_index : Dim=0, Dimen=2, Size=36, Domain=None, Ordered=False, Bounds=None
Virtual
cost_index : Dim=0, Dimen=2, Size=36, Domain=None, Ordered=False, Bounds=None
Virtual
1 Param Declarations
cost : Size=5, Index=cost_index, Domain=Any, Default=None, Mutable=False
Key : Value
('a', 'b') : 10
('a', 'j') : 2
('c', 'd') : 5
('e', 'j') : 44
('j', 'e') : 12
2 Var Declarations
X : Size=5, Index=ARCS
Key : Lower : Value : Upper : Fixed : Stale : Domain
('a', 'b') : 0 : None : 1 : False : True : Binary
('a', 'j') : 0 : None : 1 : False : True : Binary
('c', 'd') : 0 : None : 1 : False : True : Binary
('e', 'j') : 0 : None : 1 : False : True : Binary
('j', 'e') : 0 : None : 1 : False : True : Binary
bad_X : Size=36, Index=bad_X_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('a', 'a') : 0 : None : 1 : False : True : Binary
('a', 'b') : 0 : None : 1 : False : True : Binary
('a', 'c') : 0 : None : 1 : False : True : Binary
('a', 'd') : 0 : None : 1 : False : True : Binary
('a', 'e') : 0 : None : 1 : False : True : Binary
('a', 'j') : 0 : None : 1 : False : True : Binary
('b', 'a') : 0 : None : 1 : False : True : Binary
('b', 'b') : 0 : None : 1 : False : True : Binary
('b', 'c') : 0 : None : 1 : False : True : Binary
('b', 'd') : 0 : None : 1 : False : True : Binary
('b', 'e') : 0 : None : 1 : False : True : Binary
('b', 'j') : 0 : None : 1 : False : True : Binary
('c', 'a') : 0 : None : 1 : False : True : Binary
('c', 'b') : 0 : None : 1 : False : True : Binary
('c', 'c') : 0 : None : 1 : False : True : Binary
('c', 'd') : 0 : None : 1 : False : True : Binary
('c', 'e') : 0 : None : 1 : False : True : Binary
('c', 'j') : 0 : None : 1 : False : True : Binary
('d', 'a') : 0 : None : 1 : False : True : Binary
('d', 'b') : 0 : None : 1 : False : True : Binary
('d', 'c') : 0 : None : 1 : False : True : Binary
('d', 'd') : 0 : None : 1 : False : True : Binary
('d', 'e') : 0 : None : 1 : False : True : Binary
('d', 'j') : 0 : None : 1 : False : True : Binary
('e', 'a') : 0 : None : 1 : False : True : Binary
('e', 'b') : 0 : None : 1 : False : True : Binary
('e', 'c') : 0 : None : 1 : False : True : Binary
('e', 'd') : 0 : None : 1 : False : True : Binary
('e', 'e') : 0 : None : 1 : False : True : Binary
('e', 'j') : 0 : None : 1 : False : True : Binary
('j', 'a') : 0 : None : 1 : False : True : Binary
('j', 'b') : 0 : None : 1 : False : True : Binary
('j', 'c') : 0 : None : 1 : False : True : Binary
('j', 'd') : 0 : None : 1 : False : True : Binary
('j', 'e') : 0 : None : 1 : False : True : Binary
('j', 'j') : 0 : None : 1 : False : True : Binary
1 Objective Declarations
OBJ : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : minimize : 10*X[a,b] + 5*X[c,d] + 44*X[e,j] + 2*X[a,j] + 12*X[j,e]
1 Constraint Declarations
c1 : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : 3.0 : X[a,b] + X[c,d] + X[e,j] + X[a,j] + X[j,e] : +Inf : True
10 Declarations: NODES ARCS_domain ARCS cost_index cost bad_X_index bad_X X c1 OBJ
('a', 'b') 1.0
('c', 'd') 1.0
('e', 'j') 0.0
('a', 'j') 1.0
('j', 'e') 0.0
[Finished in 4.3s]
Second answer to get at your second point about indexing in constraints...
This shows some variations of the concept you asked about. Note that because I built params for every possible combination of city-year, that I needed to supply a default value for the ones that were not in my sparse data.
import pyomo.environ as pyo
mdl = pyo.ConcreteModel()
demand_data = { ('LA', 1999): 500,
('LA', 2001): 700,
('NY', 2001): 600,
('Pheonix', 2000): 300,
('Pheonix', 2001): 400 }
my_favorites = {'LA'} # a subset for example use
# Sets
mdl.CITIES = pyo.Set(initialize= {k[0] for k in demand_data.keys()})
mdl.YEARS = pyo.Set(initialize= {k[1] for k in demand_data.keys()})
# Params
mdl.demand = pyo.Param(mdl.CITIES, mdl.YEARS, initialize=demand_data, default=0)
mdl.cost = pyo.Param(mdl.YEARS, initialize={1999: 5, 2000: 10, 2001: 15}, default=0)
# Variable for supply
mdl.supply = pyo.Var(mdl.CITIES, mdl.YEARS, domain=pyo.NonNegativeReals)
#### Constraints ####
#ensure 1/2 of demand in each city-year pair
def half_demand(self, city, year):
return mdl.supply[city, year] >= 0.5 * mdl.demand[city, year]
mdl.c1 = pyo.Constraint(mdl.CITIES, mdl.YEARS, rule=half_demand)
# ensure favorite cities get all demand every year
def all_demand(self, city, year):
return mdl.supply[city, year] >= mdl.demand[city, year]
mdl.c2 = pyo.Constraint(my_favorites, mdl.YEARS, rule=all_demand)
# ensure all demand is eventually met over all years
def eventually_met(self, city):
return sum(mdl.supply[city, year] for year in mdl.YEARS) >= \
sum(mdl.demand[city, year] for year in mdl.YEARS)
mdl.c3 = pyo.Constraint(mdl.CITIES, rule=eventually_met)
#### OBJECTIVE ####
def objective(self):
return sum(mdl.supply[city, year] * mdl.cost[year]
for year in mdl.YEARS
for city in mdl.CITIES)
mdl.OBJ = pyo.Objective(rule=objective)
# solve and show results
solver = pyo.SolverFactory('cbc')
results = solver.solve(mdl)
mdl.supply.display()
Output:
supply : Size=9, Index=supply_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('LA', 1999) : 0 : 500.0 : None : False : False : NonNegativeReals
('LA', 2000) : 0 : 0.0 : None : False : False : NonNegativeReals
('LA', 2001) : 0 : 700.0 : None : False : False : NonNegativeReals
('NY', 1999) : 0 : 300.0 : None : False : False : NonNegativeReals
('NY', 2000) : 0 : 0.0 : None : False : False : NonNegativeReals
('NY', 2001) : 0 : 300.0 : None : False : False : NonNegativeReals
('Pheonix', 1999) : 0 : 350.0 : None : False : False : NonNegativeReals
('Pheonix', 2000) : 0 : 150.0 : None : False : False : NonNegativeReals
('Pheonix', 2001) : 0 : 200.0 : None : False : False : NonNegativeReals
[Finished in 3.1s]
I think I manage to solve it, please refer to the example below
##First, create the set of tuples needed for filtering
#Op_Machine: set of (operation, machine) tuples created to avoid redundancy in decision variable declaration
Op_Machine=list()
for machine_id, op_proctime in Machine_Op_Time.items():
for op in op_proctime.keys():
print(Op_Machine)
print((op,machine_id))
Op_Machine.append((op,machine_id))
print(Op_Machine)
##Next, invoke the rule using the if statement to filter across all possible indices accepting those combinations that are aligned with the tuples within the set
##Use Constraint.Skip to Skip creating constraints that do not belong to the set of tuples
def F1_rule(model,i,k):
if (i,k) in Op_Machine:
##print(i,k)
return model.Cmax>=model.completion_time[i,k]
else:
return Constraint.Skip
#model.makespan= Constraint(model.op_set, model.mach_set, rule=Cmax_rule)
model.F1= Constraint(Operation_Set, Machine_Set, rule=F1_rule)
Note that the sets Operation_Set, Machine_Set function as the universal set as it comprises all combinations of operations and machines. Hence the statement model.F1= Constraint(Operation_Set, Machine_Set, rule=F1_rule) can be thought a for loop that iterates over all combinations while the if statement within the def function acts a filter to generate the needed constraints.