001package org.cpsolver.ifs.criteria; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Locale; 006import java.util.Map; 007import java.util.Set; 008 009import org.cpsolver.coursett.criteria.TimetablingCriterion; 010import org.cpsolver.ifs.assignment.Assignment; 011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 012import org.cpsolver.ifs.assignment.context.AssignmentContext; 013import org.cpsolver.ifs.assignment.context.AssignmentContextHelper; 014import org.cpsolver.ifs.assignment.context.AssignmentContextReference; 015import org.cpsolver.ifs.assignment.context.CanHoldContext; 016import org.cpsolver.ifs.assignment.context.ConstraintWithContext; 017import org.cpsolver.ifs.assignment.context.HasAssignmentContext; 018import org.cpsolver.ifs.model.Constraint; 019import org.cpsolver.ifs.model.Model; 020import org.cpsolver.ifs.model.Value; 021import org.cpsolver.ifs.model.Variable; 022import org.cpsolver.ifs.solver.Solver; 023import org.cpsolver.ifs.util.DataProperties; 024 025 026/** 027 * Abstract Criterion. <br> 028 * <br> 029 * An optimization objective can be split into several (optimization) criteria 030 * and modeled as a weighted sum of these. This makes the implementation of a particular problem 031 * more versatile as it allows for an easier modification of the optimization objective. 032 * <br> 033 * This class implements most of the {@link Criterion} except of the {@link Criterion#getValue(Assignment, Value, Set)}. 034 * 035 * @author Tomáš Müller 036 * @version IFS 1.3 (Iterative Forward Search)<br> 037 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 038 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 039 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 040 * <br> 041 * This library is free software; you can redistribute it and/or modify 042 * it under the terms of the GNU Lesser General Public License as 043 * published by the Free Software Foundation; either version 3 of the 044 * License, or (at your option) any later version. <br> 045 * <br> 046 * This library is distributed in the hope that it will be useful, but 047 * 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. <br> 050 * <br> 051 * You should have received a copy of the GNU Lesser General Public 052 * License along with this library; if not see 053 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 054 * @param <V> Variable 055 * @param <T> Value 056 */ 057public abstract class AbstractCriterion<V extends Variable<V, T>, T extends Value<V, T>> implements Criterion<V, T>, HasAssignmentContext<V, T, AbstractCriterion<V,T>.ValueContext>, CanHoldContext { 058 private Model<V, T> iModel; 059 protected double iBest = 0.0, iWeight = 0.0; 060 protected static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.##", 061 new java.text.DecimalFormatSymbols(Locale.US)); 062 protected static java.text.DecimalFormat sPercentFormat = new java.text.DecimalFormat("0.##", 063 new java.text.DecimalFormatSymbols(Locale.US)); 064 protected boolean iDebug = false; 065 066 private AssignmentContextReference<V, T, ValueContext> iContextReference = null; 067 private AssignmentContext[] iContext = new AssignmentContext[CanHoldContext.sMaxSize]; 068 private int iLastCacheId = 0; 069 070 071 /** 072 * Defines how the overall value of the criterion should be automatically updated (using {@link Criterion#getValue(Value, Set)}). 073 */ 074 protected static enum ValueUpdateType { 075 /** Update is done before an unassignment (decrement) and before an assignment (increment). */ 076 BeforeUnassignedBeforeAssigned, 077 /** Update is done after an unassignment (decrement) and before an assignment (increment). */ 078 AfterUnassignedBeforeAssigned, 079 /** Update is done before an unassignment (decrement) and after an assignment (increment). */ 080 BeforeUnassignedAfterAssigned, 081 /** Update is done after an unassignment (decrement) and after an assignment (increment). This is the default. */ 082 AfterUnassignedAfterAssigned, 083 /** Criterion is to be updated manually (e.g., using {@link Criterion#inc(Assignment, double)}). */ 084 NoUpdate 085 } 086 private ValueUpdateType iValueUpdateType = ValueUpdateType.BeforeUnassignedBeforeAssigned; 087 088 /** Defines weight name (to be used to get the criterion weight from the configuration). 089 * @return name of the weight associated with this criterion 090 **/ 091 public String getWeightName() { 092 return "Weight." + getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')); 093 } 094 095 /** Defines default weight (when {@link AbstractCriterion#getWeightName()} parameter is not present in the criterion). 096 * @param config solver configuration 097 * @return default criterion weight value 098 **/ 099 public double getWeightDefault(DataProperties config) { 100 return 0.0; 101 } 102 103 @Override 104 public void setModel(Model<V,T> model) { 105 iModel = model; 106 if (model != null) 107 iContextReference = model.createReference(this); 108 } 109 110 @Override 111 public void configure(DataProperties properties) { 112 iWeight = properties.getPropertyDouble(getWeightName(), getWeightDefault(properties)); 113 iDebug = properties.getPropertyBoolean("Debug." + getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')), properties.getPropertyBoolean("Debug.Criterion", false)); 114 } 115 116 @Override 117 public boolean init(Solver<V, T> solver) { 118 configure(solver.getProperties()); 119 return true; 120 } 121 122 /** 123 * Returns current model 124 * @return problem model 125 **/ 126 public Model<V, T> getModel() { return iModel; } 127 128 /** 129 * Returns an assignment context associated with this criterion. If there is no 130 * assignment context associated with this criterion yet, one is created using the 131 * {@link ConstraintWithContext#createAssignmentContext(Assignment)} method. From that time on, 132 * this context is kept with the assignment and automatically updated by calling the 133 * {@link AssignmentConstraintContext#assigned(Assignment, Value)} and {@link AssignmentConstraintContext#unassigned(Assignment, Value)} 134 * whenever a variable is changed as given by the {@link ValueUpdateType}. 135 * @param assignment given assignment 136 * @return assignment context associated with this constraint and the given assignment 137 */ 138 @Override 139 public ValueContext getContext(Assignment<V, T> assignment) { 140 return AssignmentContextHelper.getContext(this, assignment); 141 } 142 143 @Override 144 public ValueContext createAssignmentContext(Assignment<V,T> assignment) { 145 return new ValueContext(assignment); 146 } 147 148 @Override 149 public AssignmentContextReference<V, T, ValueContext> getAssignmentContextReference() { return iContextReference; } 150 151 @Override 152 public void setAssignmentContextReference(AssignmentContextReference<V, T, ValueContext> reference) { iContextReference = reference; } 153 154 @Override 155 public AssignmentContext[] getContext() { 156 return iContext; 157 } 158 159 @Override 160 public double getValue(Assignment<V, T> assignment) { 161 return getContext(assignment).getTotal(); 162 } 163 164 @Override 165 public double getBest() { 166 return iBest; 167 } 168 169 @Override 170 public double getValue(Assignment<V, T> assignment, Collection<V> variables) { 171 double ret = 0; 172 for (V v: variables) { 173 T t = assignment.getValue(v); 174 if (t != null) ret += getValue(assignment, t, null); 175 } 176 return ret; 177 } 178 179 180 @Override 181 public double getWeight() { 182 return iWeight; 183 } 184 185 @Override 186 public double getWeightedBest() { 187 return getWeight() == 0.0 ? 0.0 : getWeight() * getBest(); 188 } 189 190 @Override 191 public double getWeightedValue(Assignment<V, T> assignment) { 192 return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment)); 193 } 194 195 @Override 196 public double getWeightedValue(Assignment<V, T> assignment, T value, Set<T> conflicts) { 197 return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment, value, conflicts)); 198 } 199 200 @Override 201 public double getWeightedValue(Assignment<V, T> assignment, Collection<V> variables) { 202 return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment, variables)); 203 } 204 205 /** Compute bounds (bounds are being cached by default). 206 * @param assignment current assignment 207 * @return minimum and maximum of this criterion's value 208 **/ 209 protected double[] computeBounds(Assignment<V, T> assignment) { 210 return getBounds(assignment, new ArrayList<V>(getModel().variables())); 211 } 212 213 @Override 214 public double[] getBounds(Assignment<V, T> assignment) { 215 return getContext(assignment).getBounds(assignment); 216 } 217 218 @Override 219 public double[] getBounds(Assignment<V, T> assignment, Collection<V> variables) { 220 double[] bounds = new double[] { 0.0, 0.0 }; 221 for (V v: variables) { 222 Double min = null, max = null; 223 for (T t: v.values(assignment)) { 224 double value = getValue(assignment, t, null); 225 if (min == null) { min = value; max = value; continue; } 226 min = Math.min(min, value); 227 max = Math.max(max, value); 228 } 229 if (min != null) { 230 bounds[0] += min; 231 bounds[1] += max; 232 } 233 } 234 return bounds; 235 } 236 237 @Override 238 public void beforeAssigned(Assignment<V, T> assignment, long iteration, T value) { 239 switch (getValueUpdateType()) { 240 case AfterUnassignedBeforeAssigned: 241 case BeforeUnassignedBeforeAssigned: 242 getContext(assignment).assigned(assignment, value); 243 } 244 } 245 246 @Override 247 public void afterAssigned(Assignment<V, T> assignment, long iteration, T value) { 248 switch (getValueUpdateType()) { 249 case AfterUnassignedAfterAssigned: 250 case BeforeUnassignedAfterAssigned: 251 getContext(assignment).assigned(assignment, value); 252 } 253 } 254 255 @Override 256 public void beforeUnassigned(Assignment<V, T> assignment, long iteration, T value) { 257 switch (getValueUpdateType()) { 258 case BeforeUnassignedAfterAssigned: 259 case BeforeUnassignedBeforeAssigned: 260 getContext(assignment).unassigned(assignment, value); 261 } 262 } 263 264 @Override 265 public void afterUnassigned(Assignment<V, T> assignment, long iteration, T value) { 266 switch (getValueUpdateType()) { 267 case AfterUnassignedAfterAssigned: 268 case AfterUnassignedBeforeAssigned: 269 getContext(assignment).unassigned(assignment, value); 270 } 271 } 272 273 @Override 274 public void bestSaved(Assignment<V, T> assignment) { 275 iBest = getContext(assignment).getTotal(); 276 } 277 278 @Override 279 public void bestRestored(Assignment<V, T> assignment) { 280 getContext(assignment).setTotal(iBest); 281 } 282 283 @Override 284 public void inc(Assignment<V, T> assignment, double value) { 285 getContext(assignment).inc(value); 286 } 287 288 @Override 289 public String getName() { 290 return getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')).replaceAll("(?<=[^A-Z])([A-Z])"," $1"); 291 } 292 293 /** Clear bounds cache */ 294 protected void clearCache() { 295 iLastCacheId++; 296 } 297 298 @Override 299 public void variableAdded(V variable) { 300 clearCache(); 301 } 302 303 @Override 304 public void variableRemoved(V variable) { 305 clearCache(); 306 } 307 308 @Override 309 public void constraintAdded(Constraint<V, T> constraint) { 310 clearCache(); 311 } 312 313 @Override 314 public void constraintRemoved(Constraint<V, T> constraint) { 315 clearCache(); 316 } 317 318 protected String getPerc(double value, double min, double max) { 319 if (max == min) 320 return sPercentFormat.format(100.0); 321 return sPercentFormat.format(100.0 - 100.0 * (value - min) / (max - min)); 322 } 323 324 protected String getPercRev(double value, double min, double max) { 325 if (max == min) 326 return sPercentFormat.format(0.0); 327 return sPercentFormat.format(100.0 * (value - min) / (max - min)); 328 } 329 330 @Override 331 public void getInfo(Assignment<V, T> assignment, Map<String, String> info) { 332 } 333 334 @Override 335 public void getInfo(Assignment<V, T> assignment, Map<String, String> info, Collection<V> variables) { 336 } 337 338 /** 339 * Return formatted value of this criterion, as percentage when possible 340 * @param assignment current assignment 341 * @return formatted value 342 */ 343 public String getPercentage(Assignment<V, T> assignment) { 344 double val = getValue(assignment); 345 double[] bounds = getBounds(assignment); 346 if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1]) 347 return getPerc(val, bounds[0], bounds[1]) + "%"; 348 else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0]) 349 return getPercRev(val, bounds[1], bounds[0]) + "%"; 350 else 351 return sDoubleFormat.format(val); 352 } 353 354 @Override 355 public void getExtendedInfo(Assignment<V, T> assignment, Map<String, String> info) { 356 if (iDebug) { 357 double val = getValue(assignment), w = getWeightedValue(assignment), prec = getValue(assignment, getModel().variables()); 358 double[] bounds = getBounds(assignment); 359 if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1]) 360 info.put("[C] " + getName(), 361 getPerc(val, bounds[0], bounds[1]) + "% (value: " + sDoubleFormat.format(val) + 362 (Math.abs(prec - val) > 0.0001 ? ", precise:" + sDoubleFormat.format(prec) : "") + 363 ", weighted:" + sDoubleFormat.format(w) + 364 ", bounds: " + sDoubleFormat.format(bounds[0]) + ".." + sDoubleFormat.format(bounds[1]) + ")"); 365 else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0]) 366 info.put("[C] " + getName(), 367 getPercRev(val, bounds[1], bounds[0]) + "% (value: " + sDoubleFormat.format(val) + 368 (Math.abs(prec - val) > 0.0001 ? ", precise:" + sDoubleFormat.format(prec) : "") + 369 ", weighted:" + sDoubleFormat.format(w) + 370 ", bounds: " + sDoubleFormat.format(bounds[1]) + ".." + sDoubleFormat.format(bounds[0]) + ")"); 371 else if (bounds[0] != val || val != bounds[1]) 372 info.put("[C] " + getName(), 373 sDoubleFormat.format(val) + " (" + 374 (Math.abs(prec - val) > 0.0001 ? "precise:" + sDoubleFormat.format(prec) + ", ": "") + 375 "weighted:" + sDoubleFormat.format(w) + 376 (bounds[0] != bounds[1] ? ", bounds: " + sDoubleFormat.format(bounds[0]) + ".." + sDoubleFormat.format(bounds[1]) : "") + 377 ")"); 378 } 379 } 380 381 /** 382 * Assignment context holding current value and the cached bounds. 383 */ 384 public class ValueContext implements AssignmentContext { 385 protected double iTotal = 0.0; 386 private double[] iBounds = null; 387 private int iCacheId = -1; 388 389 /** Create from an assignment 390 * @param assignment current assignment 391 **/ 392 protected ValueContext(Assignment<V, T> assignment) { 393 if (getValueUpdateType() != ValueUpdateType.NoUpdate) 394 iTotal = AbstractCriterion.this.getValue(assignment, getModel().variables()); 395 } 396 397 protected ValueContext() {} 398 399 /** Update value when unassigned 400 * @param assignment current assignment 401 * @param value recently unassigned value 402 **/ 403 protected void unassigned(Assignment<V, T> assignment, T value) { 404 iTotal -= getValue(assignment, value, null); 405 } 406 407 /** Update value when assigned 408 * @param assignment current assignment 409 * @param value recently assigned value 410 **/ 411 protected void assigned(Assignment<V, T> assignment, T value) { 412 iTotal += getValue(assignment, value, null); 413 } 414 415 /** Return value 416 * @return current value of the criterion 417 **/ 418 public double getTotal() { return iTotal; } 419 420 /** Set value 421 * @param value current value of the criterion 422 **/ 423 public void setTotal(double value) { iTotal = value; } 424 425 /** Increment value 426 * @param value increment 427 **/ 428 public void inc(double value) { iTotal += value; } 429 430 /** Return bounds 431 * @param assignment current assignment 432 * @return minimum and maximum of this criterion's value 433 **/ 434 protected double[] getBounds(Assignment<V, T> assignment) { 435 if (iBounds == null || iCacheId < iLastCacheId) { 436 iCacheId = iLastCacheId; 437 iBounds = computeBounds(assignment); 438 } 439 return (iBounds == null ? new double[] {0.0, 0.0} : iBounds); 440 } 441 442 /** Set bounds 443 * @param bounds bounds to cache 444 **/ 445 protected void setBounds(double[] bounds) { 446 iBounds = bounds; 447 } 448 } 449 450 @Override 451 @Deprecated 452 public double getWeightedValue() { 453 return getWeightedValue(getModel().getDefaultAssignment()); 454 } 455 456 @Override 457 @Deprecated 458 public double[] getBounds() { 459 return getBounds(getModel().getDefaultAssignment()); 460 } 461 462 @Override 463 @Deprecated 464 public double getWeightedValue(T value, Set<T> conflicts) { 465 return getWeightedValue(getModel().getDefaultAssignment(), value, conflicts); 466 } 467 468 @Override 469 @Deprecated 470 public double getValue(T value, Set<T> conflicts) { 471 return getValue(getModel().getDefaultAssignment(), value, conflicts); 472 } 473 474 @Override 475 @Deprecated 476 public double getWeightedValue(Collection<V> variables) { 477 return getWeightedValue(getModel().getDefaultAssignment(), variables); 478 } 479 480 @Override 481 @Deprecated 482 public double getValue(Collection<V> variables) { 483 return getValue(getModel().getDefaultAssignment(), variables); 484 } 485 486 @Override 487 @Deprecated 488 public double[] getBounds(Collection<V> variables) { 489 return getBounds(getModel().getDefaultAssignment(), variables); 490 } 491 492 @Override 493 @Deprecated 494 public void inc(double value) { 495 inc(getModel().getDefaultAssignment(), value); 496 } 497 498 /** Abbreviated name of the criterion for {@link TimetablingCriterion#toString()}. 499 * @return abbreviated name of the criterion 500 **/ 501 public String getAbbreviation() { 502 return getName().replaceAll("[a-z ]",""); 503 } 504 505 /** 506 * Simple text representation of the criterion and its value. E.g., X:x where X is the {@link AbstractCriterion#getAbbreviation()} 507 * and x is the current value {@link AbstractCriterion#getValue(Assignment)}. 508 * @param assignment current assignment 509 * @return short string representation (e.g., PP:95% for period preference) 510 */ 511 @Override 512 public String toString(Assignment<V, T> assignment) { 513 double val = getValue(assignment); 514 if (Math.abs(getWeightedValue(assignment)) < 0.005) return ""; 515 double[] bounds = getBounds(assignment); 516 if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1] && getName().endsWith(" Preferences")) 517 return getAbbreviation() + ":" + sDoubleFormat.format(getValue(assignment)) + " (" + getPerc(val, bounds[0], bounds[1]) + "%)"; 518 else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0] && getName().endsWith(" Preferences")) 519 return getAbbreviation() + ":" + sDoubleFormat.format(getValue(assignment)) + " (" + getPercRev(val, bounds[1], bounds[0]) + ")%"; 520 else if (bounds[0] != val || val != bounds[1]) 521 return getAbbreviation() + ":" + sDoubleFormat.format(getValue(assignment)); 522 else 523 return ""; 524 } 525 526 public ValueUpdateType getValueUpdateType() { 527 return iValueUpdateType; 528 } 529 530 public void setValueUpdateType(ValueUpdateType iValueUpdateType) { 531 this.iValueUpdateType = iValueUpdateType; 532 } 533}