001 package ifs.solver;
002
003 import ifs.extension.*;
004 import ifs.heuristics.*;
005 import ifs.model.*;
006 import ifs.solution.*;
007 import ifs.termination.*;
008 import ifs.util.*;
009 import ifs.perturbations.*;
010
011 import java.io.*;
012 import java.util.*;
013 import java.lang.reflect.*;
014
015 /**
016 * IFS Solver.
017 * <br><br>
018 * The iterative forward search (IFS) algorithm is based on ideas of local search methods. However, in contrast to
019 * classical local search techniques, it operates over feasible, though not necessarily complete solutions. In such
020 * a solution, some variables can be left unassigned. Still all hard constraints on assigned variables must be
021 * satisfied. Similarly to backtracking based algorithms, this means that there are no violations of hard
022 * constraints.
023 * <br><br>
024 * This search works in iterations. During each step, an unassigned or assigned variable is initially selected.
025 * Typically an unassigned variable is chosen like in backtracking-based search. An assigned variable may be selected
026 * when all variables are assigned but the solution is not good enough (for example, when there are still many
027 * violations of soft constraints). Once a variable is selected, a value from its domain is chosen for assignment.
028 * Even if the best value is selected (whatever “best” means), its assignment to the selected variable may cause some
029 * hard conflicts with already assigned variables. Such conflicting variables are removed from the solution and become
030 * unassigned. Finally, the selected value is assigned to the selected variable.
031 * <br><br>
032 * Algorithm schema:
033 * <ul><code>
034 * procedure ifs(initial) // initial solution is the parameter <br>
035 * iteration = 0; // iteration counter <br>
036 * current = initial; // current (partial) feasible solution <br>
037 * best = initial; // best solution <br>
038 * while canContinue(current, iteration) do <br>
039 * iteration = iteration + 1; <br>
040 * variable = selectVariable(current); <br>
041 * value = selectValue(current, variable); <br>
042 * UNASSIGN(current, CONFLICTING_VARIABLES(current, variable, value)); <br>
043 * ASSIGN(current, variable, value); <br>
044 * if better(current, best) then best = current; <br>
045 * end while <br>
046 * return best;<br>
047 * end procedure <br>
048 * </code>
049 * </ul><br>
050 * The algorithm attempts to move from one (partial) feasible solution to another via repetitive assignment of a
051 * selected value to a selected variable. During this search, the feasibility of all hard constraints in each
052 * iteration step is enforced by unassigning the conflicting variables. The search is terminated when the requested
053 * solution is found or when there is a timeout, expressed e.g., as a maximal number of iterations or available
054 * time being reached. The best solution found is then returned.
055 * <br><br>
056 * The above algorithm schema is parameterized by several functions, namely:
057 * <ul>
058 * <li> the termination condition (function canContinue, see {@link TerminationCondition}),
059 * <li> the solution comparator (function better, see {@link SolutionComparator}),
060 * <li> the variable selection (function selectVariable, see {@link VariableSelection}) and
061 * <li> the value selection (function selectValue, see {@link ValueSelection}).
062 * </ul>
063 * <br>
064 * Usage:<ul><code>
065 * DataProperties cfg = ToolBox.loadProperties(inputCfg); //input configuration<br>
066 * Solver solver = new Solver(cfg);<br>
067 * solver.setInitalSolution(model); //sets initial solution<br>
068 * <br>
069 * solver.start(); //server is executed in a thread<br>
070 * <br>
071 * try { //wait untill the server finishes<br>
072 * solver.getSolverThread().join(); <br>
073 * } catch (InterruptedException e) {} <br>
074 * <br>
075 * Solution solution = solver.lastSolution(); //last solution<br>
076 * solution.restoreBest(); //restore best solution ever found<br>
077 * </code></ul>
078 * <br>
079 * Solver's parameters:
080 * <br>
081 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
082 * <tr><td>General.SaveBestUnassigned</td><td>{@link Integer}</td><td>During the search, solution is saved when it is the best ever found solution and if the number of assigned variables is less or equal this parameter (if set to -1, the solution is always saved)</td></tr>
083 * <tr><td>General.Seed</td><td>{@link Long}</td><td>If set, random number generator is initialized with this seed</td></tr>
084 * <tr><td>General.SaveConfiguration</td><td>{@link Boolean}</td><td>If true, given configuration is stored into the output folder (during initialization of the solver, ${General.Output}/${General.ProblemName}.properties)</td></tr>
085 * <tr><td>Solver.AutoConfigure</td><td>{@link Boolean}</td><td>If true, IFS Solver is configured according to the following parameters</td></tr>
086 * <tr><td>Termination.Class</td><td>{@link String}</td><td>Fully qualified class name of the termination condition (see {@link TerminationCondition}, e.g. {@link GeneralTerminationCondition})</td></tr>
087 * <tr><td>Comparator.Class</td><td>{@link String}</td><td>Fully qualified class name of the solution comparator (see {@link SolutionComparator}, e.g. {@link GeneralSolutionComparator})</td></tr>
088 * <tr><td>Value.Class</td><td>{@link String}</td><td>Fully qualified class name of the value selection criterion (see {@link ValueSelection}, e.g. {@link GeneralValueSelection})</td></tr>
089 * <tr><td>Variable.Class</td><td>{@link String}</td><td>Fully qualified class name of the variable selection criterion (see {@link VariableSelection}, e.g. {@link GeneralVariableSelection})</td></tr>
090 * <tr><td>PerturbationCounter.Class</td><td>{@link String}</td><td>Fully qualified class name of the perturbation counter in case of solving minimal perturbation problem (see {@link PerturbationsCounter}, e.g. {@link DefaultPerturbationsCounter})</td></tr>
091 * <tr><td>Extensions.Classes</td><td>{@link String}</td><td>Semi-colon separated list of fully qualified class names of IFS extensions (see {@link Extension}, e.g. {@link ConflictStatistics} or {@link MacPropagation})</td></tr>
092 * </table>
093 *
094 * @see SolverListener
095 * @see Model
096 * @see Solution
097 * @see TerminationCondition
098 * @see SolutionComparator
099 * @see PerturbationsCounter
100 * @see VariableSelection
101 * @see ValueSelection
102 * @see Extension
103 *
104 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
105 * @version 1.0
106 **/
107
108 public class Solver {
109 /** log */
110 protected static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Solver.class);
111 /** current solution */
112 protected Solution iCurrentSolution = null;
113 /** last solution (after IFS Solver finishes) */
114 protected Solution iLastSolution = null;
115
116 /** solver status: done */
117 protected boolean iDone = false;
118 /** solver status: running */
119 protected boolean iRunning = false;
120 /** solver status: stopped */
121 protected boolean iStop = false;
122
123 /** solver thread */
124 protected SolverThread iSolverThread = null;
125 /** configuration */
126 private DataProperties iProperties = null;
127
128 private TerminationCondition iTerminationCondition = null;
129 private SolutionComparator iSolutionComparator = null;
130 private PerturbationsCounter iPerturbationsCounter = null;
131 private ValueSelection iValueSelection = null;
132 private VariableSelection iVariableSelection = null;
133 private Vector iExtensions = new FastVector(5);
134 private Vector iSolverListeners = new FastVector(5);
135 private int iSaveBestUnassigned = 0;
136
137 /** Constructor.
138 * @param properties input configuration
139 */
140 public Solver(DataProperties properties) {
141 iProperties = properties;
142
143 long seed = properties.getPropertyLong( "General.Seed", System.currentTimeMillis());
144 ToolBox.setSeed(seed);
145
146 iSaveBestUnassigned = properties.getPropertyInt( "General.SaveBestUnassigned", 0);
147
148 clearBest();
149 if (iProperties.getPropertyBoolean("Solver.AutoConfigure",true)) {
150 autoConfigure();
151 }
152 }
153
154 private boolean iValueExtraUsed = false;
155 private boolean iVariableExtraUsed = false;
156
157 /** Sets termination condition */
158 public void setTerminalCondition(TerminationCondition terminationCondition) {iTerminationCondition = terminationCondition; }
159 /** Sets solution comparator */
160 public void setSolutionComparator(SolutionComparator solutionComparator) {iSolutionComparator = solutionComparator; }
161 /** Sets value selection criterion */
162 public void setValueSelection(ValueSelection valueSelection) { iValueSelection = valueSelection; }
163 /** Sets variable selection criterion */
164 public void setVariableSelection(VariableSelection variableSelection) { iVariableSelection = variableSelection; }
165 /** Sets perturbation counter (minimal perturbation problem) */
166 public void setPerturbationsCounter(PerturbationsCounter perturbationsCounter) { iPerturbationsCounter = perturbationsCounter; }
167 /** Add an IFS extension */
168 public void addExtension(Extension extension) {
169 if (extension.useValueExtra() && iValueExtraUsed) {
170 sLogger.warn("Unable to add an extension "+extension+" -- value extra is already used.");
171 return;
172 }
173 if (extension.useVariableExtra() && iVariableExtraUsed) {
174 sLogger.warn("Unable to add extension "+extension+" -- variable extra is already used.");
175 return;
176 }
177 iValueExtraUsed = iValueExtraUsed | extension.useValueExtra();
178 iValueExtraUsed = iVariableExtraUsed | extension.useVariableExtra();
179 iExtensions.addElement(extension);
180 }
181
182 /** Returns termination condition */
183 public TerminationCondition getTerminationCondition() { return iTerminationCondition; }
184 /** Returns solution comparator */
185 public SolutionComparator getSolutionComparator() { return iSolutionComparator; }
186 /** Returns values selection criterion */
187 public ValueSelection getValueSelection() { return iValueSelection; }
188 /** Returns variable selection criterion */
189 public VariableSelection getVariableSelection() { return iVariableSelection; }
190 /** Returns perturbation counter (minimal perturbation problem) */
191 public PerturbationsCounter getPerturbationsCounter() { return iPerturbationsCounter; }
192 /** Returns list of all used extensions */
193 public Vector getExtensions() { return iExtensions; }
194
195 /** Adds a solver listener */
196 public void addSolverListener(SolverListener listener) {
197 iSolverListeners.addElement(listener);
198 }
199 /** Removes a solver listener */
200 public void removeSolverListener(SolverListener listener) {
201 iSolverListeners.removeElement(listener);
202 }
203
204 /** Returns configuration */
205 public DataProperties getProperties() { return iProperties; }
206
207 /** Automatic configuratin of the solver -- when Solver.AutoConfigure is true */
208 protected void autoConfigure() {
209 try {
210 boolean mpp = getProperties().getPropertyBoolean("General.MPP", false);
211
212 String terminationConditionClassName = getProperties().getProperty("Termination.Class",(mpp?"ifs.termination.MPPTerminationCondition":"ifs.termination.GeneralTerminationCondition"));
213 sLogger.info("Using "+terminationConditionClassName);
214 Class terminationConditionClass = Class.forName(terminationConditionClassName);
215 Constructor terminationConditionConstructor = terminationConditionClass.getConstructor(new Class[]{DataProperties.class});
216 setTerminalCondition((TerminationCondition)terminationConditionConstructor.newInstance(new Object[] {getProperties()}));
217
218 String solutionComparatorClassName = getProperties().getProperty("Comparator.Class",(mpp?"ifs.solution.MPPSolutionComparator":"ifs.solution.GeneralSolutionComparator"));
219 sLogger.info("Using "+solutionComparatorClassName);
220 Class solutionComparatorClass = Class.forName(solutionComparatorClassName);
221 Constructor solutionComparatorConstructor = solutionComparatorClass.getConstructor(new Class[]{DataProperties.class});
222 setSolutionComparator((SolutionComparator)solutionComparatorConstructor.newInstance(new Object[] {getProperties()}));
223
224 String valueSelectionClassName = getProperties().getProperty("Value.Class","ifs.heuristics.GeneralValueSelection");
225 sLogger.info("Using "+valueSelectionClassName);
226 Class valueSelectionClass = Class.forName(valueSelectionClassName);
227 Constructor valueSelectionConstructor = valueSelectionClass.getConstructor(new Class[]{DataProperties.class});
228 setValueSelection((ValueSelection)valueSelectionConstructor.newInstance(new Object[] {getProperties()}));
229
230 String variableSelectionClassName = getProperties().getProperty("Variable.Class","ifs.heuristics.GeneralVariableSelection");
231 sLogger.info("Using "+variableSelectionClassName);
232 Class variableSelectionClass = Class.forName(variableSelectionClassName);
233 Constructor variableSelectionConstructor = variableSelectionClass.getConstructor(new Class[]{DataProperties.class});
234 setVariableSelection((VariableSelection)variableSelectionConstructor.newInstance(new Object[] {getProperties()}));
235
236 String perturbationCounterClassName = getProperties().getProperty("PerturbationCounter.Class","ifs.perturbations.DefaultPerturbationsCounter");
237 sLogger.info("Using "+perturbationCounterClassName);
238 Class perturbationCounterClass = Class.forName(perturbationCounterClassName);
239 Constructor perturbationCounterConstructor = perturbationCounterClass.getConstructor(new Class[]{DataProperties.class});
240 setPerturbationsCounter((PerturbationsCounter)perturbationCounterConstructor.newInstance(new Object[] {getProperties()}));
241
242 String extensionClassNames = getProperties().getProperty("Extensions.Classes",null);
243 if (extensionClassNames!=null) {
244 StringTokenizer extensionClassNameTokenizer = new StringTokenizer(extensionClassNames,";");
245 while (extensionClassNameTokenizer.hasMoreTokens()) {
246 String extensionClassName = extensionClassNameTokenizer.nextToken();
247 sLogger.info("Using "+extensionClassName);
248 Class extensionClass = Class.forName(extensionClassName);
249 Constructor extensionConstructor = extensionClass.getConstructor(new Class[]{Solver.class, DataProperties.class});
250 addExtension((Extension)extensionConstructor.newInstance(new Object[] {this, getProperties()}));
251 }
252 }
253 } catch (Exception e) {
254 sLogger.error("Unable to autoconfigure solver.",e);
255 }
256 }
257
258 /** Clears best solution */
259 public void clearBest() {
260 if (iCurrentSolution!=null) iCurrentSolution.clearBest();
261 }
262
263 /** Sets initial solution */
264 public void setInitalSolution(Solution solution) {
265 iCurrentSolution = solution;
266 }
267
268 /** Sets initial solution */
269 public void setInitalSolution(Model model) {
270 iCurrentSolution = new Solution(model, 0, 0);
271 }
272
273 /** Starts solver */
274 public void start() {
275 iSolverThread = new SolverThread();
276 iSolverThread.start();
277 }
278
279 /** Returns solver's thread */
280 public Thread getSolverThread() {
281 return iSolverThread;
282 }
283
284 /** Initialization */
285 public void init() {
286 iStop = false;
287 iDone = false;
288 iRunning = false;
289 }
290
291 /** Last solution (when solver finishes) */
292 public Solution lastSolution() { return (iLastSolution==null?iCurrentSolution:iLastSolution); }
293 /** Current solution (during the search) */
294 public Solution currentSolution() { return iCurrentSolution; }
295 /** Solver status: solver is done */
296 public boolean isDone() { return iDone; }
297 /** Solver status: solver is running */
298 public boolean isRunning() { return iRunning; }
299 /** Stops the running solver */
300 public void stopSolver() { iStop = true; };
301 /** Solver status: solver is stopped */
302 public boolean isStopped() { return iStop; }
303
304 private void initSolver() {
305 Progress.getInstance().setPhase("Initializing solver");
306
307 // register extensions
308 for (Enumeration i=iExtensions.elements(); i.hasMoreElements(); ) {
309 Extension extension = (Extension)i.nextElement();
310 extension.register(iCurrentSolution.getModel());
311 }
312
313 //register solution
314 iCurrentSolution.init(Solver.this);
315
316 //register and intialize value selection
317 getValueSelection().init(Solver.this);
318
319 //register and intialize value selection
320 getVariableSelection().init(Solver.this);
321
322 //register and intialize perturbations counter
323 if (getPerturbationsCounter()!=null) getPerturbationsCounter().init(Solver.this);
324
325 //save initial configuration
326 if (iProperties.getPropertyBoolean("General.SaveConfiguration",false)) {
327 try {
328 FileOutputStream f = new FileOutputStream(iProperties.getProperty("General.Output")+File.separator+iProperties.getProperty("General.ProblemName","ifs")+".properties");
329 iProperties.store(f, iProperties.getProperty("General.ProblemNameLong","Iterative Forward Search")+" -- configuration file");
330 f.flush();f.close();
331 } catch (Exception e) {
332 sLogger.error("Unable to store configuration file :-(", e);
333 }
334 }
335 }
336
337 /** Solver thread */
338 protected class SolverThread extends Thread {
339 /** Solving rutine */
340 public void run() {
341 try {
342 // Sets thread name
343 setName("Solver");
344
345 // Status
346 iStop = false; iDone = false; iRunning = true;
347
348 // Initialization
349 Progress.getInstance().setStatus("Solving");
350 initSolver();
351 double startTime = JProf.currentTimeSec();
352 if (iCurrentSolution.getBestInfo()==null) {
353 Progress.getInstance().setPhase("Searching for initial solution ...",iCurrentSolution.getModel().variables().size());
354 } else {
355 Progress.getInstance().setPhase("Improving found solution ...");
356 }
357 long prog = 9999;
358 sLogger.info("Initial solution:"+ToolBox.dict2string(iCurrentSolution.getInfo(),1));
359 if ((iSaveBestUnassigned<0 || iSaveBestUnassigned>=iCurrentSolution.getModel().unassignedVariables().size()) && (iCurrentSolution.getBestInfo()==null || getSolutionComparator().isBetterThanBestSolution(iCurrentSolution))) {
360 if (iCurrentSolution.getModel().unassignedVariables().isEmpty())
361 sLogger.info("Complete solution "+ToolBox.dict2string(iCurrentSolution.getInfo(),1)+" was found.");
362 synchronized (iCurrentSolution) {
363 iCurrentSolution.saveBest();
364 }
365 }
366
367 // Iterations: until solver can continue
368 while (!iStop && getTerminationCondition().canContinue(iCurrentSolution)) {
369 // Variable selection
370 Variable variable = getVariableSelection().selectVariable(iCurrentSolution);
371 for (Enumeration i=iSolverListeners.elements();i.hasMoreElements();)
372 if (!((SolverListener)i.nextElement()).variableSelected(iCurrentSolution.getIteration(), variable)) continue;
373 if (variable == null) {
374 sLogger.warn("No variable selected.");
375 }
376 if (variable != null && variable.values().isEmpty()) {
377 sLogger.error("Variable "+variable.getName()+" has no values.");
378 continue;
379 }
380
381 // Value selection
382 Value value = getValueSelection().selectValue(iCurrentSolution, variable);
383 for (Enumeration i=iSolverListeners.elements();i.hasMoreElements();)
384 if (!((SolverListener)i.nextElement()).valueSelected(iCurrentSolution.getIteration(), variable, value)) continue;
385 if (variable == null) {
386 sLogger.warn("No value selected for variable "+variable+".");
387 }
388
389 // Assign selected value to the selected variable
390 synchronized (iCurrentSolution) {
391 if (value!=null) variable.assign(iCurrentSolution.getIteration(), value); else variable.unassign(iCurrentSolution.getIteration());
392 iCurrentSolution.update(JProf.currentTimeSec()-startTime);
393 }
394
395 // Check if the solution is the best ever found one
396 if ((iSaveBestUnassigned<0 || iSaveBestUnassigned>=iCurrentSolution.getModel().unassignedVariables().size()) && (iCurrentSolution.getBestInfo()==null || getSolutionComparator().isBetterThanBestSolution(iCurrentSolution))) {
397 if (iCurrentSolution.getModel().unassignedVariables().isEmpty())
398 sLogger.info("Complete solution "+ToolBox.dict2string(iCurrentSolution.getInfo(),1)+" was found.");
399 synchronized (iCurrentSolution) {
400 iCurrentSolution.saveBest();
401 }
402 }
403
404 // Increment progress bar
405 if (iCurrentSolution.getBestInfo()!=null && iCurrentSolution.getModel().getBestUnassignedVariables()==0) {
406 prog++;
407 if (prog == 10000) {
408 Progress.getInstance().setPhase("Improving found solution ...");
409 prog=0;
410 } else {
411 Progress.getInstance().setProgress(prog/100);
412 }
413 } else if ((iCurrentSolution.getBestInfo()==null || iCurrentSolution.getModel().getBestUnassignedVariables()>0) && (iCurrentSolution.getModel().variables().size()-iCurrentSolution.getModel().unassignedVariables().size())>Progress.getInstance().getProgress()) {
414 Progress.getInstance().setProgress(iCurrentSolution.getModel().variables().size()-iCurrentSolution.getModel().unassignedVariables().size());
415 }
416
417 }
418
419 // Finalization
420 iLastSolution = iCurrentSolution;
421 if (!iStop) {
422 Progress.getInstance().setPhase("Done",1); Progress.getInstance().incProgress();
423 }
424
425 //Update status
426 iDone = true; iRunning = false; iSolverThread=null;
427
428 sLogger.debug("Solver stoped.");
429 } catch (Exception ex) {
430 sLogger.error(ex.getMessage(),ex);
431 }
432 }
433 }
434
435 }