Search code examples
pythonpyomo

Expression with cumulative sum in pyomo


I'm new to Pyomo and I'm working with a model that needs cumulative sum for setting constraints. Since the cumulative sum is used multiple times in the model, I prefer to create an expression that store the cumulative sum result for later use. For example

import pyomo.environ as pyo

m = pyo.ConcreteModel()
m.set = pyo.RangeSet(1, 4)
m.x = pyo.Var(m.set, within=pyo.NonNegativeIntegers)

def cumsum(model, i):
    return sum([m.x[i] for i in m.set[:i]])
m.xc = pyo.Expression(m.set, rule=cumsum)

However, the m.xc throws a TypeError

ERROR: Rule failed for Expression 'xc' with index 1: TypeError: unhashable
    type: 'slice'
ERROR: Constructing component 'xc' from data=None failed: TypeError:
    unhashable type: 'slice'

How can I correct the code above?


Solution

  • To my knowledge, you cannot slice a pyomo.Set. It sounds odd as sets (in the general sense) are supposed to be unordered collections.... but by default, pyomo.Sets are ordered so you can get items out by index or use .first() type methods, etc, just no slicing (by my understanding).

    I think you are walking yourself into trouble here a bit on a couple points.

    First, you risk over-writing the python keyword set by using it as a variable here. Here it is just an instance variable inside the model object, but it is confusing. Perhaps that is just for the demo.

    Second, you have a unique (?) case here where the value in the set is also the index of interest for your slice, so you are leveraging the "dual meaning" of that set member, and giving it value outside the model, which tends to lead to structure issues.

    Because your set members have a natural ordering (counting numbers), I would just consider the set to be unordered and whip up a subset on the fly. I think it is a cleaner way to think about it.

    You might get a different answer from someone more familiar with the innards of pyomo.Set objects...

    import pyomo.environ as pyo
    
    m = pyo.ConcreteModel()
    m.my_set = pyo.RangeSet(1, 4)
    m.x = pyo.Var(m.my_set, within=pyo.NonNegativeIntegers)
    
    
    def cumsum(model, cutoff):
        subset = {t for t in m.my_set if t <= cutoff}
        return sum([m.x[i] for i in subset])
    m.xc = pyo.Expression(m.my_set, rule=cumsum)
    
    m.xc.pprint()
    

    Yields:

    xc : Size=4, Index=my_set
        Key : Expression
          1 : x[1]
          2 : x[1] + x[2]
          3 : x[1] + x[2] + x[3]
          4 : x[1] + x[2] + x[3] + x[4]