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