Model the domain objects

Your goal is to assign each lesson to a time slot and a room. You will create these classes:

schoolTimetablingClassDiagramPure

Timeslot

The Timeslot class represents a time interval when lessons are taught, for example, Monday 10:30 - 11:30 or Tuesday 13:30 - 14:30. For simplicity’s sake, all time slots have the same duration and there are no time slots during lunch or other breaks.

A time slot has no date, because a high school schedule just repeats every week. So there is no need for continuous planning.

  • Java

  • Kotlin

  • Python

Create the src/main/java/org/acme/schooltimetabling/domain/Timeslot.java class:

package org.acme.schooltimetabling.domain;

import java.time.DayOfWeek;
import java.time.LocalTime;

public class Timeslot {

    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;

    public Timeslot() {
    }

    public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
        this.dayOfWeek = dayOfWeek;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public DayOfWeek getDayOfWeek() {
        return dayOfWeek;
    }

    public LocalTime getStartTime() {
        return startTime;
    }

    public LocalTime getEndTime() {
        return endTime;
    }

    @Override
    public String toString() {
        return dayOfWeek + " " + startTime;
    }

}

Create the src/main/kotlin/org/acme/schooltimetabling/domain/Timeslot.kt class:

package org.acme.schooltimetabling.domain

import java.time.DayOfWeek
import java.time.LocalTime

data class Timeslot(
    val dayOfWeek: DayOfWeek,
    val startTime: LocalTime,
    val endTime: LocalTime) {

    override fun toString(): String = "$dayOfWeek $startTime"

}

Create the Timeslot class in src/hello_world/domain.py:

from dataclasses import dataclass
from datetime import time


@dataclass
class Timeslot:
    day_of_week: str
    start_time: time
    end_time: time

    def __str__(self):
        return f'{self.day_of_week} {self.start_time.strftime('%H:%M')}'

Because no Timeslot instances change during solving, a Timeslot is called a problem fact. Such classes do not require any Timefold Solver specific annotations.

Notice the toString() method keeps the output short, so it is easier to read Timefold Solver’s DEBUG or TRACE log, as shown later.

Room

The Room class represents a location where lessons are taught, for example, Room A or Room B. For simplicity’s sake, all rooms are without capacity limits and they can accommodate all lessons.

  • Java

  • Kotlin

  • Python

Create the src/main/java/org/acme/schooltimetabling/domain/Room.java class:

package org.acme.schooltimetabling.domain;

public class Room {

    private String name;

    public Room() {
    }

    public Room(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

}

Create the src/main/kotlin/org/acme/schooltimetabling/domain/Room.kt class:

package org.acme.schooltimetabling.domain

data class Room(
    val name: String) {

    override fun toString(): String = name

}

Create the Room class in src/hello_world/domain.py:

from dataclasses import dataclass


@dataclass
class Room:
    name: str

    def __str__(self):
        return f'{self.name}'

Room instances do not change during solving, so Room is also a problem fact.

Lesson

During a lesson, represented by the Lesson class, a teacher teaches a subject to a group of students, for example, Math by A.Turing for 9th grade or Chemistry by M.Curie for 10th grade. If a subject is taught multiple times per week by the same teacher to the same student group, there are multiple Lesson instances that are only distinguishable by id. For example, the 9th grade has six math lessons a week.

During solving, Timefold Solver changes the timeslot and room fields of the Lesson class, to assign each lesson to a time slot and a room. Because Timefold Solver changes these fields, Lesson is a planning entity:

schoolTimetablingClassDiagramAnnotated

Most of the fields in the previous diagram contain input data, except for the orange fields: A lesson’s timeslot and room fields are unassigned (null) in the input data and assigned (not null) in the output data. Timefold Solver changes these fields during solving. Such fields are called planning variables. In order for Timefold Solver to recognize them, both the timeslot and room fields require an @PlanningVariable annotation. Their containing class, Lesson, requires an @PlanningEntity annotation.

  • Java

  • Kotlin

  • Python

Create the src/main/java/org/acme/schooltimetabling/domain/Lesson.java class:

package org.acme.schooltimetabling.domain;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;

@PlanningEntity
public class Lesson {

    @PlanningId
    private String id;

    private String subject;
    private String teacher;
    private String studentGroup;

    @PlanningVariable
    private Timeslot timeslot;
    @PlanningVariable
    private Room room;

    public Lesson() {
    }

    public Lesson(String id, String subject, String teacher, String studentGroup) {
        this.id = id;
        this.subject = subject;
        this.teacher = teacher;
        this.studentGroup = studentGroup;
    }

    public String getId() {
        return id;
    }

    public String getSubject() {
        return subject;
    }

    public String getTeacher() {
        return teacher;
    }

    public String getStudentGroup() {
        return studentGroup;
    }

    public Timeslot getTimeslot() {
        return timeslot;
    }

    public void setTimeslot(Timeslot timeslot) {
        this.timeslot = timeslot;
    }

    public Room getRoom() {
        return room;
    }

    public void setRoom(Room room) {
        this.room = room;
    }

    @Override
    public String toString() {
        return subject + "(" + id + ")";
    }

}

Create the src/main/kotlin/org/acme/schooltimetabling/domain/Lesson.kt class:

package org.acme.schooltimetabling.domain

import ai.timefold.solver.core.api.domain.entity.PlanningEntity
import ai.timefold.solver.core.api.domain.lookup.PlanningId
import ai.timefold.solver.core.api.domain.variable.PlanningVariable

@PlanningEntity
data class Lesson (
    @PlanningId
    val id: String,
    val subject: String,
    val teacher: String,
    val studentGroup: String) {

    @PlanningVariable
    var timeslot: Timeslot? = null

    @PlanningVariable
    var room: Room? = null

    // No-arg constructor required for Timefold
    constructor() : this("0", "", "", "")

    override fun toString(): String = "$subject($id)"

}

Create the Lesson class in src/hello_world/domain.py:

from timefold.solver.domain import planning_entity, PlanningId, PlanningVariable
from dataclasses import dataclass, field
from typing import Annotated


@planning_entity
@dataclass
class Lesson:
    id: Annotated[str, PlanningId]
    subject: str
    teacher: str
    student_group: str
    timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)
    room: Annotated[Room | None, PlanningVariable] = field(default=None)

The Lesson class has an @PlanningEntity annotation, so Timefold Solver knows that this class changes during solving because it contains one or more planning variables.

The timeslot field has an @PlanningVariable annotation, so Timefold Solver knows that it can change its value. In order to find potential Timeslot instances to assign to this field, Timefold Solver uses the variable type to connect to a value range provider that provides a List<Timeslot> to pick from.

The room field also has an @PlanningVariable annotation, for the same reasons.

Determining the @PlanningVariable fields for an arbitrary constraint solving use case is often challenging the first time. Read the domain modeling guidelines to avoid common pitfalls.