Search code examples
pythonoptimizationlinear-programmingpyomogurobi

How to operate on a multi-index object in pyomo accessing only one of its indexes?


I would like to know how to operate over pyomo multiple index objects just by employing one of the index.

This question may be a bit stupid, but I have not found a possible solution. In my model, the decision variable (x) is the surface of each crop (j) in each year (t). Therefore, x is a multi-index variable:

model.x = pyomo.Var(model.crop, model.t, domain = pyomo.NonNegativeIntegers)

Eg, model.x should be indexed like model.x[crop, year]

I want to create two resources constraints that limit the cropping area and the water consumption (which has only the j index) to the established maximum per year. Something like the following code:

def area_constr(model, year):
    return sum(model.x[crop, year] for crop in model.crop for year in model.t) <=\
          model.total_cropping_area_n[year] 
model.area_constr = pyomo.Constraint(model.a, model.n, rule = area_constr, 
doc = 'Restricción de superficie disponible')
def water_constr(model, crop, year):
return sum(model.x\[crop, year\] \* model.crop_water_demand\[crop\]
for crop in model.a for year in model.n) \<= total_water_endowment\[year\]
model.water_constr = pyomo.Constraint(model.a, model.n, rule = water_constr)

I'm not able to make it work since I obtained the following message:

KeyError: "Index '0' is not valid for indexed component 'x'"

for the area constraint and f and for the water constraint add up the water consumption of all crops in all years, not year by year.

How can I operate using just one index? or is there another solution?


