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 Tomáš Müller<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.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.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 private Map<Long, Set<Long>> iAlternatives = null; 051 052 public Student(Long studentId) { 053 iStudentId = studentId; 054 } 055 056 public void addOffering(Long offeringId, double weight, Double priority) { 057 iOfferings.put(offeringId, weight); 058 if (priority != null) iOfferingPriority.put(offeringId, priority); 059 } 060 061 public void addOffering(Long offeringId, double weight) { 062 addOffering(offeringId, weight, null); 063 } 064 065 public Map<Long, Double> getOfferingsMap() { 066 return iOfferings; 067 } 068 069 public Set<Long> getOfferings() { 070 return iOfferings.keySet(); 071 } 072 073 public boolean hasOffering(Long offeringId) { 074 return iOfferings.containsKey(offeringId); 075 } 076 077 public InstructorConstraint getInstructor() { return iInstructor; } 078 079 public void setInstructor(InstructorConstraint instructor) { iInstructor = instructor; } 080 081 /** 082 * Priority of an offering (for the student). Null if not used, or between 083 * zero (no priority) and one (highest priority) 084 * @param offeringId instructional offering unique id 085 * @return student's priority 086 */ 087 public Double getPriority(Long offeringId) { 088 return offeringId == null ? null : iOfferingPriority.get(offeringId); 089 } 090 091 public Double getPriority(Configuration configuration) { 092 return configuration == null ? null : getPriority(configuration.getOfferingId()); 093 } 094 095 public Double getPriority(Lecture lecture) { 096 return lecture == null ? null : getPriority(lecture.getConfiguration()); 097 } 098 099 public Double getConflictingPriorty(Lecture l1, Lecture l2) { 100 // Conflicting priority is the lower of the two priorities 101 Double p1 = getPriority(l1); 102 Double p2 = getPriority(l2); 103 return p1 == null ? null : p2 == null ? null : Math.min(p1, p2); 104 } 105 106 public double getOfferingWeight(Configuration configuration) { 107 if (configuration == null) 108 return 1.0; 109 return getOfferingWeight(configuration.getOfferingId()); 110 } 111 112 public double getOfferingWeight(Long offeringId) { 113 Double weight = iOfferings.get(offeringId); 114 return (weight == null ? 0.0 : weight.doubleValue()); 115 } 116 117 public boolean canUnenroll(Lecture lecture) { 118 if (getInstructor() != null) 119 return !getInstructor().variables().contains(lecture); 120 return true; 121 } 122 123 public boolean canEnroll(Lecture lecture) { 124 if (iCanNotEnrollSections != null) { 125 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId()); 126 return canEnroll(canNotEnrollLectures, lecture, true); 127 } 128 return true; 129 } 130 131 private boolean canEnroll(Set<Lecture> canNotEnrollLectures, Lecture lecture, boolean checkParents) { 132 if (canNotEnrollLectures == null) 133 return true; 134 if (canNotEnrollLectures.contains(lecture)) 135 return false; 136 if (checkParents) { 137 Lecture parent = lecture.getParent(); 138 while (parent != null) { 139 if (canNotEnrollLectures.contains(parent)) 140 return false; 141 parent = parent.getParent(); 142 } 143 } 144 if (lecture.hasAnyChildren()) { 145 for (Long subpartId: lecture.getChildrenSubpartIds()) { 146 boolean canEnrollChild = false; 147 for (Lecture childLecture : lecture.getChildren(subpartId)) { 148 if (canEnroll(canNotEnrollLectures, childLecture, false)) { 149 canEnrollChild = true; 150 break; 151 } 152 } 153 if (!canEnrollChild) 154 return false; 155 } 156 } 157 return true; 158 } 159 160 public void addCanNotEnroll(Lecture lecture) { 161 if (iCanNotEnrollSections == null) 162 iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>(); 163 if (lecture.getConfiguration() == null) { 164 sLogger.warn("Student.addCanNotEnroll(" + lecture 165 + ") -- given lecture has no configuration associated with."); 166 return; 167 } 168 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId()); 169 if (canNotEnrollLectures == null) { 170 canNotEnrollLectures = new HashSet<Lecture>(); 171 iCanNotEnrollSections.put(lecture.getConfiguration().getOfferingId(), canNotEnrollLectures); 172 } 173 canNotEnrollLectures.add(lecture); 174 } 175 176 public void addCanNotEnroll(Long offeringId, Collection<Lecture> lectures) { 177 if (lectures == null || lectures.isEmpty()) 178 return; 179 if (iCanNotEnrollSections == null) 180 iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>(); 181 Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(offeringId); 182 if (canNotEnrollLectures == null) { 183 canNotEnrollLectures = new HashSet<Lecture>(); 184 iCanNotEnrollSections.put(offeringId, canNotEnrollLectures); 185 } 186 canNotEnrollLectures.addAll(lectures); 187 } 188 189 public Map<Long, Set<Lecture>> canNotEnrollSections() { 190 return iCanNotEnrollSections; 191 } 192 193 public void addLecture(Lecture lecture) { 194 iLectures.add(lecture); 195 } 196 197 public void removeLecture(Lecture lecture) { 198 iLectures.remove(lecture); 199 } 200 201 public Set<Lecture> getLectures() { 202 return iLectures; 203 } 204 205 public void addConfiguration(Configuration config) { 206 if (config != null) iConfigurations.add(config); 207 } 208 209 public void removeConfiguration(Configuration config) { 210 if (config != null) iConfigurations.remove(config); 211 } 212 213 public Set<Configuration> getConfigurations() { 214 return iConfigurations; 215 } 216 217 public Long getId() { 218 return iStudentId; 219 } 220 221 public double getDistance(Student student) { 222 Double dist = (USE_DISTANCE_CACHE && iDistanceCache != null ? iDistanceCache.get(student) : null); 223 if (dist == null) { 224 if (!getGroups().isEmpty() || !student.getGroups().isEmpty()) { 225 double total = 0.0f; 226 double same = 0.0; 227 for (StudentGroup g: getGroups()) { 228 total += g.getWeight(); 229 if (student.hasGroup(g)) 230 same += g.getWeight(); 231 } 232 for (StudentGroup g: student.getGroups()) { 233 total += g.getWeight(); 234 } 235 dist = (total - 2*same) / total; 236 } else { 237 int same = 0; 238 for (Long o : getOfferings()) { 239 if (student.getOfferings().contains(o)) 240 same++; 241 } 242 double all = student.getOfferings().size() + getOfferings().size(); 243 double dif = all - 2.0 * same; 244 dist = Double.valueOf(dif / all); 245 } 246 if (USE_DISTANCE_CACHE) { 247 if (iDistanceCache == null) 248 iDistanceCache = new HashMap<Student, Double>(); 249 iDistanceCache.put(student, dist); 250 } 251 } 252 return dist.doubleValue(); 253 } 254 255 public void clearDistanceCache() { 256 if (USE_DISTANCE_CACHE && iDistanceCache != null) 257 iDistanceCache.clear(); 258 } 259 260 @Override 261 public String toString() { 262 return String.valueOf(getId()); 263 } 264 265 @Override 266 public int hashCode() { 267 return getId().hashCode(); 268 } 269 270 @Override 271 public int compareTo(Student s) { 272 return getId().compareTo(s.getId()); 273 } 274 275 @Override 276 public boolean equals(Object o) { 277 if (o == null || !(o instanceof Student)) 278 return false; 279 return getId().equals(((Student) o).getId()); 280 } 281 282 public void addCommitedPlacement(Placement placement) { 283 if (iCommitedPlacements == null) 284 iCommitedPlacements = new HashSet<Placement>(); 285 iCommitedPlacements.add(placement); 286 } 287 288 public Set<Placement> getCommitedPlacements() { 289 return iCommitedPlacements; 290 } 291 292 public Set<Placement> conflictPlacements(Placement placement) { 293 if (iCommitedPlacements == null) 294 return null; 295 Set<Placement> ret = new HashSet<Placement>(); 296 Lecture lecture = placement.variable(); 297 for (Placement commitedPlacement : iCommitedPlacements) { 298 Lecture commitedLecture = commitedPlacement.variable(); 299 if (lecture.getSchedulingSubpartId() != null 300 && lecture.getSchedulingSubpartId().equals(commitedLecture.getSchedulingSubpartId())) 301 continue; 302 if (lecture.isToIgnoreStudentConflictsWith(commitedLecture)) continue; 303 if (JenrlConstraint.isInConflict(commitedPlacement, placement, ((TimetableModel)placement.variable().getModel()).getDistanceMetric(), 304 ((TimetableModel)placement.variable().getModel()).getStudentWorkDayLimit())) 305 ret.add(commitedPlacement); 306 } 307 return ret; 308 } 309 310 public int countConflictPlacements(Placement placement) { 311 Set<Placement> conflicts = conflictPlacements(placement); 312 double w = getOfferingWeight((placement.variable()).getConfiguration()); 313 return (int) Math.round(conflicts == null ? 0 : avg(w, 1.0) * conflicts.size()); 314 } 315 316 public double getJenrlWeight(Lecture l1, Lecture l2) { 317 if (getInstructor() != null && (getInstructor().variables().contains(l1) || getInstructor().variables().contains(l2))) 318 return 1.0; 319 if (iAlternatives != null && areAlternatives(l1, l2)) return 0.0; 320 return avg(getOfferingWeight(l1.getConfiguration()), getOfferingWeight(l2.getConfiguration())); 321 } 322 323 public void addAlternatives(Long offeringId1, Long offeringId2) { 324 if (offeringId1 == null || offeringId2 == null) return; 325 if (iAlternatives == null) 326 iAlternatives = new HashMap<Long, Set<Long>>(); 327 Set<Long> alts = iAlternatives.get(offeringId1); 328 if (alts == null) { 329 alts = new HashSet<Long>(); 330 alts.add(offeringId1); 331 iAlternatives.put(offeringId1, alts); 332 } 333 Set<Long> other = iAlternatives.get(offeringId2); 334 if (other != null) { 335 for (Long id: other) 336 iAlternatives.put(id, alts); 337 alts.addAll(other); 338 } else { 339 alts.add(offeringId2); 340 iAlternatives.put(offeringId2, alts); 341 } 342 } 343 344 public boolean areAlternatives(Lecture l1, Lecture l2) { 345 if (l1 == null || l2 == null) return false; 346 return areAlternatives(l1.getConfiguration(), l2.getConfiguration()); 347 } 348 349 public boolean areAlternatives(Configuration c1, Configuration c2) { 350 if (c1 == null || c2 == null) return false; 351 return areAlternatives(c1.getOfferingId(), c2.getOfferingId()); 352 } 353 354 public boolean areAlternatives(Long offeringId1, Long offeringId2) { 355 if (iAlternatives == null || offeringId1 == null || offeringId2 == null) return false; 356 Set<Long> alts = iAlternatives.get(offeringId1); 357 if (alts != null && alts.contains(offeringId2)) return true; 358 return false; 359 } 360 361 public Long getAlternative(Long offeringId) { 362 if (iAlternatives == null || offeringId == null) return null; 363 Set<Long> alternatives = iAlternatives.get(offeringId); 364 if (alternatives == null) return null; 365 Long bestId = null; double bestW = 0.0; 366 for (Long altId: alternatives) { 367 double w = getOfferingWeight(altId); 368 if (bestId == null || w > bestW || (bestW == w && altId < bestId)) { 369 bestId = altId; bestW = w; 370 } 371 } 372 return bestId == null || bestId.equals(offeringId) ? null : bestId; 373 } 374 375 public double avg(double w1, double w2) { 376 return Math.sqrt(w1 * w2); 377 } 378 379 public String getAcademicArea() { 380 return iAcademicArea; 381 } 382 383 public void setAcademicArea(String acadArea) { 384 iAcademicArea = acadArea; 385 } 386 387 public String getAcademicClassification() { 388 return iAcademicClassification; 389 } 390 391 public void setAcademicClassification(String acadClasf) { 392 iAcademicClassification = acadClasf; 393 } 394 395 public String getMajor() { 396 return iMajor; 397 } 398 399 public void setMajor(String major) { 400 iMajor = major; 401 } 402 403 public String getCurriculum() { 404 return iCurriculum; 405 } 406 407 public void setCurriculum(String curriculum) { 408 iCurriculum = curriculum; 409 } 410 411 public void addGroup(StudentGroup group) { 412 iGroups.add(group); 413 } 414 415 public Set<StudentGroup> getGroups() { return iGroups; } 416 417 public boolean hasGroup(StudentGroup group) { return iGroups.contains(group); } 418 419 public String getGroupNames() { 420 if (iGroups.isEmpty()) return ""; 421 if (iGroups.size() == 1) return iGroups.iterator().next().getName(); 422 String ret = ""; 423 for (StudentGroup g: new TreeSet<StudentGroup>(iGroups)) 424 ret += (ret.isEmpty() ? "" : ", ") + g.getName(); 425 return ret; 426 } 427 428 public double getSameGroupWeight(Student other) { 429 double ret = 0.0; 430 for (StudentGroup group: iGroups) 431 if (other.hasGroup(group) && group.getWeight() > ret) ret = group.getWeight(); 432 return ret; 433 } 434}