001 package net.sf.cpsolver.studentsct.model; 002 003 import java.text.DecimalFormat; 004 import java.util.Collection; 005 import java.util.Collections; 006 import java.util.Enumeration; 007 import java.util.HashSet; 008 import java.util.Iterator; 009 import java.util.Set; 010 import java.util.TreeSet; 011 import java.util.Vector; 012 013 import net.sf.cpsolver.ifs.util.ToolBox; 014 import net.sf.cpsolver.studentsct.constraint.SectionLimit; 015 016 /** 017 * Representation of a request of a student for one or more course. A student requests one of the given courses, 018 * preferably the first one. 019 * <br><br> 020 * 021 * @version 022 * StudentSct 1.1 (Student Sectioning)<br> 023 * Copyright (C) 2007 Tomáš Müller<br> 024 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 025 * Lazenska 391, 76314 Zlin, Czech Republic<br> 026 * <br> 027 * This library is free software; you can redistribute it and/or 028 * modify it under the terms of the GNU Lesser General Public 029 * License as published by the Free Software Foundation; either 030 * version 2.1 of the License, or (at your option) any later version. 031 * <br><br> 032 * This library is distributed in the hope that it will be useful, 033 * but WITHOUT ANY WARRANTY; without even the implied warranty of 034 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 035 * Lesser General Public License for more details. 036 * <br><br> 037 * You should have received a copy of the GNU Lesser General Public 038 * License along with this library; if not, write to the Free Software 039 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 040 */ 041 public class CourseRequest extends Request { 042 private static DecimalFormat sDF = new DecimalFormat("0.000"); 043 private Vector iCourses = null; 044 private Set iWaitlistedChoices = new HashSet(); 045 private Set iSelectedChoices = new HashSet(); 046 private boolean iWaitlist = false; 047 private Double iCachedBound = null, iCachedMinPenalty = null, iCachedMaxPenalty = null; 048 /** Enrollment value: value * sAltValue ^ index, where index is zero for the first course, one for the second course etc. */ 049 public static double sAltValue = 0.5; 050 public static boolean sSameTimePrecise = false; 051 052 /** Constructor 053 * @param id request unique id 054 * @param priority request priority 055 * @param alternative true if the request is alternative (alternative request can be assigned instead of a non-alternative course requests, if it is left unassigned) 056 * @param student appropriate student 057 * @param courses list of requested courses (in the correct order -- first is the requested course, second is the first alternative, etc.) 058 * @param waitlist true if the student can be put on a waitlist (no alternative course request will be given instead) 059 */ 060 public CourseRequest(long id, int priority, boolean alternative, Student student, Vector courses, boolean waitlist) { 061 super(id, priority, alternative, student); 062 iCourses = courses; 063 iWaitlist = waitlist; 064 } 065 066 /** 067 * List of requested courses (in the correct order -- first is the requested course, second is the first alternative, etc.) 068 */ 069 public Vector getCourses() { 070 return iCourses; 071 } 072 073 /** 074 * Create enrollment for the given list of sections. The list of sections needs to be correct, i.e., a section for each subpart of a 075 * configuration of one of the requested courses. 076 */ 077 public Enrollment createEnrollment(Set sections) { 078 if (sections==null || sections.isEmpty()) return null; 079 Config config = ((Section)sections.iterator().next()).getSubpart().getConfig(); 080 return new Enrollment(this, Math.pow(sAltValue, iCourses.indexOf(config.getOffering().getCourse(getStudent()))), config, sections); 081 } 082 083 /** 084 * Return all possible enrollments. 085 */ 086 public Vector computeEnrollments() { 087 Vector ret = new Vector(); 088 int idx = 0; 089 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 090 Course course = (Course)e.nextElement(); 091 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 092 Config config = (Config)f.nextElement(); 093 computeEnrollments(ret, Math.pow(sAltValue, idx), 0, config, new HashSet(), 0, false, false, false, false, -1); 094 } 095 } 096 return ret; 097 } 098 099 /** 100 * Return a subset of all enrollments -- randomly select only up to limitEachConfig enrollments of each config. 101 */ 102 public Vector computeRandomEnrollments(int limitEachConfig) { 103 Vector ret = new Vector(); 104 int idx = 0; 105 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 106 Course course = (Course)e.nextElement(); 107 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 108 Config config = (Config)f.nextElement(); 109 computeEnrollments(ret, Math.pow(sAltValue, idx), 0, config, new HashSet(), 0, false, false, false, true, (limitEachConfig<=0?limitEachConfig:ret.size()+limitEachConfig)); 110 } 111 } 112 return ret; 113 } 114 115 /** Return true if the both sets of sections contain sections of the same subparts, and each pair of sections of the same subpart is offered at the same time. */ 116 private boolean sameTimes(Set sections1, Set sections2) { 117 for (Iterator i1=sections1.iterator();i1.hasNext();) { 118 Section s1 = (Section)i1.next(); 119 Section s2 = null; 120 for (Iterator i2=sections2.iterator();i2.hasNext();) { 121 Section s = (Section)i2.next(); 122 if (s.getSubpart().equals(s1.getSubpart())) { 123 s2 = s; break; 124 } 125 } 126 if (s2==null) return false; 127 if (!ToolBox.equals(s1.getTime(), s2.getTime())) return false; 128 } 129 return true; 130 } 131 132 /** 133 * Recursive computation of enrollments 134 * @param enrollments list of enrollments to be returned 135 * @param value value of the selected sections 136 * @param penalty penalty of the selected sections 137 * @param config selected configurations 138 * @param sections sections selected so far 139 * @param idx index of the subparts (a section of 0..idx-1 subparts has been already selected) 140 * @param avaiableOnly only use available sections 141 * @param skipSameTime for each possible times, pick only one section 142 * @param selectedOnly select only sections that are selected ({@link CourseRequest#isSelected(Section)} is true) 143 * @param random pick sections in a random order (useful when limit is used) 144 * @param limit when above zero, limit the number of selected enrollments to this limit 145 */ 146 private void computeEnrollments(Collection enrollments, double value, double penalty, Config config, HashSet sections, int idx, boolean avaiableOnly, boolean skipSameTime, boolean selectedOnly, boolean random, int limit) { 147 if (limit>0 && enrollments.size()>=limit) return; 148 if (config.getSubparts().size()==idx) { 149 if (skipSameTime && sSameTimePrecise) { 150 boolean waitListedOrSelected = false; 151 if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) { 152 for (Iterator i=sections.iterator();i.hasNext();) { 153 Section section = (Section)i.next(); 154 if (isWaitlisted(section) || isSelected(section)) { waitListedOrSelected = true; break; } 155 } 156 } 157 if (!waitListedOrSelected) { 158 for (Iterator i=enrollments.iterator();i.hasNext();) { 159 Enrollment enrollment = (Enrollment)i.next(); 160 if (sameTimes(enrollment.getAssignments(), sections)) return; 161 } 162 } 163 } 164 enrollments.add(new Enrollment(this, value, config, new HashSet(sections))); 165 } else { 166 Subpart subpart = (Subpart)config.getSubparts().elementAt(idx); 167 HashSet times = (skipSameTime?new HashSet():null); 168 Vector sectionsThisSubpart = subpart.getSections(); 169 if (random) { 170 sectionsThisSubpart = new Vector(subpart.getSections()); 171 Collections.shuffle(sectionsThisSubpart); 172 } else if (skipSameTime) { 173 sectionsThisSubpart = new Vector(subpart.getSections()); 174 Collections.sort(sectionsThisSubpart); 175 } 176 boolean hasChildren = !subpart.getChildren().isEmpty(); 177 for (Enumeration e=sectionsThisSubpart.elements();e.hasMoreElements();) { 178 Section section = (Section)e.nextElement(); 179 if (section.getParent()!=null && !sections.contains(section.getParent())) continue; 180 if (section.isOverlapping(sections)) continue; 181 if (avaiableOnly && section.getLimit()>=0 && SectionLimit.getEnrollmentWeight(section,this)>section.getLimit()) continue; 182 if (selectedOnly && !isSelected(section)) continue; 183 if (skipSameTime && section.getTime()!=null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section)) continue; 184 sections.add(section); 185 computeEnrollments(enrollments, value, penalty+section.getPenalty(), config, sections, idx+1, avaiableOnly, skipSameTime, selectedOnly, random, limit); 186 sections.remove(section); 187 } 188 } 189 } 190 191 /** Return all enrollments that are available */ 192 public Vector getAvaiableEnrollments() { 193 Vector ret = new Vector(); 194 int idx = 0; 195 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 196 Course course = (Course)e.nextElement(); 197 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 198 Config config = (Config)f.nextElement(); 199 computeEnrollments(ret, Math.pow(sAltValue, idx), 0, config, new HashSet(), 0, true, false, false, false, -1); 200 } 201 } 202 return ret; 203 } 204 205 /** Return all enrollments that are selected ({@link CourseRequest#isSelected(Section)} is true) 206 * @param availableOnly pick only available sections 207 */ 208 public Vector getSelectedEnrollments(boolean availableOnly) { 209 if (getSelectedChoices().isEmpty()) return null; 210 Choice firstChoice = (Choice)getSelectedChoices().iterator().next(); 211 Vector enrollments = new Vector(); 212 for (Enumeration e=iCourses.elements();e.hasMoreElements();) { 213 Course course = (Course)e.nextElement(); 214 if (!course.getOffering().equals(firstChoice.getOffering())) continue; 215 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 216 Config config = (Config)f.nextElement(); 217 computeEnrollments(enrollments, 1.0, 0, config, new HashSet(), 0, availableOnly, false, true, false, -1); 218 } 219 } 220 return enrollments; 221 } 222 223 /** Return all enrollments that are available, pick only the first section of the sections with the same time (of each subpart, {@link Section} comparator is used) */ 224 public TreeSet getAvaiableEnrollmentsSkipSameTime() { 225 TreeSet avaiableEnrollmentsSkipSameTime = new TreeSet(); 226 if (getInitialAssignment()!=null) 227 avaiableEnrollmentsSkipSameTime.add(getInitialAssignment()); 228 int idx = 0; 229 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 230 Course course = (Course)e.nextElement(); 231 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 232 Config config = (Config)f.nextElement(); 233 computeEnrollments(avaiableEnrollmentsSkipSameTime, Math.pow(sAltValue, idx), 0, config, new HashSet(), 0, true, true, false, false, -1); 234 } 235 } 236 return avaiableEnrollmentsSkipSameTime; 237 } 238 239 /** 240 * Return all possible enrollments. 241 */ 242 public Vector getEnrollmentsSkipSameTime() { 243 Vector ret = new Vector(); 244 int idx = 0; 245 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 246 Course course = (Course)e.nextElement(); 247 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 248 Config config = (Config)f.nextElement(); 249 computeEnrollments(ret, Math.pow(sAltValue, idx), 0, config, new HashSet(), 0, false, true, false, false, -1); 250 } 251 } 252 return ret; 253 } 254 255 256 /** Wait-listed choices */ 257 public Set getWaitlistedChoices() { 258 return iWaitlistedChoices; 259 } 260 261 /** Return true when the given section is wait-listed (i.e., its choice is among wait-listed choices) */ 262 public boolean isWaitlisted(Section section) { 263 return iWaitlistedChoices.contains(section.getChoice()); 264 } 265 266 /** Selected choices */ 267 public Set getSelectedChoices() { 268 return iSelectedChoices; 269 } 270 271 /** Return true when the given section is selected (i.e., its choice is among selected choices) */ 272 public boolean isSelected(Section section) { 273 return iSelectedChoices.contains(section.getChoice()); 274 } 275 276 /** Request name: A for alternative, 1 + priority, (w) when waitlist, list of course names */ 277 public String getName() { 278 String ret = (isAlternative()?"A":"")+(1+getPriority()+(isAlternative()?-getStudent().nrRequests():0))+". "+(isWaitlist()?"(w) ":""); 279 int idx = 0; 280 for (Enumeration e=iCourses.elements();e.hasMoreElements();idx++) { 281 Course course = (Course)e.nextElement(); 282 if (idx==0) 283 ret+=course.getName(); 284 else 285 ret+=", "+idx+". alt "+course.getName(); 286 } 287 return ret; 288 } 289 290 /** True if the student can be put on a wait-list (no alternative course request will be given instead) */ 291 public boolean isWaitlist() { 292 return iWaitlist; 293 } 294 295 public String toString() { 296 return getName()+(getWeight()!=1.0?" (W:"+sDF.format(getWeight())+")":""); 297 } 298 299 /** Return course of the requested courses with the given id */ 300 public Course getCourse(long courseId) { 301 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 302 Course course = (Course)e.nextElement(); 303 if (course.getId()==courseId) return course; 304 } 305 return null; 306 } 307 308 /** Return configuration of the requested courses with the given id */ 309 public Config getConfig(long configId) { 310 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 311 Course course = (Course)e.nextElement(); 312 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 313 Config config = (Config)f.nextElement(); 314 if (config.getId()==configId) return config; 315 } 316 } 317 return null; 318 } 319 320 /** Return subpart of the requested courses with the given id */ 321 public Subpart getSubpart(long subpartId) { 322 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 323 Course course = (Course)e.nextElement(); 324 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 325 Config config = (Config)f.nextElement(); 326 for (Enumeration i=config.getSubparts().elements();i.hasMoreElements();) { 327 Subpart subpart = (Subpart)i.nextElement(); 328 if (subpart.getId()==subpartId) 329 return subpart; 330 } 331 } 332 } 333 return null; 334 } 335 336 /** Return section of the requested courses with the given id */ 337 public Section getSection(long sectionId) { 338 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 339 Course course = (Course)e.nextElement(); 340 for (Enumeration f=course.getOffering().getConfigs().elements();f.hasMoreElements();) { 341 Config config = (Config)f.nextElement(); 342 for (Enumeration i=config.getSubparts().elements();i.hasMoreElements();) { 343 Subpart subpart = (Subpart)i.nextElement(); 344 for (Enumeration g=subpart.getSections().elements();g.hasMoreElements();) { 345 Section section = (Section)g.nextElement(); 346 if (section.getId()==sectionId) return section; 347 } 348 } 349 } 350 } 351 return null; 352 } 353 354 /** Minimal penalty (minimum of {@link Offering#getMinPenalty()} among requested courses) */ 355 public double getMinPenalty() { 356 if (iCachedMinPenalty==null) { 357 double min = Double.MAX_VALUE; 358 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 359 Course course = (Course)e.nextElement(); 360 min = Math.min(min, course.getOffering().getMinPenalty()); 361 } 362 iCachedMinPenalty = new Double(min); 363 } 364 return iCachedMinPenalty.doubleValue(); 365 } 366 367 /** Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among requested courses) */ 368 public double getMaxPenalty() { 369 if (iCachedMaxPenalty==null) { 370 double max = Double.MIN_VALUE; 371 for (Enumeration e=getCourses().elements();e.hasMoreElements();) { 372 Course course = (Course)e.nextElement(); 373 max = Math.max(max, course.getOffering().getMaxPenalty()); 374 } 375 iCachedMaxPenalty = new Double(max); 376 } 377 return iCachedMaxPenalty.doubleValue(); 378 } 379 380 /** Clear cached min/max penalties and cached bound */ 381 public void clearCache() { 382 iCachedMaxPenalty = null; 383 iCachedMinPenalty = null; 384 iCachedBound = null; 385 } 386 387 /** Estimated bound for this request -- it estimates the smallest value among all possible enrollments */ 388 public double getBound() { 389 if (iCachedBound==null) { 390 iCachedBound = new Double( 391 - Math.pow(Enrollment.sPriorityWeight,getPriority()) * 392 (isAlternative()?Enrollment.sAlterativeWeight:1.0) * 393 Math.pow(Enrollment.sInitialWeight,(getInitialAssignment()==null?0:1)) * 394 Math.pow(Enrollment.sSelectedWeight,(iSelectedChoices.isEmpty()?0:1)) * 395 Math.pow(Enrollment.sWaitlistedWeight,(iWaitlistedChoices.isEmpty()?0:1)) * 396 //Math.max(Enrollment.sMinWeight,getWeight()) * 397 (getStudent().isDummy()?Student.sDummyStudentWeight:1.0) * 398 Enrollment.normalizePenalty(getMinPenalty()) 399 ); 400 } 401 return iCachedBound.doubleValue(); 402 } 403 }