Browse Source

Implement Meal optimistation

tripeur 4 năm trước cách đây
mục cha
commit
0381b3480a

+ 10 - 3
src/main/java/fr/jaquin/bdlg/planner/domain/Assignement.java

@@ -81,6 +81,9 @@ public class Assignement {
   public LocalDateTime getEndDateTime() {
     return this.slot.getEndTime();
   }
+  public LocalDateTime getRestEndDateTime() {
+    return this.slot.getEndTime().plusMinutes(30);
+  }
 
   public int getPreferenceScore() {
     int score = 0;
@@ -95,7 +98,7 @@ public class Assignement {
   public int getCompetenceLackScore() {
     int score = 0;
     for (Skill skill : this.slot.getHardCompetencies()) {
-      if (!this.volonteer.getPreferences().contains(skill)) {
+      if (!this.volonteer.getCompetencies().contains(skill)) {
         score += 1;
       }
     }
@@ -105,11 +108,15 @@ public class Assignement {
   public int getSoftCompetenceLackScore() {
     int score = 0;
     for (Skill skill : this.slot.getSoftCompetencies()) {
-      if (!this.volonteer.getPreferences().contains(skill)) {
+      if (!this.volonteer.getCompetencies().contains(skill)) {
         score += 1;
       }
     }
     return score;
   }
-
+  
+  @Override
+  public String toString() {
+    return "<Assignement for" + this.slot.getId() + ">";
+  }
 }

+ 11 - 9
src/main/java/fr/jaquin/bdlg/planner/domain/MealAssignement.java

@@ -1,16 +1,18 @@
 package fr.jaquin.bdlg.planner.domain;
 
 import java.time.LocalDateTime;
-
+import org.optaplanner.core.api.domain.entity.PlanningEntity;
 import org.optaplanner.core.api.domain.variable.PlanningVariable;
 
+@PlanningEntity
 public class MealAssignement {
   private int id;
   private Volonteer volonteer;
 
   @PlanningVariable(valueRangeProviderRefs = "mealslotRange")
-  private MealSlot MealSlot;
+  private MealSlot mealSlot;
 
+  public MealAssignement(){}
   public MealAssignement(Volonteer volonteer) {
     this.volonteer = volonteer;
   }
@@ -18,7 +20,7 @@ public class MealAssignement {
   public MealAssignement(int id, MealSlot MealSlot, Volonteer volonteer) {
     this.id = id;
     this.volonteer = volonteer;
-    this.MealSlot = MealSlot;
+    this.mealSlot = MealSlot;
   }
   // ********************************
   // Getters and setters
@@ -37,25 +39,25 @@ public class MealAssignement {
   }
 
   public MealSlot getMealSlot() {
-    return this.MealSlot;
+    return this.mealSlot;
   }
 
   public void setMealSlot(MealSlot MealSlot) {
-    this.MealSlot = MealSlot;
+    this.mealSlot = MealSlot;
   }
 
   public LocalDateTime getStartDateTime() {
-    if(this.MealSlot==null){
+    if(this.mealSlot==null){
       return LocalDateTime.MIN;
     }
-    return this.MealSlot.getStartTime();
+    return this.mealSlot.getStartTime();
   }
 
   public LocalDateTime getEndDateTime() {
-    if(this.MealSlot==null){
+    if(this.mealSlot==null){
       return LocalDateTime.MIN;
     }
-    return this.MealSlot.getEndTime();
+    return this.mealSlot.getEndTime();
   }
 
 

+ 1 - 1
src/main/java/fr/jaquin/bdlg/planner/domain/Planning.java

@@ -23,7 +23,7 @@ public class Planning {
   @ProblemFactCollectionProperty
   private ArrayList<Timeslot> timeslots;
 
-  @ProblemFactCollectionProperty
+  @PlanningEntityCollectionProperty
   private ArrayList<MealAssignement> mealAssignements;
 
   @PlanningEntityCollectionProperty

+ 3 - 3
src/main/java/fr/jaquin/bdlg/planner/domain/PlanningInput.java

@@ -89,7 +89,7 @@ public class PlanningInput {
         }
       }
       Timeslot newTimeslot =
-          new Timeslot(timeslot.getId(), timeslot.getStartTime(), timeslot.getStartTime(),
+          new Timeslot(timeslot.getId(), timeslot.getStartTime(), timeslot.getEndTime(),
               softCompetencies, hardCompetencies, preferences, timeslot.getMinAttendee());
 
       output.add(newTimeslot);
@@ -162,7 +162,7 @@ public class PlanningInput {
           throw new NullPointerException(
               "Impossible to find the corresponding assignement object." + pair.slotId);
         } else {
-          assignements.add(new Assignement(currentMaxId, timeslot, volonteer));
+          assignements.add(new Assignement(currentMaxId, timeslot, volonteer, false));
           currentMaxId++;
         }
       }
@@ -191,7 +191,7 @@ public class PlanningInput {
       }
     }
     mealAssignements = addMissingMealAssigment(mealAssignements, volonteerList);
-    
+
     // Create Planning problem to be optimized
     Planning outPlanning =
         new Planning(volonteerList, this.mealSlots, timeslots, assignements, mealAssignements);

+ 0 - 28
src/main/java/fr/jaquin/bdlg/planner/solver/AssigmentSolverConfig.xml

@@ -1,28 +0,0 @@
-<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
-  <!-- Define the model -->
-  <solutionClass>fr.jaquin.bdlg.planner.solver.domain.Planning</solutionClass>
-  <entityClass>fr.jaquin.bdlg.planner.solver.domain.MealAssignement</entityClass>
-  <entityClass>fr.jaquin.bdlg.planner.solver.domain.Assignement</entityClass>
-  
-  <scoreDirectorFactory>
-    <constraintProviderClass>fr.jaquin.bdlg.planner.solver.PlanningConstraintProvider</constraintProviderClass>
-  </scoreDirectorFactory>
-
-  <localSearch>
-    <unionMoveSelector>
-    <changeMoveSelector>
-      <entitySelector>
-        <entityClass>fr.jaquin.bdlg.planner.solver.domain.MealAssignement</entityClass>
-      </entitySelector>
-      <valueSelector/>
-    </changeMoveSelector>
-    <changeMoveSelector>
-      <entitySelector>
-        <entityClass>fr.jaquin.bdlg.planner.solver.domain.Assignement</entityClass>
-      </entitySelector>
-      <valueSelector/>
-    </changeMoveSelector>
-  </unionMoveSelector>
-  </localSearch>
-
-</solver>

+ 20 - 2
src/main/java/fr/jaquin/bdlg/planner/solver/PlanningConstraintProvider.java

@@ -28,8 +28,8 @@ public class PlanningConstraintProvider implements ConstraintProvider {
         competencyConflict(constraintFactory), mealMaxAttendee(constraintFactory),
         // Soft constraints are only implemented in the "complete"
         // implementation
-        competencyTeachingEffort(constraintFactory), preferenceApplication(constraintFactory),
-        balanceLoad(constraintFactory)};
+        volunteerMinRestTime(constraintFactory), competencyTeachingEffort(constraintFactory),
+        preferenceApplication(constraintFactory), balanceLoad(constraintFactory)};
   }
 
   private Constraint completeAllTimeslot(ConstraintFactory constraintFactory) {
@@ -62,6 +62,24 @@ public class PlanningConstraintProvider implements ConstraintProvider {
         .penalize("Volonteer conflict", HardSoftScore.ONE_HARD);
   }
 
+  private Constraint volunteerMinRestTime(ConstraintFactory constraintFactory) {
+    // a volonteer cannot go to 2 timeslot without a 30 min break.
+
+    // Select a assignement ...
+    return getAssignedSlotStream(constraintFactory)
+        // ... and pair it with another assignement ...
+        .join(Assignement.class,
+            // ... in the same volunteer ...
+            Joiners.equal(Assignement::getVolonteer),
+            // ... and the pair is unique (different id, no reverse pairs)
+            Joiners.lessThan(Assignement::getId),
+            // get only overlapping timeslot
+            Joiners.lessThan(Assignement::getStartDateTime, Assignement::getRestEndDateTime),
+            Joiners.greaterThan(Assignement::getRestEndDateTime, Assignement::getStartDateTime))
+        // then penalize each pair with a hard weight.
+        .penalize("Volonteer rest time", HardSoftScore.ONE_SOFT);
+  }
+
   private Constraint volunteerMealConflict(ConstraintFactory constraintFactory) {
     // a volonteer cannot go to 2 timeslot at the same time.
 

+ 2 - 2
src/main/resources/application.properties

@@ -6,7 +6,7 @@
 ########################
 
 # The solver runs for 30 seconds. To run for 5 minutes use "5m" and for 2 hours use "2h".
-optaplanner.solver.termination.spent-limit=5s
+optaplanner.solver.termination.spent-limit = 15s
 
 # To change how many solvers to run in parallel
 # optaplanner.solver-manager.parallel-solver-count=4
@@ -17,4 +17,4 @@ optaplanner.solver.termination.spent-limit=5s
 # optaplanner.solver.environment-mode=FULL_ASSERT
 
 # XML file for power tweaking, defaults to solverConfig.xml (directly under src/main/resources)
-# optaplanner.solver-config-xml=org/.../timeTableSolverConfig.xml
+# optaplanner.solver-config-xml=.../src/main/java/fr/jaquin/bdlg/planner/solver/AssigmentSolverConfig.xml

+ 73 - 0
src/main/resources/solverConfig.xml

@@ -0,0 +1,73 @@
+<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
+  <!-- Define the model -->
+  <solutionClass>fr.jaquin.bdlg.planner.domain.Planning</solutionClass>
+  <entityClass>fr.jaquin.bdlg.planner.domain.MealAssignement</entityClass>
+  <entityClass>fr.jaquin.bdlg.planner.domain.Assignement</entityClass>
+
+  <!-- Define the score function -->
+  <scoreDirectorFactory>
+    <constraintProviderClass>fr.jaquin.bdlg.planner.solver.PlanningConstraintProvider</constraintProviderClass>
+  </scoreDirectorFactory>
+
+  <!-- Configure the optimization algorithms (optional) -->
+  <termination>
+    <!-- Official benchmark secondsSpentLimit allowed on: - ge0ffrey's main 
+        pc: sprint 11, medium 700, long 42000 -->
+    <secondsSpentLimit>700</secondsSpentLimit>
+    <!--<bestScoreLimit>-0hard/-999999soft</bestScoreLimit> -->
+  </termination>
+  <constructionHeuristic>
+    <queuedEntityPlacer>
+      <entitySelector>
+        <entityClass>fr.jaquin.bdlg.planner.domain.MealAssignement</entityClass>
+      </entitySelector>
+      <unionMoveSelector>
+        <changeMoveSelector>
+          <entitySelector>
+            <entityClass>fr.jaquin.bdlg.planner.domain.MealAssignement</entityClass>
+          </entitySelector>
+          <valueSelector variableName="mealSlot" />
+        </changeMoveSelector>
+      </unionMoveSelector>
+    </queuedEntityPlacer>
+  </constructionHeuristic>
+
+  <constructionHeuristic>
+    <queuedEntityPlacer>
+      <entitySelector>
+        <entityClass>fr.jaquin.bdlg.planner.domain.Assignement</entityClass>
+      </entitySelector>
+      <unionMoveSelector>
+        <changeMoveSelector>
+          <entitySelector>
+            <entityClass>fr.jaquin.bdlg.planner.domain.Assignement</entityClass>
+          </entitySelector>
+          <valueSelector variableName="volonteer" />
+        </changeMoveSelector>
+      </unionMoveSelector>
+    </queuedEntityPlacer>
+  </constructionHeuristic>
+
+  <localSearch>
+    <unionMoveSelector>
+      <changeMoveSelector>
+        <entitySelector>
+          <entityClass>fr.jaquin.bdlg.planner.domain.MealAssignement</entityClass>
+        </entitySelector>
+        <valueSelector variableName="mealSlot" />
+      </changeMoveSelector>
+      <changeMoveSelector>
+        <entitySelector>
+          <entityClass>fr.jaquin.bdlg.planner.domain.Assignement</entityClass>
+        </entitySelector>
+        <valueSelector variableName="volonteer" />
+      </changeMoveSelector>
+      <swapMoveSelector>
+        <entitySelector>
+          <entityClass>fr.jaquin.bdlg.planner.domain.Assignement</entityClass>
+        </entitySelector>
+      </swapMoveSelector>
+    </unionMoveSelector>
+  </localSearch>
+
+</solver>