// Copyright 2010-2013 Google // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef OR_TOOLS_SAT_SAT_CNF_READER_H_ #define OR_TOOLS_SAT_SAT_CNF_READER_H_ #include #include #include #include "base/integral_types.h" #include "base/logging.h" #include "base/strtoint.h" #include "base/split.h" #include "sat/boolean_problem.pb.h" #include "util/filelineiter.h" namespace operations_research { namespace sat { // This class loads a file in cnf file format into a SatProblem. // The format is described here: // http://people.sc.fsu.edu/~jburkardt/data/cnf/cnf.html // // It also support the wcnf input format for partial weighted max-sat problems. class SatCnfReader { public: SatCnfReader() : interpret_cnf_as_max_sat_(false) {} // If called with true, then a cnf file will be converted to the max-sat // problem: Try to minimize the number of unsatisfiable clauses. void InterpretCnfAsMaxSat(bool v) { interpret_cnf_as_max_sat_ = v; } // Loads the given cnf filename into the given problem. bool Load(const std::string& filename, LinearBooleanProblem* problem) { problem->Clear(); problem->set_name(ExtractProblemName(filename)); is_wcnf_ = false; end_marker_seen_ = false; hard_weight_ = 0; num_skipped_soft_clauses_ = 0; num_singleton_soft_clauses_ = 0; num_slack_variables_ = 0; int num_lines = 0; for (const std::string& line : FileLines(filename)) { ++num_lines; ProcessNewLine(line, problem); } if (num_lines == 0) { LOG(FATAL) << "File '" << filename << "' is empty or can't be read."; } problem->set_original_num_variables(num_variables_); problem->set_num_variables(num_variables_ + num_slack_variables_); if (num_clauses_ != problem->constraints_size() + num_singleton_soft_clauses_ + num_skipped_soft_clauses_) { LOG(ERROR) << "Wrong number of clauses."; return false; } return true; } private: // Since the problem name is not stored in the cnf format, we infer it from // the file name. static std::string ExtractProblemName(const std::string& filename) { const int found = filename.find_last_of("/"); const std::string problem_name = found != std::string::npos ? filename.substr(found + 1) : filename; return problem_name; } int64 StringPieceAtoi(StringPiece input) { // Hack: data() is not null terminated, but we do know that it points // inside a std::string where numbers are separated by " " and since atoi64 will // stop at the first invalid char, this works. return atoi64(input.data()); } void ProcessNewLine(const std::string& line, LinearBooleanProblem* problem) { static const char kWordDelimiters[] = " "; words_ = strings::Split( line, kWordDelimiters, static_cast(strings::SkipEmpty())); if (words_.size() == 0 || words_[0] == "c" || end_marker_seen_) return; if (words_[0] == "%") { end_marker_seen_ = true; return; } if (words_[0] == "p") { if (words_[1] == "cnf" || words_[1] == "wcnf") { num_variables_ = StringPieceAtoi(words_[2]); num_clauses_ = StringPieceAtoi(words_[3]); if (words_[1] == "wcnf") { is_wcnf_ = true; hard_weight_ = (words_.size() > 4) ? StringPieceAtoi(words_[4]) : 0; problem->set_type(LinearBooleanProblem::MINIMIZATION); } else { problem->set_type(interpret_cnf_as_max_sat_ ? LinearBooleanProblem::MINIMIZATION : LinearBooleanProblem::SATISFIABILITY); } } else { LOG(FATAL) << "Unknow file type: " << words_[1]; } } else { // In the cnf file format, the last words should always be 0. DCHECK_EQ("0", words_.back()); const int size = words_.size() - 1; const int reserved_size = (!is_wcnf_ && interpret_cnf_as_max_sat_) ? size + 1 : size; LinearBooleanConstraint* constraint = problem->add_constraints(); constraint->mutable_literals()->Reserve(reserved_size); constraint->mutable_coefficients()->Reserve(reserved_size); constraint->set_lower_bound(1); int64 weight = (!is_wcnf_ && interpret_cnf_as_max_sat_) ? 1 : hard_weight_; for (int i = 0; i < size; ++i) { const int64 signed_value = StringPieceAtoi(words_[i]); if (i == 0 && is_wcnf_) { // Mathematically, a soft clause of weight 0 can be removed. if (signed_value == 0) { ++num_skipped_soft_clauses_; problem->mutable_constraints()->RemoveLast(); break; } weight = signed_value; } else { DCHECK_NE(signed_value, 0); constraint->add_literals(signed_value); constraint->add_coefficients(1); } } if (weight != hard_weight_) { if (constraint->literals_size() == 1) { // The max-sat formulation of an optimization sat problem with a // linear objective introduces many singleton soft clauses. Because we // natively work with a linear objective, we can just put the cost on // the unique variable of such clause and remove the clause. // // TODO(user): If many singleton clauses with the same variable // appear, we will create duplicate entry in the objective which is // not supported (and is checked by BooleanProblemIsValid()). If we do // encounter this case in practice, fix this. ++num_singleton_soft_clauses_; LinearObjective* objective = problem->mutable_objective(); objective->add_literals(-constraint->literals(0)); objective->add_coefficients(weight); problem->mutable_constraints()->RemoveLast(); } else { // The +1 is because a positive literal is the same as the 1-based // variable index. const int slack_literal = num_variables_ + num_slack_variables_ + 1; ++num_slack_variables_; constraint->add_literals(slack_literal); constraint->add_coefficients(1); LinearObjective* objective = problem->mutable_objective(); objective->add_literals(slack_literal); objective->add_coefficients(weight); DCHECK_EQ(constraint->literals_size(), reserved_size); } } else { // If wcnf is true, we currently reserve one more literals than needed // for the hard clauses. DCHECK_EQ(constraint->literals_size(), is_wcnf_ ? size - 1 : size); } } } bool interpret_cnf_as_max_sat_; int num_clauses_; int num_variables_; // Temporary storage for ProcessNewLine(). std::vector words_; // Used for the wcnf format. bool is_wcnf_; // Some files have text after %. This indicates if we have seen the '%'. bool end_marker_seen_; int64 hard_weight_; int num_slack_variables_; int num_skipped_soft_clauses_; int num_singleton_soft_clauses_; DISALLOW_COPY_AND_ASSIGN(SatCnfReader); }; } // namespace sat } // namespace operations_research #endif // OR_TOOLS_SAT_SAT_CNF_READER_H_