001package org.cpsolver.coursett.model; 002 003import java.util.Collection; 004import java.util.HashSet; 005import java.util.HashMap; 006import java.util.Map; 007import java.util.Set; 008 009import org.cpsolver.coursett.constraint.InstructorConstraint; 010import org.cpsolver.coursett.constraint.JenrlConstraint; 011 012 013/** 014 * Student. 015 * 016 * @version CourseTT 1.3 (University Course Timetabling)<br> 017 * Copyright (C) 2006 - 2014 Tomas Muller<br> 018 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 019 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 020 * <br> 021 * This library is free software; you can redistribute it and/or modify 022 * it under the terms of the GNU Lesser General Public License as 023 * published by the Free Software Foundation; either version 3 of the 024 * License, or (at your option) any later version. <br> 025 * <br> 026 * This library is distributed in the hope that it will be useful, but 027 * WITHOUT ANY WARRANTY; without even the implied warranty of 028 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 029 * Lesser General Public License for more details. <br> 030 * <br> 031 * You should have received a copy of the GNU Lesser General Public 032 * License along with this library; if not see 033 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 034 */ 035public class Student implements Comparable<Student> { 036 private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Student.class); 037 public static boolean USE_DISTANCE_CACHE = false; 038 Long iStudentId = null; 039 HashMap<Long, Double> iOfferings = new HashMap<Long, Double>(); 040 Set<Lecture> iLectures = new HashSet<Lecture>(); 041 Set<Configuration> iConfigurations = new HashSet<Configuration>(); 042 HashMap<Long, Set<Lecture>> iCanNotEnrollSections = null; 043 HashMap<Student, Double> iDistanceCache = null; 044 HashSet<Placement> iCommitedPlacements = null; 045 private String iAcademicArea = null, iAcademicClassification = null, iMajor = null, iCurriculum = null; 046 HashMap<Long, Double> iOfferingPriority = new HashMap<Long, Double>(); 047 private InstructorConstraint iInstructor = null; 048 049 public Student(Long studentId) { 050 iStudentId = studentId; 051 } 052 053 public void addOffering(Long offeringId, double weight, Double priority) { 054 iOfferings.put(offeringId, weight); 055 if (priority != null) iOfferingPriority.put(offeringId, priority); 056 } 057 058 public void addOffering(Long offeringId, double weight) { 059 addOffering(offeringId, weight, null); 060 } 061 062 public Map<Long, Double> getOfferingsMap() { 063 return iOfferings; 064 } 065 066 public Set<Long> getOfferings() { 067 return iOfferings.keySet(); 068 } 069 070 public boolean hasOffering(Long offeringId) { 071 return iOfferings.containsKey(offeringId); 072 } 073 074 public InstructorConstraint getInstructor() { return iInstructor; } 075 076 public void setInstructor(InstructorConstraint instructor) { iInstructor = instructor; } 077 078 /** 079 * Priority of an offering (for the student). Null if not used, or between 080 * zero (no priority) and one (highest priority) 081 * @param offeringId instructional offering unique id 082 * @return student's priority 083 */ 084 public Double getPriority(Long offeringId) { 085 return offeringId == null ? null : iOfferingPriority.get(offeringId); 086 } 087 088 public Double getPriority(Configuration configuration) { 089 return configuration == null ? null : getPriority(configuration.getOfferingId()); 090 } 091 092 public Double getPriority(Lecture lecture) { 093 return lecture == null ? null : getPriority(lecture.getConfiguration()); 094 } 095 096 public Double getConflictingPriorty(Lecture l1, Lecture l2) { 097 // Conflicting priority is the lower of the two priorities 098 Double p1 = getPriority(l1); 099 Double p2 = getPriority(l2); 100 return p1 == null ? null : p2 == null ? null : Math.min(p1, p2); 101 } 102 103 public double getOfferingWeight(Configuration configuration) { 104 if (configuration == null) 105 return 1.0; 106 return getOfferingWeight(configuration.getOfferingId()); 107 } 108 109 public double getOfferingWeight(Long offeringId) { 110 Double weight = iOfferings.get(offeringId); 111 return (weight == null ? 0.0 : weight.doubleValue()); 112 } 113 114 public boolean canUnenroll(Lecture lecture) { 115 if (getInstructor() != null) 116 return !getInstructor().variables().contains(lecture); 117 return true; 118 } 119 120 public boolean canEnroll(Lecture lecture) { 121 if (iCanNotEnrollSections != null) { 122 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId()); 123 return canEnroll(canNotEnrollLectures, lecture, true); 124 } 125 return true; 126 } 127 128 private boolean canEnroll(Set<Lecture> canNotEnrollLectures, Lecture lecture, boolean checkParents) { 129 if (canNotEnrollLectures == null) 130 return true; 131 if (canNotEnrollLectures.contains(lecture)) 132 return false; 133 if (checkParents) { 134 Lecture parent = lecture.getParent(); 135 while (parent != null) { 136 if (canNotEnrollLectures.contains(parent)) 137 return false; 138 parent = parent.getParent(); 139 } 140 } 141 if (lecture.hasAnyChildren()) { 142 for (Long subpartId: lecture.getChildrenSubpartIds()) { 143 boolean canEnrollChild = false; 144 for (Lecture childLecture : lecture.getChildren(subpartId)) { 145 if (canEnroll(canNotEnrollLectures, childLecture, false)) { 146 canEnrollChild = true; 147 break; 148 } 149 } 150 if (!canEnrollChild) 151 return false; 152 } 153 } 154 return true; 155 } 156 157 public void addCanNotEnroll(Lecture lecture) { 158 if (iCanNotEnrollSections == null) 159 iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>(); 160 if (lecture.getConfiguration() == null) { 161 sLogger.warn("Student.addCanNotEnroll(" + lecture 162 + ") -- given lecture has no configuration associated with."); 163 return; 164 } 165 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId()); 166 if (canNotEnrollLectures == null) { 167 canNotEnrollLectures = new HashSet<Lecture>(); 168 iCanNotEnrollSections.put(lecture.getConfiguration().getOfferingId(), canNotEnrollLectures); 169 } 170 canNotEnrollLectures.add(lecture); 171 } 172 173 public void addCanNotEnroll(Long offeringId, Collection<Lecture> lectures) { 174 if (lectures == null || lectures.isEmpty()) 175 return; 176 if (iCanNotEnrollSections == null) 177 iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>(); 178 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(offeringId); 179 if (canNotEnrollLectures == null) { 180 canNotEnrollLectures = new HashSet<Lecture>(); 181 iCanNotEnrollSections.put(offeringId, canNotEnrollLectures); 182 } 183 canNotEnrollLectures.addAll(lectures); 184 } 185 186 public Map<Long, Set<Lecture>> canNotEnrollSections() { 187 return iCanNotEnrollSections; 188 } 189 190 public void addLecture(Lecture lecture) { 191 iLectures.add(lecture); 192 } 193 194 public void removeLecture(Lecture lecture) { 195 iLectures.remove(lecture); 196 } 197 198 public Set<Lecture> getLectures() { 199 return iLectures; 200 } 201 202 public void addConfiguration(Configuration config) { 203 iConfigurations.add(config); 204 } 205 206 public void removeConfiguration(Configuration config) { 207 iConfigurations.remove(config); 208 } 209 210 public Set<Configuration> getConfigurations() { 211 return iConfigurations; 212 } 213 214 public Long getId() { 215 return iStudentId; 216 } 217 218 public double getDistance(Student student) { 219 Double dist = (USE_DISTANCE_CACHE && iDistanceCache != null ? iDistanceCache.get(student) : null); 220 if (dist == null) { 221 int same = 0; 222 for (Long o : getOfferings()) { 223 if (student.getOfferings().contains(o)) 224 same++; 225 } 226 double all = student.getOfferings().size() + getOfferings().size(); 227 double dif = all - 2.0 * same; 228 dist = new Double(dif / all); 229 if (USE_DISTANCE_CACHE) { 230 if (iDistanceCache == null) 231 iDistanceCache = new HashMap<Student, Double>(); 232 iDistanceCache.put(student, dist); 233 } 234 } 235 return dist.doubleValue(); 236 } 237 238 public void clearDistanceCache() { 239 if (USE_DISTANCE_CACHE && iDistanceCache != null) 240 iDistanceCache.clear(); 241 } 242 243 @Override 244 public String toString() { 245 return String.valueOf(getId()); 246 } 247 248 @Override 249 public int hashCode() { 250 return getId().hashCode(); 251 } 252 253 @Override 254 public int compareTo(Student s) { 255 return getId().compareTo(s.getId()); 256 } 257 258 @Override 259 public boolean equals(Object o) { 260 if (o == null || !(o instanceof Student)) 261 return false; 262 return getId().equals(((Student) o).getId()); 263 } 264 265 public void addCommitedPlacement(Placement placement) { 266 if (iCommitedPlacements == null) 267 iCommitedPlacements = new HashSet<Placement>(); 268 iCommitedPlacements.add(placement); 269 } 270 271 public Set<Placement> getCommitedPlacements() { 272 return iCommitedPlacements; 273 } 274 275 public Set<Placement> conflictPlacements(Placement placement) { 276 if (iCommitedPlacements == null) 277 return null; 278 Set<Placement> ret = new HashSet<Placement>(); 279 Lecture lecture = placement.variable(); 280 for (Placement commitedPlacement : iCommitedPlacements) { 281 Lecture commitedLecture = commitedPlacement.variable(); 282 if (lecture.getSchedulingSubpartId() != null 283 && lecture.getSchedulingSubpartId().equals(commitedLecture.getSchedulingSubpartId())) 284 continue; 285 if (lecture.isToIgnoreStudentConflictsWith(commitedLecture)) continue; 286 if (JenrlConstraint.isInConflict(commitedPlacement, placement, ((TimetableModel)placement.variable().getModel()).getDistanceMetric())) 287 ret.add(commitedPlacement); 288 } 289 return ret; 290 } 291 292 public int countConflictPlacements(Placement placement) { 293 Set<Placement> conflicts = conflictPlacements(placement); 294 double w = getOfferingWeight((placement.variable()).getConfiguration()); 295 return (int) Math.round(conflicts == null ? 0 : avg(w, 1.0) * conflicts.size()); 296 } 297 298 public double getJenrlWeight(Lecture l1, Lecture l2) { 299 if (getInstructor() != null && (getInstructor().variables().contains(l1) || getInstructor().variables().contains(l2))) 300 return 1.0; 301 return avg(getOfferingWeight(l1.getConfiguration()), getOfferingWeight(l2.getConfiguration())); 302 } 303 304 public double avg(double w1, double w2) { 305 return Math.sqrt(w1 * w2); 306 } 307 308 public String getAcademicArea() { 309 return iAcademicArea; 310 } 311 312 public void setAcademicArea(String acadArea) { 313 iAcademicArea = acadArea; 314 } 315 316 public String getAcademicClassification() { 317 return iAcademicClassification; 318 } 319 320 public void setAcademicClassification(String acadClasf) { 321 iAcademicClassification = acadClasf; 322 } 323 324 public String getMajor() { 325 return iMajor; 326 } 327 328 public void setMajor(String major) { 329 iMajor = major; 330 } 331 332 public String getCurriculum() { 333 return iCurriculum; 334 } 335 336 public void setCurriculum(String curriculum) { 337 iCurriculum = curriculum; 338 } 339}