3
$\begingroup$

I am trying to solve a model with Pyomo and struggling with indexing. Below is a simple problem instance, where you can also see the error. The message is straightforward and self-explanatory but failed to resolve the issue. It stems from using the k_nearest_vehicles dictionary which is keyed by the items of the Riders list. I tried to use Xindex as solution but didn't quite work. Please let me know where I am doing wrong.

import pyomo.environ as pio
M_threshold = 30
Riders = [(1926.0, 0, 0)]
k_nearest_vehicles = {(1926.0, 0, 0): [(913.0, 0, 36), (913.0, 0, 37), (917.0, 0, 0)]}
zone_to_zone_tt = {(913.0, 1926.0): 27.523453,
                  (917.0, 1926.0): 29.937351}

m= pio.ConcreteModel('Transportation_Problem')
Xindex = [(i,j) for j in Riders for i in k_nearest_vehicles[j]]
m.x = pio.Var([i for i in k_nearest_vehicles[j] for j in Riders],
              [j for j in Riders],domain=pio.NonNegativeReals)
m.OBJ = pio.Objective(expr = (sum((zone_to_zone_tt[i[0],j[0]]-M_threshold)*m.x[i,j] 
                        for (i,j) in Xindex)), sense=pio.minimize)
def Cons1(m,i):
    return (sum(m.x[i,j] for j in Riders) <= 1)
m.AxbConstraint1 = pio.Constraint([i for i in k_nearest_vehicles[j] for j in Riders], rule=Cons1)

def Cons2(m,j):
    return (sum(m.x[i,j] for i in k_nearest_vehicles[j]) <= 1)
m.AxbConstraint2 = pio.Constraint(Riders, rule=Cons2)

opt = pio.SolverFactory()
results = opt.solve(m, tee=True)


ERROR: Rule failed when generating expression for constraint AxbConstraint1
    with index (913.0, 0, 36): TypeError: Cons1() takes 2 positional arguments
    but 4 were given
ERROR: Constructing component 'AxbConstraint1' from data=None failed:
    TypeError: Cons1() takes 2 positional arguments but 4 were given
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/misc.py in apply_indexed_rule(obj, rule, model, index, options)
     56             if index.__class__ is tuple:
---> 57                 return rule(model, *index)
     58             elif index is None and not obj.is_indexed():

TypeError: Cons1() takes 2 positional arguments but 4 were given

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/misc.py in apply_indexed_rule(obj, rule, model, index, options)
     71             if options is None:
---> 72                 return rule(model)
     73             else:

TypeError: Cons1() missing 1 required positional argument: 'i'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-62-353b262f79fa> in <module>
     14 def Cons1(m,i):
     15     return (sum(m.x[i,j] for j in Riders) <= 1)
---> 16 m.AxbConstraint1 = pio.Constraint([i for i in k_nearest_vehicles[j] for j in Riders], rule=Cons1)
     17 
     18 def Cons2(m,j):

~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/block.py in __setattr__(self, name, val)
    576                 # Pyomo components are added with the add_component method.
    577                 #
--> 578                 self.add_component(name, val)
    579             else:
    580                 #

~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/block.py in add_component(self, name, val)
   1129                              _blockName, str(data))
   1130             try:
-> 1131                 val.construct(data)
   1132             except:
   1133                 err = sys.exc_info()[1]

~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/constraint.py in construct(self, data)
    777                                              _init_rule,
    778                                              _self_parent,
--> 779                                              ndx)
    780                 except Exception:
    781                     err = sys.exc_info()[1]

~/anaconda3/lib/python3.7/site-packages/pyomo/core/base/misc.py in apply_indexed_rule(obj, rule, model, index, options)
     78             if options is None:
     79                 if index.__class__ is tuple:
---> 80                     return rule(model, *index)
     81                 elif index is None and not obj.is_indexed():
     82                     return rule(model)

TypeError: Cons1() takes 2 positional arguments but 4 were given

