All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cpsolver.coursett.constraint.ExtendedStudentConflicts Maven / Gradle / Ivy

package org.cpsolver.coursett.constraint;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.Student;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.model.Constraint;
import org.cpsolver.ifs.model.GlobalConstraint;
import org.cpsolver.ifs.model.Model;
import org.cpsolver.ifs.model.ModelListener;
import org.cpsolver.ifs.solver.Solver;
import org.cpsolver.ifs.util.DataProperties;
import org.cpsolver.ifs.util.DistanceMetric;

/**
 * An experimental global constraint that does not allow any two classes that can be attended
 * by the same student to have a conflict. The constraint checks any two classes of different
 * offerings that share at least one student and that the student is allowed to take (not restricted
 * by reservations). Class pairs included in the Ignore Student Conflicts constraints are ignored.
 * Some classes may be excluded by using ExtendedStudentConflicts.IgnoreClasses parameter which may
 * contain a regular expression matching class name(s).
 * 
* Pairs of classes of the same offering are checked, too. In this case, the course structure must * allow the two classes to be attended together (e.g., they are from the same configuration), and at * least one student in the offering is allowed to take both classes. This feature can be disabled by * setting ExtendedStudentConflicts.CheckSameCourse to false. *
* @author Tomas Muller * @version CourseTT 1.3 (University Course Timetabling)
* Copyright (C) 2013 - 2024 Tomas Muller
* [email protected]
* http://muller.unitime.org
*
* 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 ExtendedStudentConflicts extends GlobalConstraint implements ModelListener{ private String iIgnoreClasses = null; private Map>> iCommonStudents = null; private Set iIgnoreClassIds = null; private Map> iClassCache = new ConcurrentHashMap>(); private boolean iCheckSameCourse = true; @Override public void setModel(Model model) { super.setModel(model); if (model != null && model instanceof TimetableModel) { DataProperties config = ((TimetableModel)model).getProperties(); iIgnoreClasses = config.getProperty("ExtendedStudentConflicts.IgnoreClasses"); iCheckSameCourse = config.getPropertyBoolean("ExtendedStudentConflicts.CheckSameCourse", true); } } protected void clearCache() { iClassCache.clear(); iCommonStudents = null; iIgnoreClassIds = null; } private DistanceMetric getDistanceMetric() { return (getModel() == null ? null : ((TimetableModel)getModel()).getDistanceMetric()); } protected List getCommonStudents(Long offeringId1, Long offeringId2) { if (iCommonStudents == null) { iCommonStudents = new ConcurrentHashMap>>(); for (Lecture lecture: getModel().variables()) { if (lecture.isCommitted() || lecture.getConfiguration() == null) continue; Map> commonStudents = iCommonStudents.get(lecture.getConfiguration().getOfferingId()); if (commonStudents != null) continue; commonStudents = new ConcurrentHashMap>(); iCommonStudents.put(lecture.getConfiguration().getOfferingId(), commonStudents); for (Lecture other: getModel().variables()) { if (other.isCommitted() || other.getConfiguration() == null) continue; // if (other.getConfiguration().getOfferingId().equals(lecture.getConfiguration().getOfferingId())) continue; if (commonStudents.containsKey(other.getConfiguration().getOfferingId())) continue; List students = new ArrayList(); for (Student student: ((TimetableModel)getModel()).getAllStudents()) { if (student.getOfferings().contains(lecture.getConfiguration().getOfferingId()) && student.getOfferings().contains(other.getConfiguration().getOfferingId())) students.add(student); } commonStudents.put(other.getConfiguration().getOfferingId(), students); } } } Map> offeringIds = iCommonStudents.get(offeringId1); return (offeringIds == null ? null : offeringIds.get(offeringId2)); } protected boolean isIgnoreClass(Lecture lecture) { if (iIgnoreClassIds == null) { iIgnoreClassIds = new HashSet(); if (iIgnoreClasses != null && !iIgnoreClasses.isEmpty()) for (Lecture l: getModel().variables()) { if (l.getName().matches(iIgnoreClasses)) iIgnoreClassIds.add(l.getClassId()); } } return iIgnoreClassIds.contains(lecture.getClassId()); } private Boolean getCachedPair(Lecture l1, Lecture l2) { if (l1.getClassId() < l2.getClassId()) { Map cache = iClassCache.get(l1.getClassId()); return (cache == null ? null : cache.get(l2.getClassId())); } else { Map cache = iClassCache.get(l2.getClassId()); return (cache == null ? null : cache.get(l1.getClassId())); } } private void setCachedPair(Lecture l1, Lecture l2, boolean value) { if (l1.getClassId() < l2.getClassId()) { Map cache = iClassCache.get(l1.getClassId()); if (cache == null) { cache = new ConcurrentHashMap(); iClassCache.put(l1.getClassId(), cache); } cache.put(l2.getClassId(), value); } else { Map cache = iClassCache.get(l2.getClassId()); if (cache == null) { cache = new ConcurrentHashMap(); iClassCache.put(l2.getClassId(), cache); } cache.put(l1.getClassId(), value); } } private boolean checkSameCourseCanTakeTogether(Lecture l1, Lecture l2) { // check if the feature is disabled if (!iCheckSameCourse) return false; // same subpart -> cannot take together if (l1.getSchedulingSubpartId().equals(l2.getSchedulingSubpartId())) return false; // different config -> cannot take together if (!l1.getConfiguration().equals(l2.getConfiguration())) return false; // subpart id > class id (classes that are given by class l1 and its parents) Map mustTake = new HashMap(); for (Lecture l = l1; l != null; l = l.getParent()) { mustTake.put(l.getSchedulingSubpartId(), l.getClassId()); } // also include top-level subparts of the same configuration that have only one class for (Map.Entry> e: l1.getConfiguration().getTopLectures().entrySet()) { if (e.getValue().size() == 1) { Lecture l = e.getValue().iterator().next(); mustTake.put(l.getSchedulingSubpartId(), l.getClassId()); } } // check l2 and its parents, if any of them does not follow mustTake -> cannot take together for (Lecture l = l2; l != null; l = l.getParent()) { Long id = mustTake.get(l.getSchedulingSubpartId()); if (id != null && !l.getClassId().equals(id)) return false; } // no issue found -> can take together return true; } protected boolean checkStudentForStudentConflicts(Lecture l1, Lecture l2) { // are student conflicts between the two classes to be ignored ? if (l1.isToIgnoreStudentConflictsWith(l2)) return false; // check the cache Boolean cache = getCachedPair(l1, l2); if (cache != null) return cache.booleanValue(); // classes of the same offering that cannot be taken together if (l1.getConfiguration().getOfferingId().equals(l2.getConfiguration().getOfferingId()) && !checkSameCourseCanTakeTogether(l1, l2)) { setCachedPair(l1, l2, false); return false; } // ignore matching class pairs if (isIgnoreClass(l1) && isIgnoreClass(l2)) { setCachedPair(l1, l2, false); return false; } // check offerings List commonStudents = getCommonStudents(l1.getConfiguration().getOfferingId(), l2.getConfiguration().getOfferingId()); // less then two students in common > do not check for conflicts if (commonStudents == null || commonStudents.size() <= 1) { setCachedPair(l1, l2, false); return false; } // check if there is a student that can attend l1 and l2 together for (Student student: commonStudents) if (student.canEnroll(l1) && student.canEnroll(l2)) { setCachedPair(l1, l2, true); return true; } // no common students that can attend both classes setCachedPair(l1, l2, false); return false; } @Override public void computeConflicts(Assignment assignment, Placement placement, Set conflicts) { Lecture lecture = placement.variable(); for (Lecture other: getModel().assignedVariables(assignment)) { Placement otherPlacement = assignment.getValue(other); if (checkStudentForStudentConflicts(lecture, other) && JenrlConstraint.isInConflict(placement, otherPlacement, getDistanceMetric(), 0)) conflicts.add(otherPlacement); } } @Override public boolean inConflict(Assignment assignment, Placement placement) { Lecture lecture = placement.variable(); for (Lecture other: getModel().assignedVariables(assignment)) { Placement otherPlacement = assignment.getValue(other); if (checkStudentForStudentConflicts(lecture, other) && JenrlConstraint.isInConflict(placement, otherPlacement, getDistanceMetric(), 0)) return true; } return false; } @Override public boolean isConsistent(Placement p1, Placement p2) { return p1 != null && p2 != null && checkStudentForStudentConflicts(p1.variable(), p2.variable()) && JenrlConstraint.isInConflict(p1, p2, getDistanceMetric(), 0); } @Override public String getName() { return "Extended Student Conflicts"; } @Override public String toString() { return "Extended Student Conflicts"; } @Override public void variableAdded(Lecture variable) { clearCache(); } @Override public void variableRemoved(Lecture variable) { clearCache(); } @Override public void constraintAdded(Constraint constraint) {} @Override public void constraintRemoved(Constraint constraint) {} @Override public void beforeAssigned(Assignment assignment, long iteration, Placement value) {} @Override public void beforeUnassigned(Assignment assignment, long iteration, Placement value) {} @Override public void afterAssigned(Assignment assignment, long iteration, Placement value) {} @Override public void afterUnassigned(Assignment assignment, long iteration, Placement value) {} @Override public boolean init(Solver solver) { clearCache(); return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy