001package org.cpsolver.studentsct.model; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.cpsolver.coursett.model.TimeLocation; 009import org.cpsolver.ifs.assignment.Assignment; 010import org.cpsolver.studentsct.constraint.LinkedSections; 011import org.cpsolver.studentsct.model.Request.RequestPriority; 012 013 014/** 015 * Representation of a student. Each student contains id, and a list of 016 * requests. <br> 017 * <br> 018 * Last-like semester students are mark as dummy. Dummy students have lower 019 * value and generally should not block "real" students from getting requested 020 * courses. <br> 021 * <br> 022 * 023 * @version StudentSct 1.3 (Student Sectioning)<br> 024 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 025 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 026 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 027 * <br> 028 * This library is free software; you can redistribute it and/or modify 029 * it under the terms of the GNU Lesser General Public License as 030 * published by the Free Software Foundation; either version 3 of the 031 * License, or (at your option) any later version. <br> 032 * <br> 033 * This library is distributed in the hope that it will be useful, but 034 * WITHOUT ANY WARRANTY; without even the implied warranty of 035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 036 * Lesser General Public License for more details. <br> 037 * <br> 038 * You should have received a copy of the GNU Lesser General Public 039 * License along with this library; if not see 040 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 041 */ 042public class Student implements Comparable<Student> { 043 private long iId; 044 private String iExternalId = null, iName = null; 045 private StudentPriority iPriority = StudentPriority.Normal; 046 private List<Request> iRequests = new ArrayList<Request>(); 047 private List<AreaClassificationMajor> iMajors = new ArrayList<AreaClassificationMajor>(); 048 private List<AreaClassificationMajor> iMinors = new ArrayList<AreaClassificationMajor>(); 049 private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>(); 050 private Set<String> iAccommodations = new HashSet<String>(); 051 private List<StudentGroup> iGroups = new ArrayList<StudentGroup>(); 052 private String iStatus = null; 053 private Long iEmailTimeStamp = null; 054 private List<Unavailability> iUnavailabilities = new ArrayList<Unavailability>(); 055 private boolean iNeedShortDistances = false; 056 private boolean iAllowDisabled = false; 057 private Float iMinCredit = null; 058 private Float iMaxCredit = null; 059 private List<Instructor> iAdvisors = new ArrayList<Instructor>(); 060 private Integer iClassFirstDate = null, iClassLastDate = null; 061 private ModalityPreference iModalityPreference = ModalityPreference.NO_PREFERENCE; 062 private BackToBackPreference iBackToBackPreference = BackToBackPreference.NO_PREFERENCE; 063 064 /** 065 * Constructor 066 * 067 * @param id 068 * student unique id 069 */ 070 public Student(long id) { 071 iId = id; 072 } 073 074 /** 075 * Constructor 076 * 077 * @param id 078 * student unique id 079 * @param dummy 080 * dummy flag 081 */ 082 public Student(long id, boolean dummy) { 083 iId = id; 084 iPriority = (dummy ? StudentPriority.Dummy : StudentPriority.Normal); 085 } 086 087 /** Student unique id 088 * @return student unique id 089 **/ 090 public long getId() { 091 return iId; 092 } 093 094 /** Set student unique id 095 * @param id student unique id 096 **/ 097 public void setId(long id) { 098 iId = id; 099 } 100 101 /** Student's course and free time requests 102 * @return student requests 103 **/ 104 public List<Request> getRequests() { 105 return iRequests; 106 } 107 108 /** Number of requests (alternative requests are ignored) 109 * @return number of non alternative student requests 110 **/ 111 public int nrRequests() { 112 int ret = 0; 113 for (Request r : getRequests()) { 114 if (!r.isAlternative()) 115 ret++; 116 } 117 return ret; 118 } 119 120 /** Number of alternative requests 121 * @return number of alternative student requests 122 **/ 123 public int nrAlternativeRequests() { 124 int ret = 0; 125 for (Request r : getRequests()) { 126 if (r.isAlternative()) 127 ret++; 128 } 129 return ret; 130 } 131 132 /** 133 * True if the given request can be assigned to the student. A request 134 * cannot be assigned to a student when the student already has the desired 135 * number of requests assigned (i.e., number of non-alternative course 136 * requests). 137 * @param assignment current assignment 138 * @param request given request of this student 139 * @return true if the given request can be assigned 140 **/ 141 public boolean canAssign(Assignment<Request, Enrollment> assignment, Request request) { 142 if (request.isAssigned(assignment)) 143 return true; 144 int alt = 0; 145 float credit = 0f; 146 boolean found = false; 147 for (Request r : getRequests()) { 148 if (r.equals(request)) 149 found = true; 150 boolean assigned = (r.isAssigned(assignment) || r.equals(request)); 151 boolean course = (r instanceof CourseRequest); 152 boolean waitlist = (course && ((CourseRequest) r).isWaitlist()); 153 if (r.isAlternative()) { 154 if (assigned || (!found && waitlist)) 155 alt--; 156 } else { 157 if (course && !waitlist && !assigned) 158 alt++; 159 } 160 if (r.equals(request)) 161 credit += r.getMinCredit(); 162 else { 163 Enrollment e = r.getAssignment(assignment); 164 if (e != null) credit += e.getCredit(); 165 } 166 } 167 return (alt >= 0 && credit <= getMaxCredit()); 168 } 169 170 /** 171 * True if the student has assigned the desired number of requests (i.e., 172 * number of non-alternative course requests). 173 * @param assignment current assignment 174 * @return true if this student has a complete schedule 175 */ 176 public boolean isComplete(Assignment<Request, Enrollment> assignment) { 177 int nrRequests = 0; 178 int nrAssignedRequests = 0; 179 float credit = 0f; 180 Float minCredit = null; 181 for (Request r : getRequests()) { 182 if (!(r instanceof CourseRequest)) 183 continue; // ignore free times 184 if (!r.isAlternative()) 185 nrRequests++; 186 if (r.isAssigned(assignment)) 187 nrAssignedRequests++; 188 Enrollment e = r.getAssignment(assignment); 189 if (e != null) { 190 credit += e.getCredit(); 191 } else if (r instanceof CourseRequest) { 192 minCredit = (minCredit == null ? r.getMinCredit() : Math.min(minCredit, r.getMinCredit())); 193 } 194 } 195 return nrAssignedRequests == nrRequests || credit + (minCredit == null ? 0f : minCredit.floatValue()) > getMaxCredit(); 196 } 197 198 /** Number of assigned COURSE requests 199 * @param assignment current assignment 200 * @return number of assigned course requests 201 **/ 202 public int nrAssignedRequests(Assignment<Request, Enrollment> assignment) { 203 int nrAssignedRequests = 0; 204 for (Request r : getRequests()) { 205 if (!(r instanceof CourseRequest)) 206 continue; // ignore free times 207 if (r.isAssigned(assignment)) 208 nrAssignedRequests++; 209 } 210 return nrAssignedRequests; 211 } 212 213 @Override 214 public String toString() { 215 return (isDummy() ? "D" : "") + "S[" + getId() + "]"; 216 } 217 218 /** 219 * Student's dummy flag. Dummy students have lower value and generally 220 * should not block "real" students from getting requested courses. 221 * @return true if projected student 222 */ 223 public boolean isDummy() { 224 return iPriority == StudentPriority.Dummy; 225 } 226 227 /** 228 * Set student's dummy flag. Dummy students have lower value and generally 229 * should not block "real" students from getting requested courses. 230 * @param dummy projected student 231 */ 232 public void setDummy(boolean dummy) { 233 if (dummy) 234 iPriority = StudentPriority.Dummy; 235 else if (iPriority == StudentPriority.Dummy) 236 iPriority = StudentPriority.Normal; 237 } 238 239 /** 240 * Student's priority. Priority students are to be assigned first. 241 * @return student priority level 242 */ 243 public StudentPriority getPriority() { 244 return iPriority; 245 } 246 247 /** 248 * Set student's priority. Priority students are to be assigned first. 249 * @param priority student priority level 250 */ 251 public void setPriority(StudentPriority priority) { 252 iPriority = priority; 253 } 254 255 /** 256 * Set student's priority. Priority students are to be assigned first. 257 * @param priority true for priority student 258 */ 259 @Deprecated 260 public void setPriority(boolean priority) { 261 if (priority) 262 iPriority = StudentPriority.Priority; 263 else if (StudentPriority.Normal.isHigher(this)) 264 iPriority = StudentPriority.Normal; 265 } 266 267 /** 268 * Student's priority. Priority students are to be assigned first. 269 * @return true if priority student 270 */ 271 @Deprecated 272 public boolean isPriority() { 273 return StudentPriority.Normal.isHigher(this); 274 } 275 276 277 /** 278 * List of student groups ({@link StudentGroup}) for the given student 279 * @return list of academic area abbreviation (group type) & group code pairs 280 */ 281 public List<StudentGroup> getGroups() { 282 return iGroups; 283 } 284 285 /** 286 * List student accommodations 287 * @return student accommodations 288 */ 289 public Set<String> getAccommodations() { 290 return iAccommodations; 291 } 292 293 /** 294 * List of academic area, classification, and major codes ({@link AreaClassificationMajor}) for the given student 295 * @return list of academic area, classification, and major codes 296 */ 297 public List<AreaClassificationMajor> getAreaClassificationMajors() { 298 return iMajors; 299 } 300 301 public AreaClassificationMajor getPrimaryMajor() { 302 if (iMajors == null) return null; 303 AreaClassificationMajor major = null; 304 for (AreaClassificationMajor m: iMajors) { 305 if (major == null || m.compareTo(major) < 0) 306 major = m; 307 } 308 return major; 309 } 310 311 /** 312 * List of academic area, classification, and minor codes ({@link AreaClassificationMajor}) for the given student 313 * @return list of academic area, classification, and minor codes 314 */ 315 public List<AreaClassificationMajor> getAreaClassificationMinors() { 316 return iMinors; 317 } 318 319 /** 320 * List of student's advisors 321 */ 322 public List<Instructor> getAdvisors() { 323 return iAdvisors; 324 } 325 326 /** 327 * Compare two students for equality. Two students are considered equal if 328 * they have the same id. 329 */ 330 @Override 331 public boolean equals(Object object) { 332 if (object == null || !(object instanceof Student)) 333 return false; 334 return getId() == ((Student) object).getId() && isDummy() == ((Student) object).isDummy(); 335 } 336 337 /** 338 * Hash code (base only on student id) 339 */ 340 @Override 341 public int hashCode() { 342 return (int) (iId ^ (iId >>> 32)); 343 } 344 345 /** 346 * Count number of free time slots overlapping with the given enrollment 347 * @param enrollment given enrollment 348 * @return number of slots overlapping with a free time request 349 */ 350 public int countFreeTimeOverlaps(Enrollment enrollment) { 351 if (!enrollment.isCourseRequest()) return 0; 352 int ret = 0; 353 for (Section section: enrollment.getSections()) { 354 TimeLocation time = section.getTime(); 355 if (time != null) 356 ret += countFreeTimeOverlaps(time); 357 } 358 return ret; 359 } 360 361 /** 362 * Count number of free time slots overlapping with the given time 363 * @param time given time 364 * @return number of time slots overlapping with a free time request 365 */ 366 public int countFreeTimeOverlaps(TimeLocation time) { 367 int ret = 0; 368 for (Request r: iRequests) { 369 if (r instanceof FreeTimeRequest) { 370 TimeLocation freeTime = ((FreeTimeRequest)r).getTime(); 371 if (time.hasIntersection(freeTime)) 372 ret += freeTime.nrSharedHours(time) * freeTime.nrSharedDays(time); 373 } 374 } 375 return ret; 376 } 377 378 /** 379 * Get student external id 380 * @return student external unique id 381 */ 382 public String getExternalId() { return iExternalId; } 383 /** 384 * Set student external id 385 * @param externalId student external id 386 */ 387 public void setExternalId(String externalId) { iExternalId = externalId; } 388 389 /** 390 * Get student name 391 * @return student name 392 */ 393 public String getName() { return iName; } 394 /** 395 * Set student name 396 * @param name student name 397 */ 398 public void setName(String name) { iName = name; } 399 400 /** 401 * Linked sections of this student 402 * @return linked sections of this student 403 */ 404 public List<LinkedSections> getLinkedSections() { return iLinkedSections; } 405 406 /** 407 * Get student status (online sectioning only) 408 * @return student sectioning status 409 */ 410 public String getStatus() { return iStatus; } 411 /** 412 * Set student status 413 * @param status student sectioning status 414 */ 415 public void setStatus(String status) { iStatus = status; } 416 417 /** 418 * Get last email time stamp (online sectioning only) 419 * @return student email time stamp 420 */ 421 public Long getEmailTimeStamp() { return iEmailTimeStamp; } 422 /** 423 * Set last email time stamp 424 * @param emailTimeStamp student email time stamp 425 */ 426 public void setEmailTimeStamp(Long emailTimeStamp) { iEmailTimeStamp = emailTimeStamp; } 427 428 @Override 429 public int compareTo(Student s) { 430 // priority students first, dummy students last 431 if (getPriority() != s.getPriority()) 432 return (getPriority().ordinal() < s.getPriority().ordinal() ? -1 : 1); 433 // then id 434 return Long.valueOf(getId()).compareTo(s.getId()); 435 } 436 437 /** 438 * List of student unavailabilities 439 * @return student unavailabilities 440 */ 441 public List<Unavailability> getUnavailabilities() { return iUnavailabilities; } 442 443 /** 444 * Check if student is available during the given section 445 * @param section given section 446 * @return true, if available (the section cannot overlap and there is no overlapping unavailability that cannot overlap) 447 */ 448 public boolean isAvailable(Section section) { 449 if (section.isAllowOverlap() || section.getTime() == null) return true; 450 for (Unavailability unavailability: getUnavailabilities()) 451 if (unavailability.isOverlapping(section)) return false; 452 return true; 453 } 454 455 /** 456 * Check if student is available during the given enrollment 457 * @param enrollment given enrollment 458 * @return true, if available 459 */ 460 public boolean isAvailable(Enrollment enrollment) { 461 if (enrollment != null && enrollment.isCourseRequest() && !enrollment.isAllowOverlap()) 462 for (Section section: enrollment.getSections()) 463 if (!isAvailable(section)) return false; 464 return true; 465 } 466 467 /** 468 * Return true if the student needs short distances. A different distance conflict checking is employed for such students. 469 * @return true if the student needs short distances 470 */ 471 public boolean isNeedShortDistances() { 472 return iNeedShortDistances; 473 } 474 475 /** 476 * Set true if the student needs short distances. A different distance conflict checking is employed for such students. 477 * @param needShortDistances true if the student needs short distances (default is false) 478 */ 479 public void setNeedShortDistances(boolean needShortDistances) { 480 iNeedShortDistances = needShortDistances; 481 } 482 483 /** 484 * True if student can be enrolled in disabled sections, regardless if his/her reservations 485 * @return does this student allow for disabled sections 486 */ 487 public boolean isAllowDisabled() { 488 return iAllowDisabled; 489 } 490 491 /** 492 * Set to true if student can be enrolled in disabled sections, regardless if his/her reservations 493 * @param allowDisabled does this student allow for disabled sections 494 */ 495 public void setAllowDisabled(boolean allowDisabled) { 496 iAllowDisabled = allowDisabled; 497 } 498 499 /** 500 * True if student has min credit defined 501 * @return true if min credit is set 502 */ 503 public boolean hasMinCredit() { return iMinCredit != null; } 504 505 /** 506 * Get student min credit (0 if not set) 507 * return student min credit 508 */ 509 public float getMinCredit() { return (iMinCredit == null ? 0 : iMinCredit.floatValue()); } 510 511 /** 512 * Has student any critical course requests? 513 * @return true if a student has at least one course request that is marked as critical 514 */ 515 @Deprecated 516 public boolean hasCritical() { 517 for (Request r: iRequests) 518 if (!r.isAlternative() && r.isCritical()) return true; 519 return false; 520 } 521 522 /** 523 * Has student any critical course requests? 524 * @return true if a student has at least one course request that is marked as critical 525 */ 526 public boolean hasCritical(RequestPriority rp) { 527 for (Request r: iRequests) 528 if (!r.isAlternative() && rp.isCritical(r)) return true; 529 return false; 530 } 531 532 /** 533 * Has student any unassigned critical course requests? 534 * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned 535 */ 536 @Deprecated 537 public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment) { 538 for (Request r: iRequests) 539 if (!r.isAlternative() && r.isCritical() && assignment.getValue(r) == null) return true; 540 return false; 541 } 542 543 /** 544 * Has student any unassigned critical course requests? 545 * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned 546 */ 547 public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment, RequestPriority rp) { 548 for (Request r: iRequests) 549 if (!r.isAlternative() && rp.isCritical(r) && assignment.getValue(r) == null) return true; 550 return false; 551 } 552 553 /** 554 * Set student min credit (null if not set) 555 * @param maxCredit student min credit 556 */ 557 public void setMinCredit(Float maxCredit) { iMinCredit = maxCredit; } 558 559 /** 560 * True if student has max credit defined 561 * @return true if max credit is set 562 */ 563 public boolean hasMaxCredit() { return iMaxCredit != null; } 564 565 /** 566 * Get student max credit ({@link Float#MAX_VALUE} if not set) 567 * return student max credit 568 */ 569 public float getMaxCredit() { return (iMaxCredit == null ? Float.MAX_VALUE : iMaxCredit.floatValue()); } 570 571 /** 572 * Set student max credit (null if not set) 573 * @param maxCredit student max credit 574 */ 575 public void setMaxCredit(Float maxCredit) { iMaxCredit = maxCredit; } 576 577 /** 578 * Return the number of assigned credits of the student 579 * @param assignment current assignment 580 * @return total assigned credit using {@link Enrollment#getCredit()} 581 */ 582 public float getAssignedCredit(Assignment<Request, Enrollment> assignment) { 583 float credit = 0f; 584 for (Request r: getRequests()) { 585 Enrollment e = r.getAssignment(assignment); 586 if (e != null) credit += e.getCredit(); 587 } 588 return credit; 589 } 590 591 /** 592 * Student priority level. Higher priority students are to be assigned first. 593 * The student priority is used to re-order students and assign them accoding 594 * to their priority. 595 */ 596 public static enum StudentPriority { 597 Priority("P", 1.00), 598 Senior("4", 0.70), 599 Junior("3", 0.49), 600 Sophomore("2", 0.33), 601 Freshmen("1", 0.24), 602 Normal("N", null), // this is the default priority 603 Dummy("D", null), // dummy students priority 604 ; 605 606 String iCode; 607 Double iBoost; 608 StudentPriority(String code, Double boost) { 609 iCode = code; 610 iBoost = boost; 611 } 612 public String code() { return iCode; } 613 public Double getBoost() { return iBoost; } 614 615 public boolean isSameOrHigher(Student s) { 616 return s.getPriority().ordinal() <= ordinal(); 617 } 618 public boolean isHigher(Student s) { 619 return ordinal() < s.getPriority().ordinal(); 620 } 621 public boolean isSame(Student s) { 622 return ordinal() == s.getPriority().ordinal(); 623 } 624 public static StudentPriority getPriority(String value) { 625 if ("true".equalsIgnoreCase(value)) return StudentPriority.Priority; 626 if ("false".equalsIgnoreCase(value)) return StudentPriority.Normal; 627 for (StudentPriority sp: StudentPriority.values()) { 628 if (sp.name().equalsIgnoreCase(value)) return sp; 629 } 630 return StudentPriority.Normal; 631 } 632 } 633 634 /** 635 * Check if a student has given accommodation 636 * @param code accommodation reference code 637 * @return true if present 638 */ 639 public boolean hasAccommodation(String code) { 640 return code != null && !code.isEmpty() && iAccommodations.contains(code); 641 } 642 643 public void setClassFirstDate(Integer classFirstDate) { 644 iClassFirstDate = classFirstDate; 645 } 646 647 public Integer getClassFirstDate() { 648 return iClassFirstDate; 649 } 650 651 public void setClassLastDate(Integer classLastDate) { 652 iClassLastDate = classLastDate; 653 } 654 655 public Integer getClassLastDate() { 656 return iClassLastDate; 657 } 658 659 public ModalityPreference getModalityPreference() { return iModalityPreference; } 660 public void setModalityPreference(ModalityPreference p) { iModalityPreference = p ;} 661 662 public BackToBackPreference getBackToBackPreference() { return iBackToBackPreference; } 663 public void setBackToBackPreference(BackToBackPreference p) { iBackToBackPreference = p; } 664 665 public static enum ModalityPreference { 666 NO_PREFERENCE, 667 ONLINE_PREFERRED, 668 ONILNE_DISCOURAGED, 669 ONLINE_REQUIRED, 670 } 671 672 public static enum BackToBackPreference { 673 NO_PREFERENCE, 674 BTB_PREFERRED, 675 BTB_DISCOURAGED, 676 } 677}