remove model_util example; port slitherlink to CP-SAT; port sports_scheduling to CP-SAT; fix test_cc

This commit is contained in:
Laurent Perron
2018-11-22 01:30:28 -08:00
parent 5d26ad890f
commit 78d31daf0f
6 changed files with 592 additions and 1218 deletions

View File

@@ -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 <memory>
#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 <class T>
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<GraphExporter> 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<SearchMonitor*> 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<IntVar*> primary_integer_variables;
std::vector<IntVar*> secondary_integer_variables;
std::vector<SequenceVar*> sequence_variables;
std::vector<IntervalVar*> 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();
}

View File

@@ -1,405 +0,0 @@
#include <deque>
#include <string>
#include <unordered_set>
#include <vector>
#include "ortools/constraint_solver/constraint_solver.h"
#include "ortools/constraint_solver/constraint_solveri.h"
#include "ortools/util/string_array.h"
const std::vector<std::vector<int>> small = {
{3, 2, -1, 3}, {-1, -1, -1, 2}, {3, -1, -1, -1}, {3, -1, 3, 1}};
const std::vector<std::vector<int>> 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<std::vector<int>> 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<IntVar*> NeighboringArcs(
int i, int j, const std::vector<std::vector<IntVar*>>& h_arcs,
const std::vector<std::vector<IntVar*>>& v_arcs) {
std::vector<IntVar*> 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<IntVar*>& 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<IntVar*> vars_;
NumericalRev<int> num_possible_true_vars_;
NumericalRev<int> num_always_true_vars_;
};
Constraint* MakeBooleanSumEven(Solver* const s, const std::vector<IntVar*>& 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<std::vector<IntVar*>>& h_arcs,
const std::vector<std::vector<IntVar*>>& 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<int> possible_points;
std::vector<std::vector<int>> 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<int> visited_points;
std::deque<int> 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<IntVar*> neighbors =
NeighboringArcs(i, j, h_arcs_, v_arcs_);
for (IntVar* const var : neighbors) {
var->SetMax(0);
}
}
}
}
private:
const std::vector<std::vector<IntVar*>> h_arcs_;
const std::vector<std::vector<IntVar*>> v_arcs_;
};
Constraint* MakeSingleLoop(Solver* const solver,
const std::vector<std::vector<IntVar*>>& h_arcs,
const std::vector<std::vector<IntVar*>>& v_arcs) {
return solver->RevAlloc(new GridSinglePath(solver, h_arcs, v_arcs));
}
void PrintSolution(const std::vector<std::vector<int>>& data,
const std::vector<std::vector<IntVar*>>& h_arcs,
const std::vector<std::vector<IntVar*>>& 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<std::vector<int>>& data) {
const int num_rows = data.size();
const int num_columns = data[0].size();
Solver solver("slitherlink");
std::vector<IntVar*> all_vars;
std::vector<std::vector<IntVar*>> 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<std::vector<IntVar*>> 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<IntVar*> 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<int> 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<IntVar*> 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<IntVar*> 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<IntVar*> 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;
}

View File

@@ -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 <deque>
#include <string>
#include <unordered_set>
#include <vector>
#include "ortools/base/stringprintf.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/model.h"
const std::vector<std::vector<int>> tiny = {{3, 3, 1}};
const std::vector<std::vector<int>> small = {
{3, 2, -1, 3}, {-1, -1, -1, 2}, {3, -1, -1, -1}, {3, -1, 3, 1}};
const std::vector<std::vector<int>> 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<std::vector<int>> 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<std::vector<int>> &data,
const std::vector<std::vector<bool>> &h_arcs,
const std::vector<std::vector<bool>> &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<std::vector<int>> &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<BoolVar> horizontal_arcs;
for (int arc = 0; arc < 2 * num_horizontal_arcs; ++arc) {
horizontal_arcs.push_back(builder.NewBoolVar());
}
std::vector<BoolVar> 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<BoolVar> 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<BoolVar> 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<std::vector<bool>> 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<std::vector<bool>> 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;
}

View File

@@ -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<IntVar*> 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<IntVar*> opponents;
std::vector<IntVar*> home_aways;
std::vector<IntVar*> 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<IntVar*> 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<int> 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<IntVar*>& opponents,
const std::vector<IntVar*>& home_aways,
const std::vector<IntVar*>& 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<IntVar*> 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<IntVar*> 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<IntVar*> home_aways;
solver.MakeBoolVarArray(full_season, "home_away_", &home_aways);
for (int day = 0; day < full_season - 2; ++day) {
std::vector<IntVar*> 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<int> 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<std::vector<IntVar*> > opponents(num_teams);
// The location of the match (home or away).
std::vector<std::vector<IntVar*> > home_aways(num_teams);
// Disambiguated version of the opponent variable incorporating the
// home_away result.
std::vector<std::vector<IntVar*> > 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<IntVar*> 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<IntVar*> 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<SearchMonitor*> 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<IntVar*> 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;
}

View File

@@ -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<std::vector<IntVar>> opponents(num_teams);
std::vector<std::vector<BoolVar>> home_aways(num_teams);
std::vector<std::vector<IntVar>> 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<IntVar> day_opponents;
std::vector<IntVar> 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<IntVar> first_part(opponents[t].begin(),
opponents[t].begin() + num_teams - 1);
builder.AddAllDifferent(first_part);
const std::vector<IntVar> 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<IntVar> 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<BoolVar> 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<std::vector<std::vector<BoolVar>>> 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<std::vector<BoolVar>> 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<BoolVar> 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<BoolVar> 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<BoolVar> first_half;
std::vector<BoolVar> 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<BoolVar> 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;
}

View File

@@ -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