Files
ortools-clone/examples/cpp/sat_cnf_reader.h

344 lines
11 KiB
C
Raw Normal View History

2021-04-02 10:08:51 +02:00
// Copyright 2010-2021 Google LLC
2013-12-12 14:43:04 +00:00
// 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.
2013-12-12 14:43:04 +00:00
#ifndef OR_TOOLS_SAT_SAT_CNF_READER_H_
#define OR_TOOLS_SAT_SAT_CNF_READER_H_
2021-04-23 14:55:51 +02:00
#include <cstdint>
#include <memory>
2013-12-12 14:43:04 +00:00
#include <string>
#include <utility>
2013-12-12 14:43:04 +00:00
#include <vector>
2022-02-25 09:47:52 +01:00
#include "absl/container/btree_map.h"
#include "absl/flags/flag.h"
#include "absl/strings/numbers.h"
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/base/macros.h"
#include "ortools/sat/boolean_problem.pb.h"
#include "ortools/sat/cp_model.pb.h"
2022-01-19 10:31:35 +01:00
#include "ortools/util/filelineiter.h"
2013-12-12 14:43:04 +00:00
2020-10-23 11:50:14 +02:00
ABSL_FLAG(bool, wcnf_use_strong_slack, true,
"If true, when we add a slack variable to reify a soft clause, we "
"enforce the fact that when it is true, the clause must be false.");
2014-06-11 20:48:53 +00:00
2013-12-12 14:43:04 +00:00
namespace operations_research {
namespace sat {
struct LinearBooleanProblemWrapper {
2020-10-29 14:25:39 +01:00
explicit LinearBooleanProblemWrapper(LinearBooleanProblem* p) : problem(p) {}
void SetNumVariables(int num) { problem->set_num_variables(num); }
void SetOriginalNumVariables(int num) {
problem->set_original_num_variables(num);
}
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
void AddConstraint(absl::Span<const int> clause) {
2020-10-29 14:25:39 +01:00
LinearBooleanConstraint* constraint = problem->add_constraints();
constraint->mutable_literals()->Reserve(clause.size());
constraint->mutable_coefficients()->Reserve(clause.size());
constraint->set_lower_bound(1);
for (const int literal : clause) {
constraint->add_literals(literal);
constraint->add_coefficients(1);
}
}
2021-04-02 14:58:16 +02:00
void AddObjectiveTerm(int literal, int64_t value) {
CHECK_GE(literal, 0) << "Negative literal not supported.";
problem->mutable_objective()->add_literals(literal);
problem->mutable_objective()->add_coefficients(value);
}
2021-04-02 14:58:16 +02:00
void SetObjectiveOffset(int64_t offset) {
problem->mutable_objective()->set_offset(offset);
}
2020-10-29 14:25:39 +01:00
LinearBooleanProblem* problem;
};
struct CpModelProtoWrapper {
2020-10-29 14:25:39 +01:00
explicit CpModelProtoWrapper(CpModelProto* p) : problem(p) {}
void SetNumVariables(int num) {
for (int i = 0; i < num; ++i) {
2020-10-29 14:25:39 +01:00
IntegerVariableProto* variable = problem->add_variables();
variable->add_domain(0);
variable->add_domain(1);
}
}
// TODO(user): Not supported. This is only used for displaying a wcnf
// solution in cnf format, so it is not useful internally. Instead of adding
// another field, we could use the variables names or the search heuristics
// to encode this info.
void SetOriginalNumVariables(int num) {}
int LiteralToRef(int signed_value) {
return signed_value > 0 ? signed_value - 1 : signed_value;
}
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
void AddConstraint(absl::Span<const int> clause) {
2020-10-29 14:25:39 +01:00
auto* constraint = problem->add_constraints()->mutable_bool_or();
constraint->mutable_literals()->Reserve(clause.size());
for (const int literal : clause) {
constraint->add_literals(LiteralToRef(literal));
}
}
2021-04-02 14:58:16 +02:00
void AddObjectiveTerm(int literal, int64_t value) {
CHECK_GE(literal, 0) << "Negative literal not supported.";
problem->mutable_objective()->add_vars(LiteralToRef(literal));
problem->mutable_objective()->add_coeffs(value);
}
2021-04-02 14:58:16 +02:00
void SetObjectiveOffset(int64_t offset) {
problem->mutable_objective()->set_offset(offset);
}
2020-10-29 14:25:39 +01:00
CpModelProto* problem;
};
2013-12-12 14:43:04 +00:00
// 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 {
2020-10-22 23:36:58 +02:00
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; }
2013-12-12 14:43:04 +00:00
// Loads the given cnf filename into the given proto.
2020-10-29 14:25:39 +01:00
bool Load(const std::string& filename, LinearBooleanProblem* problem) {
2013-12-12 14:43:04 +00:00
problem->Clear();
problem->set_name(ExtractProblemName(filename));
LinearBooleanProblemWrapper wrapper(problem);
return LoadInternal(filename, &wrapper);
}
2020-10-29 14:25:39 +01:00
bool Load(const std::string& filename, CpModelProto* problem) {
problem->Clear();
problem->set_name(ExtractProblemName(filename));
CpModelProtoWrapper wrapper(problem);
return LoadInternal(filename, &wrapper);
}
2020-10-22 23:36:58 +02:00
private:
template <class Problem>
2020-10-29 14:25:39 +01:00
bool LoadInternal(const std::string& filename, Problem* problem) {
positive_literal_to_weight_.clear();
objective_offset_ = 0;
2013-12-12 14:43:04 +00:00
is_wcnf_ = false;
2013-12-13 08:19:07 +00:00
end_marker_seen_ = false;
2014-05-21 12:56:57 +00:00
hard_weight_ = 0;
num_skipped_soft_clauses_ = 0;
num_singleton_soft_clauses_ = 0;
num_added_clauses_ = 0;
2014-05-21 12:56:57 +00:00
num_slack_variables_ = 0;
2013-12-12 14:43:04 +00:00
int num_lines = 0;
2020-10-29 14:25:39 +01:00
for (const std::string& line : FileLines(filename)) {
2013-12-12 14:43:04 +00:00
++num_lines;
2014-05-15 09:21:11 +00:00
ProcessNewLine(line, problem);
2013-12-12 14:43:04 +00:00
}
if (num_lines == 0) {
LOG(FATAL) << "File '" << filename << "' is empty or can't be read.";
}
problem->SetOriginalNumVariables(num_variables_);
problem->SetNumVariables(num_variables_ + num_slack_variables_);
2013-12-12 14:43:04 +00:00
// Fill the objective.
2014-06-11 20:48:53 +00:00
if (!positive_literal_to_weight_.empty()) {
2021-04-02 14:58:16 +02:00
for (const std::pair<int, int64_t> p : positive_literal_to_weight_) {
2014-06-11 20:48:53 +00:00
if (p.second != 0) {
problem->AddObjectiveTerm(p.first, p.second);
2014-06-11 20:48:53 +00:00
}
}
problem->SetObjectiveOffset(objective_offset_);
2014-06-11 20:48:53 +00:00
}
if (num_clauses_ != num_added_clauses_ + num_singleton_soft_clauses_ +
num_skipped_soft_clauses_) {
LOG(ERROR) << "Wrong number of clauses. " << num_clauses_ << " "
<< num_added_clauses_;
2013-12-12 14:43:04 +00:00
return false;
}
return true;
}
// Since the problem name is not stored in the cnf format, we infer it from
// the file name.
2020-10-29 14:25:39 +01:00
static std::string ExtractProblemName(const std::string& filename) {
const int found = filename.find_last_of('/');
2014-01-08 12:01:58 +00:00
const std::string problem_name =
found != std::string::npos ? filename.substr(found + 1) : filename;
2013-12-12 14:43:04 +00:00
return problem_name;
}
2021-04-02 14:58:16 +02:00
int64_t StringPieceAtoi(absl::string_view input) {
int64_t value;
2014-05-15 09:21:11 +00:00
// Hack: data() is not null terminated, but we do know that it points
2019-11-22 15:17:10 +01:00
// inside a string where numbers are separated by " " and since SimpleAtoi
// will stop at the first invalid char, this works.
CHECK(absl::SimpleAtoi(input, &value));
return value;
2014-05-15 09:21:11 +00:00
}
2020-10-29 14:25:39 +01:00
void ProcessHeader(const std::string& line) {
static const char kWordDelimiters[] = " ";
words_ = absl::StrSplit(line, kWordDelimiters, absl::SkipEmpty());
CHECK_EQ(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;
}
} else {
// TODO(user): The ToString() is only required for the open source. Fix.
LOG(FATAL) << "Unknown file type: " << words_[1];
}
}
template <class Problem>
2020-10-29 14:25:39 +01:00
void ProcessNewLine(const std::string& line, Problem* problem) {
2020-10-22 23:36:58 +02:00
if (line.empty() || end_marker_seen_) return;
if (line[0] == 'c') return;
if (line[0] == '%') {
2013-12-13 08:19:07 +00:00
end_marker_seen_ = true;
return;
}
if (line[0] == 'p') {
ProcessHeader(line);
return;
}
2013-12-12 14:43:04 +00:00
static const char kWordDelimiters[] = " ";
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
auto splitter = absl::StrSplit(line, kWordDelimiters, absl::SkipEmpty());
tmp_clause_.clear();
2021-04-23 14:55:51 +02:00
int64_t weight =
(!is_wcnf_ && interpret_cnf_as_max_sat_) ? 1 : hard_weight_;
bool first = true;
bool end_marker_seen = false;
for (const absl::string_view word : splitter) {
2021-04-02 14:58:16 +02:00
const int64_t signed_value = StringPieceAtoi(word);
if (first && is_wcnf_) {
// Mathematically, a soft clause of weight 0 can be removed.
if (signed_value == 0) {
++num_skipped_soft_clauses_;
return;
2013-12-12 14:43:04 +00:00
}
weight = signed_value;
2013-12-12 14:43:04 +00:00
} else {
if (signed_value == 0) {
end_marker_seen = true;
2020-10-22 23:36:58 +02:00
break; // end of clause.
}
tmp_clause_.push_back(signed_value);
2013-12-12 14:43:04 +00:00
}
first = false;
}
2020-10-22 23:36:58 +02:00
if (!end_marker_seen) return;
if (weight == hard_weight_) {
++num_added_clauses_;
problem->AddConstraint(tmp_clause_);
2013-12-12 14:43:04 +00:00
} else {
if (tmp_clause_.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 add the cost to
// the unique variable of such clause and remove the clause.
++num_singleton_soft_clauses_;
const int literal = -tmp_clause_[0];
if (literal > 0) {
positive_literal_to_weight_[literal] += weight;
2014-05-21 12:56:57 +00:00
} else {
positive_literal_to_weight_[-literal] -= weight;
objective_offset_ += weight;
2013-12-12 14:43:04 +00:00
}
} 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_;
tmp_clause_.push_back(slack_literal);
++num_added_clauses_;
problem->AddConstraint(tmp_clause_);
if (slack_literal > 0) {
positive_literal_to_weight_[slack_literal] += weight;
2014-05-21 12:56:57 +00:00
} else {
positive_literal_to_weight_[-slack_literal] -= weight;
objective_offset_ += weight;
}
2014-06-11 20:48:53 +00:00
if (absl::GetFlag(FLAGS_wcnf_use_strong_slack)) {
// Add the binary implications slack_literal true => all the other
// clause literals are false.
for (int i = 0; i + 1 < tmp_clause_.size(); ++i) {
2020-10-22 23:36:58 +02:00
problem->AddConstraint({-slack_literal, -tmp_clause_[i]});
2014-06-11 20:48:53 +00:00
}
2014-05-21 12:56:57 +00:00
}
2014-05-15 09:21:11 +00:00
}
2013-12-12 14:43:04 +00:00
}
}
bool interpret_cnf_as_max_sat_;
2013-12-12 14:43:04 +00:00
int num_clauses_;
int num_variables_;
2014-05-15 09:21:11 +00:00
// Temporary storage for ProcessNewLine().
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
std::vector<absl::string_view> words_;
2014-05-15 09:21:11 +00:00
2014-06-11 20:48:53 +00:00
// We stores the objective in a map because we want the variables to appear
// only once in the LinearObjective proto.
2022-02-25 09:47:52 +01:00
absl::btree_map<int, int64_t> positive_literal_to_weight_;
2021-04-02 14:58:16 +02:00
int64_t objective_offset_;
2014-06-11 20:48:53 +00:00
2013-12-12 14:43:04 +00:00
// Used for the wcnf format.
bool is_wcnf_;
// Some files have text after %. This indicates if we have seen the '%'.
bool end_marker_seen_;
2021-04-02 14:58:16 +02:00
int64_t hard_weight_;
2013-12-12 14:43:04 +00:00
2014-05-21 12:56:57 +00:00
int num_slack_variables_;
int num_skipped_soft_clauses_;
int num_singleton_soft_clauses_;
int num_added_clauses_;
std::vector<int> tmp_clause_;
2014-05-21 12:56:57 +00:00
2013-12-12 14:43:04 +00:00
DISALLOW_COPY_AND_ASSIGN(SatCnfReader);
};
2020-10-22 23:36:58 +02:00
} // namespace sat
} // namespace operations_research
2013-12-12 14:43:04 +00:00
2020-10-22 23:36:58 +02:00
#endif // OR_TOOLS_SAT_SAT_CNF_READER_H_