Search code examples
pythonconditional-statementspulpobjective-function

Conditionals on Pulp variables


I am trying to solve a professor/class asignment problem using Pulp. Below is a simplified example of my code. In the example there are 12 different subjects/year ('Maths_1', stands for Maths 1st year) to be given to 3 different groups (A, B, C). There are a total of 36 classes to be asigned to 9 professors (4 classes each). I want to minimize the number of different subjects a professor has to give. This is: a professor has to be assigned 4 clases, then, for example, Maths_1_A, Maths_1_B, Maths_1_C & Programming_1A involves only two different subjects (Maths_1, and Programming_1) and is a better option than Maths_1_A, Maths_2_A, Physics_1_B, Chemistry_3_A that involves 4 different subjects (Maths_1, Maths_2, Physics_1, Chemistry_3). I try to do this by defining an objective function that is the sum of the number of different subjects a professor is assigned.

from itertools import product
import pulp

subjects=['Maths_1','Maths_2','Maths_3', 'Physics_1','Physics_2','Physics_3',
          'Quemistry_1', 'Quemistry_2', 'Quemistry_3',
          'Programming_1', 'Programming_2', 'Programming_3']
groups=['A','B','C']

clases=[a[0]+'_'+a[1] for a in product(subjects, groups)]
professors=['professor'+str(i) for i in range(1,10)]

number_of_clases_per_professor=4

model=pulp.LpProblem('Class assignmnet', sense=pulp.LpMaximize)
assign={(prof, clas): pulp.LpVariable('prof_%r_class_%r'%(prof, clas), cat=pulp.LpBinary)
       for prof in professors
       for clas in clases}

#CONSTRAINTS
# 1. Each "class" has to be assigned exactly once:
for clas in clases:
    model.addConstraint(sum(assign[(prof, clas)] for prof in professors)==1)
    
#2. The number of classes per professor cannot exceed 4
for prof in professors:
    model.addConstraint(sum(assign[(prof, clas)] for clas in clases)<=4)

The problem I am having is in defining the objective function. I can only think in terms of conditionals on the pulp variable assign:

obj=0
for prof in professors:
    subjects_for_prof=[]
    for subject in subjects:
        for group in groups:
            clas=subject+'_'+group
            if assign[(prof, clas)]:
                if subject not in subjects_for_prof:
                    subjects_for_prof.append(subject)
    obj+=len(subjects_for_prof)
model+=obj

The question is: how can I make an objective function that counts the different number of subjects a professor is assigned?


Solution

  • I think you will make life easier by keeping a 3-component index for your primary assignment variables:

    assign={(prof, subject, group): pulp.LpVariable('prof_%r_subj_%r_grp_%r'%(prof, subj, grp), cat=pulp.LpBinary)
           for prof in professors
           for subj in subjects
           for grp in groups}
    

    If you want to count the number of different subjects a professor is assigned to teach then you can introduce a specific set of binary variables:

    assign_subj={(prof, subject): pulp.LpVariable('prof_%r_subj_%r'%(prof, subj), cat=pulp.LpBinary)
               for prof in professors
               for subj in subjects}
    

    And you can then set up constraints which in pseudocode are something like:

    for prof in professors:
        for subj in subjects:
            model += pulp.lpSum([assign[(prof, subj, grp)] for grp in groups]) <= assign_subj[(prof, subj)]*max_no_groups
    

    In this last set of constraints you'd need to set max_no_groups to the maximum expected number of groups for any subject. This constraint will mean that for any particular prof to have any assignments to a particular subj the appropriate assign_subj variable will have to be set to 1. You can then count these or do whatever you want with them in your objective.