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
404 /** Return true if request is assigned. */
405 public boolean isAssigned() {
406 return getAssignment() != null && !((Enrollment)getAssignment()).getAssignments().isEmpty();
407 }
408 }