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