001package org.cpsolver.studentsct.weights; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.BitSet; 006import java.util.HashSet; 007import java.util.Set; 008 009import org.cpsolver.coursett.model.Placement; 010import org.cpsolver.coursett.model.RoomLocation; 011import org.cpsolver.coursett.model.TimeLocation; 012import org.cpsolver.ifs.assignment.Assignment; 013import org.cpsolver.ifs.assignment.DefaultSingleAssignment; 014import org.cpsolver.ifs.solution.Solution; 015import org.cpsolver.ifs.util.DataProperties; 016import org.cpsolver.ifs.util.ToolBox; 017import org.cpsolver.studentsct.extension.DistanceConflict; 018import org.cpsolver.studentsct.extension.StudentQuality; 019import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 020import org.cpsolver.studentsct.model.Config; 021import org.cpsolver.studentsct.model.Course; 022import org.cpsolver.studentsct.model.CourseRequest; 023import org.cpsolver.studentsct.model.Enrollment; 024import org.cpsolver.studentsct.model.Offering; 025import org.cpsolver.studentsct.model.Request; 026import org.cpsolver.studentsct.model.SctAssignment; 027import org.cpsolver.studentsct.model.Section; 028import org.cpsolver.studentsct.model.Student; 029import org.cpsolver.studentsct.model.Subpart; 030 031 032/** 033 * Original weighting that was used before this student weightings model was introduced 034 * 035 * @version StudentSct 1.3 (Student Sectioning)<br> 036 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 037 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 038 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 039 * <br> 040 * This library is free software; you can redistribute it and/or modify 041 * it under the terms of the GNU Lesser General Public License as 042 * published by the Free Software Foundation; either version 3 of the 043 * License, or (at your option) any later version. <br> 044 * <br> 045 * This library is distributed in the hope that it will be useful, but 046 * WITHOUT ANY WARRANTY; without even the implied warranty of 047 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 048 * Lesser General Public License for more details. <br> 049 * <br> 050 * You should have received a copy of the GNU Lesser General Public 051 * License along with this library; if not see 052 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 053 */ 054 055public class OriginalStudentWeights implements StudentWeights { 056 private double iPriorityWeight = 0.90; 057 private double iAlterativeWeight = 1.0; 058 private double iInitialWeight = 1.2; 059 private double iSelectedWeight = 1.1; 060 private double iWaitlistedWeight = 1.01; 061 private double iDistConfWeight = 0.95; 062 protected double[] iQalityWeights; 063 /** 064 * Enrollment value: value * sAltValue ^ index, where index is zero for the 065 * first course, one for the second course etc. 066 */ 067 private double iAltValue = 0.5; 068 private double iDummyStudentWeight = 0.5; 069 private double iNormPenalty = 5.0; 070 071 public OriginalStudentWeights(DataProperties config) { 072 iDummyStudentWeight = config.getPropertyDouble("Student.DummyStudentWeight", iDummyStudentWeight); 073 iQalityWeights = new double[StudentQuality.Type.values().length]; 074 for (StudentQuality.Type type: StudentQuality.Type.values()) { 075 iQalityWeights[type.ordinal()] = config.getPropertyDouble(type.getWeightName(), type.getWeightDefault()); 076 } 077 } 078 079 /** 080 * Normalized enrollment penalty -- to be used in 081 * {@link Enrollment#toDouble(Assignment)} 082 * @param penalty given penalty 083 * @return normalized penalty 084 */ 085 public double normalizePenalty(double penalty) { 086 return iNormPenalty / (iNormPenalty + penalty); 087 } 088 089 090 public double getWeight(Request request) { 091 return Math.pow(iPriorityWeight, request.getPriority()) 092 * (request.isAlternative() ? iAlterativeWeight : 1.0) 093 * (request.getStudent().isDummy() ? iDummyStudentWeight : 1.0); 094 } 095 096 @Override 097 public double getBound(Request request) { 098 double w = getWeight(request) * Math.pow(iInitialWeight, (request.getInitialAssignment() == null ? 0 : 1)); 099 if (request instanceof CourseRequest) { 100 CourseRequest cr = (CourseRequest)request; 101 w *= Math.pow(iSelectedWeight, cr.getSelectedChoices().isEmpty() ? 0 : 1); 102 w *= Math.pow(iWaitlistedWeight, cr.getWaitlistedChoices().isEmpty() ? 0 : 1); 103 w *= normalizePenalty(cr.getMinPenalty()); 104 } 105 return w; 106 } 107 108 @Override 109 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 110 return getWeight(enrollment.getRequest()) 111 * Math.pow(iAltValue, enrollment.getPriority()) 112 * Math.pow(iInitialWeight, enrollment.percentInitial()) 113 * Math.pow(iSelectedWeight, enrollment.percentSelected()) 114 * Math.pow(iWaitlistedWeight, enrollment.percentWaitlisted()) 115 * normalizePenalty(enrollment.getPenalty()); 116 } 117 118 @Override 119 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<DistanceConflict.Conflict> distanceConflicts, Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts) { 120 int share = 0; 121 if (timeOverlappingConflicts != null) 122 for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts) 123 share += c.getShare(); 124 return getWeight(assignment, enrollment) 125 * (distanceConflicts == null || distanceConflicts.isEmpty() ? 1.0 : Math.pow(iDistConfWeight, distanceConflicts.size())) 126 * Math.max(share == 0 ? 1.0 : 1.0 - (((double)share) / enrollment.getNrSlots()) / 2.0, 0.5); 127 } 128 129 @Override 130 public double getDistanceConflictWeight(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 131 if (c.getR1().getPriority() < c.getR2().getPriority()) { 132 return (1.0 - iDistConfWeight) * getWeight(assignment, c.getE1()); 133 } else { 134 return (1.0 - iDistConfWeight) * getWeight(assignment, c.getE2()); 135 } 136 } 137 138 @Override 139 public double getTimeOverlapConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, TimeOverlapsCounter.Conflict timeOverlap) { 140 return Math.min(0.5 * timeOverlap.getShare() / enrollment.getNrSlots(), 0.5) * getWeight(assignment, enrollment); 141 } 142 143 @Override 144 public boolean isBetterThanBestSolution(Solution<Request, Enrollment> currentSolution) { 145 if (currentSolution.getBestInfo() == null) return true; 146 int unassigned = currentSolution.getModel().nrUnassignedVariables(currentSolution.getAssignment()); 147 if (currentSolution.getModel().getBestUnassignedVariables() != unassigned) 148 return currentSolution.getModel().getBestUnassignedVariables() > unassigned; 149 return currentSolution.getModel().getTotalValue(currentSolution.getAssignment()) < currentSolution.getBestValue(); 150 } 151 152 /** 153 * Test case -- run to see the weights for a few courses 154 * @param args program arguments 155 */ 156 public static void main(String[] args) { 157 OriginalStudentWeights pw = new OriginalStudentWeights(new DataProperties()); 158 DecimalFormat df = new DecimalFormat("0.000"); 159 Student s = new Student(0l); 160 new CourseRequest(1l, 0, false, s, ToolBox.toList( 161 new Course(1, "A", "1", new Offering(0, "A")), 162 new Course(1, "A", "2", new Offering(0, "A")), 163 new Course(1, "A", "3", new Offering(0, "A"))), false, null); 164 new CourseRequest(2l, 1, false, s, ToolBox.toList( 165 new Course(1, "B", "1", new Offering(0, "B")), 166 new Course(1, "B", "2", new Offering(0, "B")), 167 new Course(1, "B", "3", new Offering(0, "B"))), false, null); 168 new CourseRequest(3l, 2, false, s, ToolBox.toList( 169 new Course(1, "C", "1", new Offering(0, "C")), 170 new Course(1, "C", "2", new Offering(0, "C")), 171 new Course(1, "C", "3", new Offering(0, "C"))), false, null); 172 new CourseRequest(4l, 3, false, s, ToolBox.toList( 173 new Course(1, "D", "1", new Offering(0, "D")), 174 new Course(1, "D", "2", new Offering(0, "D")), 175 new Course(1, "D", "3", new Offering(0, "D"))), false, null); 176 new CourseRequest(5l, 4, false, s, ToolBox.toList( 177 new Course(1, "E", "1", new Offering(0, "E")), 178 new Course(1, "E", "2", new Offering(0, "E")), 179 new Course(1, "E", "3", new Offering(0, "E"))), false, null); 180 new CourseRequest(6l, 5, true, s, ToolBox.toList( 181 new Course(1, "F", "1", new Offering(0, "F")), 182 new Course(1, "F", "2", new Offering(0, "F")), 183 new Course(1, "F", "3", new Offering(0, "F"))), false, null); 184 new CourseRequest(7l, 6, true, s, ToolBox.toList( 185 new Course(1, "G", "1", new Offering(0, "G")), 186 new Course(1, "G", "2", new Offering(0, "G")), 187 new Course(1, "G", "3", new Offering(0, "G"))), false, null); 188 189 Assignment<Request, Enrollment> assignment = new DefaultSingleAssignment<Request, Enrollment>(); 190 Placement p = new Placement(null, new TimeLocation(1, 90, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>()); 191 for (Request r: s.getRequests()) { 192 CourseRequest cr = (CourseRequest)r; 193 double[] w = new double[] {0.0, 0.0, 0.0}; 194 for (int i = 0; i < cr.getCourses().size(); i++) { 195 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 196 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 197 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 198 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 199 w[i] = pw.getWeight(assignment, e, null, null); 200 } 201 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 202 } 203 204 System.out.println("With one distance conflict:"); 205 for (Request r: s.getRequests()) { 206 CourseRequest cr = (CourseRequest)r; 207 double[] w = new double[] {0.0, 0.0, 0.0}; 208 for (int i = 0; i < cr.getCourses().size(); i++) { 209 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 210 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 211 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 212 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 213 Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>(); 214 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next())); 215 w[i] = pw.getWeight(assignment, e, dc, null); 216 } 217 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 218 } 219 220 System.out.println("With two distance conflicts:"); 221 for (Request r: s.getRequests()) { 222 CourseRequest cr = (CourseRequest)r; 223 double[] w = new double[] {0.0, 0.0, 0.0}; 224 for (int i = 0; i < cr.getCourses().size(); i++) { 225 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 226 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 227 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 228 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 229 Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>(); 230 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next())); 231 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, 232 new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null))); 233 w[i] = pw.getWeight(assignment, e, dc, null); 234 } 235 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 236 } 237 238 System.out.println("With 25% time overlapping conflicts:"); 239 for (Request r: s.getRequests()) { 240 CourseRequest cr = (CourseRequest)r; 241 double[] w = new double[] {0.0, 0.0, 0.0}; 242 for (int i = 0; i < cr.getCourses().size(); i++) { 243 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 244 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 245 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 246 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 247 Set<TimeOverlapsCounter.Conflict> toc = new HashSet<TimeOverlapsCounter.Conflict>(); 248 toc.add(new TimeOverlapsCounter.Conflict(s, 3, e, sections.iterator().next(), e, sections.iterator().next())); 249 w[i] = pw.getWeight(assignment, e, null, toc); 250 } 251 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 252 } 253 } 254 255 @Override 256 public boolean isFreeTimeAllowOverlaps() { 257 return true; 258 } 259 260 @Override 261 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<StudentQuality.Conflict> qualityConflicts) { 262 double base = getWeight(assignment, enrollment); 263 double penalty = 0.0; 264 if (qualityConflicts != null) { 265 for (StudentQuality.Conflict c: qualityConflicts) { 266 Enrollment other = c.getOther(enrollment); 267 switch (c.getType().getType()) { 268 case REQUEST: 269 if (enrollment.isCourseRequest()) 270 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 271 else if (other.isCourseRequest()) 272 penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 273 break; 274 case BOTH: 275 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 276 if (other.getRequest() != null) 277 penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 278 break; 279 case LOWER: 280 other = c.getOther(enrollment); 281 if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority()) 282 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 283 else if (other.getRequest() != null) 284 penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 285 break; 286 case HIGHER: 287 other = c.getOther(enrollment); 288 if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority()) 289 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 290 else if (other.getRequest() != null) 291 penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 292 } 293 } 294 } 295 return base - penalty; 296 } 297 298 @Override 299 public double getStudentQualityConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, StudentQuality.Conflict conflict) { 300 switch (conflict.getType().getType()) { 301 case BOTH: 302 if (enrollment == null || enrollment.getRequest() == null) return 0.0; 303 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment); 304 case REQUEST: 305 if (enrollment == null || enrollment.getRequest() == null || !enrollment.isCourseRequest()) return 0.0; 306 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment); 307 case LOWER: 308 if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) { 309 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()); 310 } else { 311 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()); 312 } 313 case HIGHER: 314 if (conflict.getR1().getPriority() > conflict.getR2().getPriority()) { 315 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()); 316 } else { 317 return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()); 318 } 319 default: 320 return 0.0; 321 } 322 } 323}