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.addOption("d", "dot", true,
115 				"create a sat4j.dot file in current directory representing the search");
116 		options.addOption("f", "filename", true,
117 				"specifies the file to use (in conjunction with -d for instance)");
118 		options.addOption("m", "mute", false, "Set launcher in silent mode");
119 		options.addOption("k", "kleast", true,
120 				"limit the search to models having at least k variables set to false");
121 		options.addOption("r", "trace", true,
122 				"Search Listener to use for tracing the behavior of the solver");
123 		Option op = options.getOption("l");
124 		op.setArgName("libname");
125 		op = options.getOption("s");
126 		op.setArgName("solvername");
127 		op = options.getOption("S");
128 		op.setArgName("solverStringDefinition");
129 		op = options.getOption("t");
130 		op.setArgName("number");
131 		op = options.getOption("T");
132 		op.setArgName("number");
133 		op = options.getOption("C");
134 		op.setArgName("number");
135 		op = options.getOption("k");
136 		op.setArgName("number");
137 		op = options.getOption("d");
138 		op.setArgName("filename");
139 		op = options.getOption("f");
140 		op.setArgName("filename");
141 		op = options.getOption("r");
142 		op.setArgName("searchlistener");
143 		return options;
144 	}
145 
146 	/**
147 	 * Configure the solver according to the command line parameters.
148 	 * 
149 	 * @param args
150 	 *            the command line
151 	 * @return a solver properly configured.
152 	 */
153 	@SuppressWarnings({ "nls", "unchecked" })
154 	@Override
155 	protected ISolver configureSolver(String[] args) {
156 		Options options = createCLIOptions();
157 		if (args.length == 0) {
158 			HelpFormatter helpf = new HelpFormatter();
159 			helpf.printHelp("java -jar sat4j.jar", options, true);
160 			return null;
161 		}
162 		try {
163 			CommandLine cmd = new PosixParser().parse(options, args);
164 
165 			String framework = cmd.getOptionValue("l"); //$NON-NLS-1$
166 			if (framework == null) { //$NON-NLS-1$
167 				framework = "minisat";
168 			}
169 
170 			try {
171 				Class<?> clazz = Class
172 						.forName("org.sat4j." + framework + ".SolverFactory"); //$NON-NLS-1$ //$NON-NLS-2$
173 				Class<?>[] params = {};
174 				Method m = clazz.getMethod("instance", params); //$NON-NLS-1$
175 				factory = (ASolverFactory) m.invoke(null, (Object[]) null);
176 			} catch (Exception e) { // DLB Findbugs warning ok
177 				log("Wrong framework: " + framework
178 						+ ". Using minisat instead.");
179 				factory = org.sat4j.minisat.SolverFactory.instance();
180 			}
181 
182 			ISolver asolver;
183 			if (cmd.hasOption("s")) {
184 				log("Available solvers: "
185 						+ Arrays.asList(factory.solverNames()));
186 				String solvername = cmd.getOptionValue("s");
187 				if (solvername == null) {
188 					asolver = factory.defaultSolver();
189 				} else {
190 					asolver = factory.createSolverByName(solvername);
191 				}
192 			} else {
193 				asolver = factory.defaultSolver();
194 			}
195 
196 			if (cmd.hasOption("S")) {
197 				String configuredSolver = cmd.getOptionValue("S");
198 				if (configuredSolver == null) {
199 					stringUsage();
200 					return null;
201 				}
202 				asolver = configureFromString(configuredSolver, asolver);
203 			}
204 
205 			String timeout = cmd.getOptionValue("t");
206 			if (timeout == null) {
207 				timeout = cmd.getOptionValue("T");
208 				if (timeout != null) {
209 					asolver.setTimeoutMs(Long.parseLong(timeout));
210 				}
211 			} else {
212 				if (cmd.hasOption("C")) {
213 					asolver.setTimeoutOnConflicts(Integer.parseInt(timeout));
214 				} else {
215 					asolver.setTimeout(Integer.parseInt(timeout));
216 				}
217 			}
218 			filename = cmd.getOptionValue("f");
219 
220 			if (cmd.hasOption("d")) {
221 				String dotfilename = null;
222 				if (filename != null) {
223 					dotfilename = cmd.getOptionValue("d");
224 				}
225 				if (dotfilename == null) {
226 					dotfilename = "sat4j.dot";
227 				}
228 				asolver.setSearchListener(new DotSearchTracing(dotfilename,
229 						null));
230 			}
231 
232 			if (cmd.hasOption("m")) {
233 				setSilent(true);
234 			}
235 
236 			if (cmd.hasOption("k")) {
237 				Integer myk = Integer.valueOf(cmd.getOptionValue("k"));
238 				if (myk != null) {
239 					k = myk.intValue();
240 				}
241 			}
242 			if (cmd.hasOption("r")) {
243 				String listener = cmd.getOptionValue("r");
244 				try {
245 
246 					SearchListener slistener = (SearchListener) Class
247 							.forName(listener).getConstructor(String.class)
248 							.newInstance("sat4j.trace");
249 					asolver.setSearchListener(slistener);
250 				} catch (InstantiationException e) {
251 					log("wrong parameter for search listener: "
252 							+ e.getLocalizedMessage());
253 				} catch (IllegalAccessException e) {
254 					log("wrong parameter for search listener: "
255 							+ e.getLocalizedMessage());
256 				} catch (ClassNotFoundException e) {
257 					log("wrong parameter for search listener: " + listener);
258 				} catch (IllegalArgumentException e) {
259 					log("wrong parameter for search listener: "
260 							+ e.getLocalizedMessage());
261 				} catch (SecurityException e) {
262 					log("wrong parameter for search listener: "
263 							+ e.getLocalizedMessage());
264 				} catch (InvocationTargetException e) {
265 					log("wrong parameter for search listener: "
266 							+ e.getLocalizedMessage());
267 				} catch (NoSuchMethodException e) {
268 					log("wrong parameter for search listener: "
269 							+ e.getLocalizedMessage());
270 				}
271 			}
272 			int others = 0;
273 			String[] rargs = cmd.getArgs();
274 			if (filename == null && rargs.length > 0) {
275 				filename = rargs[others++];
276 			}
277 
278 			// use remaining data to configure the solver
279 			while (others < rargs.length) {
280 				String[] param = rargs[others].split("="); //$NON-NLS-1$
281 				assert param.length == 2;
282 				log("setting " + param[0] + " to " + param[1]); //$NON-NLS-1$ //$NON-NLS-2$
283 				try {
284 					BeanUtils.setProperty(asolver, param[0], param[1]);
285 				} catch (Exception e) {
286 					log("Cannot set parameter : " //$NON-NLS-1$
287 							+ args[others]);
288 				}
289 				others++;
290 			}
291 
292 			getLogWriter().println(asolver.toString(COMMENT_PREFIX)); //$NON-NLS-1$
293 			return asolver;
294 		} catch (ParseException e1) {
295 			HelpFormatter helpf = new HelpFormatter();
296 			helpf.printHelp("java -jar sat4j.jar", options, true);
297 			usage();
298 		}
299 		return null;
300 	}
301 
302 	@Override
303 	protected Reader createReader(ISolver theSolver, String problemname) {
304 		if (theSolver instanceof IPBSolver) {
305 			return new PBInstanceReader((IPBSolver) theSolver);
306 		}
307 		return new InstanceReader(theSolver);
308 	}
309 
310 	@Override
311 	public void displayLicense() {
312 		super.displayLicense();
313 		log("This software uses some libraries from the Jakarta Commons project. See jakarta.apache.org for details."); //$NON-NLS-1$
314 	}
315 
316 	@Override
317 	public void usage() {
318 		showAvailableSolvers(factory);
319 	}
320 
321 	@Override
322 	protected String getInstanceName(String[] args) {
323 		return filename;
324 	}
325 
326 	@SuppressWarnings("unchecked")
327 	private final ISolver configureFromString(String solverconfig,
328 			ISolver theSolver) {
329 		// AFAIK, there is no easy way to solve parameterized problems
330 		// when building the solver at runtime.
331 		StringTokenizer stk = new StringTokenizer(solverconfig, ",");
332 		Properties pf = new Properties();
333 		String token;
334 		String[] couple;
335 		while (stk.hasMoreElements()) {
336 			token = stk.nextToken();
337 			couple = token.split("=");
338 			pf.setProperty(couple[0], couple[1]);
339 		}
340 		Solver aSolver = (Solver) theSolver;
341 		DataStructureFactory dsf = setupObject("DSF", pf);
342 		if (dsf != null) {
343 			aSolver.setDataStructureFactory(dsf);
344 		}
345 		LearningStrategy learning = setupObject("LEARNING", pf);
346 		if (learning != null) {
347 			aSolver.setLearner(learning);
348 			learning.setSolver(aSolver);
349 		}
350 		IOrder order = setupObject("ORDER", pf);
351 		if (order != null) {
352 			aSolver.setOrder(order);
353 		}
354 		IPhaseSelectionStrategy pss = setupObject("PHASE", pf);
355 		if (pss != null) {
356 			aSolver.getOrder().setPhaseSelectionStrategy(pss);
357 		}
358 		RestartStrategy restarter = setupObject("RESTARTS", pf);
359 		if (restarter != null) {
360 			aSolver.setRestartStrategy(restarter);
361 		}
362 		String simp = pf.getProperty("SIMP");
363 		if (simp != null) {
364 			aSolver.setSimplifier(simp);
365 		}
366 		SearchParams params = setupObject("PARAMS", pf);
367 		if (params != null) {
368 			aSolver.setSearchParams(params);
369 		}
370 		String memory = pf.getProperty("MEMORY");
371 		if ("GLUCOSE".equalsIgnoreCase(memory)) {
372 			log("configuring MEMORY");
373 			aSolver.setLearnedConstraintsDeletionStrategy(aSolver.glucose);
374 		}
375 		return theSolver;
376 	}
377 
378 	private void stringUsage() {
379 		log("Available building blocks: DSF, LEARNING, ORDER, PHASE, RESTARTS, SIMP, PARAMS");
380 		log("Example: -S RESTARTS=org.sat4j.minisat.restarts.LubyRestarts/factor:512,LEARNING=org.sat4j.minisat.learning.MiniSATLearning");
381 	}
382 
383 	@SuppressWarnings("unchecked")
384 	private final <T> T setupObject(String component, Properties pf) {
385 		try {
386 			String configline = pf.getProperty(component);
387 			if (configline == null) {
388 				return null;
389 			}
390 			log("configuring " + component);
391 			String[] config = configline.split("/");
392 			T comp = (T) Class.forName(config[0]).newInstance();
393 			for (int i = 1; i < config.length; i++) {
394 				String[] param = config[i].split(":"); //$NON-NLS-1$
395 				assert param.length == 2;
396 				try {
397 					// Check first that the property really exists
398 					BeanUtils.getProperty(comp, param[0]);
399 					BeanUtils.setProperty(comp, param[0], param[1]);
400 				} catch (Exception e) {
401 					log("Problem with component " + config[0] + " " + e);
402 				}
403 			}
404 			return comp;
405 		} catch (InstantiationException e) {
406 			log("Problem with component " + component + " " + e);
407 		} catch (IllegalAccessException e) {
408 			log("Problem with component " + component + " " + e);
409 		} catch (ClassNotFoundException e) {
410 			log("Problem with component " + component + " " + e);
411 		}
412 		return null;
413 	}
414 
415 	@Override
416 	protected IProblem readProblem(String problemname)
417 			throws FileNotFoundException, ParseFormatException, IOException,
418 			ContradictionException {
419 		ISolver theSolver = (ISolver) super.readProblem(problemname);
420 		if (k > 0) {
421 			IVecInt literals = new VecInt();
422 			for (int i = 1; i <= theSolver.nVars(); i++) {
423 				literals.push(-i);
424 			}
425 			theSolver.addAtLeast(literals, k);
426 			log("Limiting solutions to those having at least " + k
427 					+ " variables assigned to false");
428 		}
429 		return theSolver;
430 	}
431 }