I also wanted to share the Gurobi way of modeling, which works like a champ. But, I am trying to re-write this in Pyomo to be able to use open source solvers.

from gurobipy import *
m = Model("My_problem")
x = {(i,j):m.addVar(vtype=GRB.CONTINUOUS, name= "x%s"%str([i,j]))
                    for j in Riders for i in k_nearest_vehicles[j]}
m.setObjective(quicksum((zone_to_zone_tt[i[0],j[0]]-M_threshold)*x[i,j] 
                        for (i,j) in x.keys()), GRB.MINIMIZE)
for i in Vehicles:
    m.addConstr(quicksum(x[i,j] for j in Riders if (i,j) in x.keys()) <= 1,
               name="each_vehicle_to_at_most_one_rider%s"%([i]))
for j in Riders:
    m.addConstr(quicksum(x[i,j] for i in Vehicles if (i,j) in x.keys()) <= 1,
               name="each_rider_to_at_most_one_vehicle%s"%([j]))
m.update()
m.optimize()
$\endgroup$
8
  • $\begingroup$ Hello and welcome to OR.SE, to solve your indexing problem I need to know whats the connection between Riders and k_nearest_vehicles. $\endgroup$ Jun 4, 2020 at 4:46
  • $\begingroup$ Sure, I have two lists: Riders and Vehicles. Based on a k-nearest logic, I create a dictionary called k_nearest_vehicles which is a subset of Vehicles for a given rider in Riders. Hence, k_nearest_vehicles includes a bunch of unique vehicle ids for a given index in Riders. $\endgroup$
    – tcokyasar
    Jun 4, 2020 at 4:55
  • $\begingroup$ so the combination of rider-vehicle is unique. right? $\endgroup$ Jun 4, 2020 at 4:56
  • $\begingroup$ so how many riders do you have in the your example problem? $\endgroup$ Jun 4, 2020 at 4:59
  • $\begingroup$ I can index the same vehicle for different riders, though my example involves only a single rider with an id of (1926.0, 0, 0). In this example, I have three vehicles with ids of (913.0, 0, 36), (913.0, 0, 37), (917.0, 0, 0). I understand the confusion. I wish I made an example with two riders. The second rider could have (917.0, 0, 0), which appears in the first rider, and some other vehicles. $\endgroup$
    – tcokyasar
    Jun 4, 2020 at 5:02

1 Answer 1

2
$\begingroup$

In Pyomo, indexes are sets and variables defined over those sets. In your problem, you need to define a set of all members of Riders and all members of k_nearest_vehicles. To define an index set for the combination of these two sets, in Pyomo you can indicate that the members of a set are restricted to be in the cross product of two other sets, you can use the within keyword:

model.combination = Set(within=m.Vehicles * m.Riders)

Also if you can preprocess (as you also mentioned) your driver and vehicles it will make your model easy to understand. The following is a simplified form of your problem (based on my understanding) which I could solve to optimality using Cplex and glpk.

import pyomo.environ as pio
M_threshold = 30
Riders = [1926.0]
k_nearest_vehicles = {1926.0: [913.0,917.0]}

zone_to_zone_tt = {(913.0, 1926.0): 27.523453, (917.0, 1926.0): 29.937351}

m = pio.ConcreteModel('Transportation_Problem')
m.Riders_ind = set(range(len(Riders)))
m.KNV_ind = set(range(len(k_nearest_vehicles[1926.0])))
m.x = pio.Var(m.KNV_ind,m.Riders_ind,domain=pio.NonNegativeReals)
m.OBJ = pio.Objective(expr = (sum((zone_to_zone_tt[k_nearest_vehicles[1926.0][i],Riders[j]]-M_threshold)*m.x[i,j] for i in m.KNV_ind for j in m.Riders_ind)),sense=pio.minimize)
def Cons1(m,i):
return (sum(m.x[i,j] for j in m.Riders_ind) <= 1)
m.AxbConstraint1 = pio.Constraint([i for i in m.KNV_ind for j in m.Riders_ind],rule=Cons1)

