From cdb95af18df2fd433ee1e5dc7a37748baa7a968a Mon Sep 17 00:00:00 2001
From: Alexander Kreim
Date: Sat, 10 Feb 2024 12:29:15 +0100
Subject: [PATCH 1/4] Add StudentMoreThanXExamsInYDaysConflict
---
.../StudentMoreThanXExamsInYDaysConflict.java | 386 ++++++++++++++++++
1 file changed, 386 insertions(+)
create mode 100644 src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
diff --git a/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java b/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
new file mode 100644
index 00000000..bc187dc7
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
@@ -0,0 +1,386 @@
+package org.cpsolver.exam.criteria.additional;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.Year;
+import java.time.format.DateTimeFormatter;
+
+import org.cpsolver.exam.criteria.ExamCriterion;
+import org.cpsolver.exam.model.Exam;
+import org.cpsolver.exam.model.ExamModel;
+import org.cpsolver.exam.model.ExamOwner;
+import org.cpsolver.exam.model.ExamPeriod;
+import org.cpsolver.exam.model.ExamPeriodPlacement;
+import org.cpsolver.exam.model.ExamPlacement;
+import org.cpsolver.exam.model.ExamRoomPlacement;
+import org.cpsolver.exam.model.ExamStudent;
+import org.cpsolver.ifs.assignment.Assignment;
+import org.cpsolver.ifs.util.DataProperties;
+/**
+ * Students not more than X exams in Y consecutive days.
+ * It is assumed that all exam periods are within a single year. The information about day and month
+ * of an exam period is read from the day attribute of the period tag (see solver input xml-file).
+ * The weight of the criterion can be set by problem property Exams.StudentsMoreThanXExamsInXDaysWeight,
+ * or in the input xml file, property moreThanXExamsInYDaysWeight
+ * studentStressNbrExams: Maximum number of exams a student should have within a given
+ * number of consecutive days. Can be set by problem property
+ * Exams.StudentStressNbrExams, or in the input xml file, property studentStressNbrExams.
+ * If set to -1 this criterion is disabled. Default value: -1.
+ *
+ * studentStressNbrDays: Number of consecutive days. Can be set by problem property
+ * Exams.StudentStressNbrDays, or in the input xml file, property studentStressNbrDays.
+ * If set to -1 this criterion is disabled. Default value: -1.
+ *
+ * studentStressPeriodDateFormat: Format of the period day string. The default value is "E M/d".
+ *
It can be set by problem property Exams.StudentStressPeriodDateFormat, or in the input xml file,
+ * property studentStressPeriodDateFormat.
+ * See also DateTimeFormatter documentation
+ * for valid format strings.
+ * studentStressExaminationYear Year in which the examinations take place (default: "2023").
+ * It is used for date computations. It should be set if the examination year is a leap year.
+ *
It can be set by problem property Exams.StudentStressExaminationYear, or in the input xml file,
+ * property studentStressExaminationYear.
+ * @author Alexander Kreim
+ */
+public class StudentMoreThanXExamsInYDaysConflict extends ExamCriterion {
+
+ private int iNbrOfDays=-1;
+ private int iNbrOfExams=-1;
+ private String iPeriodDateFormatString="E M/d";
+ private Locale iLocale = Locale.ENGLISH;
+ private Year iExaminationYear = Year.parse("2023");
+ private DateTimeFormatter iDateFormatter =
+ DateTimeFormatter.ofPattern(iPeriodDateFormatString, iLocale);
+
+ @Override
+ public String getName() {
+ return "More Than "
+ + String.valueOf(getNbrOfExams())
+ + " in "
+ + String.valueOf(getNbrOfDays())
+ + " Days";
+ }
+
+ @Override
+ public double getValue(Assignment assignment, ExamPlacement value,
+ Set conflicts) {
+ if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
+ List examStudents = getExamStudents(value);
+ return calcPenalty(assignment, examStudents);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public double getValue(Assignment assignment) {
+ if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
+ List examStudents = ((ExamModel)getModel()).getStudents();
+ return calcPenalty(assignment, examStudents);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public double getValue(Assignment assignment, Collection variables) {
+ if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
+ List studentsInSelection = new ArrayList();
+ for (Iterator examIter = variables.iterator(); examIter.hasNext();) {
+ Exam exam = examIter.next();
+ List examStudents = exam.getStudents();
+ for (Iterator studentIter = examStudents.iterator(); studentIter.hasNext();) {
+ ExamStudent examStudent = studentIter.next();
+ if (!studentsInSelection.contains(examStudent)) {
+ studentsInSelection.add(examStudent);
+ }
+ }
+ }
+ return calcPenalty(assignment, studentsInSelection);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Calculates the penalty for a given set or students.
+ *
+ *
+ * for each student
+ * get all days where the student has exams
+ * determine first and last days of student's exams
+ * start day = first day
+ * while start day <= last day
+ * create test period based on iNbrOfDays beginning at start day
+ * count number of exams in the test period
+ * if number of exams in test period > iNbrOfExams
+ * penalty++
+ * increment start day by one day
+ *
+ * @param assignment
+ * @param examStudents
+ * @return
+ */
+ private int calcPenalty(Assignment assignment, List examStudents) {
+ int penalty = 0;
+ for (Iterator iterator = examStudents.iterator(); iterator.hasNext();) {
+ ExamStudent student = iterator.next();
+ Map nrExamsPerDay = getNrExamsPerDay(student, assignment);
+ MonthDay firstDay = getFirstDay(nrExamsPerDay.keySet());
+ if (firstDay == null) continue;
+ MonthDay lastDay = getLastDay(nrExamsPerDay.keySet());
+ MonthDay startDay = MonthDay.from(firstDay);
+ while (!startDay.isAfter(lastDay)) {
+ int nbrOfExams = countNbrOfExamsInPeriod(startDay, addDays(startDay, getNbrOfDays()), nrExamsPerDay);
+ if (nbrOfExams > getNbrOfExams()) penalty++;
+ startDay = addDays(startDay, 1);
+ }
+ }
+ return penalty;
+ }
+
+ @Override
+ /*
+ * Estimation of the upper bound.
+ *
+ * It is assumed that all enrolled exams of a student are in a row.
+ */
+ public double[] getBounds(Assignment assignment, Collection exams) {
+ double[] bounds = new double[] { 0.0, 0.0 };
+ // get all exam placements for each student enrolled in the given exams
+ Map> allExamPlacements = getStudentExamPlacements(assignment, exams);
+ // Calculate maximum penalty, assuming all student's exams are in a row.
+ bounds[1] = calcMaxPenalty(allExamPlacements);
+ return bounds;
+ }
+
+ private int calcMaxPenalty(Map> studentExamPlacements) {
+ int nbrOfStudentExams = 0;
+ int penalty = 0;
+ for (Map.Entry> studentEnrollments : studentExamPlacements.entrySet()) {
+ // ExamStudent student = studentEnrollments.getKey();
+ Set enrollments = studentEnrollments.getValue();
+ nbrOfStudentExams = enrollments.size();
+ if (nbrOfStudentExams > iNbrOfExams) {
+ penalty += calcMaxStudentPenalty(nbrOfStudentExams);
+ }
+ }
+ return penalty;
+ }
+
+ private int calcMaxStudentPenalty(int nbrOfStudentExams) {
+ // p + iNbrOfExams - 1 >= nbrOfStudentExams
+ // p .. penalty, use min p
+ int p = nbrOfStudentExams + 1 - iNbrOfExams;
+ return p;
+ }
+
+ private HashMap> getStudentExamPlacements(
+ Assignment assignment, Collection exams) {
+
+ HashMap> studentExamPlacements =
+ new HashMap>();
+
+ for (Iterator examIter = exams.iterator(); examIter.hasNext();) {
+ Exam exam = examIter.next();
+ ExamPlacement placement = assignment.getValue(exam);
+ List students = exam.getStudents();
+ for (Iterator studentIter = students.iterator(); studentIter.hasNext();) {
+ ExamStudent student = studentIter.next();
+ if (studentExamPlacements.keySet().isEmpty()) {
+ HashSet placementsAsSet = new HashSet();
+ placementsAsSet.add(placement);
+ studentExamPlacements.put(student, placementsAsSet);
+ continue;
+ }
+ if (!studentExamPlacements.keySet().contains(student)) {
+ HashSet placementsAsSet = new HashSet();
+ placementsAsSet.add(placement);
+ studentExamPlacements.put(student, placementsAsSet);
+ continue;
+ }
+ if (studentExamPlacements.keySet().contains(student)) {
+ studentExamPlacements.get(student).add(placement);
+ }
+ }
+ }
+ return studentExamPlacements;
+ }
+
+ private MonthDay addDays(MonthDay day, int nbrOfDays) {
+ LocalDate newDate = day.atYear(iExaminationYear.getValue()).plusDays(nbrOfDays);
+ return MonthDay.from(newDate);
+ }
+
+ private int countNbrOfExamsInPeriod(MonthDay startDay, MonthDay endDay, Map nrOfExamsPerDay) {
+ int totalNbrOfExams=0;
+ for (Map.Entry entry : nrOfExamsPerDay.entrySet()) {
+ String dayString = entry.getKey();
+ Integer nbrOfExams = entry.getValue();
+ MonthDay currentDate = MonthDay.parse(dayString, iDateFormatter);
+ if ((currentDate.isAfter(startDay)) && (currentDate.isBefore(endDay))) {
+ totalNbrOfExams = totalNbrOfExams + nbrOfExams;
+ }
+ if (currentDate.equals(endDay)) totalNbrOfExams = totalNbrOfExams + nbrOfExams;
+ if (currentDate.equals(startDay)) totalNbrOfExams = totalNbrOfExams + nbrOfExams;
+ }
+ return totalNbrOfExams;
+ }
+
+ private MonthDay getFirstDay(Set dayStrings) {
+ MonthDay firstDay=null;
+
+ for (Iterator iterator = dayStrings.iterator(); iterator.hasNext();) {
+ String dayString = iterator.next();
+ if (firstDay == null) {
+ firstDay = MonthDay.parse(dayString, iDateFormatter);
+ } else {
+ MonthDay currentDay = MonthDay.parse(dayString, iDateFormatter);
+ if (currentDay.isBefore(firstDay)) firstDay=currentDay;
+ }
+ }
+ return firstDay;
+ }
+
+ private MonthDay getLastDay(Set dayStrings) {
+ MonthDay lastDay=null;
+
+ for (Iterator iterator = dayStrings.iterator(); iterator.hasNext();) {
+ String dayString = iterator.next();
+ if (lastDay == null) {
+ lastDay = MonthDay.parse(dayString, iDateFormatter);
+ } else {
+ MonthDay currentDay = MonthDay.parse(dayString, iDateFormatter);
+ if (currentDay.isAfter(lastDay)) lastDay=currentDay;
+ }
+ }
+ return lastDay;
+ }
+
+ private List getAllExamPeriods() {
+ return ((ExamModel)getModel()).getPeriods();
+ }
+
+ private Map getNrExamsPerDay(ExamStudent student, Assignment assignment) {
+ List examPeriods= getAllExamPeriods();
+ HashMap nrExamsPerDay = new HashMap();
+ for (Iterator iterator = examPeriods.iterator(); iterator.hasNext();) {
+ ExamPeriod examPeriod = iterator.next();
+ Set enrolledExams = student.getExamsADay(assignment, examPeriod);
+ if (! enrolledExams.isEmpty()) {
+ String periodDayStr = examPeriod.getDayStr();
+ if (! nrExamsPerDay.keySet().contains(periodDayStr)) {
+ nrExamsPerDay.put(periodDayStr, enrolledExams.size());
+ }
+ }
+ }
+ return nrExamsPerDay;
+ }
+
+ private List getExamStudents(ExamPlacement value) {
+ return value.variable().getStudents();
+ }
+
+ @Override
+ public String getWeightName() {
+ return "Exams.StudentsMoreThanXExamsInXDaysWeight";
+ }
+
+ @Override
+ public String getXmlWeightName() {
+ return "moreThanXExamsInYDaysWeight";
+ }
+
+ @Override
+ public double getWeightDefault(DataProperties config) {
+ return 1.0;
+ }
+
+ @Override
+ public void configure(DataProperties properties) {
+ super.configure(properties);
+ setNbrOfDays(properties.getPropertyInt("Exams.StudentStressNbrDays", iNbrOfDays));
+ setNbrOfExams(properties.getPropertyInt("Exams.StudentStressNbrExams", iNbrOfExams));
+ setPeriodDateFormatString(properties.getProperty("Exams.StudentStressPeriodDateFormat", iPeriodDateFormatString));
+ setExaminationYear(properties.getProperty("Exams.StudentStressExaminationYear", iPeriodDateFormatString));
+ }
+
+ @Override
+ public void getXmlParameters(Map params) {
+ params.put(getXmlWeightName(), String.valueOf(getWeight()));
+ params.put("studentStressNbrExams", String.valueOf(getNbrOfExams()));
+ params.put("studentStressNbrDays", String.valueOf(getNbrOfDays()));
+ params.put("studentStressPeriodDateFormat", getPeriodDateFormatString());
+ params.put("studentStressExaminationYear", getExaminationYear().toString());
+ }
+
+ @Override
+ public void setXmlParameters(Map params) {
+ try {
+ setWeight(Double.valueOf(params.get(getXmlWeightName())));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ try {
+ setNbrOfExams(Integer.valueOf(params.get("studentStressNbrExams")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ try {
+ setNbrOfDays(Integer.valueOf(params.get("studentStressNbrDays")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ try {
+ setPeriodDateFormatString(String.valueOf(params.get("studentStressPeriodDateFormat")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ try {
+ setExaminationYear(String.valueOf(params.get("studentStressExaminationYear")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ }
+
+ public int getNbrOfDays() {
+ return iNbrOfDays;
+ }
+
+ public int getNbrOfExams() {
+ return iNbrOfExams;
+ }
+
+ public String getPeriodDateFormatString() {
+ return iPeriodDateFormatString;
+ }
+
+ public void setPeriodDateFormatString(String dateFormatString) {
+ iPeriodDateFormatString = dateFormatString;
+ iDateFormatter = DateTimeFormatter.ofPattern(iPeriodDateFormatString, iLocale);
+ }
+
+ public void setNbrOfDays(int nbrOfDays) {
+ iNbrOfDays=nbrOfDays;
+ }
+
+ public void setNbrOfExams(int nbrOfExams) {
+ iNbrOfExams=nbrOfExams;
+ }
+
+ public void setExaminationYear(String year) {
+ iExaminationYear = Year.parse(year);
+ }
+
+ public Year getExaminationYear() {
+ return iExaminationYear;
+ }
+
+ @Override
+ public String toString(Assignment assignment) {
+ return "M"
+ + String.valueOf(getNbrOfExams())
+ + "I"
+ + String.valueOf(getNbrOfDays())
+ + "D:" + sDoubleFormat.format(getValue(assignment));
+ }
+}
From f38f1eb585c47019aecc2b81954a5228d96fe07b Mon Sep 17 00:00:00 2001
From: Alexander Kreim <158280011+akrHsH@users.noreply.github.com>
Date: Sat, 5 Apr 2025 11:09:54 +0200
Subject: [PATCH 2/4] Deprecate StudentMoreThanXExamsInYDaysConflict
This class is depecated. There will be a redesign (see workload criteria)
---
.../StudentMoreThanXExamsInYDaysConflict.java | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java b/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
index bc187dc7..d6228887 100644
--- a/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
+++ b/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
@@ -50,7 +50,24 @@
*
It can be set by problem property Exams.StudentStressExaminationYear, or in the input xml file,
* property studentStressExaminationYear.
* @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
*/
+
+@Deprecated
public class StudentMoreThanXExamsInYDaysConflict extends ExamCriterion {
private int iNbrOfDays=-1;
From e059bdff3be6c2baafa72597f6f23ed51a9fb6a5 Mon Sep 17 00:00:00 2001
From: Alexander Kreim
Date: Fri, 25 Apr 2025 15:09:31 +0200
Subject: [PATCH 3/4] Exam workload criteria
- Additional criteria for exam timetabling
- Provides StudentWorkload criterion and InstructorWorkload criterion
- First draft
---
.../workload/InstructorWorkload.java | 306 ++++++++++++++++
.../additional/workload/StudentWorkload.java | 318 ++++++++++++++++
.../workload/WorkloadBaseCriterion.java | 339 ++++++++++++++++++
.../additional/workload/WorkloadEntity.java | 112 ++++++
.../additional/workload/WorkloadUtils.java | 60 ++++
.../additional/workload/package-info.java | 32 ++
6 files changed, 1167 insertions(+)
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/InstructorWorkload.java
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/StudentWorkload.java
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/WorkloadBaseCriterion.java
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/WorkloadEntity.java
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/WorkloadUtils.java
create mode 100644 src/org/cpsolver/exam/criteria/additional/workload/package-info.java
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/InstructorWorkload.java b/src/org/cpsolver/exam/criteria/additional/workload/InstructorWorkload.java
new file mode 100644
index 00000000..c74285d2
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/InstructorWorkload.java
@@ -0,0 +1,306 @@
+package org.cpsolver.exam.criteria.additional.workload;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.Logger;
+import org.cpsolver.exam.model.Exam;
+import org.cpsolver.exam.model.ExamInstructor;
+import org.cpsolver.exam.model.ExamModel;
+import org.cpsolver.exam.model.ExamPlacement;
+import org.cpsolver.ifs.assignment.Assignment;
+import org.cpsolver.ifs.solver.Solver;
+import org.cpsolver.ifs.util.DataProperties;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+
+/**
+ * Instructors should not have more than numberOfExams exams in numberOfDays
+ * consecutive days.
+ *
+ * The goal of this criterion is to minimize instructor workload during exams.
+ *
+ * Only the instructors specified in a xml-file are included.
+ *
+ *
+ * <instructors>
+ * <instructor>
+ * <name>John Doe</name>
+ * <numberOfDays>5</numberOfDays>
+ * <numberOfExams>3</numberOfExams>
+ * </instructor>
+ * ... more instructors ...
+ * </instructors>
+ *
+ *
+ * The path to the xml-file can be set by the property
+ * Exams.Workload.Instructors.XMLFile or in the input xml-file, property
+ * examsWorkloadInstructorXMLFile.
+ *
+ * The weight of the criterion can be set by problem property Exams.Workload.Instructors.Weight,
+ * or in the problem xml-file (input xml file), property examWorkloadInstructorsWeight.
+ *
+ * @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
+ *
+ */
+public class InstructorWorkload extends WorkloadBaseCriterion {
+
+ private static Logger slog =
+ org.apache.logging.log4j.LogManager.getLogger(InstructorWorkload.class);
+
+ private String instructorXMLFile;
+
+ public InstructorWorkload() {
+ super();
+ }
+
+ public String getInstructorXMLFile() {
+ return instructorXMLFile;
+ }
+
+ public void setInstructorXMLFile(String iInstructorXMLFile) {
+ this.instructorXMLFile = iInstructorXMLFile;
+ }
+
+ @Override
+ public boolean init(Solver solver) {
+ // TODO Auto-generated method stub
+ super.init(solver);
+ configure( solver.getProperties() );
+ return true;
+ }
+
+ @Override
+ public double getValue(Assignment assignment, Collection variables) {
+ double penalty = 0.0;
+ if ( (getModel() != null) && (assignment != null) ) {
+ if (examPeriodsAreNotEmpty()) {
+ Set examInstructors = getExamInstructorsFromVariables(variables);
+ setWorkloadFromAssignment(assignment, null);
+
+ for (Iterator iterator = examInstructors.iterator(); iterator.hasNext();) {
+ ExamInstructor examInstructor = iterator.next();
+
+ WorkloadEntity entity = getWorkloadEntityByInstructor(assignment,
+ examInstructor);
+ if (entity != null) {
+ penalty += entity.getWorkload();
+ }
+ }
+ }
+ }
+ return penalty;
+ }
+
+ private Set getExamInstructorsFromVariables(Collection variables) {
+ Set examInstructors= new HashSet();
+ for (Iterator iterator = variables.iterator(); iterator.hasNext();) {
+ Exam exam = iterator.next();
+ List instructors = exam.getInstructors();
+ examInstructors.addAll(instructors);
+ }
+ return examInstructors;
+ }
+
+ /**
+ * Creates Workload Entities
+ */
+ @Override
+ protected List createWorkLoadEntities() {
+ if (getInstructorXMLFile() != null ) {
+ List entities = readInstructorsFromXMLFile();
+ slog.debug("Found " + entities.size() + " instructors.");
+ if ( examPeriodsAreNotEmpty() ) {
+ int numbrOfDays = ((ExamModel)getModel()).getNrDays();
+ for (Iterator iterator = entities.iterator(); iterator.hasNext();) {
+ WorkloadEntity entity = iterator.next();
+ entity.resetLoadPerDay(numbrOfDays);
+ }
+ }
+ slog.info("Instructor Workload: Created " + entities.size() + " entities. This should happen only once (per solver).");
+ return entities;
+ }
+ // slog.debug("Instructors XML-File is not set.");
+ return null;
+ }
+
+ private List readInstructorsFromXMLFile() {
+ XmlParser xmlParser = new XmlParser();
+ slog.debug("Read Instructors from file " + getInstructorXMLFile() );
+ return xmlParser.parse(new File(instructorXMLFile));
+ }
+
+ @Override
+ protected void setWorkloadFromAssignment(Assignment assignment, ExamPlacement value) {
+ if (examPeriodsAreNotEmpty() && (assignment != null)) {
+
+ List allExamInstructors = ((ExamModel) getModel()).getInstructors();
+ int nbrOfExamDays = ((ExamModel) getModel()).getNrDays();
+
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+ List entities = workloadContext.getEntityList();
+ if (entities == null) {
+ List newEntities = createWorkLoadEntities();
+ workloadContext.setEntityList(newEntities);
+ workloadContext.calcTotalFromAssignment(assignment);
+ }
+
+ for (Iterator examInsIter = allExamInstructors.iterator(); examInsIter.hasNext();) {
+ ExamInstructor examInstructor = examInsIter.next();
+ WorkloadEntity entity = getWorkloadEntityByInstructor(assignment, examInstructor);
+ if (entity != null) {
+ entity.resetLoadPerDay(nbrOfExamDays);
+ List loadPerDay = entity.getLoadPerDay();
+ for (int dayIndex = 0; dayIndex < nbrOfExamDays; dayIndex++) {
+ Set examsADay = examInstructor.getExamsADay(assignment, dayIndex);
+ int nbrOfExamsADay = examsADay.size();
+ if (value != null) {
+ Exam examInValue = value.variable();
+ if (examsADay.contains(examInValue)) {
+ nbrOfExamsADay = nbrOfExamsADay - 1;
+ }
+ }
+ loadPerDay.set(dayIndex, nbrOfExamsADay);
+ }
+ }
+ }
+ }
+ }
+
+ protected WorkloadEntity getWorkloadEntityByInstructor(Assignment assignment,
+ ExamInstructor examInstructor) {
+
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+
+ List entityList = workloadContext.getEntityList();
+ if (entityList != null) {
+ for (Iterator iterator = entityList.iterator(); iterator.hasNext();) {
+ WorkloadEntity entity = iterator.next();
+ if (entity.getName().equals(examInstructor.getName())) {
+ return entity;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void setDaysToInkrementWorkloadFromValue(Assignment assignment,
+ ExamPlacement value) {
+ if ( examPeriodsAreNotEmpty() && (value != null) ) {
+ Exam exam = value.variable();
+ List examInstructors = exam.getInstructors();
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+ workloadContext.resetDaysToInkrementWorkload();
+ for (Iterator iterator = examInstructors.iterator(); iterator.hasNext();) {
+ ExamInstructor examInstructor = iterator.next();
+ WorkloadEntity entity = getWorkloadEntityByInstructor(assignment,
+ examInstructor);
+ if (entity != null) {
+ Set daysToInkrement =
+ (workloadContext.getDaysToInkrementWorkload()).get(entity);
+ daysToInkrement.add(value.getPeriod().getDay());
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString(Assignment assignment) {
+ return "I WL:" + sDoubleFormat.format(getValue(assignment));
+ }
+
+ @Override
+ public String getName() {
+ return "Instructor Workload";
+ }
+
+ @Override
+ public String getWeightName() {
+ return "Exams.Workload.Instructors.Weight";
+ }
+
+ @Override
+ public String getXmlWeightName() {
+ return "examsWorkloadInstructorsWeight";
+ }
+
+ @Override
+ public double getWeightDefault(DataProperties config) {
+ return 1.0;
+ }
+
+ @Override
+ public void configure(DataProperties properties) {
+ super.configure(properties);
+ setInstructorXMLFile(properties.getProperty("Exams.Workload.Instructors.XMLFile", null));
+ if (getInstructorXMLFile() == null) {
+ slog.warn("Instructor XML file not set");
+ }
+ }
+
+ @Override
+ public void getXmlParameters(Map params) {
+ super.getXmlParameters(params);
+ params.put("examsWorkloadInstructorXMLFile", getInstructorXMLFile());
+ }
+
+ @Override
+ public void setXmlParameters(Map params) {
+ super.setXmlParameters(params);
+ try {
+ setInstructorXMLFile(params.get("examsWorkloadInstructorXMLFile"));
+ }
+ catch (NumberFormatException e) {}
+ catch (NullPointerException e) {}
+ }
+
+ protected class XmlParser {
+ public List parse(File xmlFile) {
+ SAXReader reader = new SAXReader();
+ List entities = new ArrayList();
+ try {
+ Document document = reader.read(xmlFile);
+ Element rootElement = document.getRootElement();
+ List instructorElements = rootElement.elements("instructor");
+
+ for (Element instructorElement: instructorElements) {
+ WorkloadEntity entity = new WorkloadEntity();
+ entity.setName(instructorElement.elementText("name"));
+ entity.setNbrOfDays(Integer.parseInt(instructorElement.elementText("numberOfDays")));
+ entity.setNbrOfExams(Integer.parseInt(instructorElement.elementText("numberOfExams")));
+ entities.add(entity);
+ slog.debug("Added Instructor" + entity.getName()
+ + " nbrOfDays: " + entity.getNbrOfDays()
+ + " nbrOfExams: " + entity.getNbrOfExams());
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return entities;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/StudentWorkload.java b/src/org/cpsolver/exam/criteria/additional/workload/StudentWorkload.java
new file mode 100644
index 00000000..41482d12
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/StudentWorkload.java
@@ -0,0 +1,318 @@
+package org.cpsolver.exam.criteria.additional.workload;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.Year;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.logging.log4j.Logger;
+import org.cpsolver.exam.criteria.ExamCriterion;
+import org.cpsolver.exam.criteria.additional.workload.WorkloadBaseCriterion.WorkloadContext;
+import org.cpsolver.exam.model.Exam;
+import org.cpsolver.exam.model.ExamInstructor;
+import org.cpsolver.exam.model.ExamModel;
+import org.cpsolver.exam.model.ExamOwner;
+import org.cpsolver.exam.model.ExamPeriod;
+import org.cpsolver.exam.model.ExamPeriodPlacement;
+import org.cpsolver.exam.model.ExamPlacement;
+import org.cpsolver.exam.model.ExamRoomPlacement;
+import org.cpsolver.exam.model.ExamStudent;
+import org.cpsolver.ifs.assignment.Assignment;
+import org.cpsolver.ifs.solver.Solver;
+import org.cpsolver.ifs.util.DataProperties;
+/**
+ * Students should not have more than nbrOfExams exams in nbrOfDays
+ * consecutive days.
+ *
+ * The goal of this criterion is to minimize student workload during exams.
+ *
+ * The number of days nbrOfDays can be set by problem property
+ * Exams.Workload.Students.NbrDays, or in the input xml file, property examsWorkloadStudentsNbrDays.
+ *
+ * The number of exams nbrOfExams can be set by problem property Exams.Workload.Students.NbrExams,
+ * or in the input xml file, property examsWorkloadStudentsNbrExams.
+ *
+ * The weight of the criterion can be set by problem property Exams.Workload.Student.Weight,
+ * or in the input xml file, property examsWorkloadStudentsWeight.
+ *
+ * @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
+ *
+ *
+ */
+public class StudentWorkload extends WorkloadBaseCriterion {
+
+ private static Logger slog = org.apache.logging.log4j.LogManager.getLogger(StudentWorkload.class);
+
+ private int nbrOfDays;
+ private int nbrOfExams;
+ private int nbrOfDaysDefault = 6;
+ private int nbrOfExamsDefault = 2;
+
+ public StudentWorkload() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "Student Workload";
+ }
+
+ @Override
+ public String getWeightName() {
+ return "Exams.Workload.Student.Weight";
+ }
+
+ @Override
+ public String getXmlWeightName() {
+ return "examsWorkloadStudentsWeight";
+ }
+
+ @Override
+ public double getWeightDefault(DataProperties config) {
+ return 1.0;
+ }
+
+ @Override
+ public void configure(DataProperties properties) {
+ super.configure(properties);
+ setNbrOfDays(properties.getPropertyInt("Exams.Workload.Students.NbrDays", getNbrOfDaysDefault()));
+ setNbrOfExams(properties.getPropertyInt("Exams.Workload.Students.NbrExams", getNbrOfExamsDefault()));
+ }
+
+
+ @Override
+ public boolean init(Solver solver) {
+ // TODO Auto-generated method stub
+ super.init(solver);
+ configure( solver.getProperties() );
+ return true;
+ }
+
+ @Override
+ public void getXmlParameters(Map params) {
+ params.put(getXmlWeightName(), String.valueOf(getWeight()));
+ params.put("examsWorkloadStudentsNbrExams", String.valueOf(getNbrOfExams()));
+ params.put("examsWorkloadStudentNbrDays", String.valueOf(getNbrOfDays()));
+ }
+
+ @Override
+ public void setXmlParameters(Map params) {
+ super.setXmlParameters(params);
+ try {
+ setNbrOfExams(Integer.valueOf(params.get("examsWorkloadStudentsNbrExams")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ try {
+ setNbrOfDays(Integer.valueOf(params.get("examsWorkloadStudentNbrDays")));
+ } catch (NumberFormatException e) {} catch (NullPointerException e) {}
+ }
+
+ @Override
+ public String toString(Assignment assignment) {
+ return "S WL:" + sDoubleFormat.format(getValue(assignment));
+ }
+
+ public int getNbrOfDays() {
+ if (nbrOfDays == 0) {
+ return getNbrOfDaysDefault();
+ }
+ return nbrOfDays;
+ }
+
+ public void setNbrOfDays(int nbrOfDays) {
+ this.nbrOfDays = nbrOfDays;
+ }
+
+ public int getNbrOfExams() {
+ if (nbrOfExams == 0) {
+ return getNbrOfExamsDefault();
+ }
+ return nbrOfExams;
+ }
+
+ public void setNbrOfExams(int nbrOfExams) {
+ this.nbrOfExams = nbrOfExams;
+ }
+
+ public int getNbrOfExamsDefault() {
+ return nbrOfExamsDefault;
+ }
+
+ public int getNbrOfDaysDefault() {
+ return nbrOfDaysDefault;
+ }
+
+ @Override
+ protected void setDaysToInkrementWorkloadFromValue(Assignment assignment,
+ ExamPlacement value) {
+
+ if (examPeriodsAreNotEmpty() && (value != null) ) {
+
+ Exam exam = value.variable();
+ List examStudents = exam.getStudents();
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+ workloadContext.resetDaysToInkrementWorkload();
+
+ for (Iterator iterator = examStudents.iterator(); iterator.hasNext();) {
+ ExamStudent examStudent = iterator.next();
+ WorkloadEntity entity = getWorkloadEntityByStudent(assignment,
+ examStudent);
+
+ if (entity != null) {
+ Set daysToInkrement =
+ (workloadContext.getDaysToInkrementWorkload()).get(entity);
+ daysToInkrement.add(value.getPeriod().getDay());
+ }
+ }
+ }
+ }
+
+ @Override
+ protected List createWorkLoadEntities() {
+
+ if (examPeriodsAreNotEmpty() ) {
+ List entities = new ArrayList();
+
+ ExamModel model = (ExamModel) getModel();
+ List students = model.getStudents();
+
+ int nbrOfExamDays = 0;
+ if (examPeriodsAreNotEmpty()) {
+ nbrOfExamDays = model.getNrDays();
+ }
+
+ if (students.size() == 0) {
+ slog.debug("The model contains no students");
+ }
+
+ for (Iterator iterator = students.iterator(); iterator.hasNext();) {
+ ExamStudent student = iterator.next();
+ WorkloadEntity entity = new WorkloadEntity();
+ entity.setName(student.getName());
+ entity.setNbrOfDays(getNbrOfDays());
+ entity.setNbrOfExams(getNbrOfExams());
+ entity.initLoadPerDay(nbrOfExamDays);
+ entities.add(entity);
+
+ // slog.debug("Added new workload entity: " + entity.getName());
+ }
+ slog.info("Student Workload: Added " + entities.size()
+ + " entities. This should happen only once (per solver).");
+
+ return entities;
+ }
+ return null;
+ }
+
+ @Override
+ protected void setWorkloadFromAssignment(Assignment assignment, ExamPlacement value) {
+
+ if (examPeriodsAreNotEmpty() && (assignment != null)) {
+
+ List allExamStudents = ((ExamModel) getModel()).getStudents();
+ int nbrOfExamDays = ((ExamModel) getModel()).getNrDays();
+
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+ List entities = workloadContext.getEntityList();
+ if (entities == null) {
+ List newEntities = createWorkLoadEntities();
+ workloadContext.setEntityList(newEntities);
+ workloadContext.calcTotalFromAssignment(assignment);
+ }
+
+ for (Iterator examStudIter = allExamStudents.iterator(); examStudIter.hasNext();) {
+ ExamStudent examStudent = examStudIter.next();
+ WorkloadEntity entity = getWorkloadEntityByStudent(assignment, examStudent);
+ if (entity != null) {
+ entity.resetLoadPerDay(nbrOfExamDays);
+ List loadPerDay = entity.getLoadPerDay();
+ for (int dayIndex = 0; dayIndex < nbrOfExamDays; dayIndex++) {
+ Set examsADay = examStudent.getExamsADay(assignment, dayIndex);
+ int nbrOfExamsADay = examsADay.size();
+ if (value != null) {
+ Exam examInValue = value.variable();
+ if (examsADay.contains(examInValue)) {
+ nbrOfExamsADay = nbrOfExamsADay - 1;
+ }
+ }
+ loadPerDay.set(dayIndex, nbrOfExamsADay);
+ }
+ }
+ }
+ }
+ }
+
+ public WorkloadEntity getWorkloadEntityByStudent(Assignment assignment,
+ ExamStudent examStudent) {
+
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+
+ List entityList = workloadContext.getEntityList();
+ if (entityList != null) {
+ for (Iterator iterator = entityList.iterator(); iterator.hasNext();) {
+ WorkloadEntity entity = iterator.next();
+ if (entity != null ) {
+ if (entity.getName().equals(examStudent.getName())) {
+ return entity;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public double getValue(Assignment assignment, Collection variables) {
+ double penalty = 0.0;
+ if ( (getModel() != null) && (assignment != null) ) {
+ if (examPeriodsAreNotEmpty()) {
+ Set examStudents = getExamStudentsFromVariables(variables);
+ setWorkloadFromAssignment(assignment, null);
+
+ for (Iterator iterator = examStudents.iterator(); iterator.hasNext();) {
+ ExamStudent examStudent = iterator.next();
+ WorkloadEntity entity = getWorkloadEntityByStudent(assignment,
+ examStudent);
+ if (entity != null) {
+ penalty += entity.getWorkload();
+ }
+ }
+ }
+ // slog.info("Method getValue(assignment, variables) called: " + String.valueOf(penalty));
+ }
+ return penalty;
+ }
+
+ private Set getExamStudentsFromVariables(Collection variables) {
+ Set examStudents = new HashSet();
+ for (Iterator iterator = variables.iterator(); iterator.hasNext();) {
+ Exam exam = iterator.next();
+ List students = exam.getStudents();
+ examStudents.addAll(students);
+ }
+ return examStudents;
+ }
+}
\ No newline at end of file
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/WorkloadBaseCriterion.java b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadBaseCriterion.java
new file mode 100644
index 00000000..4462e342
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadBaseCriterion.java
@@ -0,0 +1,339 @@
+package org.cpsolver.exam.criteria.additional.workload;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.Logger;
+import org.cpsolver.coursett.criteria.additional.InstructorFairness.InstructorFairnessContext;
+import org.cpsolver.coursett.model.Lecture;
+import org.cpsolver.coursett.model.Placement;
+import org.cpsolver.exam.criteria.ExamCriterion;
+import org.cpsolver.exam.model.Exam;
+import org.cpsolver.exam.model.ExamModel;
+import org.cpsolver.exam.model.ExamPeriod;
+import org.cpsolver.exam.model.ExamPlacement;
+import org.cpsolver.ifs.assignment.Assignment;
+import org.cpsolver.ifs.criteria.AbstractCriterion.ValueContext;
+import org.cpsolver.ifs.model.Model;
+import org.cpsolver.ifs.solver.Solver;
+
+/**
+ * Base class for implementing a workload criterion.
+ *
+ * @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
+ */
+public abstract class WorkloadBaseCriterion extends ExamCriterion {
+
+ private static Logger slog = org.apache.logging.log4j.LogManager.getLogger(WorkloadBaseCriterion.class);
+
+ public WorkloadBaseCriterion() {
+ super();
+ setValueUpdateType(ValueUpdateType.AfterUnassignedBeforeAssigned);
+ }
+
+ @Override
+ public void setModel(Model model) {
+ super.setModel(model);
+ }
+
+ @Override
+ public boolean init(Solver solver) {
+ boolean retval = super.init(solver);
+ return retval;
+ }
+
+ @Override
+ public abstract double getValue(Assignment assignment,
+ Collection variables);
+
+ @Override
+ public double getValue(Assignment assignment) {
+ WorkloadContext workLoadContext= getWorkLoadContext(assignment);
+ double totalValue = workLoadContext.calcTotalFromAssignment(assignment);
+ // slog.info("Method getValue(assignment) called: " + String.valueOf(totalValue));
+ return totalValue;
+ }
+
+ /**
+ * Tests if there are exam periods
+ * @return
+ */
+ protected boolean examPeriodsAreNotEmpty() {
+ ExamModel examMpdel = (ExamModel)this.getModel();
+ List examPeriods = examMpdel.getPeriods();
+ if ( (examPeriods != null ) && ( examPeriods.size() > 0 ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Calculates changes in the criterion value.
+ *
+ * Step 1: Calculate workload for all entities before the assignment
+ * of the placement value.
+ *
+ * Step 2: Calculate the workload for all entities including the
+ * placement value.
+ *
+ * Step 3: Return the difference
+ *
+ * see {@link org.cpsolver.ifs.criteria.AbstractCriterion}
+ */
+ @Override
+ public double getValue(Assignment assignment,
+ ExamPlacement value,
+ Set conflicts) {
+
+ double penalty = 0.0;
+
+ // if (assignment.assignedValues().contains(value)) {
+ // slog.warn("Value is in assignment.");
+ // }
+
+ if ( examPeriodsAreNotEmpty() ) {
+
+ WorkloadContext workloadContext = getWorkLoadContext(assignment);
+
+ setWorkloadFromAssignment(assignment, value);
+ setDaysToInkrementWorkloadFromValue(assignment, value);
+
+ penalty = workloadContext.calcPenalty();
+
+ workloadContext.resetDaysToInkrementWorkload();
+
+ // slog.info("Workload Criterion (penalty/value before): (" + penalty + "/" + workloadContext.getTotal() + ")" );
+ }
+
+ return penalty;
+ }
+
+ @Override
+ public double[] getBounds(Assignment assignment,
+ Collection exams) {
+ double[] bounds = new double[] { 0.0, 0.0 };
+ return bounds;
+ }
+
+ /**
+ * Set the entities and days to increment the workload.
+ */
+ protected abstract void setDaysToInkrementWorkloadFromValue(
+ Assignment assignment,
+ ExamPlacement value);
+
+ /**
+ * Sets the workload of all entities.
+ * @param assignment
+ */
+ protected abstract void setWorkloadFromAssignment(
+ Assignment assignment,
+ ExamPlacement value);
+
+ /**
+ * Creates workload entities.
+ * @return
+ */
+ protected abstract List createWorkLoadEntities();
+
+ /**
+ * String representation of all workloads
+ * @return Lines containing Name,number of exams first day,...,workload value
+ */
+ public String toCSVString(Assignment assignment) {
+ String retval = "";
+ List entities = getWorkLoadContext(assignment).getEntityList();
+ if (entities != null) {
+ for (Iterator iterator = entities.iterator(); iterator.hasNext();) {
+ WorkloadEntity workloadEntity = iterator.next();
+ if (workloadEntity.getLoadPerDay() != null) {
+ retval += workloadEntity.toCSVString() + "\n";
+ }
+ }
+ }
+ return retval;
+ }
+
+ @Override
+ public ValueContext createAssignmentContext(
+ Assignment assignment) {
+ return new WorkloadContext(assignment);
+ }
+
+ protected WorkloadContext getWorkLoadContext(
+ Assignment assignment) {
+ return (WorkloadContext) getContext(assignment);
+ }
+
+ /*
+ * Assignment related attributes
+ */
+ public class WorkloadContext extends ValueContext {
+
+ private List entityList;
+ private Map> daysToInkrementWorkload;
+
+ protected WorkloadContext() {}
+
+ public WorkloadContext(Assignment assignment) {}
+
+ protected Map> getDaysToInkrementWorkload() {
+ return daysToInkrementWorkload;
+ }
+
+ /**
+ * Getter attribute entityList
+ */
+ protected List getEntityList() {
+ return entityList;
+ }
+
+ /**
+ * Setter attribute entityList
+ */
+ protected void setEntityList(List entityList) {
+ this.entityList = entityList;
+ }
+
+ /**
+ * Marks days on which the workload is to be incremented.
+ * @param entity
+ * @param daysToInkrement
+ */
+ protected void setDaysToInkrementWorkloadForEntity(WorkloadEntity entity, Set daysToInkrement) {
+ if ( (this.getDaysToInkrementWorkload() == null) || (this.getDaysToInkrementWorkload().size() == 0) ) {
+ this.initDaysToIncrementWorkload();
+ }
+ this.daysToInkrementWorkload.put(entity, daysToInkrement);
+ }
+
+ protected void resetDaysToInkrementWorkload() {
+
+ if ( (this.getDaysToInkrementWorkload() != null) && (this.getDaysToInkrementWorkload().size() > 0) ) {
+ for (Iterator iterator =
+ this.getDaysToInkrementWorkload().keySet().iterator(); iterator.hasNext();) {
+
+ WorkloadEntity entity = iterator.next();
+ Set daysToInkrement = this.getDaysToInkrementWorkload().get(entity);
+ if (daysToInkrement == null) {
+ this.getDaysToInkrementWorkload().put(entity, new HashSet());
+ } else {
+ daysToInkrement.clear();
+ }
+ }
+ } else {
+ initDaysToIncrementWorkload();
+ }
+ }
+
+ private void initDaysToIncrementWorkload() {
+ this.daysToInkrementWorkload = new HashMap>();
+ if (entityList != null) {
+ for (Iterator iterator = entityList.iterator(); iterator.hasNext();) {
+ WorkloadEntity workloadEntity = iterator.next();
+ this.daysToInkrementWorkload.put(workloadEntity, new HashSet());
+ }
+ }
+ }
+
+ public double calcTotalFromAssignment(Assignment assignment) {
+ double retval = 0.0;
+ if (WorkloadBaseCriterion.this.examPeriodsAreNotEmpty()) {
+
+ if ((getEntityList() == null) || (getEntityList().size() == 0)) {
+ List newEntities = WorkloadBaseCriterion.this.createWorkLoadEntities();
+ if (newEntities != null) {
+ this.setEntityList(newEntities);
+ }
+ }
+
+ if (getEntityList() != null) {
+ setWorkloadFromAssignment(assignment, null);
+
+ for (Iterator iterator = entityList.iterator(); iterator.hasNext();) {
+ WorkloadEntity workloadEntity = iterator.next();
+ retval += workloadEntity.getWorkload();
+ }
+
+ }
+ }
+ // this.setTotal(retval);
+ return retval;
+ }
+
+ /**
+ * Calculates the value change in the criterion
+ * @return
+ */
+ protected double calcPenalty() {
+ double penalty = 0.0;
+ if (this.entityList != null) {
+ Iterator entityIter = this.getEntityList().iterator();
+ while(entityIter.hasNext()) {
+ WorkloadEntity entity = entityIter.next();
+ Set daysToInkrement = this.getDaysToInkrementWorkload().get(entity);
+ if (daysToInkrement != null ) {
+ penalty += calcPenaltyForEntity(entity, daysToInkrement);
+ }
+ }
+ }
+ return penalty;
+ }
+
+ /**
+ * Calculates the criterion's value change for a single workload entity
+ * @param entity
+ * @param daysToIncrement
+ * @return
+ */
+ protected int calcPenaltyForEntity(WorkloadEntity entity, Set daysToIncrement) {
+ int penaltyBeforeAssignment = entity.getWorkload() ;
+
+// String debugIncrements = "";
+// if (daysToIncrement.size() > 0) {
+// slog.debug( entity.toCSVString() );
+// debugIncrements = "Days to increment: ";
+// for (Iterator iterator = daysToIncrement.iterator(); iterator.hasNext();) {
+// Integer dayIndex = iterator.next();
+// debugIncrements += dayIndex.toString() + ", ";
+// }
+// }
+
+ for (Iterator dayIter = daysToIncrement.iterator(); dayIter.hasNext();) {
+ int dayIndex = dayIter.next();
+ entity.incrementItemWorkloadForDay(dayIndex);
+ }
+
+ int penaltyAfterAssignment = entity.getWorkload();
+ int penalty = penaltyAfterAssignment - penaltyBeforeAssignment;
+
+// if (daysToIncrement.size() > 0) {
+// debugIncrements += " Penalty: " + String.valueOf(penalty);
+// slog.debug( debugIncrements );
+// slog.debug( entity.toCSVString() );
+// }
+
+ return penalty;
+ }
+ }
+}
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/WorkloadEntity.java b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadEntity.java
new file mode 100644
index 00000000..b15dd63f
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadEntity.java
@@ -0,0 +1,112 @@
+package org.cpsolver.exam.criteria.additional.workload;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Entity for workload calculations.
+ *
+ * Models entities with a daily workload.
+ *
+ * @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see http://www.gnu.org/licenses/.
+ */
+public class WorkloadEntity {
+
+ private int nbrOfDays;
+ private int nbrOfExams;
+ private String name;
+ private List loadPerDay;
+
+ public WorkloadEntity() {}
+
+ public int getNbrOfDays() {
+ return nbrOfDays;
+ }
+ public void setNbrOfDays(int nbrOfDays) {
+ this.nbrOfDays = nbrOfDays;
+ }
+ public int getNbrOfExams() {
+ return nbrOfExams;
+ }
+ public void setNbrOfExams(int nbrOfExams) {
+ this.nbrOfExams = nbrOfExams;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public List getLoadPerDay() {
+ return loadPerDay;
+ }
+
+ public void setLoadPerDay(List loadPerDay) {
+ this.loadPerDay = loadPerDay;
+ }
+
+ public void initLoadPerDay(int nbrOfExamDays) {
+ this.loadPerDay = new ArrayList();
+ for (int i = 0; i < nbrOfExamDays; i++) {
+ loadPerDay.add(0);
+ }
+ }
+
+ public void incrementItemWorkloadForDay(int dayIndex) {
+ int newDayLoad = loadPerDay.get(dayIndex) + 1;
+ loadPerDay.set(dayIndex, newDayLoad);
+ }
+
+ public int getWorkload() {
+ int workload = 0;
+ if (loadPerDay != null ) {
+ List rollingSums = WorkloadUtils.rollingSumInt(loadPerDay, nbrOfDays);
+ workload = WorkloadUtils.numberOfValuesLargerThanThreshold(rollingSums, nbrOfExams);
+ }
+ return workload;
+ }
+
+ public String toCSVString() {
+ if (loadPerDay != null) {
+ String retval = "";
+ retval += getName() + ",";
+ for (Iterator iterator = loadPerDay.iterator(); iterator.hasNext();) {
+ Integer load = iterator.next();
+ retval += load.toString() + ",";
+ }
+ retval += getWorkload();
+ return retval;
+ }
+ return null;
+ }
+
+ public void resetLoadPerDay(int nbrOfExamDays) {
+ if (this.getLoadPerDay() == null) {
+ this.initLoadPerDay(nbrOfExamDays);
+ } else {
+ if (this.getLoadPerDay().size() != nbrOfExamDays) {
+ this.initLoadPerDay(nbrOfExamDays);
+ } else {
+ for (int i = 0; i < getLoadPerDay().size(); i++) {
+ getLoadPerDay().set(i, 0);
+ }
+ }
+ }
+ }
+}
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/WorkloadUtils.java b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadUtils.java
new file mode 100644
index 00000000..9098dff6
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/WorkloadUtils.java
@@ -0,0 +1,60 @@
+package org.cpsolver.exam.criteria.additional.workload;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tools for calculating the workload.
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
+ */
+public class WorkloadUtils {
+
+ /**
+ * Calculate rolling sums
+ * @param listOfIntValues
+ * @param windowLength
+ * @return List with the rolling totals
+ */
+ public static List rollingSumInt(List listOfIntValues, int windowLength) {
+ int sum = 0;
+ List rollingSums = new ArrayList();
+ for (int i = 0; i < listOfIntValues.size(); i++) {
+ sum += listOfIntValues.get(i);
+ if (i >= windowLength) {
+ rollingSums.add(sum);
+ sum -= listOfIntValues.get(i - windowLength);
+ }
+ }
+ return rollingSums;
+ }
+
+ /**
+ * Used to calculate the workload.
+ * @param listOfIntValues
+ * @param threshold
+ * @return number of values larger than the threshold
+ */
+ public static int numberOfValuesLargerThanThreshold(List listOfIntValues, int threshold) {
+ int count = 0;
+ for (int number: listOfIntValues) {
+ if (number > threshold) {
+ count++;
+ }
+ }
+ return count;
+ }
+}
diff --git a/src/org/cpsolver/exam/criteria/additional/workload/package-info.java b/src/org/cpsolver/exam/criteria/additional/workload/package-info.java
new file mode 100644
index 00000000..4e77eacd
--- /dev/null
+++ b/src/org/cpsolver/exam/criteria/additional/workload/package-info.java
@@ -0,0 +1,32 @@
+/**
+ * Additional exam criteria to control the workload.
+ *
+ * The workload is calculated as follows:
+ *
+ * Step 1: Calculate the daily exam load. The result is a list of integers representing the daily exam load.
+ * This list's length is equal to the number of exam days. The entry at position i represents
+ * the number of exams on the i-th day.
+ *
+ * Step 2: Calculate rolling sums of length numberOfDays using the list of daily exam loads.
+ * The result is a list of rolling sums.
+ *
+ * Step 3: The workload equals the number of rolling sums that exceed the threshold numberOfExams.
+ *
+ * @author Alexander Kreim
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not see
+ * http://www.gnu.org/licenses/.
+ */
+package org.cpsolver.exam.criteria.additional.workload;
\ No newline at end of file
From 748846f46304249c8349f25f469807c551c7df4c Mon Sep 17 00:00:00 2001
From: Alexander Kreim
Date: Fri, 25 Apr 2025 15:23:31 +0200
Subject: [PATCH 4/4] Delete deprecated class
deleted: src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
---
.../StudentMoreThanXExamsInYDaysConflict.java | 403 ------------------
1 file changed, 403 deletions(-)
delete mode 100644 src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
diff --git a/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java b/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
deleted file mode 100644
index d6228887..00000000
--- a/src/org/cpsolver/exam/criteria/additional/StudentMoreThanXExamsInYDaysConflict.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package org.cpsolver.exam.criteria.additional;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.time.LocalDate;
-import java.time.MonthDay;
-import java.time.Year;
-import java.time.format.DateTimeFormatter;
-
-import org.cpsolver.exam.criteria.ExamCriterion;
-import org.cpsolver.exam.model.Exam;
-import org.cpsolver.exam.model.ExamModel;
-import org.cpsolver.exam.model.ExamOwner;
-import org.cpsolver.exam.model.ExamPeriod;
-import org.cpsolver.exam.model.ExamPeriodPlacement;
-import org.cpsolver.exam.model.ExamPlacement;
-import org.cpsolver.exam.model.ExamRoomPlacement;
-import org.cpsolver.exam.model.ExamStudent;
-import org.cpsolver.ifs.assignment.Assignment;
-import org.cpsolver.ifs.util.DataProperties;
-/**
- * Students not more than X exams in Y consecutive days.
- * It is assumed that all exam periods are within a single year. The information about day and month
- * of an exam period is read from the day attribute of the period tag (see solver input xml-file).
- * The weight of the criterion can be set by problem property Exams.StudentsMoreThanXExamsInXDaysWeight,
- * or in the input xml file, property moreThanXExamsInYDaysWeight
- * studentStressNbrExams: Maximum number of exams a student should have within a given
- * number of consecutive days. Can be set by problem property
- * Exams.StudentStressNbrExams, or in the input xml file, property studentStressNbrExams.
- * If set to -1 this criterion is disabled. Default value: -1.
- *
- * studentStressNbrDays: Number of consecutive days. Can be set by problem property
- * Exams.StudentStressNbrDays, or in the input xml file, property studentStressNbrDays.
- * If set to -1 this criterion is disabled. Default value: -1.
- *
- * studentStressPeriodDateFormat: Format of the period day string. The default value is "E M/d".
- *
It can be set by problem property Exams.StudentStressPeriodDateFormat, or in the input xml file,
- * property studentStressPeriodDateFormat.
- * See also DateTimeFormatter documentation
- * for valid format strings.
- * studentStressExaminationYear Year in which the examinations take place (default: "2023").
- * It is used for date computations. It should be set if the examination year is a leap year.
- *
It can be set by problem property Exams.StudentStressExaminationYear, or in the input xml file,
- * property studentStressExaminationYear.
- * @author Alexander Kreim
- *
- *
- * This library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not see
- * http://www.gnu.org/licenses/.
- */
-
-@Deprecated
-public class StudentMoreThanXExamsInYDaysConflict extends ExamCriterion {
-
- private int iNbrOfDays=-1;
- private int iNbrOfExams=-1;
- private String iPeriodDateFormatString="E M/d";
- private Locale iLocale = Locale.ENGLISH;
- private Year iExaminationYear = Year.parse("2023");
- private DateTimeFormatter iDateFormatter =
- DateTimeFormatter.ofPattern(iPeriodDateFormatString, iLocale);
-
- @Override
- public String getName() {
- return "More Than "
- + String.valueOf(getNbrOfExams())
- + " in "
- + String.valueOf(getNbrOfDays())
- + " Days";
- }
-
- @Override
- public double getValue(Assignment assignment, ExamPlacement value,
- Set conflicts) {
- if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
- List examStudents = getExamStudents(value);
- return calcPenalty(assignment, examStudents);
- } else {
- return 0;
- }
- }
-
- @Override
- public double getValue(Assignment assignment) {
- if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
- List examStudents = ((ExamModel)getModel()).getStudents();
- return calcPenalty(assignment, examStudents);
- } else {
- return 0;
- }
- }
-
- @Override
- public double getValue(Assignment assignment, Collection variables) {
- if ((iNbrOfExams > 0) && (iNbrOfDays > 0)) {
- List studentsInSelection = new ArrayList();
- for (Iterator examIter = variables.iterator(); examIter.hasNext();) {
- Exam exam = examIter.next();
- List examStudents = exam.getStudents();
- for (Iterator studentIter = examStudents.iterator(); studentIter.hasNext();) {
- ExamStudent examStudent = studentIter.next();
- if (!studentsInSelection.contains(examStudent)) {
- studentsInSelection.add(examStudent);
- }
- }
- }
- return calcPenalty(assignment, studentsInSelection);
- } else {
- return 0;
- }
- }
-
- /**
- * Calculates the penalty for a given set or students.
- *
- *
- * for each student
- * get all days where the student has exams
- * determine first and last days of student's exams
- * start day = first day
- * while start day <= last day
- * create test period based on iNbrOfDays beginning at start day
- * count number of exams in the test period
- * if number of exams in test period > iNbrOfExams
- * penalty++
- * increment start day by one day
- *
- * @param assignment
- * @param examStudents
- * @return
- */
- private int calcPenalty(Assignment assignment, List examStudents) {
- int penalty = 0;
- for (Iterator iterator = examStudents.iterator(); iterator.hasNext();) {
- ExamStudent student = iterator.next();
- Map nrExamsPerDay = getNrExamsPerDay(student, assignment);
- MonthDay firstDay = getFirstDay(nrExamsPerDay.keySet());
- if (firstDay == null) continue;
- MonthDay lastDay = getLastDay(nrExamsPerDay.keySet());
- MonthDay startDay = MonthDay.from(firstDay);
- while (!startDay.isAfter(lastDay)) {
- int nbrOfExams = countNbrOfExamsInPeriod(startDay, addDays(startDay, getNbrOfDays()), nrExamsPerDay);
- if (nbrOfExams > getNbrOfExams()) penalty++;
- startDay = addDays(startDay, 1);
- }
- }
- return penalty;
- }
-
- @Override
- /*
- * Estimation of the upper bound.
- *
- * It is assumed that all enrolled exams of a student are in a row.
- */
- public double[] getBounds(Assignment assignment, Collection exams) {
- double[] bounds = new double[] { 0.0, 0.0 };
- // get all exam placements for each student enrolled in the given exams
- Map> allExamPlacements = getStudentExamPlacements(assignment, exams);
- // Calculate maximum penalty, assuming all student's exams are in a row.
- bounds[1] = calcMaxPenalty(allExamPlacements);
- return bounds;
- }
-
- private int calcMaxPenalty(Map> studentExamPlacements) {
- int nbrOfStudentExams = 0;
- int penalty = 0;
- for (Map.Entry> studentEnrollments : studentExamPlacements.entrySet()) {
- // ExamStudent student = studentEnrollments.getKey();
- Set enrollments = studentEnrollments.getValue();
- nbrOfStudentExams = enrollments.size();
- if (nbrOfStudentExams > iNbrOfExams) {
- penalty += calcMaxStudentPenalty(nbrOfStudentExams);
- }
- }
- return penalty;
- }
-
- private int calcMaxStudentPenalty(int nbrOfStudentExams) {
- // p + iNbrOfExams - 1 >= nbrOfStudentExams
- // p .. penalty, use min p
- int p = nbrOfStudentExams + 1 - iNbrOfExams;
- return p;
- }
-
- private HashMap> getStudentExamPlacements(
- Assignment assignment, Collection exams) {
-
- HashMap> studentExamPlacements =
- new HashMap>();
-
- for (Iterator examIter = exams.iterator(); examIter.hasNext();) {
- Exam exam = examIter.next();
- ExamPlacement placement = assignment.getValue(exam);
- List students = exam.getStudents();
- for (Iterator studentIter = students.iterator(); studentIter.hasNext();) {
- ExamStudent student = studentIter.next();
- if (studentExamPlacements.keySet().isEmpty()) {
- HashSet placementsAsSet = new HashSet();
- placementsAsSet.add(placement);
- studentExamPlacements.put(student, placementsAsSet);
- continue;
- }
- if (!studentExamPlacements.keySet().contains(student)) {
- HashSet placementsAsSet = new HashSet();
- placementsAsSet.add(placement);
- studentExamPlacements.put(student, placementsAsSet);
- continue;
- }
- if (studentExamPlacements.keySet().contains(student)) {
- studentExamPlacements.get(student).add(placement);
- }
- }
- }
- return studentExamPlacements;
- }
-
- private MonthDay addDays(MonthDay day, int nbrOfDays) {
- LocalDate newDate = day.atYear(iExaminationYear.getValue()).plusDays(nbrOfDays);
- return MonthDay.from(newDate);
- }
-
- private int countNbrOfExamsInPeriod(MonthDay startDay, MonthDay endDay, Map nrOfExamsPerDay) {
- int totalNbrOfExams=0;
- for (Map.Entry entry : nrOfExamsPerDay.entrySet()) {
- String dayString = entry.getKey();
- Integer nbrOfExams = entry.getValue();
- MonthDay currentDate = MonthDay.parse(dayString, iDateFormatter);
- if ((currentDate.isAfter(startDay)) && (currentDate.isBefore(endDay))) {
- totalNbrOfExams = totalNbrOfExams + nbrOfExams;
- }
- if (currentDate.equals(endDay)) totalNbrOfExams = totalNbrOfExams + nbrOfExams;
- if (currentDate.equals(startDay)) totalNbrOfExams = totalNbrOfExams + nbrOfExams;
- }
- return totalNbrOfExams;
- }
-
- private MonthDay getFirstDay(Set dayStrings) {
- MonthDay firstDay=null;
-
- for (Iterator iterator = dayStrings.iterator(); iterator.hasNext();) {
- String dayString = iterator.next();
- if (firstDay == null) {
- firstDay = MonthDay.parse(dayString, iDateFormatter);
- } else {
- MonthDay currentDay = MonthDay.parse(dayString, iDateFormatter);
- if (currentDay.isBefore(firstDay)) firstDay=currentDay;
- }
- }
- return firstDay;
- }
-
- private MonthDay getLastDay(Set dayStrings) {
- MonthDay lastDay=null;
-
- for (Iterator iterator = dayStrings.iterator(); iterator.hasNext();) {
- String dayString = iterator.next();
- if (lastDay == null) {
- lastDay = MonthDay.parse(dayString, iDateFormatter);
- } else {
- MonthDay currentDay = MonthDay.parse(dayString, iDateFormatter);
- if (currentDay.isAfter(lastDay)) lastDay=currentDay;
- }
- }
- return lastDay;
- }
-
- private List getAllExamPeriods() {
- return ((ExamModel)getModel()).getPeriods();
- }
-
- private Map getNrExamsPerDay(ExamStudent student, Assignment assignment) {
- List examPeriods= getAllExamPeriods();
- HashMap nrExamsPerDay = new HashMap();
- for (Iterator iterator = examPeriods.iterator(); iterator.hasNext();) {
- ExamPeriod examPeriod = iterator.next();
- Set enrolledExams = student.getExamsADay(assignment, examPeriod);
- if (! enrolledExams.isEmpty()) {
- String periodDayStr = examPeriod.getDayStr();
- if (! nrExamsPerDay.keySet().contains(periodDayStr)) {
- nrExamsPerDay.put(periodDayStr, enrolledExams.size());
- }
- }
- }
- return nrExamsPerDay;
- }
-
- private List getExamStudents(ExamPlacement value) {
- return value.variable().getStudents();
- }
-
- @Override
- public String getWeightName() {
- return "Exams.StudentsMoreThanXExamsInXDaysWeight";
- }
-
- @Override
- public String getXmlWeightName() {
- return "moreThanXExamsInYDaysWeight";
- }
-
- @Override
- public double getWeightDefault(DataProperties config) {
- return 1.0;
- }
-
- @Override
- public void configure(DataProperties properties) {
- super.configure(properties);
- setNbrOfDays(properties.getPropertyInt("Exams.StudentStressNbrDays", iNbrOfDays));
- setNbrOfExams(properties.getPropertyInt("Exams.StudentStressNbrExams", iNbrOfExams));
- setPeriodDateFormatString(properties.getProperty("Exams.StudentStressPeriodDateFormat", iPeriodDateFormatString));
- setExaminationYear(properties.getProperty("Exams.StudentStressExaminationYear", iPeriodDateFormatString));
- }
-
- @Override
- public void getXmlParameters(Map params) {
- params.put(getXmlWeightName(), String.valueOf(getWeight()));
- params.put("studentStressNbrExams", String.valueOf(getNbrOfExams()));
- params.put("studentStressNbrDays", String.valueOf(getNbrOfDays()));
- params.put("studentStressPeriodDateFormat", getPeriodDateFormatString());
- params.put("studentStressExaminationYear", getExaminationYear().toString());
- }
-
- @Override
- public void setXmlParameters(Map params) {
- try {
- setWeight(Double.valueOf(params.get(getXmlWeightName())));
- } catch (NumberFormatException e) {} catch (NullPointerException e) {}
- try {
- setNbrOfExams(Integer.valueOf(params.get("studentStressNbrExams")));
- } catch (NumberFormatException e) {} catch (NullPointerException e) {}
- try {
- setNbrOfDays(Integer.valueOf(params.get("studentStressNbrDays")));
- } catch (NumberFormatException e) {} catch (NullPointerException e) {}
- try {
- setPeriodDateFormatString(String.valueOf(params.get("studentStressPeriodDateFormat")));
- } catch (NumberFormatException e) {} catch (NullPointerException e) {}
- try {
- setExaminationYear(String.valueOf(params.get("studentStressExaminationYear")));
- } catch (NumberFormatException e) {} catch (NullPointerException e) {}
- }
-
- public int getNbrOfDays() {
- return iNbrOfDays;
- }
-
- public int getNbrOfExams() {
- return iNbrOfExams;
- }
-
- public String getPeriodDateFormatString() {
- return iPeriodDateFormatString;
- }
-
- public void setPeriodDateFormatString(String dateFormatString) {
- iPeriodDateFormatString = dateFormatString;
- iDateFormatter = DateTimeFormatter.ofPattern(iPeriodDateFormatString, iLocale);
- }
-
- public void setNbrOfDays(int nbrOfDays) {
- iNbrOfDays=nbrOfDays;
- }
-
- public void setNbrOfExams(int nbrOfExams) {
- iNbrOfExams=nbrOfExams;
- }
-
- public void setExaminationYear(String year) {
- iExaminationYear = Year.parse(year);
- }
-
- public Year getExaminationYear() {
- return iExaminationYear;
- }
-
- @Override
- public String toString(Assignment assignment) {
- return "M"
- + String.valueOf(getNbrOfExams())
- + "I"
- + String.valueOf(getNbrOfDays())
- + "D:" + sDoubleFormat.format(getValue(assignment));
- }
-}