Lewik
Lewik

Reputation: 757

Optaplanner. School timetabling. Force first lession

I'm trying to add constraints to School timetabling example. For example: "all groups should have the first lesson". I tried EasyScore and Streaming - no success. EasyScore cant finds a proper solution, shuffles lessons a lot. Streaming gave me an error: Undo for (Lesson(subj...)) does not exist
Code for Streaming:

            from(Lesson::class.java)
                .filter { it.timeslot != null }
                .groupBy({ it.studentGroup }, { it.timeslot!!.day }, ConstraintCollectors.toList())
                .filter { group, day, list ->
                    list.any { it.timeslot!!.number != 1 }
                }
                .penalize(
                    "Student must have first lesson",
                    HardSoftScore.ONE_HARD
                ) { group, day, list -> list.count { it.timeslot!!.number != 1 } },

Looks like I'm thinking the wrong direction.
https://github.com/Lewik/timetable
Any help will be greatly appreciated.

update: fixed == -> =!

Upvotes: 0

Views: 216

Answers (3)

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

How about using .ifNotExists()?

First, convert student group from a String into a class and add @ProblemFactCollectionProperty List<StudentGroup> on your solution, then do

from(StudentGroup.class)
.ifNotExists(from(Lesson.class).filter(Lesson::isFirstTimeslot),
    equals(this -> this, Lesson::getStudentGroup)
.penalize(...);

Upvotes: 0

Lewik
Lewik

Reputation: 757

I don't know the real source of the problem, but it's about hashCode. The exception was thrown because HashMap with Object key can't find by that Object.

Lesson class:

@Serializable
@NoArg
@PlanningEntity
data class Lesson(
    val subject: String,
    val teacher: String,
    val studentGroup: String,
    @PlanningVariable(valueRangeProviderRefs = ["timeslotRange"])
    var timeslot: TimeSlot? = null,
    @PlanningId
    val id: String = UUID.randomUUID().toString(),
)

The implementation above will not work. It could be fixed if I remove data or add override fun hashCode() = Objects.hash(id). @PlanningId does not help here. Kotlin generates hashCode for data classes and seems it not working with optaplanner (or vise versa)

Upvotes: 0

sudo
sudo

Reputation: 316

As far as I understand it, I don't think you're enforcing what you intend to enforce. From what I make from your source code, you penalize every studentgroup's first lesson of the day.

What you should do to enforce the intended goal, is to penalize every studentgroup that does NOT have a timeslot with number == 1 but DOES have one (of the same day) where timeslot number != 1.

So something like :

  • join all Lesson.class instances with all Lesson.class instances where the first lesson's studentGroup equals the second lesson's studentGroup AND the first lesson's timeSlot's day equals the second lesson's timeSlot's day. You obtain a BiConstraintStream<Lesson, Lesson> this way...
  • from this, filter all Lesson.class instances where the first lesson's timeSlot's number is less than the second lesson's timeSlot number
  • then penalise the remaining where the first lesson's timeSlot number differs from 1. That equals penalising all of a studentGroup's days where they have some lesson that day without having any lesson that day during the first timeslot.

If I understood you correctly, that's what you wanted ?

Upvotes: 1

Related Questions