Skip to content

Commit

Permalink
Merge pull request #7 from hotungkhanh/kan-32-34-45/backend-constraints
Browse files Browse the repository at this point in the history
Add Lab Constraint
  • Loading branch information
NguyenDonLam authored Sep 14, 2024
2 parents fdc8f61 + a23e2a7 commit 3d0a34d
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 53 deletions.
30 changes: 20 additions & 10 deletions backend/src/main/java/org/acme/TimetableResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.acme.domain.Timetable;
import org.acme.domain.Unit;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.util.List;
Expand Down Expand Up @@ -43,30 +44,39 @@ public Timetable hello() throws ExecutionException, InterruptedException {
Student h = new Student("h");
Student i = new Student("i");

Room r1 = new Room("Room1", 2);
Room r2 = new Room("Room2", 3);
Room r3 = new Room("Room3", 4);
Room r1 = new Room("Room1", 2, true);
Room r2 = new Room("Room2", 4, false);
Room r3 = new Room("Room3", 4, false);

var problem = new Timetable(
List.of(
new Unit(1, "1", Duration.ofHours(2), List.of(a, b)),
new Unit(2, "2", Duration.ofHours(2), List.of(a, c, d, e)),
new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i))
// new Unit(4, "4", Duration.ofHours(2), List.of(a, b)),
new Unit(1, "1", Duration.ofHours(2), List.of(a, b), true),
new Unit(2, "2", Duration.ofHours(2), List.of(a, c, d, e), true),
new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false),
new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false)
// new Unit(5, "5", Duration.ofHours(2), List.of(c, d, e)),
// new Unit(6, "6", Duration.ofHours(2), List.of(f, g, h, i))
),

List.of(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY
// DayOfWeek.THURSDAY,
// DayOfWeek.FRIDAY
),

List.of(
LocalTime.of(15, 0),
LocalTime.of(17, 0)
LocalTime.of(15, 0)
// LocalTime.of(17, 0)
// LocalTime.of(16,0),
// LocalTime.of(23,0)
),
List.of(r1, r2, r3)
);


Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution();

return solution;
}

