diff --git a/examples/cpp/model_util.cc b/examples/cpp/model_util.cc deleted file mode 100644 index 26c6be6d70..0000000000 --- a/examples/cpp/model_util.cc +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2010-2018 Google LLC -// 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. - -#include - -#include "ortools/base/commandlineflags.h" -#include "ortools/base/file.h" -#include "ortools/base/integral_types.h" -#include "ortools/base/join.h" -#include "ortools/base/logging.h" -#include "ortools/base/macros.h" -#include "ortools/base/recordio.h" -#include "ortools/base/status.h" -#include "ortools/base/stringprintf.h" -#include "ortools/constraint_solver/constraint_solver.h" -#include "ortools/constraint_solver/model.pb.h" -#include "ortools/constraint_solver/search_limit.pb.h" -#include "ortools/util/graph_export.h" -#include "ortools/util/string_array.h" - -DEFINE_string(input, "", "Input file of the problem."); -DEFINE_string(output, "", "Output file when doing modifications."); -DEFINE_string(dot_file, "", "Exports model to dot file."); -DEFINE_string(gml_file, "", "Exports model to gml file."); - -DEFINE_bool(print_proto, false, "Prints the raw model protobuf."); -DEFINE_bool(test_proto, false, "Performs various tests on the model protobuf."); -DEFINE_bool(model_stats, false, "Prints model statistics."); -DEFINE_bool(print_model, false, "Pretty print loaded model."); - -DEFINE_string(rename_model, "", "Renames to the model."); -DEFINE_bool(strip_limit, false, "Strips limits from the model."); -DEFINE_bool(strip_groups, false, "Strips variable groups from the model."); -DEFINE_bool(upgrade_proto, false, "Upgrade the model to the latest version."); -DEFINE_string(insert_license, "", - "Insert content of the given file into the license file."); -DEFINE_bool(collect_variables, false, - "Shows effect of the variable collector."); - -namespace operations_research { - -// ----- Utilities ----- - -static const int kProblem = -1; -static const int kOk = 0; - -// Colors -static const char kGreen1[] = "#A2CD5A"; -static const char kGreen2[] = "#76EEC6"; -static const char kGreen3[] = "#00CD00"; -static const char kWhite[] = "#FAFAFA"; -static const char kBlue[] = "#87CEFA"; -static const char kYellow[] = "#FFF68F"; -static const char kRed[] = "#A52A2A"; - -// Creates node labels. -std::string ExprLabel(int index) { return StringPrintf("expr_%i", index); } - -std::string IntervalLabel(int index) { - return StringPrintf("interval_%i", index); -} - -std::string SequenceLabel(int index) { - return StringPrintf("sequence_%i", index); -} - -std::string ConstraintLabel(int index) { return StringPrintf("ct_%i", index); } - -// Scans argument to add links in the graph. -template -void ExportLinks(const CpModel& model, const std::string& source, - const T& proto, GraphExporter* const exporter) { - const std::string& arg_name = model.tags(proto.argument_index()); - if (proto.type() == CpArgument::EXPRESSION) { - exporter->WriteLink(source, ExprLabel(proto.integer_expression_index()), - arg_name); - } - for (int i = 0; i < proto.integer_expression_array_size(); ++i) { - exporter->WriteLink(source, ExprLabel(proto.integer_expression_array(i)), - arg_name); - } - if (proto.type() == CpArgument::INTERVAL) { - exporter->WriteLink(source, IntervalLabel(proto.interval_index()), - arg_name); - } - for (int i = 0; i < proto.interval_array_size(); ++i) { - exporter->WriteLink(source, IntervalLabel(proto.interval_array(i)), - arg_name); - } - if (proto.type() == CpArgument::SEQUENCE) { - exporter->WriteLink(source, SequenceLabel(proto.sequence_index()), - arg_name); - } - for (int i = 0; i < proto.sequence_array_size(); ++i) { - exporter->WriteLink(source, SequenceLabel(proto.sequence_array(i)), - arg_name); - } -} - -// Scans the expression protobuf to see if it corresponds to an -// integer variable with min_value == max_value. -bool GetValueIfConstant(const CpModel& model, const CpIntegerExpression& proto, - int64* const value) { - CHECK(value != nullptr); - const int expr_type = proto.type_index(); - if (model.tags(expr_type) != ModelVisitor::kIntegerVariable) { - return false; - } - if (proto.arguments_size() != 2) { - return false; - } - const CpArgument& arg1_proto = proto.arguments(0); - if (model.tags(arg1_proto.argument_index()) != ModelVisitor::kMinArgument) { - return false; - } - const int64 value1 = arg1_proto.integer_value(); - const CpArgument& arg2_proto = proto.arguments(1); - if (model.tags(arg2_proto.argument_index()) != ModelVisitor::kMaxArgument) { - return false; - } - const int64 value2 = arg2_proto.integer_value(); - if (value1 == value2) { - *value = value1; - return true; - } else { - return false; - } -} - -// Declares a labelled expression in the graph file. -void DeclareExpression(int index, const CpModel& proto, - GraphExporter* const exporter) { - const CpIntegerExpression& expr = proto.expressions(index); - const std::string label = ExprLabel(index); - int64 value = 0; - if (!expr.name().empty()) { - exporter->WriteNode(label, expr.name(), "oval", kGreen1); - } else if (GetValueIfConstant(proto, expr, &value)) { - exporter->WriteNode(label, absl::StrCat(value), "oval", kYellow); - } else { - const std::string& type = proto.tags(expr.type_index()); - exporter->WriteNode(label, type, "oval", kWhite); - } -} - -void DeclareInterval(int index, const CpModel& proto, - GraphExporter* const exporter) { - const CpIntervalVariable& interval = proto.intervals(index); - const std::string label = IntervalLabel(index); - if (!interval.name().empty()) { - exporter->WriteNode(label, interval.name(), "circle", kGreen2); - } else { - const std::string& type = proto.tags(interval.type_index()); - exporter->WriteNode(label, type, "circle", kWhite); - } -} - -void DeclareSequence(int index, const CpModel& proto, - GraphExporter* const exporter) { - const CpSequenceVariable& sequence = proto.sequences(index); - const std::string label = SequenceLabel(index); - if (!sequence.name().empty()) { - exporter->WriteNode(label, sequence.name(), "circle", kGreen3); - } else { - const std::string& type = proto.tags(sequence.type_index()); - exporter->WriteNode(label, type, "circle", kWhite); - } -} - -void DeclareConstraint(int index, const CpModel& proto, - GraphExporter* const exporter) { - const CpConstraint& ct = proto.constraints(index); - const std::string& type = proto.tags(ct.type_index()); - const std::string label = ConstraintLabel(index); - exporter->WriteNode(label, type, "rectangle", kBlue); -} - -// Parses the proto and exports it to a graph file. -void ExportToGraphFile(const CpModel& proto, File* const file, - GraphExporter::GraphFormat format) { - std::unique_ptr exporter( - GraphExporter::MakeFileExporter(file, format)); - exporter->WriteHeader(proto.model()); - for (int i = 0; i < proto.expressions_size(); ++i) { - DeclareExpression(i, proto, exporter.get()); - } - - for (int i = 0; i < proto.intervals_size(); ++i) { - DeclareInterval(i, proto, exporter.get()); - } - - for (int i = 0; i < proto.sequences_size(); ++i) { - DeclareSequence(i, proto, exporter.get()); - } - - for (int i = 0; i < proto.constraints_size(); ++i) { - DeclareConstraint(i, proto, exporter.get()); - } - - const char kObjLabel[] = "obj"; - if (proto.has_objective()) { - const std::string name = - proto.objective().maximize() ? "Maximize" : "Minimize"; - exporter->WriteNode(kObjLabel, name, "diamond", kRed); - } - - for (int i = 0; i < proto.expressions_size(); ++i) { - const CpIntegerExpression& expr = proto.expressions(i); - const std::string label = ExprLabel(i); - for (int j = 0; j < expr.arguments_size(); ++j) { - ExportLinks(proto, label, expr.arguments(j), exporter.get()); - } - } - - for (int i = 0; i < proto.intervals_size(); ++i) { - const CpIntervalVariable& interval = proto.intervals(i); - const std::string label = IntervalLabel(i); - for (int j = 0; j < interval.arguments_size(); ++j) { - ExportLinks(proto, label, interval.arguments(j), exporter.get()); - } - } - - for (int i = 0; i < proto.sequences_size(); ++i) { - const CpSequenceVariable& sequence = proto.sequences(i); - const std::string label = SequenceLabel(i); - for (int j = 0; j < sequence.arguments_size(); ++j) { - ExportLinks(proto, label, sequence.arguments(j), exporter.get()); - } - } - - for (int i = 0; i < proto.constraints_size(); ++i) { - const CpConstraint& ct = proto.constraints(i); - const std::string label = ConstraintLabel(i); - for (int j = 0; j < ct.arguments_size(); ++j) { - ExportLinks(proto, label, ct.arguments(j), exporter.get()); - } - } - - if (proto.has_objective()) { - const CpObjective& obj = proto.objective(); - exporter->WriteLink(kObjLabel, ExprLabel(obj.objective_index()), - ModelVisitor::kExpressionArgument); - } - exporter->WriteFooter(); -} - -// ----- Main Method ----- - -int Run() { - // ----- Load input file into protobuf ----- - - File* file; - if (!file::Open(FLAGS_input, "r", &file, file::Defaults()).ok()) { - LOG(WARNING) << "Cannot open " << FLAGS_input; - return kProblem; - } - - CpModel model_proto; - recordio::RecordReader reader(file); - if (!(reader.ReadProtocolMessage(&model_proto) && reader.Close())) { - LOG(INFO) << "No model found in " << file->filename(); - return kProblem; - } - - // ----- Display loaded protobuf ----- - - LOG(INFO) << "Read model " << model_proto.model(); - if (!model_proto.license_text().empty()) { - LOG(INFO) << "License = " << model_proto.license_text(); - } - - // ----- Modifications ----- - - if (!FLAGS_rename_model.empty()) { - model_proto.set_model(FLAGS_rename_model); - } - - if (FLAGS_strip_limit) { - model_proto.clear_search_limit(); - } - - if (FLAGS_strip_groups) { - model_proto.clear_variable_groups(); - } - - if (FLAGS_upgrade_proto) { - if (!Solver::UpgradeModel(&model_proto)) { - LOG(ERROR) << "Model upgrade failed"; - return kProblem; - } - } - - if (!FLAGS_insert_license.empty()) { - File* license; - if (!file::Open(FLAGS_insert_license, "rb", &license, file::Defaults()) - .ok()) { - LOG(WARNING) << "Cannot open " << FLAGS_insert_license; - return kProblem; - } - const int size = license->Size(); - char* const text = new char[size + 1]; - license->Read(text, size); - text[size] = '\0'; - model_proto.set_license_text(text); - license->Close(file::Defaults()).IgnoreError(); - } - - // ----- Reporting ----- - - if (FLAGS_print_proto) { - LOG(INFO) << model_proto.DebugString(); - } - if (FLAGS_test_proto || FLAGS_model_stats || FLAGS_print_model || - FLAGS_collect_variables) { - Solver solver(model_proto.model()); - std::vector monitors; - if (!solver.LoadModelWithSearchMonitors(model_proto, &monitors)) { - LOG(INFO) << "Could not load model into the solver"; - return kProblem; - } - if (FLAGS_test_proto) { - LOG(INFO) << "Model " << model_proto.model() << " loaded OK"; - } - if (FLAGS_model_stats) { - ModelVisitor* const visitor = solver.MakeStatisticsModelVisitor(); - solver.Accept(visitor, monitors); - } - if (FLAGS_print_model) { - ModelVisitor* const visitor = solver.MakePrintModelVisitor(); - solver.Accept(visitor, monitors); - } - if (FLAGS_collect_variables) { - std::vector primary_integer_variables; - std::vector secondary_integer_variables; - std::vector sequence_variables; - std::vector interval_variables; - solver.CollectDecisionVariables(&primary_integer_variables, - &secondary_integer_variables, - &sequence_variables, &interval_variables); - LOG(INFO) << "Primary integer variables = " - << JoinDebugStringPtr(primary_integer_variables, ", "); - LOG(INFO) << "Secondary integer variables = " - << JoinDebugStringPtr(secondary_integer_variables, ", "); - LOG(INFO) << "Sequence variables = " - << JoinDebugStringPtr(sequence_variables, ", "); - LOG(INFO) << "Interval variables = " - << JoinDebugStringPtr(interval_variables, ", "); - } - } - - // ----- Output ----- - - if (!FLAGS_output.empty()) { - File* output; - if (!file::Open(FLAGS_output, "wb", &output, file::Defaults()).ok()) { - LOG(INFO) << "Cannot open " << FLAGS_output; - return kProblem; - } - recordio::RecordWriter writer(output); - if (!(writer.WriteProtocolMessage(model_proto) && writer.Close())) { - return kProblem; - } else { - LOG(INFO) << "Model successfully written to " << FLAGS_output; - } - } - - if (!FLAGS_dot_file.empty()) { - File* dot_file; - if (!file::Open(FLAGS_dot_file, "w", &dot_file, file::Defaults()).ok()) { - LOG(INFO) << "Cannot open " << FLAGS_dot_file; - return kProblem; - } - ExportToGraphFile(model_proto, dot_file, GraphExporter::DOT_FORMAT); - dot_file->Close(file::Defaults()).IgnoreError(); - } - - if (!FLAGS_gml_file.empty()) { - File* gml_file; - if (!file::Open(FLAGS_gml_file, "w", &gml_file, file::Defaults()).ok()) { - LOG(INFO) << "Cannot open " << FLAGS_gml_file; - return kProblem; - } - ExportToGraphFile(model_proto, gml_file, GraphExporter::GML_FORMAT); - gml_file->Close(file::Defaults()).IgnoreError(); - } - return kOk; -} -} // namespace operations_research - -int main(int argc, char** argv) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - if (FLAGS_input.empty()) { - LOG(FATAL) << "Filename not specified"; - } - return operations_research::Run(); -} diff --git a/examples/cpp/slitherlink.cc b/examples/cpp/slitherlink.cc deleted file mode 100644 index 95365664fe..0000000000 --- a/examples/cpp/slitherlink.cc +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include -#include - -#include "ortools/constraint_solver/constraint_solver.h" -#include "ortools/constraint_solver/constraint_solveri.h" -#include "ortools/util/string_array.h" - -const std::vector> small = { - {3, 2, -1, 3}, {-1, -1, -1, 2}, {3, -1, -1, -1}, {3, -1, 3, 1}}; - -const std::vector> medium = { - {-1, 0, -1, 1, -1, -1, 1, -1}, {-1, 3, -1, -1, 2, 3, -1, 2}, - {-1, -1, 0, -1, -1, -1, -1, 0}, {-1, 3, -1, -1, 0, -1, -1, -1}, - {-1, -1, -1, 3, -1, -1, 0, -1}, {1, -1, -1, -1, -1, 3, -1, -1}, - {3, -1, 1, 3, -1, -1, 3, -1}, {-1, 0, -1, -1, 3, -1, 3, -1}}; - -const std::vector> big = { - {3, -1, -1, -1, 2, -1, 1, -1, 1, 2}, - {1, -1, 0, -1, 3, -1, 2, 0, -1, -1}, - {-1, 3, -1, -1, -1, -1, -1, -1, 3, -1}, - {2, 0, -1, 3, -1, 2, 3, -1, -1, -1}, - {-1, -1, -1, 1, 1, 1, -1, -1, 3, 3}, - {2, 3, -1, -1, 2, 2, 3, -1, -1, -1}, - {-1, -1, -1, 1, 2, -1, 2, -1, 3, 3}, - {-1, 2, -1, -1, -1, -1, -1, -1, 2, -1}, - {-1, -1, 1, 1, -1, 2, -1, 1, -1, 3}, - {3, 3, -1, 1, -1, 2, -1, -1, -1, 2}}; - -namespace operations_research { -namespace { -std::vector NeighboringArcs( - int i, int j, const std::vector>& h_arcs, - const std::vector>& v_arcs) { - std::vector tmp; - if (j > 0) { - tmp.push_back(h_arcs[i][j - 1]); - } - if (j < v_arcs.size() - 1) { - tmp.push_back(h_arcs[i][j]); - } - if (i > 0) { - tmp.push_back(v_arcs[j][i - 1]); - } - if (i < h_arcs.size() - 1) { - tmp.push_back(v_arcs[j][i]); - } - return tmp; -} - -// Dedicated constraint: Sum(boolvars) is even. -class BooleanSumEven : public Constraint { - public: - BooleanSumEven(Solver* const s, const std::vector& vars) - : Constraint(s), - vars_(vars), - num_possible_true_vars_(0), - num_always_true_vars_(0) {} - - virtual ~BooleanSumEven() {} - - virtual void Post() { - for (int i = 0; i < vars_.size(); ++i) { - if (!vars_[i]->Bound()) { - Demon* const u = MakeConstraintDemon1( - solver(), this, &BooleanSumEven::Update, "Update", i); - vars_[i]->WhenBound(u); - } - } - } - - virtual void InitialPropagate() { - int num_always_true = 0; - int num_possible_true = 0; - int possible_true_index = -1; - for (int i = 0; i < vars_.size(); ++i) { - const IntVar* const var = vars_[i]; - if (var->Min() == 1) { - num_always_true++; - num_possible_true++; - } else if (var->Max() == 1) { - num_possible_true++; - possible_true_index = i; - } - } - if (num_always_true == num_possible_true && num_possible_true % 2 == 1) { - solver()->Fail(); - } else if (num_possible_true == num_always_true + 1) { - DCHECK_NE(-1, possible_true_index); - vars_[possible_true_index]->SetValue(num_always_true % 2); - } - num_possible_true_vars_.SetValue(solver(), num_possible_true); - num_always_true_vars_.SetValue(solver(), num_always_true); - } - - void Update(int index) { - DCHECK(vars_[index]->Bound()); - const int64 value = vars_[index]->Min(); // Faster than Value(). - if (value == 0) { - num_possible_true_vars_.Decr(solver()); - } else { - DCHECK_EQ(1, value); - num_always_true_vars_.Incr(solver()); - } - if (num_always_true_vars_.Value() == num_possible_true_vars_.Value() && - num_possible_true_vars_.Value() % 2 == 1) { - solver()->Fail(); - } else if (num_possible_true_vars_.Value() == - num_always_true_vars_.Value() + 1) { - int possible_true_index = -1; - for (int i = 0; i < vars_.size(); ++i) { - if (!vars_[i]->Bound()) { - possible_true_index = i; - break; - } - } - if (possible_true_index != -1) { - if (num_possible_true_vars_.Value() % 2 == 0) { - vars_[possible_true_index]->SetMin(1); - } else { - vars_[possible_true_index]->SetMax(0); - } - } - } - } - - virtual std::string DebugString() const { - return StringPrintf("BooleanSumEven([%s])", - JoinDebugStringPtr(vars_, ", ").c_str()); - } - - virtual void Accept(ModelVisitor* const visitor) const { - visitor->BeginVisitConstraint(ModelVisitor::kSumEqual, this); - visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument, - vars_); - visitor->EndVisitConstraint(ModelVisitor::kSumEqual, this); - } - - private: - const std::vector vars_; - NumericalRev num_possible_true_vars_; - NumericalRev num_always_true_vars_; -}; - -Constraint* MakeBooleanSumEven(Solver* const s, const std::vector& v) { - return s->RevAlloc(new BooleanSumEven(s, v)); -} - -// Dedicated constraint: There is a single path on the grid. -// This constraint does not enforce the non-crossing, this is done -// by the constraint on the degree of each node. -class GridSinglePath : public Constraint { - public: - GridSinglePath(Solver* const solver, - const std::vector>& h_arcs, - const std::vector>& v_arcs) - : Constraint(solver), h_arcs_(h_arcs), v_arcs_(v_arcs) {} - - ~GridSinglePath() {} - - void Post() override { - Demon* const demon = - solver()->MakeDelayedConstraintInitialPropagateCallback(this); - for (auto& row : h_arcs_) { - for (IntVar* const var : row) { - var->WhenBound(demon); - } - } - - for (auto& column : v_arcs_) { - for (IntVar* const var : column) { - var->WhenBound(demon); - } - } - } - - // This constraint implements a single propagation. - // If one point is on the path, it checks the reachability of all possible - // nodes, and zero out the unreachable parts. - void InitialPropagate() override { - const int num_rows = h_arcs_.size(); // number of points - const int num_columns = v_arcs_.size(); // number of points - - const int num_points = num_rows * num_columns; - int root_node = -1; - std::unordered_set possible_points; - std::vector> neighbors(num_points); - for (int i = 0; i < num_rows; ++i) { - for (int j = 0; j < num_columns - 1; ++j) { - IntVar* const h_arc = h_arcs_[i][j]; - if (h_arc->Max() == 1) { - const int head = i * num_columns + j; - const int tail = i * num_columns + j + 1; - neighbors[head].push_back(tail); - neighbors[tail].push_back(head); - possible_points.insert(head); - possible_points.insert(tail); - if (root_node == -1 && h_arc->Min() == 1) { - root_node = head; - } - } - } - } - - for (int i = 0; i < num_rows - 1; ++i) { - for (int j = 0; j < num_columns; ++j) { - IntVar* const v_arc = v_arcs_[j][i]; - if (v_arc->Max() == 1) { - const int head = i * num_columns + j; - const int tail = (i + 1) * num_columns + j; - neighbors[head].push_back(tail); - neighbors[tail].push_back(head); - possible_points.insert(head); - possible_points.insert(tail); - if (root_node == -1 && v_arc->Min() == 1) { - root_node = head; - } - } - } - } - if (root_node == -1) { - return; - } - std::unordered_set visited_points; - std::deque to_process; - to_process.push_back(root_node); - while (!to_process.empty()) { - const int candidate = to_process.front(); - to_process.pop_front(); - visited_points.insert(candidate); - for (int neighbor : neighbors[candidate]) { - if (!ContainsKey(visited_points, neighbor)) { - to_process.push_back(neighbor); - visited_points.insert(neighbor); - } - } - } - - if (visited_points.size() < possible_points.size()) { - for (const int point : visited_points) { - possible_points.erase(point); - } - // Loop on unreachable points and zero all neighboring arcs. - for (const int point : possible_points) { - const int i = point / num_columns; - const int j = point % num_columns; - const std::vector neighbors = - NeighboringArcs(i, j, h_arcs_, v_arcs_); - for (IntVar* const var : neighbors) { - var->SetMax(0); - } - } - } - } - - private: - const std::vector> h_arcs_; - const std::vector> v_arcs_; -}; - -Constraint* MakeSingleLoop(Solver* const solver, - const std::vector>& h_arcs, - const std::vector>& v_arcs) { - return solver->RevAlloc(new GridSinglePath(solver, h_arcs, v_arcs)); -} - -void PrintSolution(const std::vector>& data, - const std::vector>& h_arcs, - const std::vector>& v_arcs) { - const int num_rows = data.size(); - const int num_columns = data[0].size(); - - for (int i = 0; i < num_rows; ++i) { - std::string first_line; - std::string second_line; - std::string third_line; - for (int j = 0; j < num_columns; ++j) { - const int h_arc = h_arcs[i][j]->Value(); - const int v_arc = v_arcs[j][i]->Value(); - const int sum = data[i][j]; - first_line += h_arc == 1 ? " ---" : " "; - second_line += v_arc == 1 ? "|" : " "; - second_line += sum == -1 ? " " : StringPrintf(" %d ", sum).c_str(); - third_line += v_arc == 1 ? "| " : " "; - } - const int termination = v_arcs[num_columns][i]->Value(); - second_line += termination == 1 ? "|" : " "; - third_line += termination == 1 ? "|" : " "; - std::cout << first_line << std::endl; - std::cout << third_line << std::endl; - std::cout << second_line << std::endl; - std::cout << third_line << std::endl; - } - std::string last_line; - for (int j = 0; j < num_columns; ++j) { - const int h_arc = h_arcs[num_rows][j]->Value(); - last_line += h_arc == 1 ? " ---" : " "; - } - std::cout << last_line << std::endl; -} -} // namespace - -void SlitherLink(const std::vector>& data) { - const int num_rows = data.size(); - const int num_columns = data[0].size(); - - Solver solver("slitherlink"); - std::vector all_vars; - std::vector> h_arcs(num_rows + 1); - for (int i = 0; i < num_rows + 1; ++i) { - solver.MakeBoolVarArray(num_columns, StringPrintf("h_arc_%i_", i), - &h_arcs[i]); - all_vars.insert(all_vars.end(), h_arcs[i].begin(), h_arcs[i].end()); - } - - std::vector> v_arcs(num_columns + 1); - for (int i = 0; i < num_columns + 1; ++i) { - solver.MakeBoolVarArray(num_rows, StringPrintf("v_arc_%i_", i), &v_arcs[i]); - all_vars.insert(all_vars.end(), v_arcs[i].begin(), v_arcs[i].end()); - } - - // Constraint on the sum of arcs. - for (int i = 0; i < num_rows; ++i) { - for (int j = 0; j < num_columns; ++j) { - const int value = data[i][j]; - if (value != -1) { - std::vector square = {h_arcs[i][j], h_arcs[i + 1][j], - v_arcs[j][i], v_arcs[j + 1][i]}; - solver.AddConstraint(solver.MakeSumEquality(square, value)); - } - } - } - - // Single loop: each node has a degree 0 or 2. - const std::vector zero_or_two = {0, 2}; - for (int i = 0; i < num_rows + 1; ++i) { - for (int j = 0; j < num_columns + 1; ++j) { - const std::vector neighbors = - NeighboringArcs(i, j, h_arcs, v_arcs); - solver.AddConstraint( - solver.MakeMemberCt(solver.MakeSum(neighbors), zero_or_two)); - } - } - - // Single loop: sum of arc on row or column is even - for (int i = 0; i < num_columns; ++i) { - std::vector column; - for (int j = 0; j < num_rows + 1; ++j) { - column.push_back(h_arcs[j][i]); - } - solver.AddConstraint(MakeBooleanSumEven(&solver, column)); - } - for (int i = 0; i < num_rows; ++i) { - std::vector row; - for (int j = 0; j < num_columns + 1; ++j) { - row.push_back(v_arcs[j][i]); - } - solver.AddConstraint(MakeBooleanSumEven(&solver, row)); - } - - // Hamiltonian path: add single path constraint. - solver.AddConstraint(MakeSingleLoop(&solver, h_arcs, v_arcs)); - - // Special rule on corners: value == 3 implies 2 border arcs used. - if (data[0][0] == 3) { - h_arcs[0][0]->SetMin(1); - v_arcs[0][0]->SetMin(1); - } - if (data[0][num_columns - 1] == 3) { - h_arcs[0][num_columns - 1]->SetMin(1); - v_arcs[num_columns][0]->SetMin(1); - } - if (data[num_rows - 1][0] == 3) { - h_arcs[num_rows][0]->SetMin(1); - v_arcs[0][num_rows - 1]->SetMin(1); - } - if (data[num_rows - 1][num_columns - 1] == 3) { - h_arcs[num_rows][num_columns - 1]->SetMin(1); - v_arcs[num_columns][num_rows - 1]->SetMin(1); - } - - // Search. - DecisionBuilder* const db = solver.MakePhase( - all_vars, Solver::CHOOSE_FIRST_UNBOUND, Solver::ASSIGN_MAX_VALUE); - - SearchMonitor* const log = solver.MakeSearchLog(1000000); - - solver.NewSearch(db, log); - while (solver.NextSolution()) { - PrintSolution(data, h_arcs, v_arcs); - } - solver.EndSearch(); -} -} // namespace operations_research - -int main() { - std::cout << "Small problem" << std::endl; - operations_research::SlitherLink(small); - std::cout << "Medium problem" << std::endl; - operations_research::SlitherLink(medium); - std::cout << "Big problem" << std::endl; - operations_research::SlitherLink(big); - return EXIT_SUCCESS; -} diff --git a/examples/cpp/slitherlink_sat.cc b/examples/cpp/slitherlink_sat.cc new file mode 100644 index 0000000000..80b10fac86 --- /dev/null +++ b/examples/cpp/slitherlink_sat.cc @@ -0,0 +1,258 @@ +// Copyright 2010-2018 Google LLC +// 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. + +#include +#include +#include +#include + +#include "ortools/base/stringprintf.h" +#include "ortools/sat/cp_model.h" +#include "ortools/sat/model.h" + +const std::vector> tiny = {{3, 3, 1}}; + +const std::vector> small = { + {3, 2, -1, 3}, {-1, -1, -1, 2}, {3, -1, -1, -1}, {3, -1, 3, 1}}; + +const std::vector> medium = { + {-1, 0, -1, 1, -1, -1, 1, -1}, {-1, 3, -1, -1, 2, 3, -1, 2}, + {-1, -1, 0, -1, -1, -1, -1, 0}, {-1, 3, -1, -1, 0, -1, -1, -1}, + {-1, -1, -1, 3, -1, -1, 0, -1}, {1, -1, -1, -1, -1, 3, -1, -1}, + {3, -1, 1, 3, -1, -1, 3, -1}, {-1, 0, -1, -1, 3, -1, 3, -1}}; + +const std::vector> big = { + {3, -1, -1, -1, 2, -1, 1, -1, 1, 2}, + {1, -1, 0, -1, 3, -1, 2, 0, -1, -1}, + {-1, 3, -1, -1, -1, -1, -1, -1, 3, -1}, + {2, 0, -1, 3, -1, 2, 3, -1, -1, -1}, + {-1, -1, -1, 1, 1, 1, -1, -1, 3, 3}, + {2, 3, -1, -1, 2, 2, 3, -1, -1, -1}, + {-1, -1, -1, 1, 2, -1, 2, -1, 3, 3}, + {-1, 2, -1, -1, -1, -1, -1, -1, 2, -1}, + {-1, -1, 1, 1, -1, 2, -1, 1, -1, 3}, + {3, 3, -1, 1, -1, 2, -1, -1, -1, 2}}; + +namespace operations_research { +namespace sat { + +void PrintSolution(const std::vector> &data, + const std::vector> &h_arcs, + const std::vector> &v_arcs) { + const int num_rows = data.size(); + const int num_columns = data[0].size(); + + for (int i = 0; i < num_rows; ++i) { + std::string first_line; + std::string second_line; + std::string third_line; + for (int j = 0; j < num_columns; ++j) { + const bool h_arc = h_arcs[i][j]; + const bool v_arc = v_arcs[j][i]; + const int sum = data[i][j]; + first_line += h_arc ? " -----" : " "; + second_line += v_arc ? "|" : " "; + second_line += sum == -1 ? " " : StringPrintf(" %d ", sum).c_str(); + third_line += v_arc ? "| " : " "; + } + const bool termination = v_arcs[num_columns][i]; + second_line += termination == 1 ? "|" : " "; + third_line += termination == 1 ? "|" : " "; + std::cout << first_line << std::endl; + std::cout << third_line << std::endl; + std::cout << second_line << std::endl; + std::cout << third_line << std::endl; + } + std::string last_line; + for (int j = 0; j < num_columns; ++j) { + const bool h_arc = h_arcs[num_rows][j]; + last_line += h_arc ? " -----" : " "; + } + std::cout << last_line << std::endl; +} + +void SlitherLink(const std::vector> &data) { + const int num_rows = data.size(); + const int num_columns = data[0].size(); + + const int num_nodes = (num_rows + 1) * (num_columns + 1); + const int num_horizontal_arcs = num_columns * (num_rows + 1); + const int num_vertical_arcs = (num_rows) * (num_columns + 1); + + auto undirected_horizontal_arc = [=](int x, int y) { + CHECK_LT(x, num_columns); + CHECK_LT(y, num_rows + 1); + return x + (num_columns * y); + }; + + auto undirected_vertical_arc = [=](int x, int y) { + CHECK_LT(x, num_columns + 1); + CHECK_LT(y, num_rows); + return x + (num_columns + 1) * y; + }; + + auto node_index = [=](int x, int y) { + CHECK_LT(x, num_columns + 1); + CHECK_LT(y, num_rows + 1); + return x + y * (num_columns + 1); + }; + + CpModelBuilder builder; + + std::vector horizontal_arcs; + for (int arc = 0; arc < 2 * num_horizontal_arcs; ++arc) { + horizontal_arcs.push_back(builder.NewBoolVar()); + } + std::vector vertical_arcs; + for (int arc = 0; arc < 2 * num_vertical_arcs; ++arc) { + vertical_arcs.push_back(builder.NewBoolVar()); + } + + CircuitConstraint circuit = builder.AddCircuitConstraint(); + // Horizontal arcs. + for (int x = 0; x < num_columns; ++x) { + for (int y = 0; y < num_rows + 1; ++y) { + const int arc = undirected_horizontal_arc(x, y); + circuit.AddArc(node_index(x, y), node_index(x + 1, y), + horizontal_arcs[2 * arc]); + circuit.AddArc(node_index(x + 1, y), node_index(x, y), + horizontal_arcs[2 * arc + 1]); + } + } + // Vertical arcs. + for (int x = 0; x < num_columns + 1; ++x) { + for (int y = 0; y < num_rows; ++y) { + const int arc = undirected_vertical_arc(x, y); + circuit.AddArc(node_index(x, y), node_index(x, y + 1), + vertical_arcs[2 * arc]); + circuit.AddArc(node_index(x, y + 1), node_index(x, y), + vertical_arcs[2 * arc + 1]); + } + } + // Self loops. + std::vector self_nodes(num_nodes); + for (int x = 0; x < num_columns + 1; ++x) { + for (int y = 0; y < num_rows + 1; ++y) { + const int node = node_index(x, y); + const BoolVar self_node = builder.NewBoolVar(); + circuit.AddArc(node, node, self_node); + self_nodes[node] = self_node; + } + } + + for (int x = 0; x < num_columns; ++x) { + for (int y = 0; y < num_rows; ++y) { + if (data[y][x] == -1) + continue; + std::vector neighbors; + const int top_arc = undirected_horizontal_arc(x, y); + neighbors.push_back(horizontal_arcs[2 * top_arc]); + neighbors.push_back(horizontal_arcs[2 * top_arc + 1]); + const int bottom_arc = undirected_horizontal_arc(x, y + 1); + neighbors.push_back(horizontal_arcs[2 * bottom_arc]); + neighbors.push_back(horizontal_arcs[2 * bottom_arc + 1]); + const int left_arc = undirected_vertical_arc(x, y); + neighbors.push_back(vertical_arcs[2 * left_arc]); + neighbors.push_back(vertical_arcs[2 * left_arc + 1]); + const int right_arc = undirected_vertical_arc(x + 1, y); + neighbors.push_back(vertical_arcs[2 * right_arc]); + neighbors.push_back(vertical_arcs[2 * right_arc + 1]); + builder.AddEquality(LinearExpr::BooleanSum(neighbors), data[y][x]); + } + } + + // Special rule on corners: value == 3 implies 2 corner arcs used. + if (data[0][0] == 3) { + const int h_arc = undirected_horizontal_arc(0, 0); + builder.AddBoolOr( + {horizontal_arcs[2 * h_arc], horizontal_arcs[2 * h_arc + 1]}); + const int v_arc = undirected_vertical_arc(0, 0); + builder.AddBoolOr({vertical_arcs[2 * v_arc], vertical_arcs[2 * v_arc + 1]}); + } + if (data[0][num_columns - 1] == 3) { + const int h_arc = undirected_horizontal_arc(num_columns - 1, 0); + builder.AddBoolOr( + {horizontal_arcs[2 * h_arc], horizontal_arcs[2 * h_arc + 1]}); + const int v_arc = undirected_vertical_arc(num_columns, 0); + builder.AddBoolOr({vertical_arcs[2 * v_arc], vertical_arcs[2 * v_arc + 1]}); + } + if (data[num_rows - 1][0] == 3) { + const int h_arc = undirected_horizontal_arc(0, num_rows); + builder.AddBoolOr( + {horizontal_arcs[2 * h_arc], horizontal_arcs[2 * h_arc + 1]}); + const int v_arc = undirected_vertical_arc(0, num_rows - 1); + builder.AddBoolOr({vertical_arcs[2 * v_arc], vertical_arcs[2 * v_arc + 1]}); + } + if (data[num_rows - 1][num_columns - 1] == 3) { + const int h_arc = undirected_horizontal_arc(num_columns - 1, num_rows); + builder.AddBoolOr( + {horizontal_arcs[2 * h_arc], horizontal_arcs[2 * h_arc + 1]}); + const int v_arc = undirected_vertical_arc(num_columns, num_rows - 1); + builder.AddBoolOr({vertical_arcs[2 * v_arc], vertical_arcs[2 * v_arc + 1]}); + } + + // Topology rule: Border arcs are oriented in one direction. + for (int x = 0; x < num_columns; ++x) { + const int top_arc = undirected_horizontal_arc(x, 0); + builder.AddEquality(horizontal_arcs[2 * top_arc + 1], 0); + const int bottom_arc = undirected_horizontal_arc(x, num_rows); + builder.AddEquality(horizontal_arcs[2 * bottom_arc], 0); + } + + for (int y = 0; y < num_rows; ++y) { + const int left_arc = undirected_vertical_arc(0, y); + builder.AddEquality(vertical_arcs[2 * left_arc], 0); + const int right_arc = undirected_vertical_arc(num_columns, y); + builder.AddEquality(vertical_arcs[2 * right_arc + 1], 0); + } + + const CpSolverResponse response = Solve(builder); + + std::vector> h_arcs(num_rows + 1); + for (int y = 0; y < num_rows + 1; ++y) { + for (int x = 0; x < num_columns; ++x) { + const int arc = undirected_horizontal_arc(x, y); + h_arcs[y].push_back( + SolutionBooleanValue(response, horizontal_arcs[2 * arc]) || + SolutionBooleanValue(response, horizontal_arcs[2 * arc + 1])); + } + } + + std::vector> v_arcs(num_columns + 1); + for (int y = 0; y < num_rows; ++y) { + for (int x = 0; x < num_columns + 1; ++x) { + const int arc = undirected_vertical_arc(x, y); + v_arcs[x].push_back( + SolutionBooleanValue(response, vertical_arcs[2 * arc]) || + SolutionBooleanValue(response, vertical_arcs[2 * arc + 1])); + } + } + + PrintSolution(data, h_arcs, v_arcs); + LOG(INFO) << CpSolverResponseStats(response); +} + +} // namespace sat +} // namespace operations_research + +int main() { + std::cout << "Tiny problem" << std::endl; + operations_research::sat::SlitherLink(tiny); + std::cout << "Small problem" << std::endl; + operations_research::sat::SlitherLink(small); + std::cout << "Medium problem" << std::endl; + operations_research::sat::SlitherLink(medium); + std::cout << "Big problem" << std::endl; + operations_research::sat::SlitherLink(big); + return EXIT_SUCCESS; +} diff --git a/examples/cpp/sports_scheduling.cc b/examples/cpp/sports_scheduling.cc deleted file mode 100644 index 54bdabe475..0000000000 --- a/examples/cpp/sports_scheduling.cc +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2010-2018 Google LLC -// 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. - -// Sports scheduling problem. -// -// We want to solve the problem of scheduling of team matches in a -// double round robin tournament. Given a number of teams, we want -// each team to encounter all other teams, twice, once at home, and -// once away. Furthermore, you cannot meet the same team twice in the -// same half-season. -// -// Finally, there are constraints on the sequence of home or aways: -// - You cannot have 3 consecutive homes or three consecutive aways. -// - A break is a sequence of two homes or two aways, the overall objective -// of the optimization problem is to minimize the total number of breaks. -// -// We model this problem with three matrices of variables, each with -// num_teams rows and 2*(num_teams - 1) columns: the var [i][j] -// corresponds to the match of team #i at day #j. There are -// 2*(num_teams - 1) columns because each team meets num_teams - 1 -// opponents twice. - -// - The 'opponent' var [i][j] is the index of the opposing team. -// - The 'home_away' var [i][j] is a boolean: 1 for 'playing away', -// 0 for 'playing at home'. -// - The 'opponent_and_home_away' var [i][j] is the 'opponent' var [i][j] + -// num_teams * the 'home_away' var [i][j]. -// This aggregated variable will be useful to state constraints of the model -// and to do search on it. -// -// We use an original approch in this model as most of the constraints will -// be pre-computed and asserted using an AllowedAssignment constraint (see -// Solver::MakeAllowedAssignment() in constraint_solver.h). -// In particular: -// - Each day, we have a perfect matching between teams -// (A meets B <=> B meets A, and A is at home <=> B is away). -// A cannot meet itself. -// - For each team, over the length of the tournament, we have constraints -// on the sequence of home-aways. We will precompute all possible sequences -// of home_aways, as well as the corresponding number of breaks for that -// team. -// - For a given team and a given day, the link between the opponent var, -// the home_away var and the aggregated var (see third matrix of variables) -// is also maintained using a AllowedAssignment constraint. -// -// Usage: run this with --helpshort for a short usage manual. - -#include "ortools/base/commandlineflags.h" -#include "ortools/base/integral_types.h" -#include "ortools/base/logging.h" -#include "ortools/base/stringprintf.h" -#include "ortools/constraint_solver/constraint_solver.h" -#include "ortools/constraint_solver/constraint_solveri.h" - -// Problem main flags. -DEFINE_int32(num_teams, 10, "Number of teams in the problem."); - -// General solving parameters. -DEFINE_int32(time_limit, 20000, "Time limit in ms."); - -// Search tweaking parameters. These are defined to illustrate their effect. -DEFINE_bool(run_all_heuristics, true, - "Run all heuristics in impact search, see DefaultPhaseParameters" - " in constraint_solver/constraint_solver.h for details."); -DEFINE_int32(heuristics_period, 30, - "Frequency to run all heuristics, see DefaultPhaseParameters" - " in constraint_solver/constraint_solver.h for details."); -DEFINE_double(restart_log_size, 8.0, - "Threshold for automatic restarting the search in default phase," - " see DefaultPhaseParameters in " - "constraint_solver/constraint_solver.h for details."); - -namespace operations_research { -// ---------- Utility functions to help create the model ---------- - -// ----- Constraints for one day and one team ----- - -// Computes the tuple set that links opponents, home_away, and -// signed_opponent on a single day for a single team. -void ComputeOneDayOneTeamTuples(int num_teams, IntTupleSet* const tuples) { - for (int home_away = 0; home_away <= 1; ++home_away) { - for (int opponent = 0; opponent < num_teams; ++opponent) { - tuples->Insert3(opponent, home_away, opponent + home_away * num_teams); - } - } -} - -void AddOneDayOneTeamConstraint(Solver* const solver, IntVar* const opponent, - IntVar* const home_away, - IntVar* const signed_opponent, - const IntTupleSet& intra_day_tuples) { - std::vector tmp_vars; - tmp_vars.push_back(opponent); - tmp_vars.push_back(home_away); - tmp_vars.push_back(signed_opponent); - solver->AddConstraint( - solver->MakeAllowedAssignments(tmp_vars, intra_day_tuples)); -} - -// ----- Constraints for one day and all teams ----- - -// Computes all valid combination of signed_opponent for a single -// day and all teams. -void ComputeOneDayTuples(int num_teams, IntTupleSet* const day_tuples) { - LOG(INFO) << "Compute possible opponents and locations for any day."; - Solver solver("ComputeOneDayTuples"); - - // We create the variables. - std::vector opponents; - std::vector home_aways; - std::vector signed_opponents; - solver.MakeIntVarArray(num_teams, 0, num_teams - 1, "opponent_", &opponents); - solver.MakeBoolVarArray(num_teams, "home_away_", &home_aways); - solver.MakeIntVarArray(num_teams, 0, 2 * num_teams - 1, "signed_opponent_", - &signed_opponents); - - // All Diff constraint. - solver.AddConstraint(solver.MakeAllDifferent(opponents)); - - // Cannot play against itself - for (int i = 0; i < num_teams; ++i) { - solver.AddConstraint(solver.MakeNonEquality(opponents[i], i)); - } - - // Matching constraint (vars[i] == j <=> vars[j] == i). - for (int i = 0; i < num_teams; ++i) { - for (int j = 0; j < num_teams; ++j) { - if (i != j) { - solver.AddConstraint( - solver.MakeEquality(solver.MakeIsEqualCstVar(opponents[i], j), - solver.MakeIsEqualCstVar(opponents[j], i))); - } - } - } - - // num_teams/2 teams are home. - solver.AddConstraint(solver.MakeSumEquality(home_aways, num_teams / 2)); - - // Link signed_opponents, home_away and opponents - IntTupleSet one_day_one_team_tuples(3); - ComputeOneDayOneTeamTuples(num_teams, &one_day_one_team_tuples); - for (int team_index = 0; team_index < num_teams; ++team_index) { - std::vector tmp_vars; - tmp_vars.push_back(opponents[team_index]); - tmp_vars.push_back(home_aways[team_index]); - tmp_vars.push_back(signed_opponents[team_index]); - solver.AddConstraint( - solver.MakeAllowedAssignments(tmp_vars, one_day_one_team_tuples)); - } - - // if A meets B at home, B meets A away. - for (int first_team = 0; first_team < num_teams; ++first_team) { - IntVar* const first_home_away = home_aways[first_team]; - IntVar* const second_home_away = - solver.MakeElement(home_aways, opponents[first_team])->Var(); - IntVar* const reverse_second_home_away = - solver.MakeDifference(1, second_home_away)->Var(); - solver.AddConstraint( - solver.MakeEquality(first_home_away, reverse_second_home_away)); - } - - // Search for solutions and fill day_tuples. - DecisionBuilder* const db = solver.MakePhase( - signed_opponents, Solver::CHOOSE_FIRST_UNBOUND, Solver::ASSIGN_MIN_VALUE); - solver.NewSearch(db); - while (solver.NextSolution()) { - std::vector solution; - for (int i = 0; i < num_teams; ++i) { - solution.push_back(signed_opponents[i]->Value()); - } - day_tuples->Insert(solution); - } - solver.EndSearch(); - LOG(INFO) << day_tuples->NumTuples() - << " solutions to the one-day matching problem"; -} - -// Adds all constraints relating to one teams and the complete schedule. -void AddOneTeamConstraints(Solver* const solver, - const std::vector& opponents, - const std::vector& home_aways, - const std::vector& signed_opponents, - const IntTupleSet& home_away_tuples, - IntVar* const break_var, int num_teams) { - const int half_season = num_teams - 1; - - // Each team meet all opponents once by half season. - for (int half = 0; half <= 1; ++half) { - for (int team_index = 0; team_index < num_teams; ++team_index) { - std::vector tmp_vars; - for (int day = 0; day < half_season; ++day) { - tmp_vars.push_back(opponents[half * half_season + day]); - } - solver->AddConstraint(solver->MakeAllDifferent(tmp_vars)); - } - } - - // We meet each opponent once at home and once away per full season. - for (int team_index = 0; team_index < num_teams; ++team_index) { - solver->AddConstraint(solver->MakeAllDifferent(signed_opponents)); - } - - // Constraint per team on home_aways; - for (int i = 0; i < num_teams; ++i) { - std::vector tmp_vars(home_aways); - tmp_vars.push_back(break_var); - solver->AddConstraint( - solver->MakeAllowedAssignments(tmp_vars, home_away_tuples)); - } -} - -// ----- Constraints for one team and all days ----- - -// Computes all valid tuples for home_away variables for a single team -// on the full lenght of the season. -void ComputeOneTeamHomeAwayTuples(int num_teams, - IntTupleSet* const home_away_tuples) { - LOG(INFO) << "Compute possible sequence of home and aways for any team."; - const int half_season = num_teams - 1; - const int full_season = 2 * half_season; - - Solver solver("compute_home_aways"); - std::vector home_aways; - solver.MakeBoolVarArray(full_season, "home_away_", &home_aways); - for (int day = 0; day < full_season - 2; ++day) { - std::vector tmp_vars; - tmp_vars.push_back(home_aways[day]); - tmp_vars.push_back(home_aways[day + 1]); - tmp_vars.push_back(home_aways[day + 2]); - IntVar* const partial_sum = solver.MakeSum(tmp_vars)->Var(); - solver.AddConstraint(solver.MakeBetweenCt(partial_sum, 1, 2)); - } - DecisionBuilder* const db = solver.MakePhase( - home_aways, Solver::CHOOSE_FIRST_UNBOUND, Solver::ASSIGN_MIN_VALUE); - solver.NewSearch(db); - while (solver.NextSolution()) { - std::vector solution; - for (int i = 0; i < full_season; ++i) { - solution.push_back(home_aways[i]->Value()); - } - int breaks = 0; - for (int i = 0; i < full_season - 1; ++i) { - breaks += (solution[i] == solution[i + 1]); - } - solution.push_back(breaks); - home_away_tuples->Insert(solution); - } - solver.EndSearch(); - LOG(INFO) << home_away_tuples->NumTuples() - << " combination of home_aways for a team on the full season"; -} - -// ---------- Main solving method ---------- - -// Solves the sports scheduling problem with a given number of teams. -void SportsScheduling(int num_teams) { - const int half_season = num_teams - 1; - const int full_season = 2 * half_season; - - Solver solver("Sports Scheduling"); - - // ----- Variables ----- - - // The index of the opponent of a team on a given day. - std::vector > opponents(num_teams); - // The location of the match (home or away). - std::vector > home_aways(num_teams); - // Disambiguated version of the opponent variable incorporating the - // home_away result. - std::vector > signed_opponents(num_teams); - for (int team_index = 0; team_index < num_teams; ++team_index) { - solver.MakeIntVarArray(full_season, 0, num_teams - 1, - StringPrintf("opponent_%d_", team_index), - &opponents[team_index]); - solver.MakeBoolVarArray(full_season, - StringPrintf("home_away_%d_", team_index), - &home_aways[team_index]); - solver.MakeIntVarArray(full_season, 0, 2 * num_teams - 1, - StringPrintf("signed_opponent_%d", team_index), - &signed_opponents[team_index]); - } - // ----- Constraints ----- - - // Constraints on a given day. - IntTupleSet one_day_tuples(num_teams); - ComputeOneDayTuples(num_teams, &one_day_tuples); - for (int day = 0; day < full_season; ++day) { - std::vector all_vars; - for (int i = 0; i < num_teams; ++i) { - all_vars.push_back(signed_opponents[i][day]); - } - solver.AddConstraint( - solver.MakeAllowedAssignments(all_vars, one_day_tuples)); - } - - // Links signed_opponents, home_away and opponents. - IntTupleSet one_day_one_team_tuples(3); - ComputeOneDayOneTeamTuples(num_teams, &one_day_one_team_tuples); - for (int day = 0; day < full_season; ++day) { - for (int team_index = 0; team_index < num_teams; ++team_index) { - AddOneDayOneTeamConstraint( - &solver, opponents[team_index][day], home_aways[team_index][day], - signed_opponents[team_index][day], one_day_one_team_tuples); - } - } - - // Constraints on a team. - IntTupleSet home_away_tuples(full_season + 1); - ComputeOneTeamHomeAwayTuples(num_teams, &home_away_tuples); - std::vector team_breaks; - solver.MakeIntVarArray(num_teams, 0, full_season, "team_break_", - &team_breaks); - for (int team = 0; team < num_teams; ++team) { - AddOneTeamConstraints(&solver, opponents[team], home_aways[team], - signed_opponents[team], home_away_tuples, - team_breaks[team], num_teams); - } - - // ----- Search ----- - - std::vector monitors; - - // Objective. - IntVar* const objective_var = - solver.MakeSum(team_breaks)->VarWithName("SumOfBreaks"); - OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1); - monitors.push_back(objective_monitor); - - // Store all variables in a single array. - std::vector all_signed_opponents; - for (int team_index = 0; team_index < num_teams; ++team_index) { - for (int day = 0; day < full_season; ++day) { - all_signed_opponents.push_back(signed_opponents[team_index][day]); - } - } - - // Build default phase decision builder. - DefaultPhaseParameters parameters; - parameters.run_all_heuristics = FLAGS_run_all_heuristics; - parameters.heuristic_period = FLAGS_heuristics_period; - parameters.restart_log_size = FLAGS_restart_log_size; - DecisionBuilder* const db = - solver.MakeDefaultPhase(all_signed_opponents, parameters); - - // Search log. - SearchMonitor* const log = solver.MakeSearchLog(1000000, objective_monitor); - monitors.push_back(log); - - // Search limit. - SearchLimit* const limit = solver.MakeTimeLimit(FLAGS_time_limit); - monitors.push_back(limit); - - // Solution collector. - SolutionCollector* const collector = solver.MakeLastSolutionCollector(); - for (int team_index = 0; team_index < num_teams; ++team_index) { - collector->Add(opponents[team_index]); - collector->Add(home_aways[team_index]); - } - monitors.push_back(collector); - - // And search. - solver.Solve(db, monitors); - - // Display solution. - if (collector->solution_count() == 1) { - LOG(INFO) << "Solution found in " << solver.wall_time() << " ms, and " - << solver.failures() << " failures."; - for (int team_index = 0; team_index < num_teams; ++team_index) { - std::string line; - for (int day = 0; day < full_season; ++day) { - const int opponent = collector->Value(0, opponents[team_index][day]); - const int home_away = collector->Value(0, home_aways[team_index][day]); - line += StringPrintf("%2d%s ", opponent, home_away ? "@" : " "); - } - LOG(INFO) << line; - } - } -} -} // namespace operations_research - -static const char kUsage[] = - "Usage: see flags.\nThis program runs a sports scheduling problem." - "There is no output besides the debug LOGs of the solver."; - -int main(int argc, char** argv) { - gflags::SetUsageMessage(kUsage); - gflags::ParseCommandLineFlags(&argc, &argv, true); - CHECK_EQ(0, FLAGS_num_teams % 2) << "The number of teams must be even"; - CHECK_GE(FLAGS_num_teams, 2) << "At least 2 teams"; - CHECK_LT(FLAGS_num_teams, 16) << "The model does not scale beyond 14 teams"; - operations_research::SportsScheduling(FLAGS_num_teams); - return EXIT_SUCCESS; -} diff --git a/examples/cpp/sports_scheduling_sat.cc b/examples/cpp/sports_scheduling_sat.cc new file mode 100644 index 0000000000..807dac9a7b --- /dev/null +++ b/examples/cpp/sports_scheduling_sat.cc @@ -0,0 +1,330 @@ +// Copyright 2010-2018 Google LLC +// 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. + +// Sports scheduling problem. +// +// We want to solve the problem of scheduling of team matches in a +// double round robin tournament. Given a number of teams, we want +// each team to encounter all other teams, twice, once at home, and +// once away. Furthermore, you cannot meet the same team twice in the +// same half-season. +// +// Finally, there are constraints on the sequence of home or aways: +// - You cannot have 3 consecutive homes or three consecutive aways. +// - A break is a sequence of two homes or two aways, the overall objective +// of the optimization problem is to minimize the total number of breaks. +// - If team A meets team B, the reverse match cannot happen less that 6 weeks +// after. +// +// We model this problem with three matrices of variables, each with +// num_teams rows and 2*(num_teams - 1) columns: the var at position [i][j] +// corresponds to the match of team #i at day #j. There are +// 2*(num_teams - 1) columns because each team meets num_teams - 1 +// opponents twice. +// +// - The 'opponent' var [i][j] is the index of the opposing team. +// - The 'home_away' var [i][j] is a boolean: 1 for 'playing away', +// 0 for 'playing at home'. +// - The 'signed_opponent' var [i][j] is the 'opponent' var [i][j] + +// num_teams * the 'home_away' var [i][j]. +// +// This aggregated variable will be useful to state constraints of the model +// and to do search on it. + +#include "ortools/base/commandlineflags.h" +#include "ortools/base/integral_types.h" +#include "ortools/base/join.h" +#include "ortools/base/logging.h" +#include "ortools/sat/cp_model.h" + +// Problem main flags. +DEFINE_int32(num_teams, 10, "Number of teams in the problem."); +DEFINE_string(params, "", "Sat parameters."); + +namespace operations_research { +namespace sat { + +void FirstModel(int num_teams) { + const int num_days = 2 * num_teams - 2; + const int kNoRematch = 6; + + CpModelBuilder builder; + + // Calendar variables. + std::vector> opponents(num_teams); + std::vector> home_aways(num_teams); + std::vector> signed_opponents(num_teams); + + for (int t = 0; t < num_teams; ++t) { + for (int d = 0; d < num_days; ++d) { + Domain opponent_domain(0, num_teams - 1); + Domain signed_opponent_domain(0, 2 * num_teams - 1); + IntVar opp = builder.NewIntVar(opponent_domain) + .WithName(absl::StrCat("opponent_", t, "_", d)); + BoolVar home = + builder.NewBoolVar().WithName(absl::StrCat("home_aways", t, "_", d)); + IntVar signed_opp = + builder.NewIntVar(signed_opponent_domain) + .WithName(absl::StrCat("signed_opponent_", t, "_", d)); + + opponents[t].push_back(opp); + home_aways[t].push_back(home); + signed_opponents[t].push_back(signed_opp); + + // One team cannot meet itself. + builder.AddNotEqual(opp, t); + builder.AddNotEqual(signed_opp, t); + builder.AddNotEqual(signed_opp, t + num_teams); + + // Link opponent, home_away, and signed_opponent. + builder.AddEquality(opp, signed_opp).OnlyEnforceIf(Not(home)); + builder.AddEquality(LinearExpr(opp).AddConstant(num_teams), signed_opp) + .OnlyEnforceIf(home); + } + } + + // One day constraints. + for (int d = 0; d < num_days; ++d) { + std::vector day_opponents; + std::vector day_home_aways; + for (int t = 0; t < num_teams; ++t) { + day_opponents.push_back(opponents[t][d]); + day_home_aways.push_back(home_aways[t][d]); + } + + builder.AddInverseConstraint(day_opponents, day_opponents); + + for (int first_team = 0; first_team < num_teams; ++first_team) { + IntVar first_home = day_home_aways[first_team]; + IntVar second_home = builder.NewBoolVar(); + builder.AddVariableElement(day_opponents[first_team], day_home_aways, + second_home); + builder.AddEquality(LinearExpr::Sum({first_home, second_home}), 1); + } + + builder.AddEquality(LinearExpr::Sum(day_home_aways), num_teams / 2); + } + + // One team constraints. + for (int t = 0; t < num_teams; ++t) { + builder.AddAllDifferent(signed_opponents[t]); + const std::vector first_part(opponents[t].begin(), + opponents[t].begin() + num_teams - 1); + builder.AddAllDifferent(first_part); + const std::vector second_part(opponents[t].begin() + num_teams - 1, + opponents[t].end()); + + builder.AddAllDifferent(second_part); + + for (int day = num_teams - kNoRematch; day < num_teams - 1; ++day) { + const std::vector moving(opponents[t].begin() + day, + opponents[t].begin() + day + kNoRematch); + builder.AddAllDifferent(moving); + } + + builder.AddEquality(LinearExpr::BooleanSum(home_aways[t]), num_teams - 1); + + // Forbid sequence of 3 homes or 3 aways. + for (int start = 0; start < num_days - 2; ++start) { + builder.AddBoolOr({home_aways[t][start], home_aways[t][start + 1], + home_aways[t][start + 2]}); + builder.AddBoolOr({Not(home_aways[t][start]), + Not(home_aways[t][start + 1]), + Not(home_aways[t][start + 2])}); + } + } + + // Objective. + std::vector breaks; + for (int t = 0; t < num_teams; ++t) { + for (int d = 0; d < num_days - 1; ++d) { + BoolVar break_var = + builder.NewBoolVar().WithName(absl::StrCat("break_", t, "_", d)); + builder.AddBoolOr( + {Not(home_aways[t][d]), Not(home_aways[t][d + 1]), break_var}); + builder.AddBoolOr({home_aways[t][d], home_aways[t][d + 1], break_var}); + breaks.push_back(break_var); + } + } + + builder.Minimize(LinearExpr::BooleanSum(breaks)); + + Model model; + if (!FLAGS_params.empty()) { + model.Add(NewSatParameters(FLAGS_params)); + } + + const CpSolverResponse response = SolveWithModel(builder, &model); + LOG(INFO) << CpSolverResponseStats(response); + + if (response.status() == CpSolverStatus::OPTIMAL || + response.status() == CpSolverStatus::FEASIBLE) { + for (int t = 0; t < num_teams; ++t) { + std::string output; + for (int d = 0; d < num_days; ++d) { + const int opponent = SolutionIntegerValue(response, opponents[t][d]); + const bool home = SolutionBooleanValue(response, home_aways[t][d]); + if (home) { + output += absl::StrCat(" %2d@", opponent); + } else { + output += absl::StrCat(" %2d ", opponent); + } + } + LOG(INFO) << output; + } + } +} + +void SecondModel(int num_teams) { + const int num_days = 2 * num_teams - 2; + // const int kNoRematch = 6; + const int matches_per_day = num_teams - 1; + + CpModelBuilder builder; + + // Does team i receive team j at home on day d? + std::vector>> fixtures(num_days); + for (int d = 0; d < num_days; ++d) { + fixtures[d].resize(num_teams); + for (int i = 0; i < num_teams; ++i) { + fixtures[d][i].resize(num_teams); + for (int j = 0; j < num_teams; ++j) { + if (i == j) { + fixtures[d][i][i] = builder.FalseVar(); + } else { + fixtures[d][i][j] = builder.NewBoolVar(); + } + } + } + } + + // Is team t at home on day d? + std::vector> at_home(num_days); + for (int d = 0; d < num_days; ++d) { + for (int t = 0; t < num_teams; ++t) { + at_home[d].push_back(builder.NewBoolVar()); + } + } + + // Each day, Team t plays either at home or away. + for (int d = 0; d < num_days; ++d) { + for (int team = 0; team < num_teams; ++team) { + std::vector possible_opponents; + for (int other = 0; other < num_teams; ++other) { + if (team == other) + continue; + possible_opponents.push_back(fixtures[d][team][other]); + possible_opponents.push_back(fixtures[d][other][team]); + } + builder.AddEquality(LinearExpr::BooleanSum(possible_opponents), 1); + } + } + + // Each fixture happens once per season. + for (int team = 0; team < num_teams; ++team) { + for (int other = 0; other < num_teams; ++other) { + if (team == other) + continue; + std::vector possible_days; + for (int d = 0; d < num_days; ++d) { + possible_days.push_back(fixtures[d][team][other]); + } + builder.AddEquality(LinearExpr::BooleanSum(possible_days), 1); + } + } + + // Meet each opponent once per season. + for (int team = 0; team < num_teams; ++team) { + for (int other = 0; other < num_teams; ++other) { + if (team == other) + continue; + std::vector first_half; + std::vector second_half; + for (int d = 0; d < matches_per_day; ++d) { + first_half.push_back(fixtures[d][team][other]); + first_half.push_back(fixtures[d][other][team]); + second_half.push_back(fixtures[d + matches_per_day][team][other]); + second_half.push_back(fixtures[d + matches_per_day][other][team]); + } + builder.AddEquality(LinearExpr::BooleanSum(first_half), 1); + builder.AddEquality(LinearExpr::BooleanSum(second_half), 1); + } + } + + // Maintain at_home[day][team]. + for (int d = 0; d < num_days; ++d) { + for (int team = 0; team < num_teams; ++team) { + for (int other = 0; other < num_teams; ++other) { + if (team == other) + continue; + builder.AddImplication(fixtures[d][team][other], at_home[d][team]); + builder.AddImplication(fixtures[d][team][other], + Not(at_home[d][other])); + } + } + } + + // Forbid sequence of 3 homes or 3 aways. + for (int team = 0; team < num_teams; ++team) { + for (int d = 0; d < num_days - 2; ++d) { + builder.AddBoolOr( + {at_home[d][team], at_home[d + 1][team], at_home[d + 2][team]}); + builder.AddBoolOr({Not(at_home[d][team]), Not(at_home[d + 1][team]), + Not(at_home[d + 2][team])}); + } + } + + // Objective. + std::vector breaks; + for (int t = 0; t < num_teams; ++t) { + for (int d = 0; d < num_days - 1; ++d) { + BoolVar break_var = builder.NewBoolVar(); + builder.AddBoolOr( + {Not(at_home[d][t]), Not(at_home[d + 1][t]), break_var}); + builder.AddBoolOr({at_home[d][t], at_home[d + 1][t], break_var}); + builder.AddBoolOr( + {Not(at_home[d][t]), at_home[d + 1][t], Not(break_var)}); + builder.AddBoolOr( + {at_home[d][t], Not(at_home[d + 1][t]), Not(break_var)}); + breaks.push_back(break_var); + } + } + + builder.AddGreaterOrEqual(LinearExpr::BooleanSum(breaks), 2 * num_teams - 4); + + builder.Minimize(LinearExpr::BooleanSum(breaks)); + + Model model; + if (!FLAGS_params.empty()) { + model.Add(NewSatParameters(FLAGS_params)); + } + + const CpSolverResponse response = SolveWithModel(builder, &model); + LOG(INFO) << CpSolverResponseStats(response); +} + +} // namespace sat +} // namespace operations_research + +static const char kUsage[] = + "Usage: see flags.\nThis program runs a sports scheduling problem." + "There is no output besides the debug LOGs of the solver."; + +int main(int argc, char **argv) { + gflags::SetUsageMessage(kUsage); + gflags::ParseCommandLineFlags(&argc, &argv, true); + CHECK_EQ(0, FLAGS_num_teams % 2) << "The number of teams must be even"; + CHECK_GE(FLAGS_num_teams, 2) << "At least 2 teams"; + operations_research::sat::SecondModel(FLAGS_num_teams); + return EXIT_SUCCESS; +} diff --git a/makefiles/Makefile.cpp.mk b/makefiles/Makefile.cpp.mk index 1ab3e147a7..e8a10dc379 100755 --- a/makefiles/Makefile.cpp.mk +++ b/makefiles/Makefile.cpp.mk @@ -505,6 +505,7 @@ test_cc_cpp: \ rcc_mps_driver \ rcc_nqueens \ rcc_random_tsp \ + rcc_slitherlink_sat \ rcc_strawberry_fields_with_column_generation \ rcc_weighted_tardiness_sat $(MAKE) run \ @@ -535,12 +536,11 @@ test_cc_cpp: \ SOURCE=examples/cpp/network_routing.cc \ ARGS="--clients=10 --backbones=5 --demands=10 --traffic_min=5 --traffic_max=10 --min_client_degree=2 --max_client_degree=5 --min_backbone_degree=3 --max_backbone_degree=5 --max_capacity=20 --fixed_charge_cost=10" $(MAKE) run \ - SOURCE=examples/cpp/sports_scheduling.cc \ - ARGS="--num_teams=8 --time_limit=10000" + SOURCE=examples/cpp/sports_scheduling_sat.cc \ + ARGS="--params max_time_in_seconds:10.0" # $(MAKE) run SOURCE=examples/cpp/frequency_assignment_problem.cc # Need data file -# $(MAKE) run SOURCE=examples/cpp/model_util.cc # Need data file # $(MAKE) run SOURCE=examples/cpp/pdptw.cc ARGS="--pdp_file examples/data/pdptw/LC1_2_1.txt" # Fails on windows... -# $(MAKE) run SOURCE=examples/cpp/shift_minimization_sat.cc # Port to new API. + $(MAKE) run SOURCE=examples/cpp/shift_minimization_sat.cc ARGS="--input examples/data/shift_scheduling/minimization/data_1_23_40_66.dat" # $(MAKE) run SOURCE=examples/cpp/solve.cc # Need data file .PHONY: test_cc_pimpl