Skip to content

Commit

Permalink
docs: update the quarkus school-timetabling quickstart
Browse files Browse the repository at this point in the history
  • Loading branch information
rsynek committed Jun 28, 2023
1 parent 11e8c27 commit 980f433
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 59 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ The following example uses the Linux command `curl` to send a POST request:

[source,shell]
----
$ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'
$ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslots":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"rooms":[{"name":"Room A"},{"name":"Room B"}],"lessons":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'
----

After about five seconds, according to the termination spent time defined in your `application.properties`,
Expand All @@ -336,7 +336,7 @@ HTTP/1.1 200
Content-Type: application/json
...
{"timeslotList":...,"roomList":...,"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}
{"timeslots":...,"rooms":...,"lessons":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}
----

Notice that your application assigned all four lessons to one of the two time slots and one of the two rooms.
Expand Down Expand Up @@ -476,40 +476,40 @@ public class TimeTableResourceTest {
public void solve() {
TimeTable problem = generateProblem();
TimeTable solution = timeTableResource.solve(problem);
assertFalse(solution.getLessonList().isEmpty());
for (Lesson lesson : solution.getLessonList()) {
assertFalse(solution.getLessons().isEmpty());
for (Lesson lesson : solution.getLessons()) {
assertNotNull(lesson.getTimeslot());
assertNotNull(lesson.getRoom());
}
assertTrue(solution.getScore().isFeasible());
}
private TimeTable generateProblem() {
List<Timeslot> timeslotList = new ArrayList<>();
timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
List<Room> roomList = new ArrayList<>();
roomList.add(new Room("Room A"));
roomList.add(new Room("Room B"));
roomList.add(new Room("Room C"));
List<Lesson> lessonList = new ArrayList<>();
lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));
lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
return new TimeTable(timeslotList, roomList, lessonList);
List<Timeslot> timeslots = new ArrayList<>();
timeslots.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
timeslots.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
timeslots.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
timeslots.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
timeslots.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
List<Room> rooms = new ArrayList<>();
rooms.add(new Room("Room A"));
rooms.add(new Room("Room B"));
rooms.add(new Room("Room C"));
List<Lesson> lessons = new ArrayList<>();
lessons.add(new Lesson(101L, "Math", "B. May", "9th grade"));
lessons.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
lessons.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
lessons.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
lessons.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));
lessons.add(new Lesson(201L, "Math", "B. May", "10th grade"));
lessons.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
lessons.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
lessons.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
lessons.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
return new TimeTable(timeslots, rooms, lessons);
}
}
Expand Down Expand Up @@ -649,7 +649,7 @@ public class TimeTableResource {
throw new IllegalStateException("There is no timeTable with id (" + id + ").");
}
// Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance
// that is contained by the timeTable's timeslotList/roomList.
// that is contained by the timeTable's timeslots/rooms.
return new TimeTable(
Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")),
Room.listAll(Sort.by("name").and("id")),
Expand All @@ -658,7 +658,7 @@ public class TimeTableResource {
@Transactional
protected void save(TimeTable timeTable) {
for (Lesson lesson : timeTable.getLessonList()) {
for (Lesson lesson : timeTable.getLessons()) {
// TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager
Lesson attachedLesson = Lesson.findById(lesson.getId());
attachedLesson.setTimeslot(lesson.getTimeslot());
Expand Down Expand Up @@ -718,8 +718,8 @@ public class TimeTableResourceTest {
Thread.sleep(20L);
timeTable = timeTableResource.getTimeTable();
}
assertFalse(timeTable.getLessonList().isEmpty());
for (Lesson lesson : timeTable.getLessonList()) {
assertFalse(timeTable.getLessons().isEmpty());
for (Lesson lesson : timeTable.getLessons()) {
assertNotNull(lesson.getTimeslot());
assertNotNull(lesson.getRoom());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTab
@Override
public HardSoftScore calculateScore(TimeTable timeTable) {
List<Lesson> lessonList = timeTable.getLessonList();
List<Lesson> lessons = timeTable.getLessons();
int hardScore = 0;
for (Lesson a : lessonList) {
for (Lesson b : lessonList) {
for (Lesson a : lessons) {
for (Lesson b : lessons) {
if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
&& a.getId() < b.getId()) {
// A room can accommodate at most one lesson at the same time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,35 @@ public class TimeTable {
@ValueRangeProvider
@ProblemFactCollectionProperty
private List<Timeslot> timeslotList;
private List<Timeslot> timeslots;
@ValueRangeProvider
@ProblemFactCollectionProperty
private List<Room> roomList;
private List<Room> rooms;
@PlanningEntityCollectionProperty
private List<Lesson> lessonList;
private List<Lesson> lessons;
@PlanningScore
private HardSoftScore score;
public TimeTable() {
}
public TimeTable(List<Timeslot> timeslotList, List<Room> roomList, List<Lesson> lessonList) {
this.timeslotList = timeslotList;
this.roomList = roomList;
this.lessonList = lessonList;
public TimeTable(List<Timeslot> timeslots, List<Room> rooms, List<Lesson> lessons) {
this.timeslots = timeslots;
this.rooms = rooms;
this.lessons = lessons;
}
public List<Timeslot> getTimeslotList() {
return timeslotList;
public List<Timeslot> getTimeslots() {
return timeslots;
}
public List<Room> getRoomList() {
return roomList;
public List<Room> getRooms() {
return rooms;
}
public List<Lesson> getLessonList() {
return lessonList;
public List<Lesson> getLessons() {
return lessons;
}
public HardSoftScore getScore() {
Expand All @@ -75,11 +75,11 @@ so Timefold knows that this class contains all of the input and output data.

Specifically, this class is the input of the problem:

* A `timeslotList` field with all time slots
* A `timeslots` field with all time slots
** This is a list of problem facts, because they do not change during solving.
* A `roomList` field with all rooms
* A `rooms` field with all rooms
** This is a list of problem facts, because they do not change during solving.
* A `lessonList` field with all lessons
* A `lessons` field with all lessons
** This is a list of planning entities, because they change during solving.
** Of each `Lesson`:
*** The values of the `timeslot` and `room` fields are typically still `null`, so unassigned.
Expand All @@ -89,27 +89,27 @@ These fields are problem properties.
However, this class is also the output of the solution:

* A `lessonList` field for which each `Lesson` instance has non-null `timeslot` and `room` fields after solving
* A `lessons` field for which each `Lesson` instance has non-null `timeslot` and `room` fields after solving
* A `score` field that represents the quality of the output solution, for example, `0hard/-5soft`
== The value range providers

The `timeslotList` field is a value range provider.
The `timeslots` field is a value range provider.
It holds the `Timeslot` instances which Timefold can pick from to assign to the `timeslot` field of `Lesson` instances.
The `timeslotList` field has an `@ValueRangeProvider` annotation to connect the `@PlanningVariable` with the `@ValueRangeProvider`,
The `timeslots` field has an `@ValueRangeProvider` annotation to connect the `@PlanningVariable` with the `@ValueRangeProvider`,
by matching the type of the planning variable with the type returned by the xref:configuration/configuration.adoc#planningValueRangeProvider[value range provider].

Following the same logic, the `roomList` field also has an `@ValueRangeProvider` annotation.
Following the same logic, the `rooms` field also has an `@ValueRangeProvider` annotation.

== The problem fact and planning entity properties

Furthermore, Timefold needs to know which `Lesson` instances it can change
as well as how to retrieve the `Timeslot` and `Room` instances used for score calculation
by your `TimeTableConstraintProvider`.

The `timeslotList` and `roomList` fields have an `@ProblemFactCollectionProperty` annotation,
The `timeslots` and `rooms` fields have an `@ProblemFactCollectionProperty` annotation,
so your `TimeTableConstraintProvider` can select _from_ those instances.

The `lessonList` has an `@PlanningEntityCollectionProperty` annotation,
The `lessons` has an `@PlanningEntityCollectionProperty` annotation,
so Timefold can change them during solving
and your `TimeTableConstraintProvider` can select _from_ those too.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ with https://timefold.ai[Timefold]'s constraint solving Artificial Intelligence

You will build a REST application that optimizes a school timetable for students and teachers:

image::quickstart/school-timetabling/schoolTimetablingScreenshot.png[]
image::quickstart/school-timetabling/schoolTimetablingScreenshot_legacy.png[]

Your service will assign `Lesson` instances to `Timeslot` and `Room` instances automatically
by using AI to adhere to hard and soft scheduling _constraints_, such as the following examples:
Expand Down

0 comments on commit 980f433

Please sign in to comment.