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      private final IVecInt lits;
96  
97      private final IVec<BigInteger> coeffs;
98  
99      private BigInteger d;
100 
101     private 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     private 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      */
188     private void constraintTerm(BigInteger coeff, String var) {
189         coeffs.push(coeff);
190         lits.push(translateVarToId(var));
191     }
192 
193     protected int translateVarToId(String var) {
194         int id = Integer.parseInt(var.substring(1));
195         return ((savedChar == '-') ? -1 : 1) * id;
196     }
197 
198     /**
199      * callback called when we read the relational operator of a constraint
200      * 
201      * @param relop
202      *                the relational oerator (>= or =)
203      */
204     protected void constraintRelOp(String relop) {
205         operator = relop;
206     }
207 
208     /**
209      * callback called when we read the right term of a constraint (also known
210      * as the degree)
211      * 
212      * @param val
213      *                the degree of the constraint
214      */
215     protected void constraintRightTerm(BigInteger val) {
216         d = val;
217     }
218 
219     transient BufferedReader in; // the stream we're reading from
220 
221     char savedChar; // a character read from the file but not yet consumed
222 
223     boolean charAvailable = false; // true iff savedChar contains a character
224 
225     boolean eofReached = false; // true iff we've reached EOF
226 
227     private boolean eolReached = false;
228 
229     /**
230      * get the next character from the stream
231      * 
232      * @throws IOException
233      */
234     protected char get() throws IOException {
235         int c;
236 
237         if (charAvailable) {
238             charAvailable = false;
239             return savedChar;
240         }
241 
242         c = in.read();
243         if (c == -1)
244             eofReached = true;
245         if (c == '\n' || c == '\r') {
246             eolReached = true;
247         } else {
248             eolReached = false;
249         }
250         return (char) c;
251     }
252 
253     public IVecInt getVars() {
254         return objectiveVars;
255     }
256 
257     public IVec<BigInteger> getCoeffs() {
258         return objectiveCoeffs;
259     }
260 
261     /**
262      * put back a character into the stream (only one chr can be put back)
263      */
264     protected void putback(char c) {
265         savedChar = c;
266         charAvailable = true;
267     }
268 
269     /**
270      * return true iff we've reached EOF
271      */
272     protected boolean eof() {
273         return eofReached;
274     }
275 
276     protected boolean eol() {
277         return eolReached;
278     }
279 
280     /**
281      * skip white spaces
282      * 
283      * @throws IOException
284      */
285     protected void skipSpaces() throws IOException {
286         char c;
287 
288         while (Character.isWhitespace(c = get()))
289             ;
290 
291         putback(c);
292     }
293 
294     /**
295      * read a word from file
296      * 
297      * @return the word we read
298      * @throws IOException
299      */
300     public String readWord() throws IOException {
301         StringBuffer s = new StringBuffer();
302         char c;
303 
304         skipSpaces();
305 
306         while (!Character.isWhitespace(c = get()) && !eol() && !eof())
307             s.append(c);
308 
309         putback(c);
310         return s.toString();
311     }
312 
313     /**
314      * read a integer from file
315      * 
316      * @param s
317      *                a StringBuffer to store the integer that was read
318      * @throws IOException
319      */
320     public void readInteger(StringBuffer s) throws IOException {
321         char c;
322 
323         skipSpaces();
324         s.setLength(0);
325 
326         c = get();
327         if (c == '-' || Character.isDigit(c))
328             s.append(c);
329         // note: BigInteger don't like a '+' before the number, we just skip it
330 
331         while (Character.isDigit(c = get()) && !eol() && !eof())
332             s.append(c);
333 
334         putback(c);
335     }
336 
337     /**
338      * read an identifier from stream and store it in s
339      * 
340      * @return the identifier we read or null
341      * @throws IOException
342      * @throws ParseFormatException
343      */
344     protected boolean readIdentifier(StringBuffer s) throws IOException,
345             ParseFormatException {
346         char c;
347 
348         s.setLength(0);
349 
350         skipSpaces();
351 
352         // first char (must be a letter or underscore)
353         c = get();
354         if (eof())
355             return false;
356 
357         if (!isGoodFirstCharacter(c)) {
358             putback(c);
359             return false;
360         }
361 
362         s.append(c);
363 
364         // next chars (must be a letter, a digit or an underscore)
365         while (true) {
366             c = get();
367             if (eof())
368                 break;
369 
370             if (isGoodFollowingCharacter(c))
371                 s.append(c);
372             else {
373                 putback(c);
374                 break;
375             }
376         }
377         checkId(s);
378         return true;
379     }
380 
381     protected boolean isGoodFirstCharacter(char c) {
382         return Character.isLetter(c) || c == '_';
383     }
384 
385     protected boolean isGoodFollowingCharacter(char c) {
386         return Character.isLetter(c) || Character.isDigit(c) || c == '_';
387     }
388 
389     protected void checkId(StringBuffer s) throws ParseFormatException {
390         // Small check on the coefficient ID to make sure everything is ok
391         int varID = Integer.parseInt(s.substring(1));
392         if (varID > nbVars) {
393             throw new ParseFormatException(
394                     "Variable identifier larger than #variables in metadata.");
395         }
396     }
397 
398     /**
399      * read a relational operator from stream and store it in s
400      * 
401      * @return the relational operator we read or null
402      * @throws IOException
403      */
404     private String readRelOp() throws IOException {
405         char c;
406 
407         skipSpaces();
408 
409         c = get();
410         if (eof())
411             return null;
412 
413         if (c == '=')
414             return "=";
415 
416         char following = get();
417         if (c == '>' && following == '=')
418             return ">=";
419         if (c == '<' && following == '=')
420             return "<=";
421 
422         return null;
423     }
424 
425     /**
426      * read the first comment line to get the number of variables and the number
427      * of constraints in the file calls metaData with the data that was read
428      * 
429      * @throws IOException
430      * @throws ParseException
431      */
432     protected void readMetaData() throws IOException, ParseFormatException {
433         char c;
434         String s;
435 
436         // get the number of variables and constraints
437         c = get();
438         if (c != '*')
439             throw new ParseFormatException(
440                     "First line of input file should be a comment");
441 
442         s = readWord();
443         if (eof() || !"#variable=".equals(s))
444             throw new ParseFormatException(
445                     "First line should contain #variable= as first keyword");
446 
447         nbVars = Integer.parseInt(readWord());
448 
449         s = readWord();
450         if (eof() || !"#constraint=".equals(s))
451             throw new ParseFormatException(
452                     "First line should contain #constraint= as second keyword");
453 
454         nbConstr = Integer.parseInt(readWord());
455 
456         // skip the rest of the line
457         in.readLine();
458 
459         // callback to transmit the data
460         metaData(nbVars, nbConstr);
461     }
462 
463     /**
464      * skip the comments at the beginning of the file
465      * 
466      * @throws IOException
467      */
468     private void skipComments() throws IOException {
469         char c = ' ';
470 
471         // skip further comments
472 
473         while (!eof() && (c = get()) == '*') {
474             in.readLine();
475         }
476 
477         putback(c);
478     }
479 
480     /**
481      * read a term into coeff and var
482      * 
483      * @param coeff
484      *                the coefficient of the variable
485      * @param var
486      *                the identifier we read
487      * @throws IOException
488      * @throws ParseException
489      */
490     protected void readTerm(StringBuffer coeff, StringBuffer var)
491             throws IOException, ParseFormatException, ContradictionException {
492         char c;
493 
494         readInteger(coeff);
495 
496         skipSpaces();
497         c = get();
498         if (c != '*')
499             throw new ParseFormatException(
500                     "'*' expected between a coefficient and a variable");
501 
502         if (!readIdentifier(var))
503             throw new ParseFormatException("identifier expected");
504     }
505 
506     protected void readVariablesExplanation() throws IOException,
507             ParseFormatException {
508     }
509 
510     /**
511      * read the objective line (if any) calls beginObjective, objectiveTerm and
512      * endObjective
513      * 
514      * @throws IOException
515      * @throws ParseException
516      */
517     private void readObjective() throws IOException, ParseFormatException {
518         char c;
519         StringBuffer var = new StringBuffer();
520         StringBuffer coeff = new StringBuffer();
521 
522         // read objective line (if any)
523 
524         skipSpaces();
525         c = get();
526         if (c != 'm') {
527             // no objective line
528             putback(c);
529             return;
530         }
531 
532         hasObjFunc = true;
533         if (get() == 'i' && get() == 'n' && get() == ':') {
534             beginObjective(); // callback
535 
536             while (!eof()) {
537                 try {
538                     readTerm(coeff, var);
539                 } catch (ContradictionException e) {
540                     // TODO Auto-generated catch block
541                     e.printStackTrace();
542                 }
543                 constraintTerm(new BigInteger(coeff.toString()), var.toString()); // callback
544 
545                 skipSpaces();
546                 c = get();
547                 if (c == ';')
548                     break; // end of objective
549 
550                 else if (c == '-' || c == '+' || Character.isDigit(c))
551                     putback(c);
552                 else
553                     throw new ParseFormatException(
554                             "unexpected character in objective function");
555             }
556 
557             endObjective();
558         } else
559             throw new ParseFormatException(
560                     "input format error: 'min:' expected");
561     }
562 
563     /**
564      * read a constraint calls beginConstraint, constraintTerm and endConstraint
565      * 
566      * @throws ParseException
567      * @throws IOException
568      * @throws ContradictionException
569      */
570     private void readConstraint() throws IOException, ParseFormatException,
571             ContradictionException {
572         StringBuffer var = new StringBuffer();
573         StringBuffer coeff = new StringBuffer();
574         char c;
575 
576         beginConstraint();
577 
578         while (!eof()) {
579             readTerm(coeff, var);
580             constraintTerm(new BigInteger(coeff.toString()), var.toString());
581 
582             skipSpaces();
583             c = get();
584             if (c == '>' || c == '=' || c == '<') {
585                 // relational operator found
586                 putback(c);
587                 break;
588             } else if (c == '-' || c == '+' || Character.isDigit(c))
589                 putback(c);
590             else {
591                 throw new ParseFormatException(
592                         "unexpected character in constraint");
593             }
594         }
595 
596         if (eof())
597             throw new ParseFormatException(
598                     "unexpected EOF before end of constraint");
599 
600         String relop;
601         if ((relop = readRelOp()) == null) {
602             throw new ParseFormatException(
603                     "unexpected relational operator in constraint");
604 
605         }
606         constraintRelOp(relop);
607         readInteger(coeff);
608         constraintRightTerm(new BigInteger(coeff.toString()));
609 
610         skipSpaces();
611         c = get();
612         if (eof() || c != ';')
613             throw new ParseFormatException(
614                     "semicolon expected at end of constraint");
615 
616         endConstraint();
617     }
618 
619     public OPBReader2005(IPBSolver solver) {
620         this.solver = solver;
621         lits = new VecInt();
622         coeffs = new Vec<BigInteger>();
623     }
624 
625     /**
626      * parses the file and uses the callbacks to send to send the data back to
627      * the program
628      * 
629      * @throws IOException
630      * @throws ParseException
631      * @throws ContradictionException
632      */
633     public void parse() throws IOException, ParseFormatException,
634             ContradictionException {
635         readMetaData();
636 
637         skipComments();
638 
639         readObjective();
640 
641         readVariablesExplanation();
642 
643         // read constraints
644         nbConstraintsRead = 0;
645         char c;
646         while (!eof()) {
647             skipSpaces();
648             if (eof())
649                 break;
650 
651             c = get();
652             putback(c);
653             if (c == '*')
654                 skipComments();
655 
656             if (eof())
657                 break;
658 
659             readConstraint();
660             // nbConstraintsRead++;
661         }
662         // Small check on the number of constraints
663         if (nbConstraintsRead != nbConstr) {
664             throw new ParseFormatException(
665                     "Number of constraints read is different from metadata.");
666         }
667     }
668 
669     @Override
670     public final IProblem parseInstance(final java.io.Reader input)
671             throws ParseFormatException, ContradictionException {
672         IProblem problem = parseInstance(new LineNumberReader(input));
673         solver.setObjectiveFunction(getObjectiveFunction());
674         return problem;
675     }
676 
677     private IProblem parseInstance(LineNumberReader input)
678             throws ParseFormatException, ContradictionException {
679         solver.reset();
680         this.in = input;
681         try {
682             parse();
683             return solver;
684         } catch (IOException e) {
685             throw new ParseFormatException(e);
686         }
687     }
688 
689     @Override
690     public String decode(int[] model) {
691         StringBuffer stb = new StringBuffer();
692 
693         for (int i = 0; i < model.length; i++) {
694             if (model[i] < 0) {
695                 stb.append("-x");
696                 stb.append(-model[i]);
697             } else {
698                 stb.append("x");
699                 stb.append(model[i]);
700             }
701             stb.append(" ");
702         }
703         return stb.toString();
704     }
705 
706     @Override
707     public void decode(int[] model, PrintWriter out) {
708         for (int i = 0; i < model.length; i++) {
709             if (model[i] < 0) {
710                 out.print("-x");
711                 out.print(-model[i]);
712             } else {
713                 out.print("x");
714                 out.print(model[i]);
715             }
716             out.print(" ");
717         }
718     }
719 
720     public ObjectiveFunction getObjectiveFunction() {
721         if (hasObjFunc) {
722             return new ObjectiveFunction(getVars(), getCoeffs());
723         }
724         return null;
725     }
726 
727     public IVecInt getListOfVariables() {
728         return null;
729     }
730 
731 }