Search code examples
pythonpyomo

Conditional Constraint Assignment Over Indexed Variables in Pyomo


Below is an (over)simplified Pyomo model:

model.WEEKS = Set(initialize = [1,2,3], ordered = True)
model.PRODS = Set(initialize = ['Q24','J24','F24'], ordered = True)

model.volume = Var(model.WEEKS,model.PRODS, within = NonNegativeIntegers)

Which gives:

---
3 Set Declarations
    PRODS : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    3 : {'Q24', 'J24', 'F24'}
    WEEKS : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    3 : {1, 2, 3}
    volume_index : Size=1, Index=None, Ordered=True
        Key  : Dimen : Domain      : Size : Members
        None :     2 : WEEKS*PRODS :    9 : {(1, 'Q24'), (1, 'J24'), (1, 'F24'), (2, 'Q24'), (2, 'J24'), (2, 'F24'), (3, 'Q24'), (3, 'J24'), (3, 'F24')}

1 Var Declarations
    volume : Size=9, Index=volume_index
        Key        : Lower : Value : Upper : Fixed : Stale : Domain
        (1, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (1, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (1, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (2, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (2, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (2, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (3, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (3, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
        (3, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
---

I'm attempting to write a constraint that limits the sum volume per PROD over all WEEKS to i.e. 300. This is simple enough using:

def volMax_rule(model,j):
     return sum(model.volume[i,j] for i in model.WEEKS) <= 300
model.volMax_calc = Constraint(model.PRODS, rule = volMax_rule)

Which creates:

---
1 Constraint Declarations
    volMax_calc : Size=3, Index=PRODS, Active=True
        Key : Lower : Body                                          : Upper : Active
        F24 :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] : 300.0 :   True
        J24 :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] : 300.0 :   True
        Q24 :  -Inf : volume[1,Q24] + volume[2,Q24] + volume[3,Q24] : 300.0 :   True
---

However, the challenge I am facing is that I don't want Q24 to be summed up separately but rather summed within both J24 and F24. As in:

        Key : Lower : Body                                         : Upper : Active
        F :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24]: 300.0 :   True
        J :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24]: 300.0 :   True

---

I'm unsure how to achieve this. My guess is to perhaps create an additional parameter containing `['JAN', 'FEB'] for each week and then creating a conditional constraint to assign values? However, I'm pretty inexperienced and am probably off the mark. Some guidance would be appreciated please.

Many thanks!


Solution

  • You can do this a handful of different ways. 2 are shown below. As long as what you pass into the rule are legal indices for the variables used, it will construct. You can make any number of arbitrary subsets as they are frequently needed. Putting them "in the model" as model elements has some advantages in that they show when printing the model and T/S (good!) and you can add the within command to verify membership, etc. But in many cases, it isn't necessary.

    The first method uses set subtraction, If you had things in lists, you could pop them out, use a list/set comprehension, etc. etc.

    Code:

    import pyomo.environ as pyo
    
    model = pyo.ConcreteModel('example')
    
    model.WEEKS = pyo.Set(initialize = [1,2,3], ordered = True)
    model.PRODS = pyo.Set(initialize = ['Q24','J24','F24'], ordered = True)
    
    model.volume = pyo.Var(model.WEEKS,model.PRODS, within = pyo.NonNegativeIntegers)
    
    # just make a subset...  or any legal collection of index values
    
    # method 1:
    model.MY_SPECIAL_PRODS = pyo.Set(within=model.PRODS, initialize=model.PRODS - {'Q24',})
    
    def v_max_basic(model,j):
         return sum(model.volume[i,j] for i in model.WEEKS) <= 300
    model.C1a = pyo.Constraint(model.MY_SPECIAL_PRODS, rule = v_max_basic
    )
    
    # method 2:  you can just make one "on the fly" without putting it in the model
    model.C1b = pyo.Constraint(['J24', 'F24'], rule=v_max_basic)
    
    # making the full constraint.
    
    # method 1:  just write it and hard-code the unique index
    def v_max_rule_2(model, j):
         return sum(model.volume[i, j] for i in model.WEEKS) + sum(model.volume[i, 'Q24'] for i in model.WEEKS) <= 300
    model.C2a = pyo.Constraint(model.MY_SPECIAL_PRODS, rule=v_max_rule_2)
    
    
    # method 2:  or we can just make an arbitrary exrpession in terms of variables & constants and stuff it in the model for convenience...
    model.my_handy_expression = sum(model.volume[i, 'Q24'] for i in model.WEEKS)
    
    def v_max_rule_3(model, j):
         return sum(model.volume[i, j] for i in model.WEEKS) + model.my_handy_expression <= 300
    
    model.C2b = pyo.Constraint(model.MY_SPECIAL_PRODS, rule=v_max_rule_3)
    
    model.pprint()
    

    Output:

    5 Set Declarations
        C1b_index : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    2 : {'J24', 'F24'}
        MY_SPECIAL_PRODS : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :  PRODS :    2 : {'J24', 'F24'}
        PRODS : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {'Q24', 'J24', 'F24'}
        WEEKS : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {1, 2, 3}
        volume_index : Size=1, Index=None, Ordered=True
            Key  : Dimen : Domain      : Size : Members
            None :     2 : WEEKS*PRODS :    9 : {(1, 'Q24'), (1, 'J24'), (1, 'F24'), (2, 'Q24'), (2, 'J24'), (2, 'F24'), (3, 'Q24'), (3, 'J24'), (3, 'F24')}
    
    1 Var Declarations
        volume : Size=9, Index=volume_index
            Key        : Lower : Value : Upper : Fixed : Stale : Domain
            (1, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (1, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (1, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (2, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (2, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (2, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (3, 'F24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (3, 'J24') :     0 :  None :  None : False :  True : NonNegativeIntegers
            (3, 'Q24') :     0 :  None :  None : False :  True : NonNegativeIntegers
    
    4 Constraint Declarations
        C1a : Size=2, Index=MY_SPECIAL_PRODS, Active=True
            Key : Lower : Body                                          : Upper : Active
            F24 :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] : 300.0 :   True
            J24 :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] : 300.0 :   True
        C1b : Size=2, Index=C1b_index, Active=True
            Key : Lower : Body                                          : Upper : Active
            F24 :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] : 300.0 :   True
            J24 :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] : 300.0 :   True
        C2a : Size=2, Index=MY_SPECIAL_PRODS, Active=True
            Key : Lower : Body                                                                                          : Upper : Active
            F24 :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24] : 300.0 :   True
            J24 :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24] : 300.0 :   True
        C2b : Size=2, Index=MY_SPECIAL_PRODS, Active=True
            Key : Lower : Body                                                                                          : Upper : Active
            F24 :  -Inf : volume[1,F24] + volume[2,F24] + volume[3,F24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24] : 300.0 :   True
            J24 :  -Inf : volume[1,J24] + volume[2,J24] + volume[3,J24] + volume[1,Q24] + volume[2,Q24] + volume[3,Q24] : 300.0 :   True
    
    10 Declarations: WEEKS PRODS volume_index volume MY_SPECIAL_PRODS C1a C1b_index C1b C2a C2b