View Javadoc

1   /*
2    * SAT4J: a SATisfiability library for Java Copyright (C) 2004-2006 Daniel Le Berre
3    * 
4    * Based on the original minisat specification from:
5    * 
6    * An extensible SAT solver. Niklas E?n and Niklas S?rensson. Proceedings of the
7    * Sixth International Conference on Theory and Applications of Satisfiability
8    * Testing, LNCS 2919, pp 502-518, 2003.
9    * 
10   * This library is free software; you can redistribute it and/or modify it under
11   * the terms of the GNU Lesser General Public License as published by the Free
12   * Software Foundation; either version 2.1 of the License, or (at your option)
13   * any later version.
14   * 
15   * This library is distributed in the hope that it will be useful, but WITHOUT
16   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18   * details.
19   * 
20   * You should have received a copy of the GNU Lesser General Public License
21   * along with this library; if not, write to the Free Software Foundation, Inc.,
22   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23   * 
24   */
25  package org.sat4j.reader;
26  
27  import java.io.BufferedReader;
28  import java.io.IOException;
29  import java.io.LineNumberReader;
30  import java.io.PrintWriter;
31  import java.io.Serializable;
32  import java.math.BigInteger;
33  
34  import org.sat4j.core.Vec;
35  import org.sat4j.core.VecInt;
36  import org.sat4j.opt.ObjectiveFunction;
37  import org.sat4j.specs.ContradictionException;
38  import org.sat4j.specs.IProblem;
39  import org.sat4j.specs.ISolver;
40  import org.sat4j.specs.IVec;
41  import org.sat4j.specs.IVecInt;
42  
43  /**
44   * "Official" reader for the Pseudo Boolean evaluation 2005.
45   * 
46   * @author leberre
47   * @author or
48   * @author mederic baron
49   */
50  public class OPBReader2005 extends Reader implements Serializable {
51  
52      /**
53       * Comment for <code>serialVersionUID</code>
54       */
55      private static final long serialVersionUID = 1L;
56  
57      protected final ISolver solver;
58  
59      private final IVecInt lits;
60  
61      private final IVec<BigInteger> coeffs;
62  
63      private BigInteger d;
64  
65      private String operator;
66  
67      private final IVecInt objectiveVars = new VecInt();
68  
69      private final IVec<BigInteger> objectiveCoeffs = new Vec<BigInteger>();
70  
71      // does the instance have an objective function?
72      private boolean hasObjFunc = false;
73  
74      protected int nbVars, nbConstr; // MetaData: #Variables and #Constraints in
75                                      // file.
76      protected int nbConstraintsRead;
77      /**
78       * callback called when we get the number of variables and the expected
79       * number of constraints
80       * 
81       * @param nbvar
82       *            the number of variables
83       * @param nbconstr
84       *            the number of contraints
85       */
86      protected void metaData(int nbvar, int nbconstr) {
87          solver.newVar(nbvar);
88      }
89  
90      /**
91       * callback called before we read the objective function
92       */
93      protected void beginObjective() {
94      }
95  
96      /**
97       * callback called after we've read the objective function
98       */
99      protected void endObjective() {
100         assert lits.size() == coeffs.size();
101         assert lits.size() == coeffs.size();
102         for (int i = 0; i < lits.size(); i++) {
103             objectiveVars.push(lits.get(i));
104             objectiveCoeffs.push(coeffs.get(i));
105         }
106     }
107 
108     /**
109      * callback called before we read a constraint
110      */
111     protected void beginConstraint() {
112         lits.clear();
113         coeffs.clear();
114         assert lits.size() == 0;
115         assert coeffs.size() == 0;
116     }
117 
118     /**
119      * callback called after we've read a constraint
120      */
121     /**
122      * @throws ContradictionException
123      */
124     protected void endConstraint() throws ContradictionException {
125 
126         assert !(lits.size() == 0);
127         assert !(coeffs.size() == 0);
128         assert lits.size() == coeffs.size();
129 
130         if ("<=".equals(operator) || "=".equals(operator)){
131             solver.addPseudoBoolean(lits, coeffs, false, d);
132         }
133         if (">=".equals(operator) || "=".equals(operator)){
134             solver.addPseudoBoolean(lits, coeffs, true, d);
135         }
136         nbConstraintsRead++;
137     }
138 
139     /**
140      * callback called when we read a term of a constraint
141      * 
142      * @param coeff
143      *            the coefficient of the term
144      * @param var
145      *            the identifier of the variable
146      */
147     private void constraintTerm(BigInteger coeff, String var) {
148         coeffs.push(coeff);
149         lits.push(translateVarToId(var));
150     }
151 
152     protected int translateVarToId(String var) {   	
153         int id = Integer.parseInt(var.substring(1));
154         return ((savedChar == '-') ? -1 : 1) * id;
155    }
156     
157     /**
158      * callback called when we read the relational operator of a constraint
159      * 
160      * @param relop
161      *            the relational oerator (>= or =)
162      */
163     protected void constraintRelOp(String relop) {
164         operator = relop;
165     }
166 
167     /**
168      * callback called when we read the right term of a constraint (also known
169      * as the degree)
170      * 
171      * @param val
172      *            the degree of the constraint
173      */
174     protected void constraintRightTerm(BigInteger val) {
175         d = val;
176     }
177 
178     transient BufferedReader in; // the stream we're reading from
179 
180     char savedChar; // a character read from the file but not yet consumed
181 
182     boolean charAvailable = false; // true iff savedChar contains a character
183 
184     boolean eofReached = false; // true iff we've reached EOF
185 
186     /**
187      * get the next character from the stream
188      * 
189      * @throws IOException
190      */
191     protected char get() throws IOException {
192         int c;
193 
194         if (charAvailable) {
195             charAvailable = false;
196             return savedChar;
197         }
198 
199         c = in.read();
200         if (c == -1)
201             eofReached = true;
202 
203         return (char) c;
204     }
205 
206     public IVecInt getVars() {
207         return objectiveVars;
208     }
209 
210     public IVec<BigInteger> getCoeffs() {
211         return objectiveCoeffs;
212     }
213 
214     /**
215      * put back a character into the stream (only one chr can be put back)
216      */
217     private void putback(char c) {
218         savedChar = c;
219         charAvailable = true;
220     }
221 
222     /**
223      * return true iff we've reached EOF
224      */
225     protected boolean eof() {
226         return eofReached;
227     }
228 
229     /**
230      * skip white spaces
231      * 
232      * @throws IOException
233      */
234     protected void skipSpaces() throws IOException {
235         char c;
236 
237         while (Character.isWhitespace(c = get()))
238             ;
239 
240         putback(c);
241     }
242 
243     /**
244      * read a word from file
245      * 
246      * @return the word we read
247      * @throws IOException
248      */
249     public String readWord() throws IOException {
250         StringBuffer s = new StringBuffer();
251         char c;
252 
253         skipSpaces();
254 
255         while (!Character.isWhitespace(c = get()) && !eof())
256             s.append(c);
257 
258         return s.toString();
259     }
260 
261     /**
262      * read a integer from file
263      * 
264      * @param s
265      *            a StringBuffer to store the integer that was read
266      * @throws IOException
267      */
268     public void readInteger(StringBuffer s) throws IOException {
269         char c;
270 
271         skipSpaces();
272         s.setLength(0);
273 
274         c = get();
275         if (c == '-' || Character.isDigit(c))
276             s.append(c);
277         // note: BigInteger don't like a '+' before the number, we just skip it
278 
279         while (Character.isDigit(c = get()) && !eof())
280             s.append(c);
281 
282         putback(c);
283     }
284 
285     /**
286      * read an identifier from stream and store it in s
287      * 
288      * @return the identifier we read or null
289      * @throws IOException
290      * @throws ParseFormatException
291      */
292     protected boolean readIdentifier(StringBuffer s) throws IOException,
293             ParseFormatException {
294         char c;
295 
296         s.setLength(0);
297 
298         skipSpaces();
299 
300         // first char (must be a letter or underscore)
301         c = get();
302         if (eof())
303             return false;
304 
305         if (!isGoodFirstCharacter(c)) {
306             putback(c);
307             return false;
308         }
309 
310         s.append(c);
311 
312         // next chars (must be a letter, a digit or an underscore)
313         while (true) {
314             c = get();
315             if (eof())
316                 break;
317 
318             if (isGoodFollowingCharacter(c))
319                 s.append(c);
320             else {
321                 putback(c);
322                 break;
323             }
324         }
325         checkId(s);
326         return true;
327     }
328 
329     protected boolean isGoodFirstCharacter(char c){
330     	return Character.isLetter(c) || c == '_';
331     }
332     
333     protected boolean isGoodFollowingCharacter(char c){
334     	return Character.isLetter(c) || Character.isDigit(c) || c == '_';
335     }
336     
337     protected void checkId(StringBuffer s) throws ParseFormatException{
338         // Small check on the coefficient ID to make sure everything is ok
339         int varID = Integer.parseInt(s.substring(1));
340         if (varID > nbVars) {
341             throw new ParseFormatException(
342                     "Variable identifier larger than #variables in metadata.");
343         }    	
344     }
345     /**
346      * read a relational operator from stream and store it in s
347      * 
348      * @return the relational operator we read or null
349      * @throws IOException
350      */
351     private String readRelOp() throws IOException {
352         char c;
353 
354         skipSpaces();
355 
356         c = get();
357         if (eof())
358             return null;
359 
360         if (c == '=')
361             return "=";
362 
363         if (c == '>' && get() == '=')
364             return ">=";
365 
366         return null;
367     }
368 
369     /**
370      * read the first comment line to get the number of variables and the number
371      * of constraints in the file calls metaData with the data that was read
372      * 
373      * @throws IOException
374      * @throws ParseException
375      */
376     protected void readMetaData() throws IOException, ParseFormatException {
377         char c;
378         String s;
379 
380         // get the number of variables and constraints
381         c = get();
382         if (c != '*')
383             throw new ParseFormatException(
384                     "First line of input file should be a comment");
385 
386         s = readWord();
387         if (eof() || !"#variable=".equals(s))
388             throw new ParseFormatException(
389                     "First line should contain #variable= as first keyword");
390 
391         nbVars = Integer.parseInt(readWord());
392 
393         s = readWord();
394         if (eof() || !"#constraint=".equals(s))
395             throw new ParseFormatException(
396                     "First line should contain #constraint= as second keyword");
397 
398         nbConstr = Integer.parseInt(readWord());
399 
400         // skip the rest of the line
401         in.readLine();
402 
403         // callback to transmit the data
404         metaData(nbVars, nbConstr);
405     }
406 
407     /**
408      * skip the comments at the beginning of the file
409      * 
410      * @throws IOException
411      */
412     private void skipComments() throws IOException {
413         char c = ' ';
414 
415         // skip further comments
416 
417         while (!eof() && (c = get()) == '*') {
418             in.readLine();
419         }
420 
421         putback(c);
422     }
423 
424     /**
425      * read a term into coeff and var
426      * 
427      * @param coeff
428      *            the coefficient of the variable
429      * @param var
430      *            the identifier we read
431      * @throws IOException
432      * @throws ParseException
433      */
434     protected void readTerm(StringBuffer coeff, StringBuffer var)
435             throws IOException, ParseFormatException, ContradictionException {
436         char c;
437 
438         readInteger(coeff);
439 
440         skipSpaces();
441         c = get();
442         if (c != '*')
443             throw new ParseFormatException(
444                     "'*' expected between a coefficient and a variable");
445 
446         if (!readIdentifier(var))
447             throw new ParseFormatException("identifier expected");
448     }
449 
450     /**
451      * read the objective line (if any) calls beginObjective, objectiveTerm and
452      * endObjective
453      * 
454      * @throws IOException
455      * @throws ParseException
456      */
457     private void readObjective() throws IOException, ParseFormatException {
458         char c;
459         StringBuffer var = new StringBuffer();
460         StringBuffer coeff = new StringBuffer();
461 
462         // read objective line (if any)
463 
464         skipSpaces();
465         c = get();
466         if (c != 'm') {
467             // no objective line
468             putback(c);
469             return;
470         }
471 
472         hasObjFunc = true;
473         if (get() == 'i' && get() == 'n' && get() == ':') {
474             beginObjective(); // callback
475 
476             while (!eof()) {
477                 try {
478 					readTerm(coeff, var);
479 				} catch (ContradictionException e) {
480 					// TODO Auto-generated catch block
481 					e.printStackTrace();
482 				}
483                 constraintTerm(new BigInteger(coeff.toString()), var.toString()); // callback
484 
485                 skipSpaces();
486                 c = get();
487                 if (c == ';')
488                     break; // end of objective
489 
490                 else if (c == '-' || c == '+' || Character.isDigit(c))
491                     putback(c);
492                 else
493                     throw new ParseFormatException(
494                             "unexpected character in objective function");
495             }
496 
497             endObjective();
498         } else
499             throw new ParseFormatException(
500                     "input format error: 'min:' expected");
501     }
502 
503     /**
504      * read a constraint calls beginConstraint, constraintTerm and endConstraint
505      * 
506      * @throws ParseException
507      * @throws IOException
508      * @throws ContradictionException
509      */
510     private void readConstraint() throws IOException, ParseFormatException,
511             ContradictionException {
512         StringBuffer var = new StringBuffer();
513         StringBuffer coeff = new StringBuffer();
514         char c;
515 
516         beginConstraint();
517 
518         while (!eof()) {
519             readTerm(coeff, var);
520             constraintTerm(new BigInteger(coeff.toString()), var.toString());
521 
522             skipSpaces();
523             c = get();
524             if (c == '>' || c == '=') {
525                 // relational operator found
526                 putback(c);
527                 break;
528             } else if (c == '-' || c == '+' || Character.isDigit(c))
529                 putback(c);
530             else {
531                 throw new ParseFormatException(
532                         "unexpected character in constraint");
533             }
534         }
535 
536         if (eof())
537             throw new ParseFormatException(
538                     "unexpected EOF before end of constraint");
539 
540         String relop;
541         if ((relop = readRelOp()) == null) {
542             throw new ParseFormatException(
543                     "unexpected relational operator in constraint");
544 
545         }
546         constraintRelOp(relop);
547         readInteger(coeff);
548         constraintRightTerm(new BigInteger(coeff.toString()));
549 
550         skipSpaces();
551         c = get();
552         if (eof() || c != ';')
553             throw new ParseFormatException(
554                     "semicolon expected at end of constraint");
555 
556         endConstraint();
557     }
558 
559     public OPBReader2005(ISolver solver) {
560         this.solver = solver;
561         lits = new VecInt();
562         coeffs = new Vec<BigInteger>();
563     }
564 
565     /**
566      * parses the file and uses the callbacks to send to send the data back to
567      * the program
568      * 
569      * @throws IOException
570      * @throws ParseException
571      * @throws ContradictionException
572      */
573     public void parse() throws IOException, ParseFormatException,
574             ContradictionException {
575         readMetaData();
576 
577         skipComments();
578 
579         readObjective();
580 
581         // read constraints
582         nbConstraintsRead = 0;
583         char c;
584         while (!eof()) {
585             skipSpaces();
586             if (eof())
587                 break;
588 
589             c = get();
590             putback(c);
591             if (c == '*')
592                 skipComments();
593 
594             if (eof())
595                 break;
596 
597             readConstraint();
598             //nbConstraintsRead++;
599         }
600         // Small check on the number of constraints
601         if (nbConstraintsRead != nbConstr) {
602             throw new ParseFormatException(
603                     "Number of constraints read is different from metadata.");
604         }
605     }
606 
607     @Override
608     public final IProblem parseInstance(final java.io.Reader in)
609             throws ParseFormatException, ContradictionException {
610         return parseInstance(new LineNumberReader(in));
611     }
612 
613     private IProblem parseInstance(LineNumberReader in)
614             throws ParseFormatException, ContradictionException {
615         solver.reset();
616         this.in = in;
617         try {
618             parse();
619             return solver;
620         } catch (IOException e) {
621             throw new ParseFormatException(e);
622         }
623     }
624 
625     @Override
626     public String decode(int[] model) {
627         StringBuffer stb = new StringBuffer();
628 
629         for (int i = 0; i < model.length; i++) {
630             if (model[i] < 0) {
631                 stb.append("-x");
632                 stb.append(-model[i]);
633             } else {
634                 stb.append("x");
635                 stb.append(model[i]);
636             }
637             stb.append(" ");
638         }
639         return stb.toString();
640     }
641 
642     @Override
643     public void decode(int[] model, PrintWriter out) {
644         for (int i = 0; i < model.length; i++) {
645             if (model[i] < 0) {
646                 out.print("-x");
647                 out.print(-model[i]);
648             } else {
649                 out.print("x");
650                 out.print(model[i]);
651             }
652             out.print(" ");
653         }
654     }
655 
656     public ObjectiveFunction getObjectiveFunction() {
657         if (hasObjFunc){
658             return new ObjectiveFunction(getVars(), getCoeffs());
659         }
660         return null;
661     }
662 
663 }