pythonoptimizationquery-optimizationpyomo

Using discontinuous indices for pyomo optimization


I am using pyomo for optimization in python and I plan to use indices in the range (t_start,t_end). Now these t_start and t_end values are a function of EV such that:

m = pyo.ConcreteModel()
m.EV = pyo.RangeSet(0,len(chargedata))
m.t = pyo.RangeSet(round(100*chargedata['t_start'][m.EV]),round(100*chargedata['t_end'][m.EV]))

Hence the m.t should be:

m.t = [[t_start[EV1],t_end[EV1]],[t_start[EV2],t_end[EV2]],.....]

and I want to use these time values for summing up a function. However, I am receiving the following error:

Constructing component 't' from data=None failed: ValueError: The
    truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(),
    a.any() or a.all().

How do I solve it? Thanks in advance for your help :)


Solution

  • What you are looking for is an "indexed set" in pyomo. This is a set that is indexed by another set. You appear to be looking for a set of time periods for each EV. So I'd set it up as below. Note the first parameter in the construction is the set that the other set should be indexed by. When you want to use a set of times within m.EV_T you have to access it by the EV index.

    Depending on your constraints, you will almost certainly want to have a set of "all available time periods" for various purposes, so I included that too.

    There are several ways to "ingest" this data depending on what you are starting with, a dictionary is shown, but it could be a .csv file, data frame, etc.

    Code

    import pyomo.environ as pyo
    from itertools import chain
    
    data = {'Tesla':  [2, 3, 4, 5, 6, 7],
            'Bolt' :  [4, 5, 6],
            'Rivian': [13, 14, 15]}
    times = set(chain(*data.values()))
    all_times = list(range(min(times), max(times)+1))
    
    m = pyo.ConcreteModel()
    
    m.EV =   pyo.Set(initialize=data.keys())
    m.T =    pyo.Set(initialize=all_times)
    
    m.EV_T = pyo.Set(m.EV, initialize=data)
    
    m.pprint()
    

    Output

    3 Set Declarations
        EV : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {'Tesla', 'Bolt', 'Rivian'}
        EV_T : Size=3, Index=EV, Ordered=Insertion
            Key    : Dimen : Domain : Size : Members
              Bolt :     1 :    Any :    3 : {4, 5, 6}
            Rivian :     1 :    Any :    3 : {13, 14, 15}
             Tesla :     1 :    Any :    6 : {2, 3, 4, 5, 6, 7}
        T : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :   14 : {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
    

    Edit

    Below is an enhanced model that shows use of the indexed set. As you discovered, you cannot use the indexed set to index a variable (to my knowledge). You need to "flatten" out the tree set to all pairs and use that for a variable to cover all of the possible combinations. Of note, you could also just index your variable in the full "cross set" of m.EV * m.T, but that is just adding bloat if the combination is sparse.

    Anyhow, here is an extension which shows the construction of a convenience "flat set" and a couple of constraints that use this

    Code:

    import pyomo.environ as pyo
    from itertools import chain
    
    data = {'Tesla':  [2, 3, 4, 5, 6, 7],
            'Bolt' :  [4, 5, 6],
            'Rivian': [13, 14, 15]}
    times = set(chain(*data.values()))
    all_times = list(range(min(times), max(times)+1))
    
    m = pyo.ConcreteModel()
    
    # SETS
    m.EV =   pyo.Set(initialize=data.keys())
    m.T =    pyo.Set(initialize=all_times)
    
    # an indexed set...  index is EV
    m.EV_T = pyo.Set(m.EV, initialize=data)
    
    # make a "flat" set of the tree set in m.EV_T
    m.EV_T_flat = pyo.Set(within=m.EV * m.T, initialize={(k,v) for k in data.keys() for v in data.get(k)})
    
    # VARS
    m.charge = pyo.Var(m.EV_T_flat)
    
    # CONSTRAINTS
    
    # each EV can only receive 10 charge units
    @m.Constraint(m.EV)
    def vehicle_limit(m, ev):
        return sum(m.charge[ev, t] for t in m.EV_T[ev]) <= 10
    
    # the charger can only deliver 5 charge units per time period
    @m.Constraint(m.T)
    def charger_capacity(m, t):
        elig_vehicles = {(ev, tt) for (ev, tt) in m.EV_T_flat if tt==t}
        # if no elig vehicles, return Skip
        if not elig_vehicles:
            return pyo.Constraint.Skip
        return sum(m.charge[ev, t] for ev in m.EV if (ev, t) in m.EV_T_flat) <= 5
    
    # OBJ
    # maximize charge
    m.obj = pyo.Objective(expr=sum(m.charge[ev, t] for (ev, t) in m.EV_T_flat), sense=pyo.maximize)
    
    m.pprint()
    

    Output

    5 Set Declarations
        EV : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :    3 : {'Tesla', 'Bolt', 'Rivian'}
        EV_T : Size=3, Index=EV, Ordered=Insertion
            Key    : Dimen : Domain : Size : Members
              Bolt :     1 :    Any :    3 : {4, 5, 6}
            Rivian :     1 :    Any :    3 : {13, 14, 15}
             Tesla :     1 :    Any :    6 : {2, 3, 4, 5, 6, 7}
        EV_T_flat : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain           : Size : Members
            None :     2 : EV_T_flat_domain :   12 : {('Tesla', 7), ('Tesla', 4), ('Bolt', 5), ('Tesla', 3), ('Rivian', 14), ('Tesla', 6), ('Bolt', 6), ('Rivian', 13), ('Tesla', 5), ('Tesla', 2), ('Rivian', 15), ('Bolt', 4)}
        EV_T_flat_domain : Size=1, Index=None, Ordered=True
            Key  : Dimen : Domain : Size : Members
            None :     2 :   EV*T :   42 : {('Tesla', 2), ('Tesla', 3), ('Tesla', 4), ('Tesla', 5), ('Tesla', 6), ('Tesla', 7), ('Tesla', 8), ('Tesla', 9), ('Tesla', 10), ('Tesla', 11), ('Tesla', 12), ('Tesla', 13), ('Tesla', 14), ('Tesla', 15), ('Bolt', 2), ('Bolt', 3), ('Bolt', 4), ('Bolt', 5), ('Bolt', 6), ('Bolt', 7), ('Bolt', 8), ('Bolt', 9), ('Bolt', 10), ('Bolt', 11), ('Bolt', 12), ('Bolt', 13), ('Bolt', 14), ('Bolt', 15), ('Rivian', 2), ('Rivian', 3), ('Rivian', 4), ('Rivian', 5), ('Rivian', 6), ('Rivian', 7), ('Rivian', 8), ('Rivian', 9), ('Rivian', 10), ('Rivian', 11), ('Rivian', 12), ('Rivian', 13), ('Rivian', 14), ('Rivian', 15)}
        T : Size=1, Index=None, Ordered=Insertion
            Key  : Dimen : Domain : Size : Members
            None :     1 :    Any :   14 : {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
    
    1 Var Declarations
        charge : Size=12, Index=EV_T_flat
            Key            : Lower : Value : Upper : Fixed : Stale : Domain
               ('Bolt', 4) :  None :  None :  None : False :  True :  Reals
               ('Bolt', 5) :  None :  None :  None : False :  True :  Reals
               ('Bolt', 6) :  None :  None :  None : False :  True :  Reals
            ('Rivian', 13) :  None :  None :  None : False :  True :  Reals
            ('Rivian', 14) :  None :  None :  None : False :  True :  Reals
            ('Rivian', 15) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 2) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 3) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 4) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 5) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 6) :  None :  None :  None : False :  True :  Reals
              ('Tesla', 7) :  None :  None :  None : False :  True :  Reals
    
    1 Objective Declarations
        obj : Size=1, Index=None, Active=True
            Key  : Active : Sense    : Expression
            None :   True : minimize : charge[Tesla,7] + charge[Tesla,4] + charge[Bolt,5] + charge[Tesla,3] + charge[Rivian,14] + charge[Tesla,6] + charge[Bolt,6] + charge[Rivian,13] + charge[Tesla,5] + charge[Tesla,2] + charge[Rivian,15] + charge[Bolt,4]
    
    2 Constraint Declarations
        charger_capacity : Size=9, Index=T, Active=True
            Key : Lower : Body                             : Upper : Active
              2 :  -Inf :                  charge[Tesla,2] :   5.0 :   True
              3 :  -Inf :                  charge[Tesla,3] :   5.0 :   True
              4 :  -Inf : charge[Tesla,4] + charge[Bolt,4] :   5.0 :   True
              5 :  -Inf : charge[Tesla,5] + charge[Bolt,5] :   5.0 :   True
              6 :  -Inf : charge[Tesla,6] + charge[Bolt,6] :   5.0 :   True
              7 :  -Inf :                  charge[Tesla,7] :   5.0 :   True
             13 :  -Inf :                charge[Rivian,13] :   5.0 :   True
             14 :  -Inf :                charge[Rivian,14] :   5.0 :   True
             15 :  -Inf :                charge[Rivian,15] :   5.0 :   True
        vehicle_limit : Size=3, Index=EV, Active=True
            Key    : Lower : Body                                                                                                      : Upper : Active
              Bolt :  -Inf :                                                          charge[Bolt,4] + charge[Bolt,5] + charge[Bolt,6] :  10.0 :   True
            Rivian :  -Inf :                                                 charge[Rivian,13] + charge[Rivian,14] + charge[Rivian,15] :  10.0 :   True
             Tesla :  -Inf : charge[Tesla,2] + charge[Tesla,3] + charge[Tesla,4] + charge[Tesla,5] + charge[Tesla,6] + charge[Tesla,7] :  10.0 :   True
    
    9 Declarations: EV T EV_T EV_T_flat_domain EV_T_flat charge vehicle_limit charger_capacity obj