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   * Based on the pseudo boolean algorithms described in:
20   * A fast pseudo-Boolean constraint solver Chai, D.; Kuehlmann, A.
21   * Computer-Aided Design of Integrated Circuits and Systems, IEEE Transactions on
22   * Volume 24, Issue 3, March 2005 Page(s): 305 - 317
23   * 
24   * and 
25   * Heidi E. Dixon, 2004. Automating Pseudo-Boolean Inference within a DPLL 
26   * Framework. Ph.D. Dissertation, University of Oregon.
27   *******************************************************************************/
28  /*=============================================================================
29   * parser for pseudo-Boolean instances
30   * 
31   * Copyright (c) 2005-2007 Olivier ROUSSEL and Vasco MANQUINHO
32   * 
33   * Permission is hereby granted, free of charge, to any person obtaining a copy
34   * of this software and associated documentation files (the "Software"), to deal
35   * in the Software without restriction, including without limitation the rights
36   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37   * copies of the Software, and to permit persons to whom the Software is
38   * furnished to do so, subject to the following conditions:
39   * 
40   * The above copyright notice and this permission notice shall be included in
41   * all copies or substantial portions of the Software.
42   * 
43   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
49   * THE SOFTWARE.
50   *=============================================================================
51   */
52  package org.sat4j.pb.reader;
53  
54  import java.io.BufferedReader;
55  import java.io.IOException;
56  import java.io.LineNumberReader;
57  import java.io.PrintWriter;
58  import java.io.Serializable;
59  import java.math.BigInteger;
60  
61  import org.sat4j.core.Vec;
62  import org.sat4j.core.VecInt;
63  import org.sat4j.pb.IPBSolver;
64  import org.sat4j.pb.ObjectiveFunction;
65  import org.sat4j.reader.ParseFormatException;
66  import org.sat4j.reader.Reader;
67  import org.sat4j.specs.ContradictionException;
68  import org.sat4j.specs.IProblem;
69  import org.sat4j.specs.IVec;
70  import org.sat4j.specs.IVecInt;
71  
72  /**
73   * Based on the "Official" reader for the Pseudo Boolean evaluation 2005.
74   * http://www.cril.univ-artois.fr/PB05/parser/SimpleParser.java provided by
75   * Olivier Roussel and Vasco Manquinho.
76   * 
77   * Modified to comply with SAT4J architecture by Mederic Baron
78   * 
79   * Updated since then by Daniel Le Berre
80   * 
81   * @author or
82   * @author vm
83   * @author mederic baron
84   * @author leberre
85   */
86  public class OPBReader2005 extends Reader implements Serializable {
87  
88  	/**
89  	 * Comment for <code>serialVersionUID</code>
90  	 */
91  	private static final long serialVersionUID = 1L;
92  
93  	protected final IPBSolver solver;
94  
95  	protected final IVecInt lits;
96  
97  	protected final IVec<BigInteger> coeffs;
98  
99  	protected BigInteger d;
100 
101 	protected String operator;
102 
103 	private final IVecInt objectiveVars = new VecInt();
104 
105 	private final IVec<BigInteger> objectiveCoeffs = new Vec<BigInteger>();
106 
107 	// does the instance have an objective function?
108 	protected boolean hasObjFunc = false;
109 
110 	// does the instance need variables explanation?
111 	protected boolean hasVariablesExplanation = false;
112 
113 	protected int nbVars, nbConstr; // MetaData: #Variables and #Constraints in
114 
115 	// file.
116 	protected int nbConstraintsRead;
117 
118 	/**
119 	 * callback called when we get the number of variables and the expected
120 	 * number of constraints
121 	 * 
122 	 * @param nbvar
123 	 *            the number of variables
124 	 * @param nbconstr
125 	 *            the number of contraints
126 	 */
127 	protected void metaData(int nbvar, int nbconstr) {
128 		solver.newVar(nbvar);
129 	}
130 
131 	/**
132 	 * callback called before we read the objective function
133 	 */
134 	protected void beginObjective() {
135 	}
136 
137 	/**
138 	 * callback called after we've read the objective function
139 	 */
140 	protected void endObjective() {
141 		assert lits.size() == coeffs.size();
142 		assert lits.size() == coeffs.size();
143 		for (int i = 0; i < lits.size(); i++) {
144 			objectiveVars.push(lits.get(i));
145 			objectiveCoeffs.push(coeffs.get(i));
146 		}
147 	}
148 
149 	/**
150 	 * callback called before we read a constraint
151 	 */
152 	protected void beginConstraint() {
153 		lits.clear();
154 		coeffs.clear();
155 		assert lits.size() == 0;
156 		assert coeffs.size() == 0;
157 	}
158 
159 	/**
160 	 * callback called after we've read a constraint
161 	 */
162 	/**
163 	 * @throws ContradictionException
164 	 */
165 	protected void endConstraint() throws ContradictionException {
166 
167 		assert !(lits.size() == 0);
168 		assert !(coeffs.size() == 0);
169 		assert lits.size() == coeffs.size();
170 
171 		if ("<=".equals(operator) || "=".equals(operator)) {
172 			solver.addPseudoBoolean(lits, coeffs, false, d);
173 		}
174 		if (">=".equals(operator) || "=".equals(operator)) {
175 			solver.addPseudoBoolean(lits, coeffs, true, d);
176 		}
177 		nbConstraintsRead++;
178 	}
179 
180 	/**
181 	 * callback called when we read a term of a constraint
182 	 * 
183 	 * @param coeff
184 	 *            the coefficient of the term
185 	 * @param var
186 	 *            the identifier of the variable
187 	 * @throws ParseFormatException
188 	 */
189 	private void constraintTerm(BigInteger coeff, String var)
190 			throws ParseFormatException {
191 		coeffs.push(coeff);
192 		lits.push(translateVarToId(var));
193 	}
194 
195 	protected int translateVarToId(String var) throws ParseFormatException {
196 		int id = Integer.parseInt(var.substring(1));
197 		return ((savedChar == '-') ? -1 : 1) * id;
198 	}
199 
200 	/**
201 	 * callback called when we read the relational operator of a constraint
202 	 * 
203 	 * @param relop
204 	 *            the relational oerator (>= or =)
205 	 */
206 	protected void constraintRelOp(String relop) {
207 		operator = relop;
208 	}
209 
210 	/**
211 	 * callback called when we read the right term of a constraint (also known
212 	 * as the degree)
213 	 * 
214 	 * @param val
215 	 *            the degree of the constraint
216 	 */
217 	protected void constraintRightTerm(BigInteger val) {
218 		d = val;
219 	}
220 
221 	transient BufferedReader in; // the stream we're reading from
222 
223 	char savedChar; // a character read from the file but not yet consumed
224 
225 	boolean charAvailable = false; // true iff savedChar contains a character
226 
227 	boolean eofReached = false; // true iff we've reached EOF
228 
229 	private boolean eolReached = false;
230 
231 	/**
232 	 * get the next character from the stream
233 	 * 
234 	 * @throws IOException
235 	 */
236 	protected char get() throws IOException {
237 		int c;
238 
239 		if (charAvailable) {
240 			charAvailable = false;
241 			return savedChar;
242 		}
243 
244 		c = in.read();
245 		if (c == -1)
246 			eofReached = true;
247 		if (c == '\n' || c == '\r') {
248 			eolReached = true;
249 		} else {
250 			eolReached = false;
251 		}
252 		return (char) c;
253 	}
254 
255 	public IVecInt getVars() {
256 		return objectiveVars;
257 	}
258 
259 	public IVec<BigInteger> getCoeffs() {
260 		return objectiveCoeffs;
261 	}
262 
263 	/**
264 	 * put back a character into the stream (only one chr can be put back)
265 	 */
266 	protected void putback(char c) {
267 		savedChar = c;
268 		charAvailable = true;
269 	}
270 
271 	/**
272 	 * return true iff we've reached EOF
273 	 */
274 	protected boolean eof() {
275 		return eofReached;
276 	}
277 
278 	protected boolean eol() {
279 		return eolReached;
280 	}
281 
282 	/**
283 	 * skip white spaces
284 	 * 
285 	 * @throws IOException
286 	 */
287 	protected void skipSpaces() throws IOException {
288 		char c;
289 
290 		do {
291 			c = get();
292 		} while (Character.isWhitespace(c));
293 
294 		putback(c);
295 	}
296 
297 	/**
298 	 * read a word from file
299 	 * 
300 	 * @return the word we read
301 	 * @throws IOException
302 	 */
303 	public String readWord() throws IOException {
304 		StringBuffer s = new StringBuffer();
305 		char c;
306 
307 		skipSpaces();
308 
309 		while (!Character.isWhitespace(c = get()) && !eol() && !eof())
310 			s.append(c);
311 
312 		putback(c);
313 		return s.toString();
314 	}
315 
316 	/**
317 	 * read a integer from file
318 	 * 
319 	 * @param s
320 	 *            a StringBuffer to store the integer that was read
321 	 * @throws IOException
322 	 */
323 	public void readInteger(StringBuffer s) throws IOException {
324 		char c;
325 
326 		skipSpaces();
327 		s.setLength(0);
328 
329 		c = get();
330 		if (c == '-' || Character.isDigit(c))
331 			s.append(c);
332 		// note: BigInteger don't like a '+' before the number, we just skip it
333 
334 		while (Character.isDigit(c = get()) && !eol() && !eof())
335 			s.append(c);
336 
337 		putback(c);
338 	}
339 
340 	/**
341 	 * read an identifier from stream and store it in s
342 	 * 
343 	 * @return the identifier we read or null
344 	 * @throws IOException
345 	 * @throws ParseFormatException
346 	 */
347 	protected boolean readIdentifier(StringBuffer s) throws IOException,
348 			ParseFormatException {
349 		char c;
350 
351 		s.setLength(0);
352 
353 		skipSpaces();
354 
355 		// first char (must be a letter or underscore)
356 		c = get();
357 		if (eof())
358 			return false;
359 
360 		if (!isGoodFirstCharacter(c)) {
361 			putback(c);
362 			return false;
363 		}
364 
365 		s.append(c);
366 
367 		// next chars (must be a letter, a digit or an underscore)
368 		while (true) {
369 			c = get();
370 			if (eof())
371 				break;
372 
373 			if (isGoodFollowingCharacter(c))
374 				s.append(c);
375 			else {
376 				putback(c);
377 				break;
378 			}
379 		}
380 		checkId(s);
381 		return true;
382 	}
383 
384 	protected boolean isGoodFirstCharacter(char c) {
385 		return Character.isLetter(c) || c == '_';
386 	}
387 
388 	protected boolean isGoodFollowingCharacter(char c) {
389 		return Character.isLetter(c) || Character.isDigit(c) || c == '_';
390 	}
391 
392 	protected void checkId(StringBuffer s) throws ParseFormatException {
393 		// Small check on the coefficient ID to make sure everything is ok
394 		int varID = Integer.parseInt(s.substring(1));
395 		if (varID > nbVars) {
396 			throw new ParseFormatException(
397 					"Variable identifier larger than #variables in metadata.");
398 		}
399 	}
400 
401 	/**
402 	 * read a relational operator from stream and store it in s
403 	 * 
404 	 * @return the relational operator we read or null
405 	 * @throws IOException
406 	 */
407 	private String readRelOp() throws IOException {
408 		char c;
409 
410 		skipSpaces();
411 
412 		c = get();
413 		if (eof())
414 			return null;
415 
416 		if (c == '=')
417 			return "=";
418 
419 		char following = get();
420 		if (c == '>' && following == '=')
421 			return ">=";
422 		if (c == '<' && following == '=')
423 			return "<=";
424 
425 		return null;
426 	}
427 
428 	/**
429 	 * read the first comment line to get the number of variables and the number
430 	 * of constraints in the file calls metaData with the data that was read
431 	 * 
432 	 * @throws IOException
433 	 * @throws ParseException
434 	 */
435 	protected void readMetaData() throws IOException, ParseFormatException {
436 		char c;
437 		String s;
438 
439 		// get the number of variables and constraints
440 		c = get();
441 		if (c != '*')
442 			throw new ParseFormatException(
443 					"First line of input file should be a comment");
444 
445 		s = readWord();
446 		if (eof() || !"#variable=".equals(s))
447 			throw new ParseFormatException(
448 					"First line should contain #variable= as first keyword");
449 
450 		nbVars = Integer.parseInt(readWord());
451 
452 		s = readWord();
453 		if (eof() || !"#constraint=".equals(s))
454 			throw new ParseFormatException(
455 					"First line should contain #constraint= as second keyword");
456 
457 		nbConstr = Integer.parseInt(readWord());
458 
459 		// skip the rest of the line
460 		in.readLine();
461 
462 		// callback to transmit the data
463 		metaData(nbVars, nbConstr);
464 	}
465 
466 	/**
467 	 * skip the comments at the beginning of the file
468 	 * 
469 	 * @throws IOException
470 	 */
471 	private void skipComments() throws IOException {
472 		char c = ' ';
473 
474 		// skip further comments
475 
476 		while (!eof() && (c = get()) == '*') {
477 			in.readLine();
478 		}
479 
480 		putback(c);
481 	}
482 
483 	/**
484 	 * read a term into coeff and var
485 	 * 
486 	 * @param coeff
487 	 *            the coefficient of the variable
488 	 * @param var
489 	 *            the identifier we read
490 	 * @throws IOException
491 	 * @throws ParseException
492 	 */
493 	protected void readTerm(StringBuffer coeff, StringBuffer var)
494 			throws IOException, ParseFormatException {
495 		char c;
496 
497 		readInteger(coeff);
498 
499 		skipSpaces();
500 		c = get();
501 		if (c != '*')
502 			throw new ParseFormatException(
503 					"'*' expected between a coefficient and a variable");
504 
505 		if (!readIdentifier(var))
506 			throw new ParseFormatException("identifier expected");
507 	}
508 
509 	/**
510 	 * @throws IOException
511 	 * @throws ParseFormatException
512 	 */
513 	protected void readVariablesExplanation() throws IOException,
514 			ParseFormatException {
515 	}
516 
517 	/**
518 	 * read the objective line (if any) calls beginObjective, objectiveTerm and
519 	 * endObjective
520 	 * 
521 	 * @throws IOException
522 	 * @throws ParseException
523 	 */
524 	protected void readObjective() throws IOException, ParseFormatException {
525 		char c;
526 		StringBuffer var = new StringBuffer();
527 		StringBuffer coeff = new StringBuffer();
528 
529 		// read objective line (if any)
530 
531 		skipSpaces();
532 		c = get();
533 		if (c != 'm') {
534 			// no objective line
535 			putback(c);
536 			return;
537 		}
538 
539 		hasObjFunc = true;
540 		if (get() == 'i' && get() == 'n' && get() == ':') {
541 			beginObjective(); // callback
542 
543 			while (!eof()) {
544 				readTerm(coeff, var);
545 				constraintTerm(new BigInteger(coeff.toString()), var.toString()); // callback
546 
547 				skipSpaces();
548 				c = get();
549 				if (c == ';')
550 					break; // end of objective
551 
552 				else if (c == '-' || c == '+' || Character.isDigit(c))
553 					putback(c);
554 				else
555 					throw new ParseFormatException(
556 							"unexpected character in objective function");
557 			}
558 
559 			endObjective();
560 		} else
561 			throw new ParseFormatException(
562 					"input format error: 'min:' expected");
563 	}
564 
565 	/**
566 	 * read a constraint calls beginConstraint, constraintTerm and endConstraint
567 	 * 
568 	 * @throws ParseException
569 	 * @throws IOException
570 	 * @throws ContradictionException
571 	 */
572 	protected void readConstraint() throws IOException, ParseFormatException,
573 			ContradictionException {
574 		StringBuffer var = new StringBuffer();
575 		StringBuffer coeff = new StringBuffer();
576 		char c;
577 
578 		beginConstraint();
579 
580 		while (!eof()) {
581 			readTerm(coeff, var);
582 			constraintTerm(new BigInteger(coeff.toString()), var.toString());
583 
584 			skipSpaces();
585 			c = get();
586 			if (c == '>' || c == '=' || c == '<') {
587 				// relational operator found
588 				putback(c);
589 				break;
590 			} else if (c == '-' || c == '+' || Character.isDigit(c))
591 				putback(c);
592 			else {
593 				throw new ParseFormatException(
594 						"unexpected character in constraint");
595 			}
596 		}
597 
598 		if (eof())
599 			throw new ParseFormatException(
600 					"unexpected EOF before end of constraint");
601 
602 		String relop;
603 		if ((relop = readRelOp()) == null) {
604 			throw new ParseFormatException(
605 					"unexpected relational operator in constraint");
606 
607 		}
608 		constraintRelOp(relop);
609 		readInteger(coeff);
610 		constraintRightTerm(new BigInteger(coeff.toString()));
611 
612 		skipSpaces();
613 		c = get();
614 		if (eof() || c != ';')
615 			throw new ParseFormatException(
616 					"semicolon expected at end of constraint");
617 
618 		endConstraint();
619 	}
620 
621 	public OPBReader2005(IPBSolver solver) {
622 		this.solver = solver;
623 		lits = new VecInt();
624 		coeffs = new Vec<BigInteger>();
625 	}
626 
627 	/**
628 	 * parses the file and uses the callbacks to send to send the data back to
629 	 * the program
630 	 * 
631 	 * @throws IOException
632 	 * @throws ParseException
633 	 * @throws ContradictionException
634 	 */
635 	public void parse() throws IOException, ParseFormatException,
636 			ContradictionException {
637 		readMetaData();
638 
639 		skipComments();
640 
641 		readObjective();
642 
643 		readVariablesExplanation();
644 
645 		// read constraints
646 		nbConstraintsRead = 0;
647 		char c;
648 		while (!eof()) {
649 			skipSpaces();
650 			if (eof())
651 				break;
652 
653 			c = get();
654 			putback(c);
655 			if (c == '*')
656 				skipComments();
657 
658 			if (eof())
659 				break;
660 
661 			readConstraint();
662 			// nbConstraintsRead++;
663 		}
664 		// Small check on the number of constraints
665 		if (nbConstraintsRead != nbConstr) {
666 			throw new ParseFormatException("Number of constraints read ("
667 					+ nbConstraintsRead + ") is different from metadata ("
668 					+ nbConstr + ")");
669 		}
670 	}
671 
672 	@Override
673 	public IProblem parseInstance(final java.io.Reader input)
674 			throws ParseFormatException, ContradictionException {
675 		IProblem problem = parseInstance(new LineNumberReader(input));
676 		solver.setObjectiveFunction(getObjectiveFunction());
677 		return problem;
678 	}
679 
680 	private IProblem parseInstance(LineNumberReader input)
681 			throws ParseFormatException, ContradictionException {
682 		solver.reset();
683 		this.in = input;
684 		try {
685 			parse();
686 			return solver;
687                 } catch (ContradictionException ce) {
688                         throw ce;
689 		} catch (Exception e) {
690 			String message;
691 
692 			if (e instanceof ParseFormatException) {
693 				message = e.getMessage().substring(
694 						"DIMACS Format error: ".length());
695 			} else {
696 				message = e.getMessage();
697 			}
698 			throw new ParseFormatException(" line " + input.getLineNumber()
699 					+ ", " + message);
700 
701 		}
702 	}
703 
704 	@Override
705 	public String decode(int[] model) {
706 		StringBuffer stb = new StringBuffer();
707 
708 		for (int i = 0; i < model.length; i++) {
709 			if (model[i] < 0) {
710 				stb.append("-x");
711 				stb.append(-model[i]);
712 			} else {
713 				stb.append("x");
714 				stb.append(model[i]);
715 			}
716 			stb.append(" ");
717 		}
718 		return stb.toString();
719 	}
720 
721 	@Override
722 	public void decode(int[] model, PrintWriter out) {
723 		for (int i = 0; i < model.length; i++) {
724 			if (model[i] < 0) {
725 				out.print("-x");
726 				out.print(-model[i]);
727 			} else {
728 				out.print("x");
729 				out.print(model[i]);
730 			}
731 			out.print(" ");
732 		}
733 	}
734 
735 	public ObjectiveFunction getObjectiveFunction() {
736 		if (hasObjFunc) {
737 			return new ObjectiveFunction(getVars(), getCoeffs());
738 		}
739 		return null;
740 	}
741 
742 	public IVecInt getListOfVariables() {
743 		return null;
744 	}
745 
746 }