Solution

  • The problem with your area_constr is that while you are correctly passing in the year value to the rule because you want to make an area constraint for each year you are overriding that by supplying the variable year again inside of the summation.

    Here is an example that shows what I think you intend. Realize that in each of the 2 constraints, you should be using the values that are passed in as function parameters as shown.

    If your area limits change by year then you could (and should) index that data with (crop, year) tuples in the dictionary and index the corresponding parameter the same way.

    Code:

    import pyomo.environ as pyo
    
    ### DATA
    
    years = [2023, 2024, 2025]
    
    water_demand = {    'corn': 1.5,
                        'rice': 2.3,
                        'wheat': 1.1}
    
    water_limit = {     2023: 10.1,
                        2024: 12.3,
                        2025: 9.8}
    
    area_limit = {      'corn': 10_000,
                        'rice': 12_500,
                        'wheat': 8_800}
    
    ### MODEL
    
    m = pyo.ConcreteModel('crops')
    
    # SETS
    m.Y = pyo.Set(initialize=years, doc='year')
    m.C = pyo.Set(initialize=water_demand.keys(), doc='crop')
    
    # PARAMS
    m.demand     = pyo.Param(m.C, initialize=water_demand)
    m.limit      = pyo.Param(m.Y, initialize=water_limit)
    m.area_limit = pyo.Param(m.C, initialize=area_limit)
    
    # VARS
    m.plant = pyo.Var(m.C, m.Y, domain=pyo.NonNegativeReals, doc='amount to plant of crop c in year y')
    
    # CONSTRAINTS
    
    # 1.  Don't bust the water limit FOR EVERY YEAR
    def water_lim_constraint(m, y):
        # the "y" in this comes from the function params, we just need to supply "c" internally
        return sum(m.plant[c, y]*water_demand[c] for c in m.C) <= m.limit[y]
    m.C1 = pyo.Constraint(m.Y, rule=water_lim_constraint, doc='total water limit by year')
    
    # 2.  Don't overplant FOR EVERY CROP, FOR EVERY YEAR
    def plant_limit(m, c, y):
        # in this constraint, the limit is applied for every year and crop, which
        # are supplied as function arguments
        return m.plant[c, y] <= m.area_limit[c]
    m.C2 = pyo.Constraint(m.C, m.Y, rule=plant_limit, doc='area planting limit by crop and year')
    
    m.pprint()
    

    Output:

    4 Set Declarations
        C : crop
            Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {'corn', 'rice', 'wheat'}
        C2_index : Size=1, Index=None, Ordered=True
            Key  : Dimen : Domain : Size : Members
            None :     2 :    C*Y :    9 : {('corn', 2023), ('corn', 2024), ('corn', 2025), ('rice', 2023), ('rice', 2024), ('rice', 2025), ('wheat', 2023), ('wheat', 2024), ('wheat', 2025)}
        Y : year
            Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {2023, 2024, 2025}
        plant_index : Size=1, Index=None, Ordered=True
            Key  : Dimen : Domain : Size : Members
            None :     2 :    C*Y :    9 : {('corn', 2023), ('corn', 2024), ('corn', 2025), ('rice', 2023), ('rice', 2024), ('rice', 2025), ('wheat', 2023), ('wheat', 2024), ('wheat', 2025)}
    
    3 Param Declarations
        area_limit : Size=3, Index=C, Domain=Any, Default=None, Mutable=False
            Key   : Value
             corn : 10000
             rice : 12500
            wheat :  8800
        demand : Size=3, Index=C, Domain=Any, Default=None, Mutable=False
            Key   : Value
             corn :   1.5
             rice :   2.3
            wheat :   1.1
        limit : Size=3, Index=Y, Domain=Any, Default=None, Mutable=False
            Key  : Value
            2023 :  10.1
            2024 :  12.3
            2025 :   9.8
    
    1 Var Declarations
        plant : amount to plant of crop c in year y
            Size=9, Index=plant_index
            Key             : Lower : Value : Upper : Fixed : Stale : Domain
             ('corn', 2023) :     0 :  None :  None : False :  True : NonNegativeReals
             ('corn', 2024) :     0 :  None :  None : False :  True : NonNegativeReals
             ('corn', 2025) :     0 :  None :  None : False :  True : NonNegativeReals
             ('rice', 2023) :     0 :  None :  None : False :  True : NonNegativeReals
             ('rice', 2024) :     0 :  None :  None : False :  True : NonNegativeReals
             ('rice', 2025) :     0 :  None :  None : False :  True : NonNegativeReals
            ('wheat', 2023) :     0 :  None :  None : False :  True : NonNegativeReals
            ('wheat', 2024) :     0 :  None :  None : False :  True : NonNegativeReals
            ('wheat', 2025) :     0 :  None :  None : False :  True : NonNegativeReals
    
    2 Constraint Declarations
        C1 : total water limit by year
            Size=3, Index=Y, Active=True
            Key  : Lower : Body                                                                : Upper : Active
            2023 :  -Inf : 1.5*plant[corn,2023] + 2.3*plant[rice,2023] + 1.1*plant[wheat,2023] :  10.1 :   True
            2024 :  -Inf : 1.5*plant[corn,2024] + 2.3*plant[rice,2024] + 1.1*plant[wheat,2024] :  12.3 :   True
            2025 :  -Inf : 1.5*plant[corn,2025] + 2.3*plant[rice,2025] + 1.1*plant[wheat,2025] :   9.8 :   True
        C2 : area planting limit by crop and year
            Size=9, Index=C2_index, Active=True
            Key             : Lower : Body              : Upper   : Active
             ('corn', 2023) :  -Inf :  plant[corn,2023] : 10000.0 :   True
             ('corn', 2024) :  -Inf :  plant[corn,2024] : 10000.0 :   True
             ('corn', 2025) :  -Inf :  plant[corn,2025] : 10000.0 :   True
             ('rice', 2023) :  -Inf :  plant[rice,2023] : 12500.0 :   True
             ('rice', 2024) :  -Inf :  plant[rice,2024] : 12500.0 :   True
             ('rice', 2025) :  -Inf :  plant[rice,2025] : 12500.0 :   True
            ('wheat', 2023) :  -Inf : plant[wheat,2023] :  8800.0 :   True
            ('wheat', 2024) :  -Inf : plant[wheat,2024] :  8800.0 :   True
            ('wheat', 2025) :  -Inf : plant[wheat,2025] :  8800.0 :   True
    
    10 Declarations: Y C demand limit area_limit plant_index plant C1 C2_index C2
    [Finished in 267ms]