Search code examples
optaplanner

Does Optaplanner support "combinatorial" planning variables?


Example:

Students want to enroll in multiple courses of different course groups (Math, English, Spanish, History) and have issued preferences for each course group (ENG-1 > ENG-2 means course ENG-1 is preferred to course ENG-2).

Student A:
MATH-2 > MATH-4 > MATH-1 > ... > MATH-9
ENG-3 > ENG-4 > ENG-1 > ... > ENG-2

Student B:
ENG-1 > ENG-2 > ENG-4 > ... > ENG-3
SPA-4 > SPA-6 > SPA-3 > ... > SPA-2
HIST-1 > HIST-3 > HIST-2 ... > HIST-5

Student C:
...

Is it possible for a planning variable of each student (planning entity) to be a combination of each of their preferences? I.e. student A would be assigned MATH-2 and ENG-3, student B would be assigned ENG-1, SPA-4, and HIST-1, if the constraints allow for this.


Solution

  • Yes (and no). Technically no, because @PlanningVariable can only hold a single value.

    But YES, OptaPlanner can handle your use case. You just need to choose the right way to map your domain to Java classes. You need to model a N:M relation between Student and Course:

    • Student A needs to enroll in 2 courses (one from the MATH group and one from the ENG group).
    • Student B needs to enroll in 3 courses (ENG, SPA, HIST).
    • etc.

    You can model this type of relationship with the CourseAssignment class, which is your @PlanningEntity. It could look like this:

    @PlanningEntity
    class CourseAssignment {
      final Student student; // e.g. Ann
      final CourseGroup courseGroup; // e.g. MATH
    
      @PlanningVariable(valueRangeProviderRefs = { "courseRange" })
      Course course; // changed by Solver - could be MATH-1, MATH-2, ENG-1, HIST-...
    }
    

    Since the number of course assignments is known for each student and it's fixed, you'd simply create 2 CourseAssignment instances for Student A, 3 instances for Student B, etc.

    Next, design your constraints to penalize each courseAssignment with a hard score penalty, if courseAssignment.course.group != courseAssignment.courseGroup and with a soft score penalty based on courseAssignment.student.getPreference(courseAssignment.course).