001 package net.sf.cpsolver.studentsct; 002 003 import java.util.Enumeration; 004 import java.util.HashSet; 005 import java.util.Hashtable; 006 import java.util.Iterator; 007 import java.util.Set; 008 import java.util.Vector; 009 010 import org.apache.log4j.Logger; 011 012 import net.sf.cpsolver.ifs.model.Constraint; 013 import net.sf.cpsolver.ifs.model.ConstraintListener; 014 import net.sf.cpsolver.ifs.model.Model; 015 import net.sf.cpsolver.ifs.model.Value; 016 import net.sf.cpsolver.ifs.util.DataProperties; 017 import net.sf.cpsolver.ifs.util.EnumerableHashSet; 018 import net.sf.cpsolver.studentsct.constraint.SectionLimit; 019 import net.sf.cpsolver.studentsct.constraint.StudentConflict; 020 import net.sf.cpsolver.studentsct.extension.DistanceConflict; 021 import net.sf.cpsolver.studentsct.model.Config; 022 import net.sf.cpsolver.studentsct.model.CourseRequest; 023 import net.sf.cpsolver.studentsct.model.Enrollment; 024 import net.sf.cpsolver.studentsct.model.Offering; 025 import net.sf.cpsolver.studentsct.model.Request; 026 import net.sf.cpsolver.studentsct.model.Section; 027 import net.sf.cpsolver.studentsct.model.Student; 028 import net.sf.cpsolver.studentsct.model.Subpart; 029 030 /** 031 * Student sectioning model. 032 * 033 * <br><br> 034 * 035 * @version 036 * StudentSct 1.1 (Student Sectioning)<br> 037 * Copyright (C) 2007 Tomáš Müller<br> 038 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 039 * Lazenska 391, 76314 Zlin, Czech Republic<br> 040 * <br> 041 * This library is free software; you can redistribute it and/or 042 * modify it under the terms of the GNU Lesser General Public 043 * License as published by the Free Software Foundation; either 044 * version 2.1 of the License, or (at your option) any later version. 045 * <br><br> 046 * This library is distributed in the hope that it will be useful, 047 * but WITHOUT ANY WARRANTY; without even the implied warranty of 048 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 049 * Lesser General Public License for more details. 050 * <br><br> 051 * You should have received a copy of the GNU Lesser General Public 052 * License along with this library; if not, write to the Free Software 053 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 054 */ 055 public class StudentSectioningModel extends Model { 056 private static Logger sLog = Logger.getLogger(StudentSectioningModel.class); 057 private Vector iStudents = new Vector(); 058 private Vector iOfferings = new Vector(); 059 private HashSet iCompleteStudents = new HashSet(); 060 private double iTotalValue = 0.0; 061 private DataProperties iProperties; 062 private DistanceConflict iDistanceConflict = null; 063 private int iNrDummyStudents = 0, iNrDummyRequests = 0, iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0; 064 065 /** 066 * Constructor 067 * @param properties configuration 068 */ 069 public StudentSectioningModel(DataProperties properties) { 070 super(); 071 iAssignedVariables = new EnumerableHashSet(); 072 iUnassignedVariables = new EnumerableHashSet(); 073 iPerturbVariables = new EnumerableHashSet(); 074 SectionLimit sectionLimit = new SectionLimit(properties); 075 addGlobalConstraint(sectionLimit); 076 sectionLimit.addConstraintListener(new ConstraintListener() { 077 public void constraintBeforeAssigned(long iteration, Constraint constraint, Value assigned, Set unassigned) { 078 Enrollment enrollment = (Enrollment)assigned; 079 if (enrollment.getStudent().isDummy()) 080 for (Iterator i=unassigned.iterator();i.hasNext();) { 081 Enrollment conflict = (Enrollment)i.next(); 082 if (!conflict.getStudent().isDummy()) { 083 sLog.warn("Enrolment of a real student "+conflict.getStudent()+" is unassigned "+ 084 "\n -- "+conflict+ 085 "\ndue to an enrollment of a dummy student "+enrollment.getStudent()+" " + 086 "\n -- "+enrollment); 087 } 088 } 089 } 090 public void constraintAfterAssigned(long iteration, Constraint constraint, Value assigned, Set unassigned) {} 091 }); 092 iProperties = properties; 093 } 094 095 /** 096 * Students 097 */ 098 public Vector getStudents() { 099 return iStudents; 100 } 101 102 /** 103 * Students with complete schedules (see {@link Student#isComplete()}) 104 */ 105 public Set getCompleteStudents() { 106 return iCompleteStudents; 107 } 108 109 /** 110 * Add a student into the model 111 */ 112 public void addStudent(Student student) { 113 iStudents.addElement(student); 114 if (student.isDummy()) iNrDummyStudents++; 115 StudentConflict conflict = new StudentConflict(); 116 for (Enumeration e=student.getRequests().elements();e.hasMoreElements();) { 117 Request request = (Request)e.nextElement(); 118 conflict.addVariable(request); 119 addVariable(request); 120 if (student.isDummy()) iNrDummyRequests++; 121 } 122 addConstraint(conflict); 123 if (student.isComplete()) 124 iCompleteStudents.add(student); 125 } 126 127 /** 128 * Remove a student from the model 129 */ 130 public void removeStudent(Student student) { 131 iStudents.removeElement(student); 132 if (student.isDummy()) iNrDummyStudents--; 133 if (student.isComplete()) iCompleteStudents.remove(student); 134 StudentConflict conflict = null; 135 for (Enumeration e=student.getRequests().elements();e.hasMoreElements();) { 136 Request request = (Request)e.nextElement(); 137 for (Enumeration f=request.constraints().elements();conflict==null && f.hasMoreElements();) { 138 Constraint c = (Constraint)f.nextElement(); 139 if (c instanceof StudentConflict) conflict = (StudentConflict)c; 140 } 141 conflict.removeVariable(request); 142 removeVariable(request); 143 if (student.isDummy()) iNrDummyRequests--; 144 } 145 removeConstraint(conflict); 146 } 147 148 /** 149 * List of offerings 150 */ 151 public Vector getOfferings() { 152 return iOfferings; 153 } 154 155 /** 156 * Add an offering into the model 157 */ 158 public void addOffering(Offering offering) { 159 iOfferings.add(offering); 160 } 161 162 /** 163 * Number of students with complete schedule 164 */ 165 public int nrComplete() { 166 return getCompleteStudents().size(); 167 } 168 169 /** 170 * Model info 171 */ 172 public Hashtable getInfo() { 173 Hashtable info = super.getInfo(); 174 info.put("Students with complete schedule" , 175 sDoubleFormat.format(100.0*nrComplete()/getStudents().size())+"% ("+nrComplete()+"/"+getStudents().size()+")"); 176 if (getDistanceConflict()!=null) 177 info.put("Student distance conflicts", sDoubleFormat.format(getDistanceConflict().getTotalNrConflicts())); 178 return info; 179 } 180 181 /** 182 * Overall solution value 183 */ 184 public double getTotalValue() { 185 return iTotalValue; 186 } 187 188 /** 189 * Called after an enrollment was assigned to a request. The list of complete students 190 * and the overall solution value are updated. 191 */ 192 public void afterAssigned(long iteration, Value value) { 193 super.afterAssigned(iteration, value); 194 Enrollment enrollment = (Enrollment)value; 195 Student student = enrollment.getStudent(); 196 if (student.isComplete()) 197 iCompleteStudents.add(student); 198 iTotalValue += value.toDouble(); 199 if (student.isDummy()) { 200 iNrAssignedDummyRequests++; 201 if (student.isComplete()) iNrCompleteDummyStudents++; 202 } 203 } 204 205 /** 206 * Called before an enrollment was unassigned from a request. The list of complete students 207 * and the overall solution value are updated. 208 */ 209 public void afterUnassigned(long iteration, Value value) { 210 super.afterUnassigned(iteration, value); 211 Enrollment enrollment = (Enrollment)value; 212 Student student = enrollment.getStudent(); 213 if (iCompleteStudents.contains(student) && !student.isComplete()) 214 iCompleteStudents.remove(student); 215 iTotalValue -= value.toDouble(); 216 if (student.isDummy()) { 217 iNrAssignedDummyRequests--; 218 if (student.isComplete()) iNrCompleteDummyStudents--; 219 } 220 } 221 222 /** 223 * Configuration 224 */ 225 public DataProperties getProperties() { 226 return iProperties; 227 } 228 229 /** 230 * Empty online student sectioning infos for all sections (see {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 231 */ 232 public void clearOnlineSectioningInfos() { 233 for (Enumeration e=iOfferings.elements();e.hasMoreElements();) { 234 Offering offering = (Offering)e.nextElement(); 235 for (Enumeration f=offering.getConfigs().elements();f.hasMoreElements();) { 236 Config config = (Config)f.nextElement(); 237 for (Enumeration g=config.getSubparts().elements();g.hasMoreElements();) { 238 Subpart subpart = (Subpart)g.nextElement(); 239 for (Enumeration h=subpart.getSections().elements();h.hasMoreElements();) { 240 Section section = (Section)h.nextElement(); 241 section.setSpaceExpected(0); 242 section.setSpaceHeld(0); 243 } 244 } 245 } 246 } 247 } 248 249 /** 250 * Compute online student sectioning infos for all sections (see {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 251 */ 252 public void computeOnlineSectioningInfos() { 253 clearOnlineSectioningInfos(); 254 for (Enumeration e=getStudents().elements();e.hasMoreElements();) { 255 Student student = (Student)e.nextElement(); 256 if (!student.isDummy()) continue; 257 for (Enumeration f=student.getRequests().elements();f.hasMoreElements();) { 258 Request request = (Request)f.nextElement(); 259 if (!(request instanceof CourseRequest)) continue; 260 CourseRequest courseRequest = (CourseRequest)request; 261 Enrollment enrollment = (Enrollment)courseRequest.getAssignment(); 262 if (enrollment!=null) { 263 for (Iterator i=enrollment.getAssignments().iterator();i.hasNext();) { 264 Section section = (Section)i.next(); 265 section.setSpaceHeld(courseRequest.getWeight()+section.getSpaceHeld()); 266 } 267 } 268 Vector feasibleEnrollments = new Vector(); 269 for (Enumeration g=courseRequest.values().elements();g.hasMoreElements();) { 270 Enrollment enrl = (Enrollment)g.nextElement(); 271 boolean overlaps = false; 272 for (Enumeration h=student.getRequests().elements();h.hasMoreElements();) { 273 CourseRequest otherCourseRequest = (CourseRequest)h.nextElement(); 274 if (otherCourseRequest.equals(courseRequest)) continue; 275 Enrollment otherErollment = (Enrollment)otherCourseRequest.getAssignment(); 276 if (otherErollment==null) continue; 277 if (enrl.isOverlapping(otherErollment)) { 278 overlaps = true; break; 279 } 280 } 281 if (!overlaps) 282 feasibleEnrollments.add(enrl); 283 } 284 double increment = courseRequest.getWeight() / feasibleEnrollments.size(); 285 for (Enumeration g=feasibleEnrollments.elements();g.hasMoreElements();) { 286 Enrollment feasibleEnrollment = (Enrollment)g.nextElement(); 287 for (Iterator i=feasibleEnrollment.getAssignments().iterator();i.hasNext();) { 288 Section section = (Section)i.next(); 289 section.setSpaceExpected(section.getSpaceExpected()+increment); 290 } 291 } 292 } 293 } 294 } 295 296 /** 297 * Sum of weights of all requests that are not assigned (see {@link Request#getWeight()}). 298 */ 299 public double getUnassignedRequestWeight() { 300 double weight = 0.0; 301 for (Enumeration e=unassignedVariables().elements();e.hasMoreElements();) { 302 Request request = (Request)e.nextElement(); 303 weight += request.getWeight(); 304 } 305 return weight; 306 } 307 308 /** 309 * Sum of weights of all requests (see {@link Request#getWeight()}). 310 */ 311 public double getTotalRequestWeight() { 312 double weight = 0.0; 313 for (Enumeration e=variables().elements();e.hasMoreElements();) { 314 Request request = (Request)e.nextElement(); 315 weight += request.getWeight(); 316 } 317 return weight; 318 } 319 320 /** 321 * Set distance conflict extension 322 */ 323 public void setDistanceConflict(DistanceConflict dc) { 324 iDistanceConflict = dc; 325 } 326 327 /** 328 * Return distance conflict extension 329 */ 330 public DistanceConflict getDistanceConflict() { 331 return iDistanceConflict; 332 } 333 334 /** 335 * Average priority of unassigned requests (see {@link Request#getPriority()}) 336 */ 337 public double avgUnassignPriority() { 338 double totalPriority = 0.0; 339 for (Enumeration e=unassignedVariables().elements();e.hasMoreElements();) { 340 Request request = (Request)e.nextElement(); 341 if (request.isAlternative()) continue; 342 totalPriority += request.getPriority(); 343 } 344 return 1.0 + totalPriority / unassignedVariables().size(); 345 } 346 347 /** 348 * Average number of requests per student (see {@link Student#getRequests()}) 349 */ 350 public double avgNrRequests() { 351 double totalRequests = 0.0; 352 int totalStudents = 0; 353 for (Enumeration e=getStudents().elements();e.hasMoreElements();) { 354 Student student = (Student)e.nextElement(); 355 if (student.nrRequests()==0) continue; 356 totalRequests += student.nrRequests(); 357 totalStudents ++; 358 } 359 return totalRequests / totalStudents; 360 } 361 362 /** Number of last like ({@link Student#isDummy()} equals true) students. */ 363 public int getNrLastLikeStudents(boolean precise) { 364 if (!precise) return iNrDummyStudents; 365 int nrLastLikeStudents = 0; 366 for (Enumeration e=getStudents().elements();e.hasMoreElements();) { 367 Student student = (Student)e.nextElement(); 368 if (student.isDummy()) nrLastLikeStudents++; 369 } 370 return nrLastLikeStudents; 371 } 372 373 /** Number of real ({@link Student#isDummy()} equals false) students. */ 374 public int getNrRealStudents(boolean precise) { 375 if (!precise) return getStudents().size()-iNrDummyStudents; 376 int nrRealStudents = 0; 377 for (Enumeration e=getStudents().elements();e.hasMoreElements();) { 378 Student student = (Student)e.nextElement(); 379 if (!student.isDummy()) nrRealStudents++; 380 } 381 return nrRealStudents; 382 } 383 384 /** Number of last like ({@link Student#isDummy()} equals true) students with a complete schedule ({@link Student#isComplete()} equals true). */ 385 public int getNrCompleteLastLikeStudents(boolean precise) { 386 if (!precise) return iNrCompleteDummyStudents; 387 int nrLastLikeStudents = 0; 388 for (Iterator i=getCompleteStudents().iterator();i.hasNext();) { 389 Student student = (Student)i.next(); 390 if (student.isDummy()) nrLastLikeStudents++; 391 } 392 return nrLastLikeStudents; 393 } 394 395 /** Number of real ({@link Student#isDummy()} equals false) students with a complete schedule ({@link Student#isComplete()} equals true). */ 396 public int getNrCompleteRealStudents(boolean precise) { 397 if (!precise) return getCompleteStudents().size()-iNrCompleteDummyStudents; 398 int nrRealStudents = 0; 399 for (Iterator i=getCompleteStudents().iterator();i.hasNext();) { 400 Student student = (Student)i.next(); 401 if (!student.isDummy()) nrRealStudents++; 402 } 403 return nrRealStudents; 404 } 405 406 /** Number of requests from last-like ({@link Student#isDummy()} equals true) students. */ 407 public int getNrLastLikeRequests(boolean precise) { 408 if (!precise) return iNrDummyRequests; 409 int nrLastLikeRequests = 0; 410 for (Enumeration e=variables().elements();e.hasMoreElements();) { 411 Request request = (Request)e.nextElement(); 412 if (request.getStudent().isDummy()) nrLastLikeRequests++; 413 } 414 return nrLastLikeRequests; 415 } 416 417 /** Number of requests from real ({@link Student#isDummy()} equals false) students. */ 418 public int getNrRealRequests(boolean precise) { 419 if (!precise) return variables().size()-iNrDummyRequests; 420 int nrRealRequests = 0; 421 for (Enumeration e=variables().elements();e.hasMoreElements();) { 422 Request request = (Request)e.nextElement(); 423 if (!request.getStudent().isDummy()) nrRealRequests++; 424 } 425 return nrRealRequests; 426 } 427 428 /** Number of requests from last-like ({@link Student#isDummy()} equals true) students that are assigned. */ 429 public int getNrAssignedLastLikeRequests(boolean precise) { 430 if (!precise) return iNrAssignedDummyRequests; 431 int nrLastLikeRequests = 0; 432 for (Enumeration e=assignedVariables().elements();e.hasMoreElements();) { 433 Request request = (Request)e.nextElement(); 434 if (request.getStudent().isDummy()) nrLastLikeRequests++; 435 } 436 return nrLastLikeRequests; 437 } 438 439 /** Number of requests from real ({@link Student#isDummy()} equals false) students that are assigned. */ 440 public int getNrAssignedRealRequests(boolean precise) { 441 if (!precise) return assignedVariables().size()-iNrAssignedDummyRequests; 442 int nrRealRequests = 0; 443 for (Enumeration e=assignedVariables().elements();e.hasMoreElements();) { 444 Request request = (Request)e.nextElement(); 445 if (!request.getStudent().isDummy()) nrRealRequests++; 446 } 447 return nrRealRequests; 448 } 449 450 /** 451 * Model extended info. Some more information (that is more expensive to compute) is added to an ordinary {@link Model#getInfo()}. 452 */ 453 public Hashtable getExtendedInfo() { 454 Hashtable info = getInfo(); 455 int nrLastLikeStudents = getNrLastLikeStudents(true); 456 if (nrLastLikeStudents!=0 && nrLastLikeStudents!=getStudents().size()) { 457 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 458 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true); 459 int nrRealCompleteStudents = getCompleteStudents().size()-nrLastLikeCompleteStudents; 460 info.put("Last-like students with complete schedule" , 461 sDoubleFormat.format(100.0*nrLastLikeCompleteStudents/nrLastLikeStudents)+"% ("+nrLastLikeCompleteStudents+"/"+nrLastLikeStudents+")"); 462 info.put("Real students with complete schedule" , 463 sDoubleFormat.format(100.0*nrRealCompleteStudents/nrRealStudents)+"% ("+nrRealCompleteStudents+"/"+nrRealStudents+")"); 464 int nrLastLikeRequests = getNrLastLikeRequests(true); 465 int nrRealRequests = variables().size()-nrLastLikeRequests; 466 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true); 467 int nrRealAssignedRequests = assignedVariables().size()-nrLastLikeAssignedRequests; 468 info.put("Last-like assigned requests" , 469 sDoubleFormat.format(100.0*nrLastLikeAssignedRequests/nrLastLikeRequests)+"% ("+nrLastLikeAssignedRequests+"/"+nrLastLikeRequests+")"); 470 info.put("Real assigned requests" , 471 sDoubleFormat.format(100.0*nrRealAssignedRequests/nrRealRequests)+"% ("+nrRealAssignedRequests+"/"+nrRealRequests+")"); 472 } 473 info.put("Average unassigned priority", sDoubleFormat.format(avgUnassignPriority())); 474 info.put("Average number of requests", sDoubleFormat.format(avgNrRequests())); 475 info.put("Unassigned request weight", sDoubleFormat.format(getUnassignedRequestWeight())+" / "+sDoubleFormat.format(getTotalRequestWeight())); 476 return info; 477 } 478 479 }