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     private 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 (Exception e) {
716             String message;
717 
718             if (e instanceof ParseFormatException) {
719                 message = e.getMessage().substring(
720                         ParseFormatException.PARSING_ERROR.length());
721             } else {
722                 message = e.toString();
723             }
724             throw new ParseFormatException(" line "
725                     + (input.getLineNumber() + 1) + ", " + message);
726 
727         }
728     }
729 
730     @Override
731     public String decode(int[] model) {
732         StringBuffer stb = new StringBuffer();
733 
734         for (int i = 0; i < model.length; i++) {
735             if (model[i] < 0) {
736                 stb.append("-x");
737                 stb.append(-model[i]);
738             } else {
739                 stb.append("x");
740                 stb.append(model[i]);
741             }
742             stb.append(" ");
743         }
744         return stb.toString();
745     }
746 
747     @Override
748     public void decode(int[] model, PrintWriter out) {
749         for (int i = 0; i < model.length; i++) {
750             if (model[i] < 0) {
751                 out.print("-x");
752                 out.print(-model[i]);
753             } else {
754                 out.print("x");
755                 out.print(model[i]);
756             }
757             out.print(" ");
758         }
759     }
760 
761     public ObjectiveFunction getObjectiveFunction() {
762         if (this.hasObjFunc) {
763             return new ObjectiveFunction(getVars(), getCoeffs());
764         }
765         return null;
766     }
767 
768     public IVecInt getListOfVariables() {
769         return null;
770     }
771 
772     @Override
773     public IProblem parseInstance(final InputStream in)
774             throws ParseFormatException, ContradictionException, IOException {
775         return parseInstance(new InputStreamReader(in));
776     }
777 }