Expand Down
6 changes: 3 additions & 3 deletions backend/src/main/java/org/acme/domain/ConflictingUnit.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* @author Jet Edge
*/
public class ConflictingUnit {
Unit unit1;
private Unit unit1;

Unit unit2;
private Unit unit2;

int numStudent;
private int numStudent;

/**
* Creates a pair of conflicting units.
Expand Down
14 changes: 13 additions & 1 deletion backend/src/main/java/org/acme/domain/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
public class Room {
@PlanningId
private String id;

private int capacity;
private boolean isLab;

public Room() {
}
Expand All @@ -20,10 +22,12 @@ public Room() {
*
* @param id The room’s id.
* @param capacity The room's capacity.
* @param isLab Whether the room is a laboratory.
*/
public Room(String id, int capacity) {
public Room(String id, int capacity, boolean isLab) {
this.id = id;
this.capacity = capacity;
this.isLab = isLab;
}

public String getId() {
Expand All @@ -41,4 +45,12 @@ public int getCapacity() {
public void setCapacity(int capacity) {
this.capacity = capacity;
}

public boolean isLab() {
return isLab;
}

public void setLab(boolean lab) {
isLab = lab;
}
}
2 changes: 1 addition & 1 deletion backend/src/main/java/org/acme/domain/Student.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class Student {

// String studentID;
// @PlanningId

String name;

public Student() {
Expand Down
40 changes: 34 additions & 6 deletions backend/src/main/java/org/acme/domain/Timetable.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -19,17 +20,21 @@
@PlanningSolution
public class Timetable {

@PlanningEntityCollectionProperty
List<Unit> units;

@ValueRangeProvider
List<LocalTime> startTimes;
@PlanningScore
HardSoftScore score;
private List<DayOfWeek> daysOfWeek;
@ValueRangeProvider
private List<LocalTime> startTimes;

@ProblemFactCollectionProperty
@ValueRangeProvider
private List<Room> rooms;

@PlanningEntityCollectionProperty
private List<Unit> units;

@PlanningScore
private HardSoftScore score;

public Timetable() {

}
Expand Down Expand Up @@ -58,6 +63,29 @@ public Timetable(List<Unit> units, List<LocalTime> startTimes, List<Room> rooms)
this.rooms = rooms;
}

/**
* Creates a timetable.
*
* @param units The list of units to be allocated.
* @param daysOfWeek The list of available days of the week.
* @param startTimes The list of available starting times.
* @param rooms The list of available rooms.
*/
public Timetable(List<Unit> units, List<DayOfWeek> daysOfWeek, List<LocalTime> startTimes, List<Room> rooms) {
this.units = units;
this.daysOfWeek = daysOfWeek;
this.startTimes = startTimes;
this.rooms = rooms;
}

public List<DayOfWeek> getDaysOfWeek() {
return daysOfWeek;
}

public void setDaysOfWeek(List<DayOfWeek> daysOfWeek) {
this.daysOfWeek = daysOfWeek;
}

public List<LocalTime> getStartTimes() {
return startTimes;
}
Expand Down
52 changes: 34 additions & 18 deletions backend/src/main/java/org/acme/domain/Unit.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.util.List;
Expand All @@ -17,24 +18,23 @@
@PlanningEntity
public class Unit {

private List<Student> students;
@PlanningId
int unitID;

String name;

Duration duration;

private int unitID;
private String name;
private Duration duration;
@PlanningVariable
private DayOfWeek dayOfWeek;
@PlanningVariable
LocalTime start;
List<Student> students;
private LocalTime startTime;
@PlanningVariable
private Room room;

private boolean wantsLab;

public Unit() {
}

;

/**
* Creates a unit.
*
Expand All @@ -57,14 +57,14 @@ public Unit(int unitID, String name, Duration duration, List<Student> students)
* @param name The unit’s ID.
* @param duration The unit’s duration.
* @param students The list of students enrolled in the unit.
* @param room The room assigned to the unit.
* @param wantsLab Whether the unit wants a laboratory room.
*/
public Unit(int unitID, String name, Duration duration, List<Student> students, Room room) {
public Unit(int unitID, String name, Duration duration, List<Student> students, boolean wantsLab) {
this.unitID = unitID;
this.name = name;
this.duration = duration;
this.students = students;
this.room = room;
this.wantsLab = wantsLab;
}

public int getUnitID() {
Expand All @@ -91,16 +91,24 @@ public void setDuration(Duration duration) {
this.duration = duration;
}

public LocalTime getStart() {
return start;
public DayOfWeek getDayOfWeek() {
return dayOfWeek;
}

public void setStart(LocalTime start) {
this.start = start;
public void setDayOfWeek(DayOfWeek dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}

public LocalTime getStartTime() {
return startTime;
}

public void setStartTime(LocalTime startTime) {
this.startTime = startTime;
}

public LocalTime getEnd() {
return start.plus(duration);
return startTime.plus(duration);
}

public List<Student> getStudents() {
Expand All @@ -127,4 +135,12 @@ public Room getRoom() {
public void setRoom(Room room) {
this.room = room;
}

public boolean isWantsLab() {
return wantsLab;
}

public void setWantsLab(boolean wantsLab) {
this.wantsLab = wantsLab;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
import ai.timefold.solver.core.api.score.stream.Joiners;
import org.acme.domain.ConflictingUnit;
import org.acme.domain.Unit;

import java.util.function.Function;

import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
import static ai.timefold.solver.core.api.score.stream.Joiners.overlapping;

/**
Expand All @@ -29,21 +29,32 @@ public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
studentConflict(constraintFactory),
roomConflict(constraintFactory),
roomCapacity(constraintFactory)
roomCapacity(constraintFactory),
labPreference(constraintFactory)
};
}

/**
* Penalize 1 hard score for each student with overlapping units.
*/
private Constraint studentConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(ConflictingUnit.class)
.join(Unit.class, Joiners.equal(ConflictingUnit::getUnit1, Function.identity()))
.join(Unit.class, Joiners.equal((conflictingUnit, unit1) -> conflictingUnit.getUnit2(), Function.identity()),
overlapping((conflictingUnit, unit1) -> unit1.getStart(),
// A student can be in at most one unit at the same time.
return constraintFactory
// Select each pair of conflicting units.
.forEach(ConflictingUnit.class)
// Find the first unit.
.join(Unit.class, equal(ConflictingUnit::getUnit1, Function.identity()))
// Find the second unit.
.join(Unit.class, equal((conflictingUnit, unit1) -> conflictingUnit.getUnit2(), Function.identity()),
// Check if the 2 units are on the same weekday ...
equal((conflictingUnit, unit1) -> unit1.getDayOfWeek(), Unit::getDayOfWeek),
// ... in the same timeslot ...
overlapping((conflictingUnit, unit1) -> unit1.getStartTime(),
(conflictingUnit, unit1) -> unit1.getEnd(),
Unit::getStart, Unit::getEnd))
.penalize(HardSoftScore.ofHard(1), (conflictingUnit, unit1, unit2) -> conflictingUnit.getNumStudent())
Unit::getStartTime, Unit::getEnd))
// ... and penalize each pair with a hard weight.
.penalize(HardSoftScore.ofHard(1),
(conflictingUnit, unit1, unit2) -> conflictingUnit.getNumStudent())
.asConstraint("Student conflict");

}
Expand All @@ -52,14 +63,16 @@ private Constraint studentConflict(ConstraintFactory constraintFactory) {
* Penalize 1 hard score for each room with overlapping units.
*/
Constraint roomConflict(ConstraintFactory constraintFactory) {
// A room can accommodate at most one lesson at the same time.
// A room can accommodate at most one unit at the same time.
return constraintFactory
// Select each pair of 2 different lessons ...
// Select each pair of 2 different units ...
.forEachUniquePair(Unit.class,
// ... on the same weekday ...
equal(Unit::getDayOfWeek),
// ... in the same timeslot ...
overlapping(Unit::getStart, Unit::getEnd),
overlapping(Unit::getStartTime, Unit::getEnd),
// ... in the same room ...
Joiners.equal(Unit::getRoom))
equal(Unit::getRoom))
// ... and penalize each pair with a hard weight.
.penalize(HardSoftScore.ofHard(1))
.asConstraint("Room conflict");
Expand All @@ -69,10 +82,28 @@ Constraint roomConflict(ConstraintFactory constraintFactory) {
* Penalize 1 soft score for each student overflowing the capacity of the room.
*/
Constraint roomCapacity(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Unit.class)
// A room cannot accommodate more students than its capacity.
return constraintFactory
.forEach(Unit.class)
.filter(unit -> unit.getStudentSize() > unit.getRoom().getCapacity())
.penalize(HardSoftScore.ofSoft(1), unit -> unit.getStudentSize() - unit.getRoom().getCapacity())
.penalize(HardSoftScore.ofSoft(1),
unit -> unit.getStudentSize() - unit.getRoom().getCapacity())
.asConstraint("Room capacity conflict");
}

/**
* Penalize 1 soft score for each laboratory unit not assigned to a laboratory.
*/
Constraint labPreference(ConstraintFactory constraintFactory) {
// Some units prefer to have a laboratory room.
return constraintFactory
.forEach(Unit.class)
// Select a laboratory unit ...
.filter(Unit::isWantsLab)
// ... in a non-lab room ...
.filter(unit -> !unit.getRoom().isLab())
.penalize(HardSoftScore.ofSoft(1))
.asConstraint("Unit laboratory preference");
}

}

0 comments on commit 3d0a34d

Please sign in to comment.