/*
* Copyright © 2010 by Ondrej Skalicka. All Rights Reserved
*/
package cz.cvut.felk.cig.jcop.problem.sat;
import cz.cvut.felk.cig.jcop.problem.*;
import cz.cvut.felk.cig.jcop.util.JcopRandom;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Boolean satisfiability problem.
* <p/>
* Satisfiability is the problem of determining if the variables of a given Boolean formula can be assigned in such a
* way as to make the formula evaluate to TRUE.
*
* @author Ondrej Skalicka
* @see <a href="http://service.felk.cvut.cz/courses/X36PAA/maxwsat.html">WSAT on felk.cvut.cz</a>
*/
public class SAT extends BaseProblem implements StartingConfigurationProblem, RandomConfigurationProblem, GlobalSearchProblem {
/**
* List of all possible {@link SetTrueOperation SetTrueOperations}. Index is {@link Variable#index}.
*/
protected List<SetTrueOperation> setTrueOperations;
/**
* List of all possible {@link SetFalseOperation SetFalseOperations}. Index is {@link Variable#index}.
*/
protected List<SetFalseOperation> setFalseOperations;
/**
* List of all items for this knapsack.
*/
protected Formula formula;
/**
* List of all variables in problem.
*/
protected List<Variable> variables;
/**
* Starting configuration in SAT - all variables set to false.
*/
protected Configuration startingConfiguration;
/**
* Creates SAT from file.
* <p/>
* Ignores lines beginning with 'c'. Requires configuration line (beginning with 'p') in format 'p cnf X Y' where
* X is number of variables, Y is number of clauses in formula. Optional weight line (beginning with 'w') in format
* 'w X1 X2 X3 X4..Xn", where n is number of variables. Assigns weight to each variable
* <p/>
* Then expects list of clauses. Each clause contains several numbers, indicating index of variable (starting with
* 1!). Negative number means negated variable. Each clause ends with zero (new line is not required. Clauses must
* be after configuration and weight line.
*
* @param configFile input file to load data from
* @throws java.io.IOException if problem loading file occurs
* @throws ProblemFormatException if line format is invalid
*/
public SAT(File configFile) throws IOException, ProblemFormatException {
BufferedReader br = new BufferedReader(new FileReader(configFile));
String line;
Pattern commentPattern = Pattern.compile("^c");
Pattern configPattern = Pattern.compile("^p cnf\\s+(\\d+)\\s+(\\d+)");
Pattern clausePattern = Pattern.compile("^\\s*-?\\d+(\\s+-?\\d+)*\\s*$");
Pattern clauseAtomPattern = Pattern.compile("(-)?(\\d+)");
Pattern ignoredPattern = Pattern.compile("^\\s*(0|%)?\\s*$");
Pattern weightAtomPattern = Pattern.compile("\\d+");
Pattern weightPattern = Pattern.compile("^w(\\s+(\\d+))+\\s*$");
boolean configurationFound = false;
int clausesCount = -1;
int lineCounter = 0;
Clause activeClause = new Clause();
this.formula = new Formula();
while ((line = br.readLine()) != null) {
Matcher m;
lineCounter++;
// skip comments
m = commentPattern.matcher(line);
if (m.find()) continue;
// config line
m = configPattern.matcher(line);
if (m.find()) {
if (configurationFound)
throw new ProblemFormatException("Found two sets of configurations, 2nd on line " + lineCounter);
configurationFound = true;
this.dimension = Integer.parseInt(m.group(1));
this.variables = new ArrayList<Variable>(this.dimension);
for (int i = 0; i < this.dimension; ++i)
this.variables.add(new Variable(i, i + 1));
clausesCount = Integer.parseInt(m.group(2));
continue;
}
// weight line
m = weightPattern.matcher(line);
if (m.find()) {
if (!configurationFound)
throw new ProblemFormatException("Configuration not found but weight found on line " + lineCounter);
m = weightAtomPattern.matcher(line);
for (int i = 0; i < this.dimension; ++i) {
if (!m.find())
throw new ProblemFormatException("" + (this.dimension) + " weights required, " + i + " found");
this.variables.get(i).setWeight(Integer.parseInt(m.group()));
}
if (m.find()) {
throw new ProblemFormatException(String.format(
"%d weights required, at least %d found", this.dimension, this.dimension + 1));
}
continue;
}
// clause line
m = clausePattern.matcher(line);
if (m.find()) {
if (!configurationFound)
throw new ProblemFormatException("Configuration not found but clause found on line " + lineCounter);
m = clauseAtomPattern.matcher(line);
while (m.find()) {
int value = Integer.parseInt(m.group(2));
boolean negated = m.group(1) != null;
// zero found, add new clause
if (value == 0) {
// skip empty clauses
if (activeClause.size() > 0) {
this.formula.addClause(activeClause);
activeClause = new Clause();
}
continue;
}
Variable tmp;
try {
tmp = this.variables.get(value - 1);
} catch (IndexOutOfBoundsException e) {
throw new ProblemFormatException("Unknown variable " + value + " on line " + lineCounter);
}
if (negated) activeClause.addNegativeVariable(tmp);
else activeClause.addPositiveVariable(tmp);
}
continue;
}
m = ignoredPattern.matcher(line);
if (m.find()) continue;
throw new ProblemFormatException("Invalid line format (" + line + ") on line " + lineCounter);
}
if (activeClause.size() > 0) this.formula.addClause(activeClause);
if (this.formula.clauses.size() != clausesCount) {
throw new ProblemFormatException(String.format(
"Invalid number of clauses found, expected %d, found %d", clausesCount, this.formula.clauses.size()));
}
this.setLabel(configFile.getName());
this.initCommons();
}
/**
* Initializes common attributes such as operations and default fitness.
* <p/>
* Requires {@link #formula}/{@link #variables} to be already fully loaded.
*/
protected void initCommons() {
this.setTrueOperations = new ArrayList<SetTrueOperation>(this.dimension);
this.setFalseOperations = new ArrayList<SetFalseOperation>(this.dimension);
SetTrueOperation setTrueOperation;
SetFalseOperation setFalseOperation;
for (int i = 0; i < this.dimension; ++i) {
setTrueOperation = new SetTrueOperation(this.variables.get(i));
setFalseOperation = new SetFalseOperation(this.variables.get(i));
setTrueOperation.setReverse(setFalseOperation);
setFalseOperation.setReverse(setTrueOperation);
this.setTrueOperations.add(setTrueOperation);
this.setFalseOperations.add(setFalseOperation);
}
// create starting configuration
List<Integer> tmp = new ArrayList<Integer>(this.dimension);
for (int i = 0; i < this.dimension; ++i) tmp.add(0);
this.startingConfiguration = new Configuration(tmp, "Empty SAT created");
}
/**
* Returns formula of current SAT problem
*
* @return formula of current SAT problem
*/
public Formula getFormula() {
return formula;
}
/* Problem interface */
public boolean isSolution(Configuration configuration) {
return this.formula.isTrue(configuration);
}
public SATIterator getOperationIterator(Configuration configuration) {
return new SATIterator(configuration, this);
}
public Fitness getDefaultFitness() {
return new SATFitness(this);
}
/* StartingConfigurationProblem interface */
public Configuration getStartingConfiguration() {
return this.startingConfiguration;
}
/* RandomConfigurationProblem interface */
public Configuration getRandomConfiguration() {
List<Integer> tmp = new ArrayList<Integer>(this.dimension);
for (int i = 0; i < this.dimension; ++i)
tmp.add(JcopRandom.nextBoolean() ? 1 : 0);
return new Configuration(tmp, "Empty SAT created (random)");
}
/* GlobalSearchProblem interface */
public Integer getMaximum(int index) {
return 1;
}
}