001package org.cpsolver.ifs.solution; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.List; 006import java.util.Locale; 007import java.util.Map; 008import java.util.concurrent.locks.ReadWriteLock; 009import java.util.concurrent.locks.ReentrantReadWriteLock; 010 011import org.cpsolver.coursett.criteria.TimetablingCriterion; 012import org.cpsolver.ifs.assignment.Assignment; 013import org.cpsolver.ifs.model.Model; 014import org.cpsolver.ifs.model.Value; 015import org.cpsolver.ifs.model.Variable; 016import org.cpsolver.ifs.perturbations.PerturbationsCounter; 017import org.cpsolver.ifs.solver.Solver; 018 019 020/** 021 * Generic solution. <br> 022 * <br> 023 * It consist from the model and information about current iteration and 024 * solution time. 025 * 026 * @see Model 027 * @see org.cpsolver.ifs.solver.Solver 028 * 029 * @version IFS 1.3 (Iterative Forward Search)<br> 030 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 031 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 032 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 033 * <br> 034 * This library is free software; you can redistribute it and/or modify 035 * it under the terms of the GNU Lesser General Public License as 036 * published by the Free Software Foundation; either version 3 of the 037 * License, or (at your option) any later version. <br> 038 * <br> 039 * This library is distributed in the hope that it will be useful, but 040 * WITHOUT ANY WARRANTY; without even the implied warranty of 041 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 042 * Lesser General Public License for more details. <br> 043 * <br> 044 * You should have received a copy of the GNU Lesser General Public 045 * License along with this library; if not see 046 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 047 * 048 * @param <V> Variable 049 * @param <T> Value 050 */ 051public class Solution<V extends Variable<V, T>, T extends Value<V, T>> { 052 private static java.text.DecimalFormat sTimeFormat = new java.text.DecimalFormat("0.00", new java.text.DecimalFormatSymbols(Locale.US)); 053 054 private Model<V, T> iModel; 055 private Assignment<V, T> iAssignment; 056 private long iIteration = 0; 057 private long iFailedIterations = 0; 058 private double iTime = 0.0; 059 060 private Map<String, String> iBestInfo = null; 061 private long iBestIteration = -1; 062 private long iBestFailedIterations = -1; 063 private double iBestTime = -1; 064 private double iBestPerturbationsPenaly = -1.0; 065 private int iBestIndex = -1; 066 067 private List<SolutionListener<V, T>> iSolutionListeners = new ArrayList<SolutionListener<V, T>>(); 068 private PerturbationsCounter<V, T> iPerturbationsCounter = null; 069 private final ReadWriteLock iLock = new ReentrantReadWriteLock(false); 070 071 /** Constructor 072 * @param model problem model 073 **/ 074 @Deprecated 075 public Solution(Model<V, T> model) { 076 this(model, model.getDefaultAssignment(), 0, 0.0); 077 } 078 079 /** Constructor 080 * @param model problem model 081 * @param assignment current assignment 082 **/ 083 public Solution(Model<V, T> model, Assignment<V, T> assignment) { 084 this(model, assignment, 0, 0.0); 085 } 086 087 /** Constructor 088 * @param model problem model 089 * @param assignment current assignment 090 * @param iteration current iteration 091 * @param time current solver time 092 **/ 093 public Solution(Model<V, T> model, Assignment<V, T> assignment, long iteration, double time) { 094 iModel = model; 095 iAssignment = assignment; 096 iIteration = iteration; 097 iTime = time; 098 } 099 100 /** Current iteration 101 * @return current iteration 102 **/ 103 public long getIteration() { 104 return iIteration; 105 } 106 107 /** Number of failed iterations (i.e., number of calls {@link Solution#update(double, boolean)} with false success) 108 * @return number of failed iterations 109 **/ 110 public long getFailedIterations() { 111 return iFailedIterations; 112 } 113 114 /** Number of failed iterations (i.e., number of calls {@link Solution#update(double, boolean)} with false success) in the best solution 115 * @return number of failed iterations in the best solution 116 **/ 117 public long getBestFailedIterations() { 118 return (iBestIteration < 0 ? getFailedIterations() : iBestFailedIterations); 119 } 120 121 /** The model associated with the solution 122 * @return problem model 123 **/ 124 public Model<V, T> getModel() { 125 return iModel; 126 } 127 128 /** The assignment associated with this solution 129 * @return current assignment 130 **/ 131 public Assignment<V, T> getAssignment() { 132 return iAssignment; 133 } 134 135 /** Set a new assignment 136 * @param assignment current assignment 137 **/ 138 public void setAssignment(Assignment<V, T> assignment) { 139 iAssignment = assignment; 140 } 141 142 /** Current solution time (time in seconds from the start of the solver) 143 * @return solver time 144 **/ 145 public double getTime() { 146 return iTime; 147 } 148 149 /** Update time, increment current iteration 150 * @param time updated solver time 151 * @param success true if the last iteration was successful 152 * @param master master solution 153 **/ 154 public void update(double time, boolean success, Solution<V, T> master) { 155 iLock.writeLock().lock(); 156 try { 157 iTime = time; 158 iIteration++; 159 if (!success) iFailedIterations ++; 160 for (SolutionListener<V, T> listener : iSolutionListeners) 161 listener.solutionUpdated(this); 162 if (master != null) { 163 master.iTime = iTime; 164 master.iIteration++; 165 if (!success) master.iFailedIterations ++; 166 } 167 } finally { 168 iLock.writeLock().unlock(); 169 } 170 } 171 172 /** Update time, increment current iteration 173 * @param time updated solver time 174 * @param success true if the last iteration was successful 175 **/ 176 public void update(double time, boolean success) { 177 update(time, success, null); 178 } 179 180 /** Update time, increment current iteration 181 * @param time updated solver time 182 * @param master master solution 183 **/ 184 public void update(double time, Solution<V, T> master) { 185 update(time, true, master); 186 } 187 188 /** Update time, increment current iteration 189 * @param time updated solver time 190 **/ 191 public void update(double time) { 192 update(time, true, null); 193 } 194 195 /** Initialization 196 * @param solver current solver 197 **/ 198 public void init(Solver<V, T> solver) { 199 iIteration = 0; 200 iFailedIterations = 0; 201 iTime = 0; 202 if (iModel != null) 203 iModel.init(solver); 204 iPerturbationsCounter = solver.getPerturbationsCounter(); 205 } 206 207 /** 208 * String representation -- returns a list of values of objective criteria 209 * @return comma separated string of {@link TimetablingCriterion#toString(Assignment)} 210 */ 211 @Override 212 public String toString() { 213 return getModel().toString(getAssignment()) + (getFailedIterations() > 0 ? ", F:" + sTimeFormat.format(100.0 * getFailedIterations() / getIteration()) + "%" : ""); 214 } 215 216 /** 217 * Solution information. It consists from info from the model which is 218 * associated with the solution, time, iteration, speed and infos from all 219 * solution listeners. 220 * @return info table 221 */ 222 public Map<String, String> getInfo() { 223 Map<String, String> ret = getModel().getInfo(iAssignment); 224 if (getPerturbationsCounter() != null) 225 getPerturbationsCounter().getInfo(getAssignment(), getModel(), ret); 226 ret.put("Time", sTimeFormat.format(getTime() / 60.0) + " min"); 227 ret.put("Iteration", getIteration() + (getFailedIterations() > 0 ? " (" + sTimeFormat.format(100.0 * getFailedIterations() / getIteration())+ "% failed)" : "")); 228 if (getTime() > 0) 229 ret.put("Speed", sTimeFormat.format((getIteration()) / getTime()) + " it/s"); 230 for (SolutionListener<V, T> listener : iSolutionListeners) 231 listener.getInfo(this, ret); 232 return ret; 233 } 234 235 /** 236 * Extended solution information. Similar to {@link Solution#getInfo()}, but 237 * some more information (that is more expensive to compute) might be added. 238 * Also extended model information is added (see 239 * {@link Model#getExtendedInfo(Assignment)}) into the resultant table. 240 * @return extended info table 241 */ 242 public Map<String, String> getExtendedInfo() { 243 Map<String, String> ret = getModel().getExtendedInfo(iAssignment); 244 if (getPerturbationsCounter() != null) 245 getPerturbationsCounter().getInfo(getAssignment(), getModel(), ret); 246 ret.put("Time", sTimeFormat.format(getTime() / 60.0) + " min"); 247 ret.put("Iteration", getIteration() + (getFailedIterations() > 0 ? " (" + sTimeFormat.format(100.0 * getFailedIterations() / getIteration())+ "% failed)" : "")); 248 if (getTime() > 0) 249 ret.put("Speed", sTimeFormat.format((getIteration()) / getTime()) + " it/s"); 250 if (getBestIteration() > 0) 251 ret.put("Best Iteration", getBestIteration() + (getBestFailedIterations() > 0 ? " (" + sTimeFormat.format(100.0 * getBestFailedIterations() / getBestIteration())+ "% failed)" : "")); 252 if (getBestTime() > 0) 253 ret.put("Best Time", sTimeFormat.format(getBestTime() / 60.0) + " min (" + sTimeFormat.format((getBestIteration()) / getBestTime()) + " it/s)"); 254 for (SolutionListener<V, T> listener : iSolutionListeners) 255 listener.getInfo(this, ret); 256 return ret; 257 } 258 259 /** 260 * Solution information. It consists from info from the model which is 261 * associated with the solution, time, iteration, speed and infos from all 262 * solution listeners. Only variables from the given set are included. 263 * @param variables sub-problem 264 * @return info table 265 */ 266 public Map<String, String> getInfo(Collection<V> variables) { 267 Map<String, String> ret = getModel().getInfo(iAssignment, variables); 268 if (getPerturbationsCounter() != null) 269 getPerturbationsCounter().getInfo(getAssignment(), getModel(), ret, variables); 270 ret.put("Time", sTimeFormat.format(getTime()) + " sec"); 271 ret.put("Iteration", String.valueOf(getIteration())); 272 if (getTime() > 0) 273 ret.put("Speed", sTimeFormat.format((getIteration()) / getTime()) + " it/s"); 274 for (SolutionListener<V, T> listener : iSolutionListeners) 275 listener.getInfo(this, ret, variables); 276 return ret; 277 } 278 279 /** Info of the best ever found solution 280 * @return info table of the best solution 281 **/ 282 public Map<String, String> getBestInfo() { 283 return iBestInfo; 284 } 285 286 /** Iteration when the best ever found solution was found 287 * @return iteration of the best solution 288 **/ 289 public long getBestIteration() { 290 return (iBestIteration < 0 ? getIteration() : iBestIteration); 291 } 292 293 /** Solution time when the best ever found solution was found 294 * @return solver time of the best solution 295 **/ 296 public double getBestTime() { 297 return (iBestTime < 0 ? getTime() : iBestTime); 298 } 299 300 /** 301 * Returns true, if all variables of the best ever solution found are 302 * assigned 303 * @return true if the best solution has all the variables assigned 304 */ 305 public boolean isBestComplete() { 306 return getModel().getBestUnassignedVariables() == 0; 307 } 308 309 /** 310 * Index of the best assignment. 311 * @return {@link Assignment#getIndex()} of the best saved solution 312 */ 313 public int getBestIndex() { 314 return iBestIndex; 315 } 316 317 /** 318 * Total value of the best ever found solution -- sum of all assigned values 319 * (see {@link Value#toDouble(Assignment)}). 320 * @return value of the best solution 321 */ 322 public double getBestValue() { 323 return getModel().getBestValue(); 324 } 325 326 /** Set total value of the best ever found solution 327 * @param bestValue value of the best solution 328 **/ 329 public void setBestValue(double bestValue) { 330 getModel().setBestValue(bestValue); 331 } 332 333 /** 334 * Perturbation penalty of the best ever found solution (see 335 * {@link PerturbationsCounter}) 336 * @return perturbation penalty of the best solution 337 */ 338 public double getBestPerturbationsPenalty() { 339 return iBestPerturbationsPenaly; 340 } 341 342 /** Returns perturbation counter 343 * @return perturbations counter 344 **/ 345 public PerturbationsCounter<V, T> getPerturbationsCounter() { 346 return iPerturbationsCounter; 347 } 348 349 /** Clear the best ever found solution */ 350 public void clearBest() { 351 iLock.writeLock().lock(); 352 try { 353 getModel().clearBest(); 354 iBestInfo = null; 355 iBestTime = -1; 356 iBestIteration = -1; 357 iBestFailedIterations = 0; 358 iBestIndex = -1; 359 iBestPerturbationsPenaly = -1.0; 360 for (SolutionListener<V, T> listener : iSolutionListeners) 361 listener.bestCleared(this); 362 } finally { 363 iLock.writeLock().unlock(); 364 } 365 } 366 367 /** True if the solution is complete, i.e., all the variables are assigned 368 * @return true if all the variables are assigned 369 **/ 370 public boolean isComplete() { 371 return getAssignment().nrAssignedVariables() == getModel().variables().size(); 372 } 373 374 /** 375 * Save the current solution as the best ever found solution (it also calls 376 * {@link Model#saveBest(Assignment)}) 377 * @param master master solution into which information about the best solution are to be copied as well 378 */ 379 public void saveBest(Solution<V, T> master) { 380 iLock.writeLock().lock(); 381 try { 382 getModel().saveBest(iAssignment); 383 iBestInfo = getInfo(); 384 iBestTime = getTime(); 385 iBestIteration = getIteration(); 386 iBestFailedIterations = getFailedIterations(); 387 iBestIndex = getAssignment().getIndex(); 388 iBestPerturbationsPenaly = (iPerturbationsCounter == null ? 0.0 : iPerturbationsCounter.getPerturbationPenalty(getAssignment(), getModel())); 389 for (SolutionListener<V, T> listener : iSolutionListeners) 390 listener.bestSaved(this); 391 392 if (master != null) { 393 // master.iIteration = iIteration; 394 // master.iFailedIterations = iFailedIterations; 395 // master.iTime = iTime; 396 master.iBestInfo = iBestInfo; 397 master.iBestTime = iBestTime; 398 master.iBestIteration = iBestIteration; 399 master.iBestFailedIterations = iBestFailedIterations; 400 master.iBestPerturbationsPenaly = iBestPerturbationsPenaly; 401 master.iBestIndex = iBestIndex; 402 } 403 } finally { 404 iLock.writeLock().unlock(); 405 } 406 } 407 408 public boolean saveBestIfImproving(Solution<V, T> master, SolutionComparator<V, T> comparator) { 409 master.iLock.readLock().lock(); 410 try { 411 if (iBestInfo != null && !comparator.isBetterThanBestSolution(this)) return false; 412 } finally { 413 master.iLock.readLock().unlock(); 414 } 415 master.iLock.writeLock().lock(); 416 try { 417 if (iBestInfo != null && !comparator.isBetterThanBestSolution(this)) return false; 418 getModel().saveBest(iAssignment); 419 iBestInfo = getInfo(); 420 iBestTime = getTime(); 421 iBestIteration = getIteration(); 422 iBestFailedIterations = getFailedIterations(); 423 iBestIndex = getAssignment().getIndex(); 424 iBestPerturbationsPenaly = (iPerturbationsCounter == null ? 0.0 : iPerturbationsCounter.getPerturbationPenalty(getAssignment(), getModel())); 425 for (SolutionListener<V, T> listener : iSolutionListeners) 426 listener.bestSaved(this); 427 428 // master.iIteration = iIteration; 429 // master.iFailedIterations = iFailedIterations; 430 // master.iTime = iTime; 431 master.iBestInfo = iBestInfo; 432 master.iBestTime = iBestTime; 433 master.iBestIteration = iBestIteration; 434 master.iBestFailedIterations = iBestFailedIterations; 435 master.iBestPerturbationsPenaly = iBestPerturbationsPenaly; 436 master.iBestIndex = iBestIndex; 437 438 return true; 439 } finally { 440 master.iLock.writeLock().unlock(); 441 } 442 } 443 444 445 /** 446 * Save the current solution as the best ever found solution (it also calls 447 * {@link Model#saveBest(Assignment)}) 448 */ 449 public void saveBest() { 450 iLock.writeLock().lock(); 451 try { 452 saveBest(null); 453 } finally { 454 iLock.writeLock().unlock(); 455 } 456 } 457 458 /** 459 * Restore the best ever found solution into the current solution (it also 460 * calls {@link Model#restoreBest(Assignment)}) 461 */ 462 public void restoreBest() { 463 iLock.writeLock().lock(); 464 try { 465 getModel().restoreBest(iAssignment); 466 // iTime = iBestTime; 467 // iIteration = iBestIteration; 468 // iFailedIterations = iBestFailedIterations; 469 for (SolutionListener<V, T> listener : iSolutionListeners) 470 listener.bestRestored(this); 471 } finally { 472 iLock.writeLock().unlock(); 473 } 474 } 475 476 /** Adds solution listener 477 * @param listener a solution listener 478 **/ 479 public void addSolutionListener(SolutionListener<V, T> listener) { 480 iSolutionListeners.add(listener); 481 } 482 483 /** Removes solution listener 484 * @param listener a solution listener 485 **/ 486 public void removeSolutionListener(SolutionListener<V, T> listener) { 487 iSolutionListeners.remove(listener); 488 } 489 490 /** Registered of solution listeners 491 * @return list of registered solution listener 492 **/ 493 public List<SolutionListener<V, T>> getSolutionListeners() { 494 return iSolutionListeners; 495 } 496 497 /** 498 * Return solution lock 499 * @return read-write lock used to lock the solution during a change 500 */ 501 public ReadWriteLock getLock() { return iLock; } 502}