001 package net.sf.cpsolver.studentsct.model; 002 003 import java.text.DecimalFormat; 004 import java.util.HashSet; 005 import java.util.Iterator; 006 import java.util.Set; 007 import java.util.Vector; 008 009 import net.sf.cpsolver.coursett.model.Placement; 010 import net.sf.cpsolver.coursett.model.TimeLocation; 011 012 /** 013 * Representation of a class. Each section contains id, name, scheduling subpart, time/room placement, and a limit. 014 * Optionally, parent-child relation between sections can be defined. 015 * <br><br> 016 * Each student requesting a course needs to be enrolled in a class of each subpart of a selected configuration. 017 * In the case of parent-child relation between classes, if a student is enrolled in a section that has a parent 018 * section defined, he/she has to be enrolled in the parent section as well. If there is a parent-child relation between 019 * two sections, the same relation is defined on their subparts as well, i.e., if section A is a parent section B, subpart 020 * of section A isa parent of subpart of section B. 021 * <br><br> 022 * 023 * @version 024 * StudentSct 1.1 (Student Sectioning)<br> 025 * Copyright (C) 2007 Tomáš Müller<br> 026 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 027 * Lazenska 391, 76314 Zlin, Czech Republic<br> 028 * <br> 029 * This library is free software; you can redistribute it and/or 030 * modify it under the terms of the GNU Lesser General Public 031 * License as published by the Free Software Foundation; either 032 * version 2.1 of the License, or (at your option) any later version. 033 * <br><br> 034 * This library is distributed in the hope that it will be useful, 035 * but WITHOUT ANY WARRANTY; without even the implied warranty of 036 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 037 * Lesser General Public License for more details. 038 * <br><br> 039 * You should have received a copy of the GNU Lesser General Public 040 * License along with this library; if not, write to the Free Software 041 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 042 */ 043 public class Section implements Assignment, Comparable { 044 private static DecimalFormat sDF = new DecimalFormat("0.000"); 045 private long iId = -1; 046 private String iName = null; 047 private Subpart iSubpart = null; 048 private Section iParent = null; 049 private Placement iPlacement = null; 050 private int iLimit = 0; 051 private HashSet iEnrollments = new HashSet(); 052 private Choice iChoice = null; 053 private double iPenalty = 0.0; 054 private double iEnrollmentWeight = 0.0; 055 private double iMaxEnrollmentWeight = 0.0; 056 private double iMinEnrollmentWeight = 0.0; 057 private double iSpaceExpected = 0.0; 058 private double iSpaceHeld = 0.0; 059 060 /** Constructor 061 * @param id section unique id 062 * @param limit section limit, i.e., the maximal number of students that can be enrolled in this section at the same time 063 * @param name section name 064 * @param subpart subpart of this section 065 * @param placement time/room placement 066 * @param instructorIds instructor(s) id -- needed for {@link Section#getChoice()} 067 * @param instructorNames instructor(s) name -- needed for {@link Section#getChoice()} 068 * @param parent parent section -- if there is a parent section defined, a student that is enrolled in this 069 * section has to be enrolled in the parent section as well. Also, the same relation needs to be defined between subpart of 070 * this section and the subpart of the parent section 071 */ 072 public Section(long id, int limit, String name, Subpart subpart, Placement placement, String instructorIds, String instructorNames, Section parent) { 073 iId = id; 074 iLimit = limit; 075 iName = name; 076 iSubpart = subpart; 077 iSubpart.getSections().add(this); 078 iPlacement = placement; 079 iParent = parent; 080 iChoice = new Choice(getSubpart().getConfig().getOffering(),getSubpart().getInstructionalType(), getTime(), instructorIds, instructorNames); 081 } 082 083 /** Section id */ 084 public long getId() { 085 return iId; 086 } 087 088 /** Section limit. This is defines the maximal number of students that can be enrolled into this section at 089 * the same time. It is -1 in the case of an unlimited section */ 090 public int getLimit() { 091 return iLimit; 092 } 093 094 /** Set section limit */ 095 public void setLimit(int limit) { 096 iLimit = limit; 097 } 098 099 /** Section name */ 100 public String getName() { 101 return iName; 102 } 103 104 /** Scheduling subpart to which this section belongs */ 105 public Subpart getSubpart() { 106 return iSubpart; 107 } 108 109 /** Parent section of this section (can be null). If there is a parent section defined, a student that is enrolled in this 110 * section has to be enrolled in the parent section as well. Also, the same relation needs to be defined between subpart of 111 * this section and the subpart of the parent section. 112 */ 113 public Section getParent() { 114 return iParent; 115 } 116 117 /** Time/room placement of the section. This can be null, for arranged sections. */ 118 public Placement getPlacement() { 119 return iPlacement; 120 } 121 122 /** Time placement of the section. */ 123 public TimeLocation getTime() { 124 return (iPlacement==null?null:iPlacement.getTimeLocation()); 125 } 126 127 /** Number of rooms in which the section meet. */ 128 public int getNrRooms() { 129 return (iPlacement==null?0:iPlacement.getNrRooms()); 130 } 131 132 /** Room placement -- list of {@link net.sf.cpsolver.coursett.model.RoomLocation} */ 133 public Vector getRooms() { 134 if (iPlacement==null) return null; 135 if (iPlacement.getRoomLocations()==null && iPlacement.getRoomLocation()!=null) { 136 Vector ret = new Vector(1); 137 ret.add(iPlacement.getRoomLocation()); 138 return ret; 139 } 140 return iPlacement.getRoomLocations(); 141 } 142 143 /** True, if this section overlaps with the given assignment in time and space */ 144 public boolean isOverlapping(Assignment assignment) { 145 if (getTime()==null || assignment.getTime()==null) return false; 146 return getTime().hasIntersection(assignment.getTime()); 147 } 148 149 /** True, if this section overlaps with one of the given set of assignments in time and space */ 150 public boolean isOverlapping(Set assignments) { 151 if (getTime()==null) return false; 152 for (Iterator i=assignments.iterator();i.hasNext();) { 153 Assignment assignment = (Assignment)i.next(); 154 if (assignment.getTime()==null) continue; 155 if (getTime().hasIntersection(assignment.getTime())) return true; 156 } 157 return false; 158 } 159 160 /** Called when an enrollment with this section is assigned to a request */ 161 public void assigned(Enrollment enrollment) { 162 if (iEnrollments.isEmpty()) { 163 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 164 } else { 165 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 166 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 167 } 168 iEnrollments.add(enrollment); 169 iEnrollmentWeight += enrollment.getRequest().getWeight(); 170 } 171 172 /** Called when an enrollment with this section is unassigned from a request */ 173 public void unassigned(Enrollment enrollment) { 174 iEnrollments.remove(enrollment); 175 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 176 if (iEnrollments.isEmpty()) { 177 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 178 } else if (iMinEnrollmentWeight!=iMaxEnrollmentWeight) { 179 if (iMinEnrollmentWeight==enrollment.getRequest().getWeight()) { 180 double newMinEnrollmentWeight = Double.MAX_VALUE; 181 for (Iterator i=iEnrollments.iterator();i.hasNext();) { 182 Enrollment e = (Enrollment)i.next(); 183 if (e.getRequest().getWeight()==iMinEnrollmentWeight) { 184 newMinEnrollmentWeight = iMinEnrollmentWeight; break; 185 } else { 186 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 187 } 188 } 189 iMinEnrollmentWeight = newMinEnrollmentWeight; 190 } 191 if (iMaxEnrollmentWeight==enrollment.getRequest().getWeight()) { 192 double newMaxEnrollmentWeight = Double.MIN_VALUE; 193 for (Iterator i=iEnrollments.iterator();i.hasNext();) { 194 Enrollment e = (Enrollment)i.next(); 195 if (e.getRequest().getWeight()==iMaxEnrollmentWeight) { 196 newMaxEnrollmentWeight = iMaxEnrollmentWeight; break; 197 } else { 198 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 199 } 200 } 201 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 202 } 203 } 204 } 205 206 /** Set of assigned enrollments */ 207 public Set getEnrollments() { 208 return iEnrollments; 209 } 210 211 /** Enrollment weight -- weight of all requests which have an enrollment that contains this 212 * section, excluding the given one. See {@link Request#getWeight()}.*/ 213 public double getEnrollmentWeight(Request excludeRequest) { 214 double weight = iEnrollmentWeight; 215 if (excludeRequest!=null && excludeRequest.getAssignment()!=null && iEnrollments.contains(excludeRequest.getAssignment())) 216 weight -= excludeRequest.getWeight(); 217 return weight; 218 } 219 220 /** 221 * Maximal weight of a single enrollment in the section 222 */ 223 public double getMaxEnrollmentWeight() { 224 return iMaxEnrollmentWeight; 225 } 226 227 /** 228 * Minimal weight of a single enrollment in the section 229 */ 230 public double getMinEnrollmentWeight() { 231 return iMinEnrollmentWeight; 232 } 233 234 /** Long name: subpart name + time long name + room names + instructor names */ 235 public String getLongName() { 236 return getSubpart().getName()+ 237 (getTime()==null?"":" "+getTime().getLongName())+ 238 (getNrRooms()==0?"":" "+getPlacement().getRoomName(","))+ 239 (getChoice().getInstructorNames()==null?"":" "+getChoice().getInstructorNames()); 240 } 241 242 public String toString() { 243 return getName()+ 244 (getTime()==null?"":" "+getTime().getLongName())+ 245 (getNrRooms()==0?"":" "+getPlacement().getRoomName(","))+ 246 (getChoice().getInstructorNames()==null?"":" "+getChoice().getInstructorNames())+ 247 " (L:"+(getLimit()<0?"unlimited":""+getLimit())+(getPenalty()==0.0?"":",P:"+sDF.format(getPenalty()))+")"; 248 } 249 250 /** A (student) choice representing this section. */ 251 public Choice getChoice() { 252 return iChoice; 253 } 254 255 /** Return penalty which is added to an enrollment that contains this section. */ 256 public double getPenalty() { 257 return iPenalty; 258 } 259 260 /** Set penalty which is added to an enrollment that contains this section. */ 261 public void setPenalty(double penalty) { 262 iPenalty = penalty; 263 } 264 265 /** Compare two sections, prefer sections with lower penalty and more open space*/ 266 public int compareTo(Object o) { 267 if (o==null || !(o instanceof Section)) return -1; 268 Section s = (Section)o; 269 int cmp = Double.compare(getPenalty(),s.getPenalty()); 270 if (cmp!=0) return cmp; 271 cmp = Double.compare(getLimit()-getEnrollmentWeight(null),s.getLimit()-s.getEnrollmentWeight(null)); 272 if (cmp!=0) return cmp; 273 return Double.compare(getId(),s.getId()); 274 } 275 276 /** 277 * Return the amount of space of this section that is held for incoming students. 278 * This attribute is computed during the batch sectioning (it is the overall weight of 279 * dummy students enrolled in this section) and it is being updated with each incomming student during the 280 * online sectioning. 281 */ 282 public double getSpaceHeld() { 283 return iSpaceHeld; 284 } 285 286 /** 287 * Set the amount of space of this section that is held for incoming students. 288 * See {@link Section#getSpaceHeld()} for more info. 289 */ 290 public void setSpaceHeld(double spaceHeld) { 291 iSpaceHeld = spaceHeld; 292 } 293 294 /** 295 * Return the amount of space of this section that is expected to be taken by incoming students. 296 * This attribute is computed during the batch sectioning (for each dummy student that can attend 297 * this section (without any conflict with other enrollments of that student), 1 / x where x is the 298 * number of such sections of this subpart is added to this value). Also, this value is being updated with 299 * each incomming student during the online sectioning. 300 */ 301 public double getSpaceExpected() { 302 return iSpaceExpected; 303 } 304 305 /** 306 * Set the amount of space of this section that is expected to be taken by incoming students. 307 * See {@link Section#getSpaceExpected()} for more info. 308 */ 309 public void setSpaceExpected(double spaceExpected) { 310 iSpaceExpected = spaceExpected; 311 } 312 313 /** 314 * Online sectioning penalty. 315 */ 316 public double getOnlineSectioningPenalty() { 317 if (getLimit()<=0)return 0.0; 318 319 double available = getLimit() - getEnrollmentWeight(null); 320 321 double penalty = (getSpaceExpected() - available) / getLimit(); 322 323 return Math.max(-1.0,Math.min(1.0,penalty)); 324 } 325 326 }