001 package net.sf.cpsolver.studentsct.constraint; 002 003 import java.util.Iterator; 004 import java.util.Set; 005 import java.util.Vector; 006 007 import net.sf.cpsolver.ifs.model.GlobalConstraint; 008 import net.sf.cpsolver.ifs.model.Value; 009 import net.sf.cpsolver.ifs.util.DataProperties; 010 import net.sf.cpsolver.ifs.util.ToolBox; 011 import net.sf.cpsolver.studentsct.model.Enrollment; 012 import net.sf.cpsolver.studentsct.model.Request; 013 import net.sf.cpsolver.studentsct.model.Section; 014 015 /** 016 * Section limit constraint. This global constraint ensures that 017 * a limit of each section is not exceeded. This means that the total 018 * sum of weights of course requests (see {@link Request#getWeight()}) enrolled 019 * into a section is below the section's limit (see {@link Section#getLimit()}). 020 * 021 * <br><br> 022 * Sections with negative limit are considered unlimited, and therefore 023 * completely ignored by this constraint. 024 * 025 * <br><br> 026 * Parameters: 027 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr> 028 * <tr><td>SectionLimit.PreferDummyStudents</td><td>{@link Boolean}</td><td>If true, requests of dummy (last-like) students are preferred to be selected as conflicting.</td></tr> 029 * </table> 030 * <br><br> 031 * 032 * @version 033 * StudentSct 1.1 (Student Sectioning)<br> 034 * Copyright (C) 2007 Tomáš Müller<br> 035 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 036 * Lazenska 391, 76314 Zlin, Czech Republic<br> 037 * <br> 038 * This library is free software; you can redistribute it and/or 039 * modify it under the terms of the GNU Lesser General Public 040 * License as published by the Free Software Foundation; either 041 * version 2.1 of the License, or (at your option) any later version. 042 * <br><br> 043 * This library is distributed in the hope that it will be useful, 044 * but WITHOUT ANY WARRANTY; without even the implied warranty of 045 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 046 * Lesser General Public License for more details. 047 * <br><br> 048 * You should have received a copy of the GNU Lesser General Public 049 * License along with this library; if not, write to the Free Software 050 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 051 */ 052 public class SectionLimit extends GlobalConstraint { 053 private static double sNominalWeight = 0.00001; 054 private boolean iPreferDummyStudents = false; 055 056 /** 057 * Constructor 058 * @param cfg solver configuration 059 */ 060 public SectionLimit(DataProperties cfg) { 061 super(); 062 iPreferDummyStudents = cfg.getPropertyBoolean("SectionLimit.PreferDummyStudents", false); 063 } 064 065 066 /** 067 * Enrollment weight of a section if the given request is assigned. 068 * In order to overcome rounding problems with last-like students ( 069 * e.g., 5 students are projected to two sections of limit 2 -- 070 * each section can have up to 3 of these last-like students), 071 * the weight of the request with the highest weight in the section is 072 * changed to a small nominal weight. 073 * @param section a section that is of concern 074 * @param request a request of a student to be assigned containing the given section 075 * @return section's new weight 076 */ 077 public static double getEnrollmentWeight(Section section, Request request) { 078 return 079 section.getEnrollmentWeight(request) + request.getWeight() 080 - Math.max(section.getMaxEnrollmentWeight(),request.getWeight()) 081 + sNominalWeight; 082 } 083 084 /** 085 * A given enrollment is conflicting, if there is a section which limit (computed 086 * by {@link SectionLimit#getEnrollmentWeight(Section, Request)}) 087 * exceeds the section limit. 088 * <br> 089 * For each of such sections, one or more existing enrollments are (randomly) 090 * selected as conflicting until the overall weight is under the limit. 091 * 092 * @param value {@link Enrollment} that is being considered 093 * @param conflicts all computed conflicting requests are added into this set 094 */ 095 public void computeConflicts(Value value, Set conflicts) { 096 //get enrollment 097 Enrollment enrollment = (Enrollment)value; 098 099 //exclude free time requests 100 if (!enrollment.isCourseRequest()) return; 101 102 //for each section 103 for (Iterator i=enrollment.getAssignments().iterator();i.hasNext();) { 104 105 Section section = (Section)i.next(); 106 107 //unlimited section 108 if (section.getLimit()<0) continue; 109 110 //new enrollment weight 111 double enrlWeight = getEnrollmentWeight(section, enrollment.getRequest()); 112 113 //below limit -> ok 114 if (enrlWeight<=section.getLimit()) continue; 115 116 //above limit -> compute adepts (current assignments that are not yet conflicting) 117 //exclude all conflicts as well 118 Vector dummyAdepts = new Vector(section.getEnrollments().size()); 119 Vector adepts = new Vector(section.getEnrollments().size()); 120 for (Iterator j=section.getEnrollments().iterator();j.hasNext();) { 121 Enrollment e = (Enrollment)j.next(); 122 if (e.getRequest().equals(enrollment.getRequest())) continue; 123 if (conflicts.contains(e)) 124 enrlWeight -= e.getRequest().getWeight(); 125 else if (iPreferDummyStudents && e.getStudent().isDummy()) 126 dummyAdepts.addElement(e); 127 else 128 adepts.addElement(e); 129 } 130 131 //while above limit -> pick an adept and make it conflicting 132 while (enrlWeight>section.getLimit()) { 133 //pick adept (prefer dummy students), decrease enrollment weight, make conflict 134 if (iPreferDummyStudents && !dummyAdepts.isEmpty()) { 135 Enrollment conflict = (Enrollment)ToolBox.random(dummyAdepts); 136 dummyAdepts.remove(conflict); 137 enrlWeight -= conflict.getRequest().getWeight(); 138 conflicts.add(conflict); 139 } else if (!adepts.isEmpty()) { 140 Enrollment conflict = (Enrollment)ToolBox.random(adepts); 141 adepts.remove(conflict); 142 enrlWeight -= conflict.getRequest().getWeight(); 143 conflicts.add(conflict); 144 } else { 145 //no adepts -> enrollment cannot be assigned 146 conflicts.add(enrollment); break; 147 } 148 } 149 } 150 } 151 152 /** 153 * A given enrollment is conflicting, if there is a section which limit(computed 154 * by {@link SectionLimit#getEnrollmentWeight(Section, Request)}) 155 * exceeds the section limit. 156 * 157 * @param value {@link Enrollment} that is being considered 158 * @return true, if there is a section which will exceed its limit when the given 159 * enrollment is assigned 160 */ 161 public boolean inConflict(Value value) { 162 //get enrollment 163 Enrollment enrollment = (Enrollment)value; 164 165 //exclude free time requests 166 if (!enrollment.isCourseRequest()) return false; 167 168 //for each section 169 for (Iterator i=enrollment.getAssignments().iterator();i.hasNext();) { 170 171 Section section = (Section)i.next(); 172 173 //unlimited section 174 if (section.getLimit()<0) continue; 175 176 //new enrollment weight 177 double enrlWeight = getEnrollmentWeight(section, enrollment.getRequest()); 178 179 //above limit -> conflict 180 if (enrlWeight>section.getLimit()) return true; 181 } 182 183 //no conflicting section -> no conflict 184 return false; 185 } 186 187 public String toString() { 188 return "SectioningLimit"; 189 } 190 }