Search code examples
pythonconstraintsmodelingpyomogurobi

Mathematical modeling from gurobipy in pyomo: Define the domain of a concrete model (enumeration, ord,etc.)


I am trying to reformulate a mathematical model from gurobipy to pyomo.I have difficulties with one specific constraint: I formulated the following code in gurobipy (it all works well):

model.addConstrs(
    (quicksum(start[l, i, s, t] for s in SM[n]) <= quicksum(start[l, k, s, T[tau]] for s in SM[D[n]]
                                                            for tau in range(index_t, index_t + dur[n]) if tau < len(T))
     for n in M if n in D for l in LM[n] for index_i, i in enumerate(KM[n]) for index_k, k in enumerate(KM[D[n]])
     for index_t, t in enumerate(T) if index_i == index_k), name='Doppelbelegungskurse')

These are the indices: l-> indice teacher ; n,m->indice coursetype; i,k-> indice course (alias); s-> stations; t,tau->periods. I have defined different Subsets in form of dictionaries: KM-> Courses k that belong to coursetype n. SM-> Stations that are allowed for course type n. LM: Teachers who can teach course type n. D-> coursetype n that is to be scheduled at the same time as coursetype m. start and gamma are binary variables. start is 1 if course k starts with teacher l in station s at the beginning of period t. dur[n] is the duration in periods of coursetype n. The constraint ensures that when course i starts, course k should be scheduled during course i. The dictionary D is built as follows: D={n1:m1, n2:m2,..}. I have difficulties in rewriting this constraint in pyomo, especially with the enumerations of the courses and with regard to the subset D in the domain of the restriction. What I already have:

    def doppelbelegung_rule(model, n, l, i, k, t):
    index_t = T.index(t)
    if n in D:
        if l in LM[n]:
            if i in KM[n]:
                index_i = KM[n].index(i)
                print('i', i)
                print('index_i', index_i)
                if k in KM[D[n]]:
                    index_k = KM[D[n]].index(k)
                    print('k', k)
                    print('index_k', index_k)
                    if index_i == index_k:
                        return sum(model.start[l, i, s, t] for s in SM[n]) <=\
                               sum(model.start[l, k, s, T[tau]] for s in SM[D[n]]
                                   for tau in range(index_t, index_t + dur[n]) if tau < len(T))
                    else:
                        return Constraint.Skip
                else:
                    return Constraint.Skip
            else:
                return Constraint.Skip
        else:
            return Constraint.Skip
    else:
        return Constraint.Skip
    model.doppelbelegung = Constraint(M, L, K, K, T, rule=doppelbelegung_rule)

Unfortunately this does not work. I would be really happy if someone could help me to find the problem. Is it also correct to skip the constraints like that in pyomo ? I am totally new when using pyomo ! Further I construct a Concrete model in Pyomo because the data is known beforehand.

Best regards! Zeineb


Solution

  • OK. I think I understand... Though to figure parts of this out without any corresponding data. If this doesn't work below, suggest you update (edit) your orig post and include some (just some) data for all of the elements involved

    I think that anytime you are getting into a bunch of if statements inside of the construction, it suggests that you should make a better subset beforehand instead of trying to weed things out on the fly. The first suggestion here I think is what you are looking to do, namely within a pair of compatible types, pair up the classes based on index position in a list, and retain reference to the types for purpose of finding instructors and stations...

    M = {'n1', 'n2', 'n3'}
    KM = {  'n1': ['course1', 'course2'],
            'n2': ['course3',],
            'n3': ['course1a', 'course2a']}
    
    D = {'n1':'n3'}
    
    pairings = {(i, k, n, m) for (n, m) in D.items() for (i, k) in zip(KM[n], KM[m]) }
    
    print(pairings)
    

    Output:

    {('course2', 'course2a', 'n1', 'n3'), ('course1', 'course1a', 'n1', 'n3')}
    

    Then you can just pass that to your initializer function kind of as shown below and you have all the pieces to construct your constraint with the already-paired courses and course types:

    def dubblebubble(model, i, k, n, m, l, t):
        pass
    
    model.dubblebubble = Constraint(pairings, L, T,rule= dubblebubble)
    

    However... I think there are probably better ways to set this up, unless you are beyond that point. There seems to be no need for using course types for this pairing process if I understand the problem. You already know which courses are paired and the "course type" is just unnecessary information causing all of this weird look up and index matching. Here is a suggestion (I have no idea what form your orig data is in ) that might produce a structure that is easier to work with:

    Alternate approach:

    import pyomo.environ as pyo
    
    # some data
    course_types = ['math', 'science', 'english']
    
    #               name        type        duration
    all_courses = [ ('math1',   'math',     3),
                    ('math2',   'math',     2),
                    ('math2-a', 'math',     1),
                    ('math3',   'math',     3),
                    ('chem',    'science',  2),
                    ('chem-a',  'science',  1),
                    ('physics', 'science',  3),
                    ('reading1','english',  2),
                    ('writing1','english',  3),
                ]
    
    sub_courses = { 'math2': 'math2-a',
                    'chem' : 'chem-a'}
    
    # model parts...
    m = pyo.ConcreteModel()
    
    ### SETS
    m.N                 = pyo.Set(initialize=course_types)
    m.all_courses       = pyo.Set(initialize=[t[0] for t in all_courses])
    m.primary_courses   = pyo.Set(within=m.all_courses, initialize=m.all_courses-sub_courses.values())
    m.course_pairs      = pyo.Set(within=m.all_courses*m.all_courses, initialize=sub_courses.items())
    
    def group_initializer(m, group):
        return [c[0] for c in all_courses if c[1] == group]
    m.type_groups       = pyo.Set(m.N, initialize=group_initializer)
    
    # inspect results...
    m.primary_courses.pprint()
    m.course_pairs.pprint()
    m.type_groups.pprint()
    

    Output:

    primary_courses : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain      : Size : Members
        None :     1 : all_courses :    7 : {'math1', 'math2', 'math3', 'chem', 'physics', 'reading1', 'writing1'}
    course_pairs : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain              : Size : Members
        None :     2 : course_pairs_domain :    2 : {('math2', 'math2-a'), ('chem', 'chem-a')}
    type_groups : Size=3, Index=N, Ordered=Insertion
        Key     : Dimen : Domain : Size : Members
        english :     1 :    Any :    2 : {'reading1', 'writing1'}
           math :     1 :    Any :    4 : {'math1', 'math2', 'math2-a', 'math3'}
        science :     1 :    Any :    3 : {'chem', 'chem-a', 'physics'}