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