View Javadoc

1   /*******************************************************************************
2    * SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
3    *
4    * All rights reserved. This program and the accompanying materials
5    * are made available under the terms of the Eclipse Public License v1.0
6    * which accompanies this distribution, and is available at
7    * http://www.eclipse.org/legal/epl-v10.html
8    *
9    * Alternatively, the contents of this file may be used under the terms of
10   * either the GNU Lesser General Public License Version 2.1 or later (the
11   * "LGPL"), in which case the provisions of the LGPL are applicable instead
12   * of those above. If you wish to allow use of your version of this file only
13   * under the terms of the LGPL, and not to allow others to use your version of
14   * this file under the terms of the EPL, indicate your decision by deleting
15   * the provisions above and replace them with the notice and other provisions
16   * required by the LGPL. If you do not delete the provisions above, a recipient
17   * may use your version of this file under the terms of the EPL or the LGPL.
18   *******************************************************************************/
19  package org.sat4j.sat;
20  
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.Arrays;
26  import java.util.Properties;
27  import java.util.StringTokenizer;
28  
29  import org.apache.commons.beanutils.BeanUtils;
30  import org.apache.commons.cli.CommandLine;
31  import org.apache.commons.cli.HelpFormatter;
32  import org.apache.commons.cli.Option;
33  import org.apache.commons.cli.Options;
34  import org.apache.commons.cli.ParseException;
35  import org.apache.commons.cli.PosixParser;
36  import org.sat4j.AbstractLauncher;
37  import org.sat4j.core.ASolverFactory;
38  import org.sat4j.core.VecInt;
39  import org.sat4j.minisat.core.DataStructureFactory;
40  import org.sat4j.minisat.core.IOrder;
41  import org.sat4j.minisat.core.IPhaseSelectionStrategy;
42  import org.sat4j.minisat.core.LearningStrategy;
43  import org.sat4j.minisat.core.RestartStrategy;
44  import org.sat4j.minisat.core.SearchParams;
45  import org.sat4j.minisat.core.Solver;
46  import org.sat4j.pb.IPBSolver;
47  import org.sat4j.pb.reader.PBInstanceReader;
48  import org.sat4j.reader.InstanceReader;
49  import org.sat4j.reader.ParseFormatException;
50  import org.sat4j.reader.Reader;
51  import org.sat4j.specs.ContradictionException;
52  import org.sat4j.specs.IProblem;
53  import org.sat4j.specs.ISolver;
54  import org.sat4j.specs.IVecInt;
55  import org.sat4j.specs.SearchListener;
56  import org.sat4j.tools.DotSearchTracing;
57  
58  /**
59   * This class is used to launch the SAT solvers from the command line. It is
60   * compliant with the SAT competition (www.satcompetition.org) I/O format. The
61   * launcher is to be used as follows:
62   * 
63   * <pre>
64   *                [solvername] filename [key=value]*
65   * </pre>
66   * 
67   * If no solver name is given, then the default solver of the solver factory is
68   * used (@see org.sat4j.core.ASolverFactory#defaultSolver()).
69   * 
70   * @author leberre
71   */
72  public class Lanceur extends AbstractLauncher {
73  
74  	/**
75  	 * 
76  	 */
77  	private static final long serialVersionUID = 1L;
78  
79  	/**
80  	 * Lance le prouveur sur un fichier Dimacs.
81  	 * 
82  	 * @param args
83  	 *            doit contenir le nom d'un fichier Dimacs, eventuellement
84  	 *            compress?.
85  	 */
86  	public static void main(final String[] args) {
87  		AbstractLauncher lanceur = new Lanceur();
88  		lanceur.run(args);
89  		System.exit(lanceur.getExitCode().value());
90  	}
91  
92  	protected ASolverFactory<ISolver> factory;
93  
94  	private String filename;
95  
96  	private int k = -1;
97  
98  	@SuppressWarnings("nls")
99  	private Options createCLIOptions() {
100 		Options options = new Options();
101 
102 		options.addOption("l", "library", true,
103 				"specifies the name of the library used (minisat by default)");
104 		options.addOption("s", "solver", true,
105 				"specifies the name of a prebuilt solver from the library");
106 		options.addOption("S", "Solver", true,
107 				"setup a solver using a solver config string");
108 		options.addOption("t", "timeout", true,
109 				"specifies the timeout (in seconds)");
110 		options.addOption("T", "timeoutms", true,
111 				"specifies the timeout (in milliseconds)");
112 		options.addOption("C", "conflictbased", false,
113 				"conflict based timeout (for deterministic behavior)");
114 		options
115 				.addOption("d", "dot", true,
116 						"create a sat4j.dot file in current directory representing the search");
117 		options
118 				.addOption("f", "filename", true,
119 						"specifies the file to use (in conjunction with -d for instance)");
120 		options.addOption("m", "mute", false, "Set launcher in silent mode");
121 		options
122 				.addOption("k", "kleast", true,
123 						"limit the search to models having at least k variables set to false");
124 		options
125 				.addOption("r", "trace", true,
126 						"Search Listener to use for tracing the behavior of the solver");
127 		Option op = options.getOption("l");
128 		op.setArgName("libname");
129 		op = options.getOption("s");
130 		op.setArgName("solvername");
131 		op = options.getOption("S");
132 		op.setArgName("solverStringDefinition");
133 		op = options.getOption("t");
134 		op.setArgName("number");
135 		op = options.getOption("T");
136 		op.setArgName("number");
137 		op = options.getOption("C");
138 		op.setArgName("number");
139 		op = options.getOption("k");
140 		op.setArgName("number");
141 		op = options.getOption("d");
142 		op.setArgName("filename");
143 		op = options.getOption("f");
144 		op.setArgName("filename");
145 		op = options.getOption("r");
146 		op.setArgName("searchlistener");
147 		return options;
148 	}
149 
150 	/**
151 	 * Configure the solver according to the command line parameters.
152 	 * 
153 	 * @param args
154 	 *            the command line
155 	 * @return a solver properly configured.
156 	 */
157 	@SuppressWarnings( { "nls", "unchecked" })
158 	@Override
159 	protected ISolver configureSolver(String[] args) {
160 		Options options = createCLIOptions();
161 		if (args.length == 0) {
162 			HelpFormatter helpf = new HelpFormatter();
163 			helpf.printHelp("java -jar sat4j.jar", options, true);
164 			return null;
165 		}
166 		try {
167 			CommandLine cmd = new PosixParser().parse(options, args);
168 
169 			String framework = cmd.getOptionValue("l"); //$NON-NLS-1$
170 			if (framework == null) { //$NON-NLS-1$
171 				framework = "minisat";
172 			}
173 
174 			try {
175 				Class<?> clazz = Class
176 						.forName("org.sat4j." + framework + ".SolverFactory"); //$NON-NLS-1$ //$NON-NLS-2$
177 				Class<?>[] params = {};
178 				Method m = clazz.getMethod("instance", params); //$NON-NLS-1$
179 				factory = (ASolverFactory) m.invoke(null, (Object[]) null);
180 			} catch (Exception e) { // DLB Findbugs warning ok
181 				log("Wrong framework: " + framework
182 						+ ". Using minisat instead.");
183 				factory = org.sat4j.minisat.SolverFactory.instance();
184 			}
185 
186 			ISolver asolver;
187 			if (cmd.hasOption("s")) {
188 				log("Available solvers: "
189 						+ Arrays.asList(factory.solverNames()));
190 				String solvername = cmd.getOptionValue("s");
191 				if (solvername == null) {
192 					asolver = factory.defaultSolver();
193 				} else {
194 					asolver = factory.createSolverByName(solvername);
195 				}
196 			} else {
197 				asolver = factory.defaultSolver();
198 			}
199 
200 			if (cmd.hasOption("S")) {
201 				String configuredSolver = cmd.getOptionValue("S");
202 				if (configuredSolver == null) {
203 					stringUsage();
204 					return null;
205 				}
206 				asolver = configureFromString(configuredSolver, asolver);
207 			}
208 
209 			String timeout = cmd.getOptionValue("t");
210 			if (timeout == null) {
211 				timeout = cmd.getOptionValue("T");
212 				if (timeout != null) {
213 					asolver.setTimeoutMs(Long.parseLong(timeout));
214 				}
215 			} else {
216 				if (cmd.hasOption("C")) {
217 					asolver.setTimeoutOnConflicts(Integer.parseInt(timeout));
218 				} else {
219 					asolver.setTimeout(Integer.parseInt(timeout));
220 				}
221 			}
222 			filename = cmd.getOptionValue("f");
223 
224 			if (cmd.hasOption("d")) {
225 				String dotfilename = null;
226 				if (filename != null) {
227 					dotfilename = cmd.getOptionValue("d");
228 				}
229 				if (dotfilename == null) {
230 					dotfilename = "sat4j.dot";
231 				}
232 				asolver.setSearchListener(new DotSearchTracing(dotfilename,
233 						null));
234 			}
235 
236 			if (cmd.hasOption("m")) {
237 				setSilent(true);
238 			}
239 
240 			if (cmd.hasOption("k")) {
241 				Integer myk = Integer.valueOf(cmd.getOptionValue("k"));
242 				if (myk != null) {
243 					k = myk.intValue();
244 				}
245 			}
246 			if (cmd.hasOption("r")) {
247 				String listener = cmd.getOptionValue("r");
248 				try {
249 
250 					SearchListener slistener = (SearchListener) Class.forName(
251 							listener).getConstructor(String.class).newInstance(
252 							"sat4j.trace");
253 					asolver.setSearchListener(slistener);
254 				} catch (InstantiationException e) {
255 					log("wrong parameter for search listener: "
256 							+ e.getLocalizedMessage());
257 				} catch (IllegalAccessException e) {
258 					log("wrong parameter for search listener: "
259 							+ e.getLocalizedMessage());
260 				} catch (ClassNotFoundException e) {
261 					log("wrong parameter for search listener: " + listener);
262 				} catch (IllegalArgumentException e) {
263 					log("wrong parameter for search listener: "
264 							+ e.getLocalizedMessage());
265 				} catch (SecurityException e) {
266 					log("wrong parameter for search listener: "
267 							+ e.getLocalizedMessage());
268 				} catch (InvocationTargetException e) {
269 					log("wrong parameter for search listener: "
270 							+ e.getLocalizedMessage());
271 				} catch (NoSuchMethodException e) {
272 					log("wrong parameter for search listener: "
273 							+ e.getLocalizedMessage());
274 				}
275 			}
276 			int others = 0;
277 			String[] rargs = cmd.getArgs();
278 			if (filename == null && rargs.length > 0) {
279 				filename = rargs[others++];
280 			}
281 
282 			// use remaining data to configure the solver
283 			while (others < rargs.length) {
284 				String[] param = rargs[others].split("="); //$NON-NLS-1$
285 				assert param.length == 2;
286 				log("setting " + param[0] + " to " + param[1]); //$NON-NLS-1$ //$NON-NLS-2$
287 				try {
288 					BeanUtils.setProperty(asolver, param[0], param[1]);
289 				} catch (Exception e) {
290 					log("Cannot set parameter : " //$NON-NLS-1$
291 							+ args[others]);
292 				}
293 				others++;
294 			}
295 
296 			log(asolver.toString(COMMENT_PREFIX)); //$NON-NLS-1$
297 			return asolver;
298 		} catch (ParseException e1) {
299 			HelpFormatter helpf = new HelpFormatter();
300 			helpf.printHelp("java -jar sat4j.jar", options, true);
301 			usage();
302 		}
303 		return null;
304 	}
305 
306 	@Override
307 	protected Reader createReader(ISolver theSolver, String problemname) {
308 		if (theSolver instanceof IPBSolver) {
309 			return new PBInstanceReader((IPBSolver) theSolver);
310 		}
311 		return new InstanceReader(theSolver);
312 	}
313 
314 	@Override
315 	public void displayLicense() {
316 		super.displayLicense();
317 		log("This software uses some libraries from the Jakarta Commons project. See jakarta.apache.org for details."); //$NON-NLS-1$
318 	}
319 
320 	@Override
321 	public void usage() {
322 		showAvailableSolvers(factory);
323 	}
324 
325 	@Override
326 	protected String getInstanceName(String[] args) {
327 		return filename;
328 	}
329 
330 	@SuppressWarnings("unchecked")
331 	private final ISolver configureFromString(String solverconfig,
332 			ISolver theSolver) {
333 		// AFAIK, there is no easy way to solve parameterized problems
334 		// when building the solver at runtime.
335 		StringTokenizer stk = new StringTokenizer(solverconfig, ",");
336 		Properties pf = new Properties();
337 		String token;
338 		String[] couple;
339 		while (stk.hasMoreElements()) {
340 			token = stk.nextToken();
341 			couple = token.split("=");
342 			pf.setProperty(couple[0], couple[1]);
343 		}
344 		Solver aSolver = (Solver) theSolver;
345 		DataStructureFactory dsf = setupObject("DSF", pf);
346 		if (dsf != null) {
347 			aSolver.setDataStructureFactory(dsf);
348 		}
349 		LearningStrategy learning = setupObject("LEARNING", pf);
350 		if (learning != null) {
351 			aSolver.setLearner(learning);
352 			learning.setSolver(aSolver);
353 		}
354 		IOrder order = setupObject("ORDER", pf);
355 		if (order != null) {
356 			aSolver.setOrder(order);
357 		}
358 		IPhaseSelectionStrategy pss = setupObject("PHASE", pf);
359 		if (pss != null) {
360 			aSolver.getOrder().setPhaseSelectionStrategy(pss);
361 		}
362 		RestartStrategy restarter = setupObject("RESTARTS", pf);
363 		if (restarter != null) {
364 			aSolver.setRestartStrategy(restarter);
365 		}
366 		String simp = pf.getProperty("SIMP");
367 		if (simp != null) {
368 			aSolver.setSimplifier(simp);
369 		}
370 		SearchParams params = setupObject("PARAMS", pf);
371 		if (params != null) {
372 			aSolver.setSearchParams(params);
373 		}
374 		String memory = pf.getProperty("MEMORY");
375 		if ("GLUCOSE".equalsIgnoreCase(memory)) {
376 			log("configuring MEMORY");
377 			aSolver.setLearnedConstraintsDeletionStrategy(aSolver.glucose);
378 		}
379 		return theSolver;
380 	}
381 
382 	private void stringUsage() {
383 		log("Available building blocks: DSF, LEARNING, ORDER, PHASE, RESTARTS, SIMP, PARAMS");
384 		log("Example: -S RESTARTS=org.sat4j.minisat.restarts.LubyRestarts/factor:512,LEARNING=org.sat4j.minisat.learning.MiniSATLearning");
385 	}
386 
387 	@SuppressWarnings("unchecked")
388 	private final <T> T setupObject(String component, Properties pf) {
389 		try {
390 			String configline = pf.getProperty(component);
391 			if (configline == null) {
392 				return null;
393 			}
394 			log("configuring " + component);
395 			String[] config = configline.split("/");
396 			T comp = (T) Class.forName(config[0]).newInstance();
397 			for (int i = 1; i < config.length; i++) {
398 				String[] param = config[i].split(":"); //$NON-NLS-1$
399 				assert param.length == 2;
400 				try {
401 					// Check first that the property really exists
402 					BeanUtils.getProperty(comp, param[0]);
403 					BeanUtils.setProperty(comp, param[0], param[1]);
404 				} catch (Exception e) {
405 					log("Problem with component " + config[0] + " " + e);
406 				}
407 			}
408 			return comp;
409 		} catch (InstantiationException e) {
410 			log("Problem with component " + component + " " + e);
411 		} catch (IllegalAccessException e) {
412 			log("Problem with component " + component + " " + e);
413 		} catch (ClassNotFoundException e) {
414 			log("Problem with component " + component + " " + e);
415 		}
416 		return null;
417 	}
418 
419 	@Override
420 	protected IProblem readProblem(String problemname)
421 			throws FileNotFoundException, ParseFormatException, IOException,
422 			ContradictionException {
423 		ISolver theSolver = (ISolver) super.readProblem(problemname);
424 		if (k > 0) {
425 			IVecInt literals = new VecInt();
426 			for (int i = 1; i <= theSolver.nVars(); i++) {
427 				literals.push(-i);
428 			}
429 			theSolver.addAtLeast(literals, k);
430 			log("Limiting solutions to those having at least " + k
431 					+ " variables assigned to false");
432 		}
433 		return theSolver;
434 	}
435 }