def Cons2(m,j):
return (sum(m.x[i,j] for i in m.KNV_ind) <= 1)
m.AxbConstraint2 = pio.Constraint(m.Riders_ind, rule=Cons2)

opt = pio.SolverFactory('cplex')
results = opt.solve(m, tee=True)
print(results)

and the results:

GLPSOL: GLPK LP/MIP Solver, v4.65
Parameter(s) specified in the command line:
--write C:\TEMP\tmpfm31ikz2.glpk.raw --wglp C:\TEMP\tmpi5vh_ads.glpk.glp
--cpxlp C:\TEMP\tmp4niztoc0.pyomo.lp
Reading problem data from 'C:\TEMP\tmp4niztoc0.pyomo.lp'...
4 rows, 3 columns, 5 non-zeros
29 lines were read
Writing problem data to 'C:\TEMP\tmpi5vh_ads.glpk.glp'...
21 lines were written
GLPK Simplex Optimizer, v4.65
4 rows, 3 columns, 5 non-zeros
Preprocessing...
1 row, 2 columns, 2 non-zeros
Scaling...
 A: min|aij| =  1.000e+00  max|aij| =  1.000e+00  ratio =  1.000e+00
Problem data seem to be well scaled
Constructing initial basis...
Size of triangular part is 1
*     0: obj =   0.000000000e+00 inf =   0.000e+00 (2)
*     2: obj =  -2.476547000e+00 inf =   0.000e+00 (0)
OPTIMAL LP SOLUTION FOUND
Time used:   0.0 secs
Memory used: 0.0 Mb (40400 bytes)
Writing basic solution to 'C:\TEMP\tmpfm31ikz2.glpk.raw'...
16 lines were written

Problem: 
- Name: unknown
  Lower bound: -2.476547
  Upper bound: -2.476547
  Number of objectives: 1
  Number of constraints: 4
  Number of variables: 3
  Number of nonzeros: 5
  Sense: minimize
Solver: 
- Status: ok
  Termination condition: optimal
  Statistics: 
    Branch and bound: 
      Number of bounded subproblems: 0
      Number of created subproblems: 0
  Error rc: 0
  Time: 0.2938816547393799
Solution: 
- number of solutions: 0
  number of solutions displayed: 0
$\endgroup$
5
  • $\begingroup$ Oguz, unfortunately, this is not a solution to my problem. I intentionally keep Riders = [(1926.0, 0, 0)] as a list of tuple(s) because that tuple is a unique id. I know that is what causes the issue as Pyomo treats each component of the tuple as a key when you feed it into the constraint. So, I cannot reduce (1926.0, 0, 0) to 1926. But, sure, I have to convert each of these unique ids into range(len(input)), populate the problem, solve it, and fetch the results back by mapping the ranges with my initial list conversion. Pyomo also handles lists and arrays as iterators. $\endgroup$
    – tcokyasar
    Jun 4, 2020 at 14:43
  • $\begingroup$ Shortly, I sort of knew the solution. But, the implementation was a pain in the ..., so I looked for a shortcut if possible. $\endgroup$
    – tcokyasar
    Jun 4, 2020 at 14:44
  • $\begingroup$ You are right. It sometimes takes lots of effort to preprocess model input data and post-process the result. But as you said there should be an easier way for that. Good luck in solving this issue. $\endgroup$ Jun 4, 2020 at 16:02
  • 1
    $\begingroup$ By mapping the ids into list of ranges, I took care of the issue. Indeed, pre-process was not necessary (as seen in Gurobi example) if Pyomo could accept tuples as indices. It is weird that it doesn't because strings and tuples are common indices in programming. $\endgroup$
    – tcokyasar
    Jun 4, 2020 at 17:54
  • $\begingroup$ Happy to hear that you could solve the problem. $\endgroup$ Jun 4, 2020 at 17:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.