reorganize all examples in the examples directory
This commit is contained in:
55
examples/cpp/README
Normal file
55
examples/cpp/README
Normal file
@@ -0,0 +1,55 @@
|
||||
<summary> C++ examples to demonstrate usage of the different
|
||||
Operations Research libraries. </summary>
|
||||
|
||||
- %Constraint Solver examples:
|
||||
- cryptarithm.cc Demonstrates the use of basic modeling objects
|
||||
(integer variables, arithmetic constraints and expressions,
|
||||
simple search).
|
||||
- golomb.cc Demonstrates how to handle objective functions and collect
|
||||
solutions found during the search.
|
||||
- magic_square.cc Shows how to use the automatic search to solve your
|
||||
problem.
|
||||
- costas_array.cc Solves the problem of Costas Array (a constrained
|
||||
assignment problem used for radars) with two version. On version is
|
||||
a feasibility version with hard constraints, the other version is
|
||||
an optimization version with soft constraints and violation costs.
|
||||
- jobshop.cc Demonstrates scheduling of jobs on different machines.
|
||||
- jobshop_ls.cc Demonstrates scheduling of jobs on different machines with
|
||||
a search using Local Search and Large Neighorhood Search.
|
||||
- nqueens.cc Solves the n-queen problem. It also demonstrates how to break
|
||||
symmetries during search.
|
||||
- network_routing.cc Solves a multicommodity mono-routing
|
||||
problem with capacity constraints and a max usage cost structure.
|
||||
- sports_scheduling.cc Finds a soccer championship schedule. Its uses an
|
||||
original approach where all constraints attached to either one team,
|
||||
or one week are regrouped into one global 'AllowedAssignment' constraints.
|
||||
- dobble_ls.cc Shows how to write Local Search operators and Local Search
|
||||
filters in a context of an assignment/partitioning problem. It also
|
||||
shows how to write a simple constraint.
|
||||
|
||||
- Routing examples:
|
||||
- tsp.cc Travelling Salesman Problem.
|
||||
- cvrptw.cc Capacitated Vehicle Routing Problem with Time Windows.
|
||||
- pdptw.cc Pickup and Delivery Problem with Time Windows.
|
||||
|
||||
- Graph examples:
|
||||
- flow_api.cc Demonstrates how to use Min-Cost Flow and Max-Flow api.
|
||||
- linear_assignment_api.cc Demonstrates how to use the Linear Sum
|
||||
Assignment solver.
|
||||
- dimacs_assignment.cc Solves DIMACS challenge on assignment
|
||||
problems.
|
||||
|
||||
- Linear and integer programming examples:
|
||||
- linear_programming.cc Demonstrates how to use the linear solver
|
||||
wrapper API to solve Linear Programming problems.
|
||||
- integer_programming.cc Demonstrates how to use the linear solver
|
||||
wrapper API to solve Integer Programming problems.
|
||||
- linear_solver_protocol_buffers.cc Demonstrates how protocol
|
||||
buffers can be used as input and output to the linear solver wrapper.
|
||||
- strawberry_fields_with_column_generation.cc Complex example that
|
||||
demonstrates how to use dynamic column generation to solve a 2D
|
||||
covering problem.
|
||||
|
||||
- Utilities
|
||||
- model_util.cc A utility to manipulate model files (.cp) dumped by the
|
||||
solver.
|
||||
476
examples/cpp/costas_array.cc
Normal file
476
examples/cpp/costas_array.cc
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Costas Array Problem
|
||||
//
|
||||
// Finds an NxN matrix of 0s and 1s, with only one 1 per row,
|
||||
// and one 1 per column, such that all displacement vectors
|
||||
// between each pair of 1s are distinct.
|
||||
//
|
||||
// This example contains two separate implementations. CostasHard()
|
||||
// uses hard constraints, whereas CostasSoft() uses a minimizer to
|
||||
// minimize the number of duplicates.
|
||||
#include <time.h>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include "base/callback.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
#include "constraint_solver/constraint_solveri.h"
|
||||
#include "base/random.h"
|
||||
|
||||
DEFINE_int32(minsize, 0, "Minimum degree of Costas matrix.");
|
||||
DEFINE_int32(maxsize, 0, "Maximum degree of Costas matrix.");
|
||||
DEFINE_int32(freevar, 5, "Number of free variables.");
|
||||
DEFINE_int32(freeorderedvar, 4, "Number of variables in ordered subset.");
|
||||
DEFINE_int32(sublimit, 20, "Number of attempts per subtree.");
|
||||
DEFINE_int32(timelimit, 120000, "Time limit for local search.");
|
||||
DEFINE_bool(soft_constraints, false, "Use soft constraints.");
|
||||
DEFINE_string(export_profile, "", "filename to save the profile overview");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// Checks that all pairwise distances are unique and returns all violators
|
||||
void CheckConstraintViolators(const std::vector<int64>& vars,
|
||||
std::vector<int>* const violators) {
|
||||
int dim = vars.size();
|
||||
|
||||
// Check that all indices are unique
|
||||
for (int i = 0; i < dim; ++i) {
|
||||
for (int k = i + 1; k < dim; ++k) {
|
||||
if (vars[i] == vars[k]) {
|
||||
violators->push_back(i);
|
||||
violators->push_back(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all differences are unique for each level
|
||||
for (int level = 1; level < dim; ++level) {
|
||||
for (int i = 0; i < dim - level; ++i) {
|
||||
const int difference = vars[i + level] - vars[i];
|
||||
|
||||
for (int k = i + 1; k < dim - level; ++k) {
|
||||
if (difference == vars[k + level] - vars[k]) {
|
||||
violators->push_back(k + level);
|
||||
violators->push_back(k);
|
||||
violators->push_back(i + level);
|
||||
violators->push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all pairwise differences are unique
|
||||
bool CheckCostas(const std::vector<int64>& vars) {
|
||||
std::vector<int> violators;
|
||||
|
||||
CheckConstraintViolators(vars, &violators);
|
||||
|
||||
return violators.empty();
|
||||
}
|
||||
|
||||
// Cycles all possible permutations
|
||||
class OrderedLNS: public BaseLNS {
|
||||
public:
|
||||
OrderedLNS(const std::vector<IntVar*>& vars, int free_elements) :
|
||||
BaseLNS(vars.data(), vars.size()),
|
||||
free_elements_(free_elements) {
|
||||
index_ = 0;
|
||||
|
||||
// Start of with the first free_elements_ as a permutations, eg. 0,1,2,3,...
|
||||
for (int i = 0; i < free_elements; ++i) {
|
||||
index_ += i * pow(static_cast<double>(vars.size()),
|
||||
free_elements - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool NextFragment(std::vector<int>* const fragment) {
|
||||
int dim = Size();
|
||||
std::set<int> fragment_set;
|
||||
|
||||
do {
|
||||
int work_index = index_;
|
||||
|
||||
fragment->clear();
|
||||
fragment_set.clear();
|
||||
|
||||
for (int i = 0; i < free_elements_; ++i) {
|
||||
int current_index = work_index % dim;
|
||||
work_index = work_index / dim;
|
||||
|
||||
std::pair<std::set<int>::iterator, bool> ret =
|
||||
fragment_set.insert(current_index);
|
||||
|
||||
// Check if element has been used before
|
||||
if (ret.second) {
|
||||
fragment->push_back(current_index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Go to next possible permutation
|
||||
++index_;
|
||||
// Try again if a duplicate index is used
|
||||
} while (fragment_set.size() < free_elements_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int index_;
|
||||
const int free_elements_;
|
||||
};
|
||||
|
||||
// RandomLNS is used for the local search and frees the
|
||||
// number of elements specified in 'free_elements' randomly.
|
||||
class RandomLNS: public BaseLNS {
|
||||
public:
|
||||
RandomLNS(const std::vector<IntVar*>& vars, int free_elements) :
|
||||
BaseLNS(vars.data(), vars.size()),
|
||||
free_elements_(free_elements),
|
||||
rand_(ACMRandom::HostnamePidTimeSeed()) {
|
||||
}
|
||||
|
||||
virtual bool NextFragment(std::vector<int>* const fragment) {
|
||||
std::vector<int> weighted_elements;
|
||||
std::vector<int64> values;
|
||||
|
||||
// Create weighted vector for randomizer. Add one of each possible elements
|
||||
// to the weighted vector and then add more elements depending on the
|
||||
// number of conflicts
|
||||
for (int i = 0; i < Size(); ++i) {
|
||||
values.push_back(Value(i));
|
||||
|
||||
// Insert each variable at least once.
|
||||
weighted_elements.push_back(i);
|
||||
}
|
||||
|
||||
CheckConstraintViolators(values, &weighted_elements);
|
||||
int size = weighted_elements.size();
|
||||
|
||||
// Randomly insert elements in vector until no more options remain
|
||||
while (fragment->size() < std::min(free_elements_, size)) {
|
||||
const int index = weighted_elements[rand_.Next() % size];
|
||||
fragment->push_back(index);
|
||||
|
||||
// Remove all elements with this index from weighted_elements
|
||||
for (std::vector<int>::iterator pos = weighted_elements.begin();
|
||||
pos != weighted_elements.end(); ) {
|
||||
if (*pos == index) {
|
||||
// Try to erase as many elements as possible at the same time
|
||||
std::vector<int>::iterator end = pos;
|
||||
|
||||
while ((end + 1) != weighted_elements.end() && *(end + 1) == *pos) {
|
||||
++end;
|
||||
}
|
||||
|
||||
pos = weighted_elements.erase(pos, end);
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
size = weighted_elements.size();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const int free_elements_;
|
||||
ACMRandom rand_;
|
||||
};
|
||||
|
||||
class Evaluator {
|
||||
public:
|
||||
explicit Evaluator(const std::vector<IntVar*>& vars) : vars_(vars) {}
|
||||
|
||||
// Prefer the value with the smallest domain
|
||||
int64 VarEvaluator(int64 index) {
|
||||
return vars_[index]->Size();
|
||||
}
|
||||
|
||||
// Penalize for each time the value appears in a different domain,
|
||||
// as values have to be unique
|
||||
int64 ValueEvaluator(int64 id, int64 value) {
|
||||
int appearance = 0;
|
||||
|
||||
for (int i = 0; i < vars_.size(); ++i) {
|
||||
if (i != id && vars_[i]->Contains(value)) {
|
||||
++appearance;
|
||||
}
|
||||
}
|
||||
|
||||
return appearance;
|
||||
}
|
||||
private:
|
||||
std::vector<IntVar*> vars_;
|
||||
};
|
||||
|
||||
// Computes a Costas Array using soft constraints.
|
||||
// Instead of enforcing that all distance vectors are unique, we
|
||||
// minimize the number of duplicate distance vectors.
|
||||
void CostasSoft(const int dim) {
|
||||
Solver solver("Costas");
|
||||
|
||||
// For the matrix and for the count of occurrences of each possible distance
|
||||
// for each stage
|
||||
const int num_elements = dim + (2 * dim + 1) * (dim);
|
||||
|
||||
// create the variables
|
||||
std::vector<IntVar*> vars;
|
||||
solver.MakeIntVarArray(num_elements, -dim, dim, "var_", &vars);
|
||||
|
||||
// the costas matrix
|
||||
std::vector<IntVar*> matrix(dim);
|
||||
// number of occurrences per stage
|
||||
std::vector<IntVar*> occurences;
|
||||
|
||||
// All possible values of the distance vector
|
||||
// used to count the occurrence of all these values
|
||||
std::vector<int64> possible_values(2 * dim + 1);
|
||||
|
||||
for (int64 i = -dim; i <= dim; ++i) {
|
||||
possible_values[i + dim] = i;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
|
||||
// Initialize the matrix that contains the coordinates of all '1' per row
|
||||
for (; index < dim; ++index) {
|
||||
matrix[index] = vars[index];
|
||||
vars[index]->SetMin(1);
|
||||
}
|
||||
|
||||
// First constraint for the elements in the Costas Matrix. We want
|
||||
// them to be unique.
|
||||
std::vector<IntVar*> matrix_count;
|
||||
solver.MakeIntVarArray(2 * dim + 1, 0, dim, "matrix_count_", &matrix_count);
|
||||
solver.AddConstraint(solver.MakeDistribute(matrix, possible_values,
|
||||
matrix_count));
|
||||
|
||||
// Here we only consider the elements from 1 to dim.
|
||||
for (int64 j = dim + 1; j <= 2 * dim; ++j) {
|
||||
// Penalize if an element occurs more than once.
|
||||
vars[index]
|
||||
= solver.MakeSemiContinuousExpr(solver.MakeSum(matrix_count[j], -1),
|
||||
0, 1)->Var();
|
||||
|
||||
occurences.push_back(vars[index++]);
|
||||
}
|
||||
|
||||
// Count the number of duplicates for each stage
|
||||
for (int i = 1; i < dim; ++i) {
|
||||
std::vector<IntVar*> subset(dim - i);
|
||||
|
||||
// Initialize each stage
|
||||
for (int j = 0; j < dim - i; ++j) {
|
||||
IntVar* const diff = solver.MakeDifference(vars[j + i], vars[j])->Var();
|
||||
subset[j] = diff;
|
||||
}
|
||||
|
||||
// Count the number of occurrences for all possible values
|
||||
std::vector<IntVar*> domain_count;
|
||||
solver.MakeIntVarArray(2 * dim + 1, 0, dim, "domain_count_", &domain_count);
|
||||
solver.AddConstraint(solver.MakeDistribute(subset,
|
||||
possible_values,
|
||||
domain_count));
|
||||
|
||||
// Penalize occurrences of more than one
|
||||
for (int64 j = 0; j <= 2 * dim; ++j) {
|
||||
vars[index] =
|
||||
solver.MakeSemiContinuousExpr(solver.MakeSum(domain_count[j], -1),
|
||||
0, dim - i)->Var();
|
||||
|
||||
occurences.push_back(vars[index++]);
|
||||
}
|
||||
}
|
||||
|
||||
// We would like to minimize the penalties that we just computed
|
||||
IntVar* const objective_var = solver.MakeSum(occurences)->Var();
|
||||
OptimizeVar* const total_duplicates = solver.MakeMinimize(objective_var, 1);
|
||||
|
||||
SearchMonitor* const log = solver.MakeSearchLog(1000, objective_var);
|
||||
|
||||
// Out of all solutions, we just want to store the last one.
|
||||
SolutionCollector* const collector = solver.MakeLastSolutionCollector();
|
||||
collector->Add(vars);
|
||||
|
||||
// The first solution that the local optimization is based on
|
||||
Evaluator evaluator(matrix);
|
||||
DecisionBuilder* const first_solution =
|
||||
solver.MakePhase(matrix,
|
||||
NewPermanentCallback(&evaluator,
|
||||
&Evaluator::VarEvaluator),
|
||||
NewPermanentCallback(&evaluator,
|
||||
&Evaluator::ValueEvaluator));
|
||||
|
||||
SearchLimit* const search_time_limit =
|
||||
solver.MakeLimit(FLAGS_timelimit, kint64max, kint64max, kint64max);
|
||||
|
||||
// Locally optimize solutions for LNS
|
||||
SearchLimit* const fail_limit = solver.MakeLimit(kint64max, kint64max,
|
||||
FLAGS_sublimit, kint64max);
|
||||
|
||||
DecisionBuilder* const subdecision_builder =
|
||||
solver.MakeSolveOnce(first_solution, fail_limit);
|
||||
|
||||
std::vector<LocalSearchOperator*> localSearchOperators;
|
||||
|
||||
// Apply RandomLNS to free FLAGS_freevar variables at each stage
|
||||
localSearchOperators.push_back(
|
||||
solver.RevAlloc(new OrderedLNS(matrix, FLAGS_freevar)));
|
||||
|
||||
// Go through all possible permutations one by one
|
||||
localSearchOperators.push_back(
|
||||
solver.RevAlloc(new OrderedLNS(matrix, FLAGS_freeorderedvar)));
|
||||
|
||||
LocalSearchPhaseParameters* const ls_params =
|
||||
solver.MakeLocalSearchPhaseParameters(
|
||||
solver.ConcatenateOperators(localSearchOperators),
|
||||
subdecision_builder);
|
||||
|
||||
DecisionBuilder* const second_phase =
|
||||
solver.MakeLocalSearchPhase(matrix.data(),
|
||||
matrix.size(),
|
||||
first_solution,
|
||||
ls_params);
|
||||
|
||||
// Try to find a solution
|
||||
solver.Solve(second_phase,
|
||||
collector,
|
||||
log,
|
||||
total_duplicates,
|
||||
search_time_limit);
|
||||
|
||||
if (collector->solution_count() > 0) {
|
||||
std::vector<int64> costas_matrix;
|
||||
string output;
|
||||
|
||||
for (int n = 0; n < dim; ++n) {
|
||||
const int64 v = collector->Value(0, vars[n]);
|
||||
costas_matrix.push_back(v);
|
||||
StringAppendF(&output, "%3lld", v);
|
||||
}
|
||||
|
||||
if (!CheckCostas(costas_matrix)) {
|
||||
LOG(INFO) << "No Costas Matrix found, closest solution displayed.";
|
||||
}
|
||||
|
||||
LOG(INFO) << output;
|
||||
} else {
|
||||
LOG(INFO) << "No solution found";
|
||||
}
|
||||
}
|
||||
|
||||
// Computes a Costas Array.
|
||||
void CostasHard(const int dim) {
|
||||
SolverParameters parameters;
|
||||
if (!FLAGS_export_profile.empty()) {
|
||||
parameters.profile_level = SolverParameters::NORMAL_PROFILING;
|
||||
}
|
||||
Solver solver("costas", parameters);
|
||||
|
||||
// we need space for the matrix and for each pair
|
||||
const int num_elements = dim + dim * (dim - 1) / 2;
|
||||
|
||||
// create the variables
|
||||
std::vector<IntVar*> vars;
|
||||
solver.MakeIntVarArray(num_elements, -dim, dim, "var", &vars);
|
||||
|
||||
std::vector<IntVar*> matrix(dim);
|
||||
|
||||
// Initialize the matrix that contains the coordinates of all '1' per row
|
||||
for (int m = 0; m < dim; ++m) {
|
||||
matrix[m] = vars[m];
|
||||
vars[m]->SetMin(1);
|
||||
}
|
||||
|
||||
solver.AddConstraint(solver.MakeAllDifferent(matrix));
|
||||
|
||||
int index = dim;
|
||||
|
||||
// Check that the pairwise difference is unique
|
||||
for (int i = 1; i < dim; ++i) {
|
||||
std::vector<IntVar*> subset(dim - i);
|
||||
|
||||
for (int j = 0; j < dim - i; ++j) {
|
||||
IntVar* const diff = solver.MakeDifference(vars[j + i], vars[j])->Var();
|
||||
vars[index++] = diff;
|
||||
subset[j] = diff;
|
||||
}
|
||||
|
||||
solver.AddConstraint(solver.MakeAllDifferent(subset));
|
||||
}
|
||||
|
||||
DecisionBuilder* const db = solver.MakePhase(vars,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
solver.NewSearch(db);
|
||||
|
||||
if (solver.NextSolution()) {
|
||||
std::vector<int64> costas_matrix;
|
||||
string output;
|
||||
|
||||
for (int n = 0; n < dim; ++n) {
|
||||
const int64 v = vars[n]->Value();
|
||||
costas_matrix.push_back(v);
|
||||
StringAppendF(&output, "%3lld", v);
|
||||
}
|
||||
|
||||
LOG(INFO) << output << " (" << solver.wall_time() << "ms)";
|
||||
|
||||
CHECK(CheckCostas(costas_matrix)) <<
|
||||
": Solution is not a valid Costas Matrix.";
|
||||
} else {
|
||||
LOG(INFO) << "No solution found.";
|
||||
}
|
||||
if (!FLAGS_export_profile.empty()) {
|
||||
solver.ExportProfilingOverview(FLAGS_export_profile);
|
||||
}
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
int min = 1;
|
||||
int max = 10;
|
||||
|
||||
if (FLAGS_minsize != 0) {
|
||||
min = FLAGS_minsize;
|
||||
|
||||
if (FLAGS_maxsize != 0) {
|
||||
max = FLAGS_maxsize;
|
||||
} else {
|
||||
max = min;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = min; i <= max; ++i) {
|
||||
LOG(INFO) << "Computing Costas Array for dim = " << i;
|
||||
if (FLAGS_soft_constraints) {
|
||||
operations_research::CostasSoft(i);
|
||||
} else {
|
||||
operations_research::CostasHard(i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
111
examples/cpp/cryptarithm.cc
Normal file
111
examples/cpp/cryptarithm.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Cryptoarithmetics problem
|
||||
//
|
||||
// Solves equation SEND + MORE = MONEY among numbers
|
||||
// where each digit is represented by a letter.
|
||||
//
|
||||
// Solution:
|
||||
// S=9; M=1; O=0; E=5; N=6; D=7; R=8; Y=2.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void Cryptoarithmetics() {
|
||||
Solver solver("cryptarithm");
|
||||
|
||||
// model with carry
|
||||
IntVar* s = solver.MakeIntVar(1, 9, "s");
|
||||
IntVar* m = solver.MakeIntVar(1, 9, "m");
|
||||
IntVar* o = solver.MakeIntVar(0, 9, "o");
|
||||
IntVar* e = solver.MakeIntVar(0, 9, "e");
|
||||
IntVar* n = solver.MakeIntVar(0, 9, "n");
|
||||
IntVar* d = solver.MakeIntVar(0, 9, "d");
|
||||
IntVar* r = solver.MakeIntVar(0, 9, "r");
|
||||
IntVar* y = solver.MakeIntVar(0, 9, "y");
|
||||
|
||||
std::vector<IntVar*> letters;
|
||||
letters.push_back(s);
|
||||
letters.push_back(m);
|
||||
letters.push_back(o);
|
||||
letters.push_back(e);
|
||||
letters.push_back(n);
|
||||
letters.push_back(d);
|
||||
letters.push_back(r);
|
||||
letters.push_back(y);
|
||||
|
||||
solver.AddConstraint(solver.MakeAllDifferent(letters));
|
||||
|
||||
// carry variables
|
||||
IntVar* c1 = solver.MakeIntVar(0, 1, "c1");
|
||||
IntVar* c2 = solver.MakeIntVar(0, 1, "c2");
|
||||
IntVar* c3 = solver.MakeIntVar(0, 1, "c3");
|
||||
|
||||
// initial constraint is separated into four small constraints
|
||||
IntVar* v1 = solver.MakeSum(e, d)->Var();
|
||||
IntVar* v2 = solver.MakeSum(y, solver.MakeProd(c1, 10))->Var();
|
||||
solver.AddConstraint(solver.MakeEquality(v1, v2));
|
||||
|
||||
v1 = solver.MakeSum(solver.MakeSum(c1, n), r)->Var();
|
||||
v2 = solver.MakeSum(e, solver.MakeProd(c2, 10))->Var();
|
||||
solver.AddConstraint(solver.MakeEquality(v1, v2));
|
||||
|
||||
v1 = solver.MakeSum(solver.MakeSum(c2, e), o)->Var();
|
||||
v2 = solver.MakeSum(n, solver.MakeProd(c3, 10))->Var();
|
||||
solver.AddConstraint(solver.MakeEquality(v1, v2));
|
||||
|
||||
v1 = solver.MakeSum(solver.MakeSum(c3, s), m)->Var();
|
||||
v2 = solver.MakeSum(o, solver.MakeProd(m, 10))->Var();
|
||||
solver.AddConstraint(solver.MakeEquality(v1, v2));
|
||||
|
||||
DecisionBuilder* const db = solver.MakePhase(letters,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
solver.NewSearch(db);
|
||||
if (solver.NextSolution()) {
|
||||
CHECK_EQ(s->Value(), 9);
|
||||
CHECK_EQ(m->Value(), 1);
|
||||
CHECK_EQ(o->Value(), 0);
|
||||
CHECK_EQ(e->Value(), 5);
|
||||
CHECK_EQ(n->Value(), 6);
|
||||
CHECK_EQ(d->Value(), 7);
|
||||
CHECK_EQ(r->Value(), 8);
|
||||
CHECK_EQ(y->Value(), 2);
|
||||
|
||||
LG << "S=" << s->Value();
|
||||
LG << "M=" << m->Value();
|
||||
LG << "O=" << o->Value();
|
||||
LG << "E=" << e->Value();
|
||||
LG << "N=" << n->Value();
|
||||
LG << "D=" << d->Value();
|
||||
LG << "R=" << r->Value();
|
||||
LG << "Y=" << y->Value();
|
||||
} else {
|
||||
LG << "Cannot solve problem: number of failures " << solver.failures();
|
||||
}
|
||||
solver.EndSearch();
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::Cryptoarithmetics();
|
||||
return 0;
|
||||
}
|
||||
282
examples/cpp/cvrptw.cc
Normal file
282
examples/cpp/cvrptw.cc
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Capacitated Vehicle Routing Problem with Time Windows (and optional orders).
|
||||
// A description of the problem can be found here:
|
||||
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
|
||||
// The variant which is tackled by this model includes a capacity dimension,
|
||||
// time windows and optional orders, with a penalty cost if orders are not
|
||||
// performed. For the sake of simplicty, orders are randomly located and
|
||||
// distances are computed using the Manhattan distance. Distances are assumed
|
||||
// to be in meters and times in seconds.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "constraint_solver/routing.h"
|
||||
#include "base/random.h"
|
||||
|
||||
using operations_research::Assignment;
|
||||
using operations_research::IntVar;
|
||||
using operations_research::RoutingModel;
|
||||
using operations_research::Solver;
|
||||
using operations_research::ACMRandom;
|
||||
using operations_research::StringAppendF;
|
||||
using operations_research::StringPrintf;
|
||||
using operations_research::scoped_array;
|
||||
using operations_research::scoped_ptr;
|
||||
|
||||
|
||||
DEFINE_int32(vrp_orders, 100, "Nodes in the problem.");
|
||||
DEFINE_int32(vrp_vehicles, 20, "Size of Traveling Salesman Problem instance.");
|
||||
DEFINE_bool(vrp_use_deterministic_random_seed, false,
|
||||
"Use deterministic random seeds.");
|
||||
|
||||
const char* kTime = "Time";
|
||||
const char* kCapacity = "Capacity";
|
||||
|
||||
// Random seed generator.
|
||||
int32 GetSeed() {
|
||||
if (FLAGS_vrp_use_deterministic_random_seed) {
|
||||
return ACMRandom::DeterministicSeed();
|
||||
} else {
|
||||
return ACMRandom::HostnamePidTimeSeed();
|
||||
}
|
||||
}
|
||||
|
||||
// Location container, contains positions of orders and can be used to obtain
|
||||
// Manhattan distances/times between locations.
|
||||
class LocationContainer {
|
||||
public:
|
||||
explicit LocationContainer(int64 speed)
|
||||
: randomizer_(GetSeed()), speed_(speed) {
|
||||
CHECK_LT(0, speed_);
|
||||
}
|
||||
void AddLocation(int64 x, int64 y) {
|
||||
locations_.push_back(Location(x, y));
|
||||
}
|
||||
void AddRandomLocation(int64 x_max, int64 y_max) {
|
||||
AddLocation(randomizer_.Uniform(x_max + 1), randomizer_.Uniform(y_max + 1));
|
||||
}
|
||||
int64 ManhattanDistance(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return locations_[from].DistanceTo(locations_[to]);
|
||||
}
|
||||
int64 ManhattanTime(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return ManhattanDistance(from, to) / speed_;
|
||||
}
|
||||
|
||||
private:
|
||||
class Location {
|
||||
public:
|
||||
Location() : x_(0), y_(0) {}
|
||||
Location(int64 x, int64 y) : x_(x), y_(y) {}
|
||||
int64 DistanceTo(const Location& location) const {
|
||||
return Abs(x_ - location.x_) + Abs(y_ - location.y_);
|
||||
}
|
||||
private:
|
||||
static int64 Abs(int64 value) { return std::max(value, -value); }
|
||||
|
||||
int64 x_;
|
||||
int64 y_;
|
||||
};
|
||||
|
||||
ACMRandom randomizer_;
|
||||
const int64 speed_;
|
||||
ITIVector<RoutingModel::NodeIndex, Location> locations_;
|
||||
};
|
||||
|
||||
// Random demand.
|
||||
class RandomDemand {
|
||||
public:
|
||||
RandomDemand(int size, RoutingModel::NodeIndex depot)
|
||||
: size_(size), depot_(depot) {
|
||||
CHECK_LT(0, size_);
|
||||
}
|
||||
void Initialize() {
|
||||
const int64 kDemandMax = 5;
|
||||
const int64 kDemandMin = 1;
|
||||
demand_.reset(new int64[size_]);
|
||||
ACMRandom randomizer(GetSeed());
|
||||
for (int order = 0; order < size_; ++order) {
|
||||
if (order == depot_) {
|
||||
demand_[order] = 0;
|
||||
} else {
|
||||
demand_[order] =
|
||||
kDemandMin + randomizer.Uniform(kDemandMax - kDemandMin + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
int64 Demand(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return demand_[from.value()];
|
||||
}
|
||||
|
||||
private:
|
||||
scoped_array<int64> demand_;
|
||||
const int size_;
|
||||
const RoutingModel::NodeIndex depot_;
|
||||
};
|
||||
|
||||
// Service time (proportional to demand) + transition time callback.
|
||||
class ServiceTimePlusTransition {
|
||||
public:
|
||||
ServiceTimePlusTransition(int64 time_per_demand_unit,
|
||||
RoutingModel::NodeEvaluator2* demand,
|
||||
RoutingModel::NodeEvaluator2* transition_time)
|
||||
: time_per_demand_unit_(time_per_demand_unit),
|
||||
demand_(demand),
|
||||
transition_time_(transition_time) {}
|
||||
int64 Compute(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return time_per_demand_unit_ * demand_->Run(from, to)
|
||||
+ transition_time_->Run(from, to);
|
||||
}
|
||||
private:
|
||||
const int64 time_per_demand_unit_;
|
||||
scoped_ptr<RoutingModel::NodeEvaluator2> demand_;
|
||||
scoped_ptr<RoutingModel::NodeEvaluator2> transition_time_;
|
||||
};
|
||||
|
||||
// Route plan displayer.
|
||||
// TODO(user): Move the display code to the routing library.
|
||||
void DisplayPlan(const RoutingModel& routing, const Assignment& plan) {
|
||||
// Display plan cost.
|
||||
string plan_output = StringPrintf("Cost %lld\n", plan.ObjectiveValue());
|
||||
|
||||
// Display dropped orders.
|
||||
string dropped;
|
||||
for (int order = 1; order < routing.nodes(); ++order) {
|
||||
if (plan.Value(routing.NextVar(order)) == order) {
|
||||
if (dropped.empty()) {
|
||||
StringAppendF(&dropped, " %d", order);
|
||||
} else {
|
||||
StringAppendF(&dropped, ", %d", order);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dropped.empty()) {
|
||||
plan_output += "Dropped orders:" + dropped + "\n";
|
||||
}
|
||||
|
||||
// Display actual output for each vehicle.
|
||||
for (int route_number = 0;
|
||||
route_number < routing.vehicles();
|
||||
++route_number) {
|
||||
int64 order = routing.Start(route_number);
|
||||
StringAppendF(&plan_output, "Route %d: ", route_number);
|
||||
if (routing.IsEnd(plan.Value(routing.NextVar(order)))) {
|
||||
plan_output += "Empty\n";
|
||||
} else {
|
||||
while (!routing.IsEnd(order)) {
|
||||
IntVar* load_var = routing.CumulVar(order, kCapacity);
|
||||
IntVar* time_var = routing.CumulVar(order, kTime);
|
||||
StringAppendF(&plan_output, "%lld Load(%lld) Time(%lld, %lld) -> ",
|
||||
order,
|
||||
plan.Value(load_var),
|
||||
plan.Min(time_var),
|
||||
plan.Max(time_var));
|
||||
order = plan.Value(routing.NextVar(order));
|
||||
}
|
||||
IntVar* load_var = routing.CumulVar(order, kCapacity);
|
||||
IntVar* time_var = routing.CumulVar(order, kTime);
|
||||
StringAppendF(&plan_output, "%lld Load(%lld) Time(%lld, %lld)\n",
|
||||
order,
|
||||
plan.Value(load_var),
|
||||
plan.Min(time_var),
|
||||
plan.Max(time_var));
|
||||
}
|
||||
}
|
||||
LG << plan_output;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK_LT(0, FLAGS_vrp_orders) << "Specify an instance size greater than 0.";
|
||||
CHECK_LT(0, FLAGS_vrp_vehicles) << "Specify a non-null vehicle fleet size.";
|
||||
// VRP of size FLAGS_vrp_size.
|
||||
// Nodes are indexed from 0 to FLAGS_vrp_orders, the starts and ends of
|
||||
// the routes are at node 0.
|
||||
const RoutingModel::NodeIndex kDepot(0);
|
||||
RoutingModel routing(FLAGS_vrp_orders + 1, FLAGS_vrp_vehicles);
|
||||
routing.SetDepot(kDepot);
|
||||
// Setting first solution heuristic (cheapest addition).
|
||||
routing.SetCommandLineOption("routing_first_solution", "PathCheapestArc");
|
||||
// Disabling Large Neighborhood Search, comment out to activate it.
|
||||
routing.SetCommandLineOption("routing_no_lns", "true");
|
||||
|
||||
// Setting up locations.
|
||||
const int64 kXMax = 100000;
|
||||
const int64 kYMax = 100000;
|
||||
const int64 kSpeed = 10;
|
||||
LocationContainer locations(kSpeed);
|
||||
for (int location = 0; location <= FLAGS_vrp_orders; ++location) {
|
||||
locations.AddRandomLocation(kXMax, kYMax);
|
||||
}
|
||||
|
||||
// Setting the cost function.
|
||||
routing.SetCost(NewPermanentCallback(&locations,
|
||||
&LocationContainer::ManhattanDistance));
|
||||
|
||||
// Adding capacity dimension constraints.
|
||||
const int64 kVehicleCapacity = 40;
|
||||
const int64 kNullCapacitySlack = 0;
|
||||
RandomDemand demand(routing.nodes(), kDepot);
|
||||
demand.Initialize();
|
||||
routing.AddDimension(NewPermanentCallback(&demand, &RandomDemand::Demand),
|
||||
kNullCapacitySlack, kVehicleCapacity, kCapacity);
|
||||
|
||||
// Adding time dimension constraints.
|
||||
const int64 kTimePerDemandUnit = 300;
|
||||
const int64 kHorizon = 24 * 3600;
|
||||
ServiceTimePlusTransition time(
|
||||
kTimePerDemandUnit,
|
||||
NewPermanentCallback(&demand, &RandomDemand::Demand),
|
||||
NewPermanentCallback(&locations, &LocationContainer::ManhattanTime));
|
||||
routing.AddDimension(
|
||||
NewPermanentCallback(&time,
|
||||
&ServiceTimePlusTransition::Compute),
|
||||
kHorizon, kHorizon, kTime);
|
||||
// Adding time windows.
|
||||
ACMRandom randomizer(GetSeed());
|
||||
const int64 kTWDuration = 5 * 3600;
|
||||
for (int order = 1; order < routing.nodes(); ++order) {
|
||||
const int64 start = randomizer.Uniform(kHorizon - kTWDuration);
|
||||
routing.CumulVar(order, kTime)->SetRange(start, start + kTWDuration);
|
||||
}
|
||||
|
||||
// Adding penalty costs to allow skipping orders.
|
||||
const int64 kPenalty = 100000;
|
||||
const RoutingModel::NodeIndex kFirstNodeAfterDepot(1);
|
||||
for (RoutingModel::NodeIndex order = kFirstNodeAfterDepot;
|
||||
order < routing.nodes(); ++order) {
|
||||
std::vector<RoutingModel::NodeIndex> orders(1, order);
|
||||
routing.AddDisjunction(orders, kPenalty);
|
||||
}
|
||||
|
||||
// Solve, returns a solution if any (owned by RoutingModel).
|
||||
const Assignment* solution = routing.Solve();
|
||||
if (solution != NULL) {
|
||||
DisplayPlan(routing, *solution);
|
||||
} else {
|
||||
LG << "No solution found.";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
184
examples/cpp/dimacs_assignment.cc
Normal file
184
examples/cpp/dimacs_assignment.cc
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include "base/hash.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "base/timer.h"
|
||||
#include "algorithms/hungarian.h"
|
||||
#include "graph/ebert_graph.h"
|
||||
#include "graph/linear_assignment.h"
|
||||
#include "cpp/parse_dimacs_assignment.h"
|
||||
#include "cpp/print_dimacs_assignment.h"
|
||||
|
||||
DEFINE_bool(assignment_compare_hungarian, false,
|
||||
"Compare result and speed against Hungarian method.");
|
||||
DEFINE_string(assignment_problem_output_file, "",
|
||||
"Print the problem to this file in DIMACS format (after layout "
|
||||
"is optimized, if applicable).");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
CostValue BuildAndSolveHungarianInstance(
|
||||
const LinearSumAssignment<ForwardStarGraph>& assignment) {
|
||||
const ForwardStarGraph& graph = assignment.Graph();
|
||||
typedef std::vector<double> HungarianRow;
|
||||
typedef std::vector<HungarianRow> HungarianProblem;
|
||||
HungarianProblem hungarian_cost;
|
||||
hungarian_cost.resize(assignment.NumLeftNodes());
|
||||
// First we have to find the biggest cost magnitude so we can
|
||||
// initialize the arc costs that aren't really there.
|
||||
CostValue largest_cost_magnitude = 0;
|
||||
for (ForwardStarGraph::ArcIterator arc_it(graph);
|
||||
arc_it.Ok();
|
||||
arc_it.Next()) {
|
||||
ArcIndex arc = arc_it.Index();
|
||||
CostValue cost_magnitude = ::std::abs(assignment.ArcCost(arc));
|
||||
largest_cost_magnitude = ::std::max(largest_cost_magnitude, cost_magnitude);
|
||||
}
|
||||
double missing_arc_cost = static_cast<double>((assignment.NumLeftNodes() *
|
||||
largest_cost_magnitude) +
|
||||
1);
|
||||
for (HungarianProblem::iterator row = hungarian_cost.begin();
|
||||
row != hungarian_cost.end();
|
||||
++row) {
|
||||
row->resize(assignment.NumNodes() - assignment.NumLeftNodes(),
|
||||
missing_arc_cost);
|
||||
}
|
||||
// We're using a graph representation without forward arcs, so in
|
||||
// order to use the generic ForwardStarGraph::ArcIterator we would
|
||||
// need to increase our memory footprint by building the array of
|
||||
// arc tails (since we need tails to build the input to the
|
||||
// hungarian algorithm). We opt for the alternative of iterating
|
||||
// over hte arcs via adjacency lists, which gives us the arc tails
|
||||
// implicitly.
|
||||
for (ForwardStarGraph::NodeIterator node_it(graph);
|
||||
node_it.Ok();
|
||||
node_it.Next()) {
|
||||
NodeIndex node = node_it.Index();
|
||||
NodeIndex tail = (node - ForwardStarGraph::kFirstNode);
|
||||
for (ForwardStarGraph::OutgoingArcIterator arc_it(graph, node);
|
||||
arc_it.Ok();
|
||||
arc_it.Next()) {
|
||||
ArcIndex arc = arc_it.Index();
|
||||
NodeIndex head = (graph.Head(arc) - assignment.NumLeftNodes() -
|
||||
ForwardStarGraph::kFirstNode);
|
||||
double cost = static_cast<double>(assignment.ArcCost(arc));
|
||||
hungarian_cost[tail][head] = cost;
|
||||
}
|
||||
}
|
||||
hash_map<int, int> result;
|
||||
hash_map<int, int> wish_this_could_be_null;
|
||||
WallTimer timer;
|
||||
VLOG(1) << "Beginning Hungarian method.";
|
||||
timer.Start();
|
||||
MinimizeLinearAssignment(hungarian_cost, &result, &wish_this_could_be_null);
|
||||
double elapsed = timer.GetInMs() / 1000.0;
|
||||
LOG(INFO) << "Hungarian result computed in " << elapsed << " seconds.";
|
||||
double result_cost = 0.0;
|
||||
for (int i = 0; i < assignment.NumLeftNodes(); ++i) {
|
||||
int mate = result[i];
|
||||
result_cost += hungarian_cost[i][mate];
|
||||
}
|
||||
return static_cast<CostValue>(result_cost);
|
||||
}
|
||||
|
||||
void DisplayAssignment(
|
||||
const LinearSumAssignment<ForwardStarGraph>& assignment) {
|
||||
for (LinearSumAssignment<ForwardStarGraph>::BipartiteLeftNodeIterator
|
||||
node_it(assignment);
|
||||
node_it.Ok();
|
||||
node_it.Next()) {
|
||||
const NodeIndex left_node = node_it.Index();
|
||||
const ArcIndex matching_arc = assignment.GetAssignmentArc(left_node);
|
||||
const NodeIndex right_node = assignment.Head(matching_arc);
|
||||
VLOG(5) << "assigned (" << left_node << ", " << right_node << "): "
|
||||
<< assignment.ArcCost(matching_arc);
|
||||
}
|
||||
}
|
||||
|
||||
static const char* const kUsageTemplate = "usage: %s <filename>";
|
||||
|
||||
int solve_dimacs_assignment(int argc, char* argv[]) {
|
||||
string usage;
|
||||
if (argc < 1) {
|
||||
usage = StringPrintf(kUsageTemplate, "solve_dimacs_assignment");
|
||||
} else {
|
||||
usage = StringPrintf(kUsageTemplate, argv[0]);
|
||||
}
|
||||
google::SetUsageMessage(usage);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (argc < 2) {
|
||||
LOG(FATAL) << usage;
|
||||
}
|
||||
string error_message;
|
||||
// Handle on the graph we will need to delete because the
|
||||
// LinearSumAssignment object does not take ownership of it.
|
||||
ForwardStarGraph* graph = NULL;
|
||||
LinearSumAssignment<ForwardStarGraph>* assignment =
|
||||
ParseDimacsAssignment(argv[1], &error_message, &graph);
|
||||
if (assignment == NULL) {
|
||||
LOG(FATAL) << error_message;
|
||||
}
|
||||
if (!FLAGS_assignment_problem_output_file.empty()) {
|
||||
// The following tail array management stuff is done in a generic
|
||||
// way so we can plug in different types of graphs for which the
|
||||
// TailArrayManager template can be instantiated, even though we
|
||||
// know the type of the graph explicitly. In this way, the type of
|
||||
// the graph can be switched just by changing the graph type in
|
||||
// this file and making no other changes to the code.
|
||||
TailArrayManager<ForwardStarGraph> tail_array_manager(graph);
|
||||
PrintDimacsAssignmentProblem(*assignment, tail_array_manager,
|
||||
FLAGS_assignment_problem_output_file);
|
||||
tail_array_manager.ReleaseTailArrayIfForwardGraph();
|
||||
}
|
||||
CostValue hungarian_cost = 0.0;
|
||||
bool hungarian_solved = false;
|
||||
if (FLAGS_assignment_compare_hungarian) {
|
||||
hungarian_cost = BuildAndSolveHungarianInstance(*assignment);
|
||||
hungarian_solved = true;
|
||||
}
|
||||
WallTimer timer;
|
||||
timer.Start();
|
||||
bool success = assignment->ComputeAssignment();
|
||||
double elapsed = timer.GetInMs() / 1000.0;
|
||||
if (success) {
|
||||
CostValue cost = assignment->GetCost();
|
||||
DisplayAssignment(*assignment);
|
||||
LOG(INFO) << "Cost of optimum assignment: " << cost;
|
||||
LOG(INFO) << "Computed in " << elapsed << " seconds.";
|
||||
LOG(INFO) << assignment->StatsString();
|
||||
if (hungarian_solved && (cost != hungarian_cost)) {
|
||||
LOG(ERROR) << "Optimum cost mismatch: " << cost << " vs. "
|
||||
<< hungarian_cost << ".";
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "Given problem is infeasible.";
|
||||
}
|
||||
delete assignment;
|
||||
delete graph;
|
||||
return 0;
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return ::operations_research::solve_dimacs_assignment(argc, argv);
|
||||
}
|
||||
814
examples/cpp/dobble_ls.cc
Normal file
814
examples/cpp/dobble_ls.cc
Normal file
@@ -0,0 +1,814 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This problem is inspired by the Dobble game (aka Spot-It in the
|
||||
// USA). In this game, we have 57 cards, 57 symbols, and 8 symbols
|
||||
// per card. We want to assign symbols per card such that any two
|
||||
// cards have exactly one symbol in common. These numbers can be
|
||||
// generalized: we have N cards, each with K different symbols, and
|
||||
// there are N different symbols overall.
|
||||
//
|
||||
// This is a feasability problem. We transform that into an
|
||||
// optimization problem where we penalize cards whose intersection is
|
||||
// of cardinality different from 1. A feasible solution of the
|
||||
// original problem is a solution with a zero cost.
|
||||
//
|
||||
// Furthermore, we solve this problem using local search, and with a
|
||||
// dedicated constraint.
|
||||
//
|
||||
// The purpose of the example is to demonstrates how to write local
|
||||
// search operators and local search filters.
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/concise_iterator.h"
|
||||
#include "base/map-util.h"
|
||||
#include "constraint_solver/constraint_solveri.h"
|
||||
#include "util/bitset.h"
|
||||
#include "base/random.h"
|
||||
|
||||
DEFINE_int32(symbols_per_card, 8, "Number of symbols per card.");
|
||||
DEFINE_int32(ls_seed, 1, "Seed for the random number generator (used by "
|
||||
"the Local Neighborhood Search).");
|
||||
DEFINE_bool(use_filter, true, "Use filter in the local search to prune moves.");
|
||||
DEFINE_int32(num_swaps, 4, "If num_swap > 0, the search for an optimal "
|
||||
"solution will be allowed to use an operator that swaps the "
|
||||
"symbols of up to num_swap pairs ((card1, symbol on card1), "
|
||||
"(card2, symbol on card2)).");
|
||||
DEFINE_int32(time_limit_in_ms,
|
||||
60000,
|
||||
"Time limit for the global search in ms.");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// ----- Dedicated constraint to count the symbols shared by two cards -----
|
||||
|
||||
// This constraint maintains:
|
||||
// sum_i(card1_symbol_vars[i]*card2_symbol_vars[i]) == count_var.
|
||||
// with all card_symbol_vars[i] being boolean variables.
|
||||
class SymbolsSharedByTwoCardsConstraint : public Constraint {
|
||||
public:
|
||||
// This constructor does not take any ownership on its arguments.
|
||||
SymbolsSharedByTwoCardsConstraint(Solver* const solver,
|
||||
const std::vector<IntVar*>& card1_symbol_vars,
|
||||
const std::vector<IntVar*>& card2_symbol_vars,
|
||||
IntVar* const num_symbols_in_common_var)
|
||||
: Constraint(solver),
|
||||
card1_symbol_vars_(card1_symbol_vars),
|
||||
card2_symbol_vars_(card2_symbol_vars),
|
||||
num_symbols_(card1_symbol_vars.size()),
|
||||
num_symbols_in_common_var_(num_symbols_in_common_var) {
|
||||
// Checks that cards have the same size.
|
||||
CHECK_EQ(card1_symbol_vars.size(), card2_symbol_vars.size());
|
||||
|
||||
// Verify that we are really dealing with boolean variables.
|
||||
for (int i = 0; i < num_symbols_; ++i) {
|
||||
CHECK_GE(card1_symbol_vars_[i]->Min(), 0);
|
||||
CHECK_GE(card2_symbol_vars_[i]->Min(), 0);
|
||||
CHECK_LE(card1_symbol_vars_[i]->Max(), 1);
|
||||
CHECK_LE(card2_symbol_vars_[i]->Max(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~SymbolsSharedByTwoCardsConstraint() {}
|
||||
|
||||
// Adds observers (named Demon) to variable events. These demons are
|
||||
// responsible for implementing the propagation algorithm of the
|
||||
// constraint.
|
||||
virtual void Post() {
|
||||
// Create a demon 'global_demon' that will bind events on
|
||||
// variables to the calling of the 'InitialPropagate()' method. As
|
||||
// this method is expensive, 'global_demon' has a low priority. As
|
||||
// such, InitialPropagate will be called after all normal demons
|
||||
// and constraints have reached a fixed point. Note
|
||||
// that ownership of the 'global_demon' belongs to the solver.
|
||||
Demon* const global_demon =
|
||||
solver()->MakeDelayedConstraintInitialPropagateCallback(this);
|
||||
// Attach to all variables.
|
||||
for (int i = 0; i < num_symbols_; ++i) {
|
||||
card1_symbol_vars_[i]->WhenBound(global_demon);
|
||||
card2_symbol_vars_[i]->WhenBound(global_demon);
|
||||
}
|
||||
// Attach to cardinality variable.
|
||||
num_symbols_in_common_var_->WhenBound(global_demon);
|
||||
}
|
||||
|
||||
// This is the main propagation method.
|
||||
//
|
||||
// It scans all card1_symbol_vars * card2_symbol_vars and increments 3
|
||||
// counters:
|
||||
// - min_symbols_in_common if both booleans variables are bound to true.
|
||||
// - max_symbols_in_common if both booleans are not bound to false.
|
||||
//
|
||||
// Then we know that num_symbols_in_common_var is in the range
|
||||
// [min_symbols_in_common .. max_symbols_in_common].
|
||||
//
|
||||
// Now, if num_symbols_in_common_var->Max() ==
|
||||
// min_symbols_in_common, then all products that contribute to
|
||||
// max_symbols_in_common but not to min_symbols_in_common should be
|
||||
// set to 0.
|
||||
//
|
||||
// Conversely, if num_symbols_in_common_var->Min() ==
|
||||
// max_symbols_in_common, then all products that contribute to
|
||||
// max_symbols_in_common should be set to 1.
|
||||
virtual void InitialPropagate() {
|
||||
int max_symbols_in_common = 0;
|
||||
int min_symbols_in_common = 0;
|
||||
for (int i = 0; i < num_symbols_; ++i) {
|
||||
if (card1_symbol_vars_[i]->Min() == 1 &&
|
||||
card2_symbol_vars_[i]->Min() == 1) {
|
||||
min_symbols_in_common++;
|
||||
}
|
||||
if (card1_symbol_vars_[i]->Max() == 1 &&
|
||||
card2_symbol_vars_[i]->Max() == 1) {
|
||||
max_symbols_in_common++;
|
||||
}
|
||||
}
|
||||
num_symbols_in_common_var_->SetRange(min_symbols_in_common,
|
||||
max_symbols_in_common);
|
||||
// If min_symbols_in_common == max_symbols_in_common, it means
|
||||
// that num_symbols_in_common_var_ is already fully determined: we
|
||||
// have nothing to do.
|
||||
if (min_symbols_in_common == max_symbols_in_common) {
|
||||
DCHECK_EQ(min_symbols_in_common, num_symbols_in_common_var_->Max());
|
||||
DCHECK_EQ(min_symbols_in_common, num_symbols_in_common_var_->Min());
|
||||
return;
|
||||
}
|
||||
if (num_symbols_in_common_var_->Max() == min_symbols_in_common) {
|
||||
// All undecided product terms should be forced to 0.
|
||||
for (int i = 0; i < num_symbols_; ++i) {
|
||||
// If both Min() are 0, then we can't force either variable to
|
||||
// be zero (even if we know that their product is zero),
|
||||
// because either variable could be 1 as long as the other is
|
||||
// 0.
|
||||
if (card1_symbol_vars_[i]->Min() == 1 &&
|
||||
card2_symbol_vars_[i]->Min() == 0) {
|
||||
card2_symbol_vars_[i]->SetValue(0);
|
||||
} else if (card2_symbol_vars_[i]->Min() == 1 &&
|
||||
card1_symbol_vars_[i]->Min() == 0) {
|
||||
card1_symbol_vars_[i]->SetValue(0);
|
||||
}
|
||||
}
|
||||
} else if (num_symbols_in_common_var_->Min() == max_symbols_in_common) {
|
||||
// All undecided product terms should be forced to 1.
|
||||
for (int i = 0; i < num_symbols_; ++i) {
|
||||
if (card1_symbol_vars_[i]->Max() == 1 &&
|
||||
card2_symbol_vars_[i]->Max() == 1) {
|
||||
// Note that we also force already-decided product terms,
|
||||
// but this doesn't change anything.
|
||||
card1_symbol_vars_[i]->SetValue(1);
|
||||
card2_symbol_vars_[i]->SetValue(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<IntVar*> card1_symbol_vars_;
|
||||
std::vector<IntVar*> card2_symbol_vars_;
|
||||
const int num_symbols_;
|
||||
IntVar* const num_symbols_in_common_var_;
|
||||
};
|
||||
|
||||
// Creates two integer variables: one that counts the number of
|
||||
// symbols common to two cards, and one that counts the absolute
|
||||
// difference between the first var and 1 (i.e. the violation of the
|
||||
// objective). Returns the latter (both vars are owned by the Solver
|
||||
// anyway).
|
||||
IntVar* CreateViolationVar(Solver* const solver,
|
||||
const std::vector<IntVar*>& card1_symbol_vars,
|
||||
const std::vector<IntVar*>& card2_symbol_vars,
|
||||
int num_symbols_per_card) {
|
||||
IntVar* const num_symbols_in_common_var =
|
||||
solver->MakeIntVar(0, num_symbols_per_card);
|
||||
// RevAlloc transfers the ownership of the constraint to the solver.
|
||||
solver->AddConstraint(
|
||||
solver->RevAlloc(
|
||||
new SymbolsSharedByTwoCardsConstraint(solver,
|
||||
card1_symbol_vars,
|
||||
card2_symbol_vars,
|
||||
num_symbols_in_common_var)));
|
||||
return solver->MakeAbs(solver->MakeSum(num_symbols_in_common_var, -1))->Var();
|
||||
}
|
||||
|
||||
// ---------- Local Search ----------
|
||||
|
||||
// The "local search", or "local neighborhood search", works like
|
||||
// this: starting from a given solution to the problem, other
|
||||
// solutions in its neighborhood are generated from it, some of them
|
||||
// might be selected (because they're better, for example) to become a
|
||||
// starting point for other neighborhood searches, and so on.. The
|
||||
// detailed search algorithm can vary and depends on the problem to
|
||||
// solve.
|
||||
//
|
||||
// The fundamental building block for the local search is the "search
|
||||
// operator", which has three fundamental methods in its API:
|
||||
//
|
||||
// - Its constructor, which keeps (mutable) references to the
|
||||
// solver's internal variables (here, the card symbol variables).
|
||||
//
|
||||
// - OnStart(), which is called at the start of a local search, and
|
||||
// after each solution (i.e. when the local search starts again from
|
||||
// that new solution). The solver variables are supposed to represent
|
||||
// a valid solution at this point. This method is used by the search
|
||||
// operator to initialize its state and be ready to start the
|
||||
// exploration of the neighborhood of the given solution.
|
||||
//
|
||||
// - MakeOneNeighbor(), which picks a neighbor of the initial
|
||||
// solution, and changes the solver's internal variables accordingly
|
||||
// to represent that new state.
|
||||
//
|
||||
// All local search operators on this problem will derive from the
|
||||
// parent class below, which contains some shared code to store a
|
||||
// compact representation of which symbols appeal on each cards.
|
||||
class DobbleOperator : public IntVarLocalSearchOperator {
|
||||
public:
|
||||
DobbleOperator(const IntVar* const* card_symbol_vars,
|
||||
int num_vars,
|
||||
int num_cards,
|
||||
int num_symbols,
|
||||
int num_symbols_per_card)
|
||||
: IntVarLocalSearchOperator(card_symbol_vars, num_vars),
|
||||
num_cards_(num_cards),
|
||||
num_symbols_(num_symbols),
|
||||
num_symbols_per_card_(num_symbols_per_card),
|
||||
symbols_per_card_(num_cards) {
|
||||
CHECK_GT(num_cards, 0);
|
||||
CHECK_GT(num_vars, 0);
|
||||
CHECK_GT(num_symbols, 0);
|
||||
CHECK_GT(num_symbols_per_card, 0);
|
||||
for (int card = 0; card < num_cards; ++card) {
|
||||
symbols_per_card_[card].assign(num_symbols_per_card, -1);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~DobbleOperator() {}
|
||||
|
||||
protected:
|
||||
// OnStart() simply stores the current symbols per card in
|
||||
// symbols_per_card_, and defers further initialization to the
|
||||
// subclass's InitNeighborhoodSearch() method.
|
||||
virtual void OnStart() {
|
||||
for (int card = 0; card < num_cards_; ++card) {
|
||||
int found = 0;
|
||||
for (int symbol = 0; symbol < num_symbols_; ++symbol) {
|
||||
if (Value(VarIndex(card, symbol)) == 1) {
|
||||
symbols_per_card_[card][found++] = symbol;
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(num_symbols_per_card_, found);
|
||||
}
|
||||
InitNeighborhoodSearch();
|
||||
}
|
||||
|
||||
virtual void InitNeighborhoodSearch() = 0;
|
||||
|
||||
// Find the index of the variable corresponding to the given symbol
|
||||
// on the given card.
|
||||
int VarIndex(int card, int symbol) {
|
||||
return card * num_symbols_ + symbol;
|
||||
}
|
||||
|
||||
// Move symbol1 from card1 to card2, and symbol2 from card2 to card1.
|
||||
void SwapTwoSymbolsOnCards(int card1, int symbol1, int card2, int symbol2) {
|
||||
SetValue(VarIndex(card1, symbol1), 0);
|
||||
SetValue(VarIndex(card2, symbol2), 0);
|
||||
SetValue(VarIndex(card1, symbol2), 1);
|
||||
SetValue(VarIndex(card2, symbol1), 1);
|
||||
}
|
||||
|
||||
const int num_cards_;
|
||||
const int num_symbols_;
|
||||
const int num_symbols_per_card_;
|
||||
std::vector<std::vector<int> > symbols_per_card_;
|
||||
};
|
||||
|
||||
// ----- Swap 2 symbols -----
|
||||
|
||||
// This operator explores *all* pairs (card1, some symbol on card1),
|
||||
// (card2, some symbol on card2) and swaps the symbols between the two
|
||||
// cards.
|
||||
//
|
||||
// Note that this could create invalid moves (for example, by adding a
|
||||
// symbol to a card that already had it); see the DobbleFilter class
|
||||
// below to see how we filter those out.
|
||||
class SwapSymbols : public DobbleOperator {
|
||||
public:
|
||||
SwapSymbols(const IntVar* const* card_symbol_vars,
|
||||
int num_vars,
|
||||
int num_cards,
|
||||
int num_symbols,
|
||||
int num_symbols_per_card)
|
||||
: DobbleOperator(card_symbol_vars,
|
||||
num_vars,
|
||||
num_cards,
|
||||
num_symbols,
|
||||
num_symbols_per_card),
|
||||
current_card1_(-1),
|
||||
current_card2_(-1),
|
||||
current_symbol1_(-1),
|
||||
current_symbol2_(-1) {
|
||||
}
|
||||
|
||||
virtual ~SwapSymbols() {}
|
||||
|
||||
// Finds the next swap, returns false when it has finished.
|
||||
virtual bool MakeOneNeighbor() {
|
||||
if (!PickNextSwap()) {
|
||||
VLOG(1) << "finished neighborhood";
|
||||
return false;
|
||||
}
|
||||
|
||||
const int symbol1 = symbols_per_card_[current_card1_][current_symbol1_];
|
||||
const int symbol2 = symbols_per_card_[current_card2_][current_symbol2_];
|
||||
SwapTwoSymbolsOnCards(current_card1_, symbol1, current_card2_, symbol2);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Reinit the exploration loop.
|
||||
virtual void InitNeighborhoodSearch() {
|
||||
current_card1_ = 0;
|
||||
current_card2_ = 1;
|
||||
current_symbol1_ = 0;
|
||||
current_symbol2_ = -1;
|
||||
}
|
||||
|
||||
// Compute the next move. It returns false when there are none.
|
||||
bool PickNextSwap() {
|
||||
current_symbol2_++;
|
||||
if (current_symbol2_ == num_symbols_per_card_) {
|
||||
current_symbol2_ = 0;
|
||||
current_symbol1_++;
|
||||
if (current_symbol1_ == num_symbols_per_card_) {
|
||||
current_symbol1_ = 0;
|
||||
current_card2_++;
|
||||
if (current_card2_ == num_cards_) {
|
||||
current_card1_++;
|
||||
current_card2_ = current_card1_ + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current_card1_ < num_cards_ - 1;
|
||||
}
|
||||
|
||||
int current_card1_;
|
||||
int current_card2_;
|
||||
int current_symbol1_;
|
||||
int current_symbol2_;
|
||||
};
|
||||
|
||||
// Multiple swaps of two symbols. This operator is an expanded version
|
||||
// of the previous operator.
|
||||
//
|
||||
// At each step, it will pick a number num_swaps at random in
|
||||
// [2 .. max_num_swaps], and then pick num_swaps random pairs (card1,
|
||||
// some symbol on card1), (card2, some symbol on card2), and swap the
|
||||
// symbols of each pair.
|
||||
//
|
||||
// As the search space (the "neighborhood") is huge, we use a
|
||||
// randomized "infinite" version instead of an iterative, exhaustive
|
||||
// one.
|
||||
class SwapSymbolsOnCardPairs : public DobbleOperator {
|
||||
public:
|
||||
SwapSymbolsOnCardPairs(const IntVar* const* card_symbol_vars,
|
||||
int num_vars,
|
||||
int num_cards,
|
||||
int num_symbols,
|
||||
int num_symbols_per_card,
|
||||
int max_num_swaps)
|
||||
: DobbleOperator(card_symbol_vars,
|
||||
num_vars,
|
||||
num_cards,
|
||||
num_symbols,
|
||||
num_symbols_per_card),
|
||||
rand_(FLAGS_ls_seed),
|
||||
max_num_swaps_(max_num_swaps) {
|
||||
CHECK_GE(max_num_swaps, 2);
|
||||
}
|
||||
|
||||
virtual ~SwapSymbolsOnCardPairs() {}
|
||||
|
||||
protected:
|
||||
virtual bool MakeOneNeighbor() {
|
||||
const int num_swaps = rand_.Uniform(max_num_swaps_ - 1) + 2;
|
||||
for (int i = 0; i < num_swaps; ++i) {
|
||||
const int card_1 = rand_.Uniform(num_cards_);
|
||||
const int symbol_index_1 = rand_.Uniform(num_symbols_per_card_);
|
||||
const int symbol_1 = symbols_per_card_[card_1][symbol_index_1];
|
||||
const int card_2 = rand_.Uniform(num_cards_);
|
||||
const int symbol_index_2 = rand_.Uniform(num_symbols_per_card_);
|
||||
const int symbol_2 = symbols_per_card_[card_2][symbol_index_2];
|
||||
SwapTwoSymbolsOnCards(card_1, symbol_1, card_2, symbol_2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void InitNeighborhoodSearch() {}
|
||||
|
||||
private:
|
||||
ACMRandom rand_;
|
||||
const int max_num_swaps_;
|
||||
};
|
||||
|
||||
// ----- Local Search Filter -----
|
||||
|
||||
// A filter is responsible for rejecting a local search move faster
|
||||
// than what the propagation of the constraint solver would do.
|
||||
// Its API consists in:
|
||||
// - The constructor, which takes as input a reference to all the
|
||||
// variables relevant to the filter.
|
||||
// - OnSynchronize(), called at the beginning of the search and
|
||||
// after each move to a new solution (when the local search
|
||||
// restarts from it).
|
||||
// - Accept(), which takes as input an attempted move (in the form
|
||||
// of a Delta to tentatively apply to the variables), and returns
|
||||
// true iff this move is found valid.
|
||||
//
|
||||
// To decide if a move is valid, first this DobbleFilter builds a
|
||||
// bitmap of symbols per card. Then for each move, it updates the
|
||||
// bitmap according to the move and checks the following constraints:
|
||||
// - First, each card still has num_symbols_per_card symbols. - The
|
||||
// cost of the assignment described by the move is better than the
|
||||
// current one.
|
||||
|
||||
// After the check is done, the original bitmap is restored if the
|
||||
// move was rejected, so as to be ready for the next evaluation.
|
||||
//
|
||||
// Please note that this filter uses a fixed size bitset and
|
||||
// effectively limits the number of cards to 63, and thus the number
|
||||
// of symbols per card to 8.
|
||||
class DobbleFilter : public IntVarLocalSearchFilter {
|
||||
public:
|
||||
DobbleFilter(const IntVar* const* card_symbol_vars,
|
||||
int num_vars,
|
||||
int num_cards,
|
||||
int num_symbols,
|
||||
int num_symbols_per_card)
|
||||
: IntVarLocalSearchFilter(card_symbol_vars, num_vars),
|
||||
num_cards_(num_cards),
|
||||
num_symbols_(num_symbols),
|
||||
num_symbols_per_card_(num_symbols_per_card),
|
||||
temporary_bitset_(0),
|
||||
symbol_bitmask_per_card_(num_cards, 0),
|
||||
violation_costs_(num_cards_, std::vector<int>(num_cards_, 0)) {}
|
||||
|
||||
// We build the current bitmap and the matrix of violation cost
|
||||
// between any two cards.
|
||||
virtual void OnSynchronize() {
|
||||
symbol_bitmask_per_card_.assign(num_cards_, 0);
|
||||
for (int card = 0; card < num_cards_; ++card) {
|
||||
for (int symbol = 0; symbol < num_symbols_; ++symbol) {
|
||||
if (Value(VarIndex(card, symbol))) {
|
||||
SetBit64(&symbol_bitmask_per_card_[card], symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int card1 = 0; card1 < num_cards_; ++card1) {
|
||||
for (int card2 = 0; card2 < num_cards_; ++card2) {
|
||||
violation_costs_[card1][card2] =
|
||||
ViolationCost(BitCount64(symbol_bitmask_per_card_[card1] &
|
||||
symbol_bitmask_per_card_[card2]));
|
||||
}
|
||||
}
|
||||
DCHECK(CheckCards());
|
||||
}
|
||||
|
||||
// The LocalSearchFilter::Accept() API also takes a deltadelta,
|
||||
// which is the difference between the current delta and the last
|
||||
// delta that was given to Accept() -- but we don't use it here.
|
||||
virtual bool Accept(const Assignment* delta,
|
||||
const Assignment* unused_deltadelta) {
|
||||
const Assignment::IntContainer& solution_delta = delta->IntVarContainer();
|
||||
const int solution_delta_size = solution_delta.Size();
|
||||
|
||||
// The input const Assignment* delta given to Accept() may
|
||||
// actually contain "Deactivated" elements, which represent
|
||||
// variables that have been freed -- they are not bound to a
|
||||
// single value anymore. This happens with LNS-type (Large
|
||||
// Neighborhood Search) LocalSearchOperator, which are not used in
|
||||
// this example as of 2012-01; and we refer the reader to
|
||||
// ./routing.cc for an example of such LNS-type operators.
|
||||
//
|
||||
// For didactical purposes, we will assume for a moment that a
|
||||
// LNS-type operator might be applied. The Filter will still be
|
||||
// called, but our DobbleFilter here won't be able to work, since
|
||||
// it needs every variable to be bound (i.e. have a fixed value),
|
||||
// in the assignment that it considers. Therefore, we include here
|
||||
// a snippet of code that will detect if the input assignment is
|
||||
// not fully bound. For further details, read ./routing.cc -- but
|
||||
// we strongly advise the reader to first try and understand all
|
||||
// of this file.
|
||||
for (int i = 0; i < solution_delta_size; ++i) {
|
||||
if (!solution_delta.Element(i).Activated()) {
|
||||
VLOG(1)
|
||||
<< "Element #" << i << " of the delta assignment given to"
|
||||
<< " DobbleFilter::Accept() is not activated (i.e. its variable"
|
||||
<< " is not bound to a single value anymore). This means that"
|
||||
<< " we are in a LNS phase, and the DobbleFilter won't be able"
|
||||
<< " to filter anything. Returning true.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
VLOG(1) << "No LNS, size = " << solution_delta_size;
|
||||
|
||||
// Collect the set of cards that have been modified by this move.
|
||||
std::vector<int> touched_cards;
|
||||
ComputeTouchedCards(solution_delta, &touched_cards);
|
||||
|
||||
// Check basic metrics to fail fast.
|
||||
if (!CheckCards()) {
|
||||
RestoreBitsetPerCard();
|
||||
DCHECK(CheckCards());
|
||||
VLOG(1) << "reject by size";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute new cost.
|
||||
const int cost_delta = ComputeNewCost(touched_cards);
|
||||
|
||||
// Reset the data structure to the state before the evaluation.
|
||||
RestoreBitsetPerCard();
|
||||
|
||||
// And exit (this is only valid for a greedy descent and would
|
||||
// reject valid moves in tabu search for instance).
|
||||
if (cost_delta >= 0) {
|
||||
VLOG(1) << "reject";
|
||||
}
|
||||
return cost_delta < 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Undo information after an evaluation.
|
||||
struct UndoChange {
|
||||
UndoChange(int c, uint64 b) : card(c), bitset(b) {}
|
||||
int card;
|
||||
uint64 bitset;
|
||||
};
|
||||
|
||||
int VarIndex(int card, int symbol) {
|
||||
return card * num_symbols_ + symbol;
|
||||
}
|
||||
|
||||
void ClearBitset() {
|
||||
temporary_bitset_ = 0;
|
||||
}
|
||||
|
||||
// For each touched card, compare against all others to compute the
|
||||
// delta in term of cost. We use an bitset to avoid counting twice
|
||||
// between two cards appearing in the local search move.
|
||||
int ComputeNewCost(const std::vector<int>& touched_cards) {
|
||||
ClearBitset();
|
||||
int cost_delta = 0;
|
||||
for (int i = 0; i < touched_cards.size(); ++i) {
|
||||
const int touched = touched_cards[i];
|
||||
SetBit64(&temporary_bitset_, touched);
|
||||
const uint64 card_bitset = symbol_bitmask_per_card_[touched];
|
||||
const std::vector<int>& row_cost = violation_costs_[touched];
|
||||
for (int other_card = 0; other_card < num_cards_; ++other_card) {
|
||||
if (!IsBitSet64(&temporary_bitset_, other_card)) {
|
||||
cost_delta +=
|
||||
ViolationCost(BitCount64(card_bitset &
|
||||
symbol_bitmask_per_card_[other_card]));
|
||||
cost_delta -= row_cost[other_card];
|
||||
}
|
||||
}
|
||||
}
|
||||
return cost_delta;
|
||||
}
|
||||
|
||||
// Collects all card indices appearing in the local search move.
|
||||
void ComputeTouchedCards(const Assignment::IntContainer& solution_delta,
|
||||
std::vector<int>* const touched_cards) {
|
||||
ClearBitset();
|
||||
const int solution_delta_size = solution_delta.Size();
|
||||
const int kUnassigned = -1;
|
||||
for (int index = 0; index < solution_delta_size; ++index) {
|
||||
int64 touched_var = kUnassigned;
|
||||
FindIndex(solution_delta.Element(index).Var(), &touched_var);
|
||||
CHECK_NE(touched_var, kUnassigned);
|
||||
const int card = touched_var / num_symbols_;
|
||||
const int symbol = touched_var % num_symbols_;
|
||||
const int new_value = solution_delta.Element(index).Value();
|
||||
if (!IsBitSet64(&temporary_bitset_, card)) {
|
||||
SaveRestoreInformation(card);
|
||||
touched_cards->push_back(card);
|
||||
SetBit64(&temporary_bitset_, card);
|
||||
}
|
||||
if (new_value) {
|
||||
SetBit64(&symbol_bitmask_per_card_[card], symbol);
|
||||
} else {
|
||||
ClearBit64(&symbol_bitmask_per_card_[card], symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Undo all modifications done when evaluating a move.
|
||||
void RestoreBitsetPerCard() {
|
||||
for (int i = 0; i < restore_information_.size(); ++i) {
|
||||
symbol_bitmask_per_card_[restore_information_[i].card] =
|
||||
restore_information_[i].bitset;
|
||||
}
|
||||
restore_information_.clear();
|
||||
}
|
||||
|
||||
// Stores undo information for a given card.
|
||||
void SaveRestoreInformation(int card) {
|
||||
restore_information_.push_back(UndoChange(card,
|
||||
symbol_bitmask_per_card_[card]));
|
||||
}
|
||||
|
||||
// Checks that after the local search move, each card would still have
|
||||
// num_symbols_per_card symbols on it.
|
||||
bool CheckCards() {
|
||||
for (int i = 0; i < num_cards_; ++i) {
|
||||
if (num_symbols_per_card_ != BitCount64(symbol_bitmask_per_card_[i])) {
|
||||
VLOG(1) << "card " << i << " has bitset of size "
|
||||
<< BitCount64(symbol_bitmask_per_card_[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ViolationCost(uint64 cardinality) const {
|
||||
return (cardinality > 0 ? cardinality - 1 : 1);
|
||||
}
|
||||
|
||||
const int num_cards_;
|
||||
const int num_symbols_;
|
||||
const int num_symbols_per_card_;
|
||||
uint64 temporary_bitset_;
|
||||
std::vector<uint64> symbol_bitmask_per_card_;
|
||||
std::vector<std::vector<int> > violation_costs_;
|
||||
std::vector<UndoChange> restore_information_;
|
||||
};
|
||||
|
||||
// ----- Main Method -----
|
||||
|
||||
void SolveDobble(int num_cards, int num_symbols, int num_symbols_per_card) {
|
||||
LOG(INFO) << "Solving dobble assignment problem:";
|
||||
LOG(INFO) << " - " << num_cards << " cards";
|
||||
LOG(INFO) << " - " << num_symbols << " symbols";
|
||||
LOG(INFO) << " - " << num_symbols_per_card << " symbols per card";
|
||||
|
||||
// Creates the solver.
|
||||
Solver solver("dobble");
|
||||
// Creates the matrix of boolean variables (cards * symbols).
|
||||
std::vector<std::vector<IntVar*> > card_symbol_vars(num_cards);
|
||||
std::vector<IntVar*> all_card_symbol_vars;
|
||||
for (int card_index = 0; card_index < num_cards; ++card_index) {
|
||||
solver.MakeBoolVarArray(num_symbols,
|
||||
StringPrintf("card_%i_", card_index),
|
||||
&card_symbol_vars[card_index]);
|
||||
for (int symbol_index = 0;
|
||||
symbol_index < num_symbols;
|
||||
++symbol_index) {
|
||||
all_card_symbol_vars.push_back(
|
||||
card_symbol_vars[card_index][symbol_index]);
|
||||
}
|
||||
}
|
||||
// Creates cardinality intersection variables and remember the
|
||||
// violation variables.
|
||||
std::vector<IntVar*> violation_vars;
|
||||
for (int card1 = 0; card1 < num_cards; ++card1) {
|
||||
for (int card2 = 0; card2 < num_cards; ++card2) {
|
||||
if (card1 != card2) {
|
||||
violation_vars.push_back(CreateViolationVar(&solver,
|
||||
card_symbol_vars[card1],
|
||||
card_symbol_vars[card2],
|
||||
num_symbols_per_card));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create the objective variable.
|
||||
IntVar* const objective_var = solver.MakeSum(violation_vars)->Var();
|
||||
|
||||
// Add constraint: there must be exactly num_symbols_per_card
|
||||
// symbols per card.
|
||||
for (int card = 0; card < num_cards; ++card) {
|
||||
solver.AddConstraint(solver.MakeSumEquality(card_symbol_vars[card],
|
||||
num_symbols_per_card));
|
||||
}
|
||||
|
||||
// IMPORTANT OPTIMIZATION:
|
||||
// Add constraint: each symbol appears on exactly
|
||||
// num_symbols_per_card cards (i.e. symbols are evenly
|
||||
// distributed). This constraint is actually redundant, because it
|
||||
// is a (non-trivial) consequence of the other constraints and of
|
||||
// the model. But adding it makes the search go faster.
|
||||
for (int symbol_index = 0; symbol_index < num_symbols; ++symbol_index) {
|
||||
std::vector<IntVar*> tmp;
|
||||
for (int card_index = 0; card_index < num_cards; ++card_index) {
|
||||
tmp.push_back(card_symbol_vars[card_index][symbol_index]);
|
||||
}
|
||||
solver.AddConstraint(solver.MakeSumEquality(tmp, num_symbols_per_card));
|
||||
}
|
||||
|
||||
// Search.
|
||||
LOG(INFO) << "Solving with Local Search";
|
||||
LOG(INFO) << " - time limit = " << FLAGS_time_limit_in_ms << " ms";
|
||||
|
||||
// Start a DecisionBuilder phase to find a first solution, using the
|
||||
// strategy "Pick some random, yet unassigned card symbol variable
|
||||
// and set its value to 1".
|
||||
DecisionBuilder* const build_db = solver.MakePhase(
|
||||
all_card_symbol_vars,
|
||||
Solver::CHOOSE_RANDOM, // Solver::IntVarStrategy
|
||||
Solver::ASSIGN_MAX_VALUE); // Solver::IntValueStrategy
|
||||
|
||||
// Creates local search operators.
|
||||
std::vector<LocalSearchOperator*> operators;
|
||||
LocalSearchOperator* const switch_operator =
|
||||
solver.RevAlloc(new SwapSymbols(all_card_symbol_vars.data(),
|
||||
all_card_symbol_vars.size(),
|
||||
num_cards,
|
||||
num_symbols,
|
||||
num_symbols_per_card));
|
||||
operators.push_back(switch_operator);
|
||||
LOG(INFO) << " - add switch operator";
|
||||
if (FLAGS_num_swaps > 0) {
|
||||
LocalSearchOperator* const swaps_operator =
|
||||
solver.RevAlloc(new SwapSymbolsOnCardPairs(all_card_symbol_vars.data(),
|
||||
all_card_symbol_vars.size(),
|
||||
num_cards,
|
||||
num_symbols,
|
||||
num_symbols_per_card,
|
||||
FLAGS_num_swaps));
|
||||
operators.push_back(swaps_operator);
|
||||
LOG(INFO) << " - add swaps operator with at most "
|
||||
<< FLAGS_num_swaps << " swaps";
|
||||
}
|
||||
|
||||
// Creates filter.
|
||||
std::vector<LocalSearchFilter*> filters;
|
||||
if (FLAGS_use_filter) {
|
||||
filters.push_back(solver.RevAlloc(
|
||||
new DobbleFilter(all_card_symbol_vars.data(),
|
||||
all_card_symbol_vars.size(),
|
||||
num_cards,
|
||||
num_symbols,
|
||||
num_symbols_per_card)));
|
||||
}
|
||||
|
||||
// Main decision builder that regroups the first solution decision
|
||||
// builder and the combination of local search operators and
|
||||
// filters.
|
||||
DecisionBuilder* const final_db =
|
||||
solver.MakeLocalSearchPhase(
|
||||
all_card_symbol_vars,
|
||||
build_db,
|
||||
solver.MakeLocalSearchPhaseParameters(
|
||||
solver.ConcatenateOperators(operators, true),
|
||||
NULL, // Sub decision builder, not needed here.
|
||||
NULL, // Limit the search for improving move, we will stop
|
||||
// the exploration of the local search at the first
|
||||
// improving solution (first accept).
|
||||
filters));
|
||||
|
||||
|
||||
std::vector<SearchMonitor*> monitors;
|
||||
// Optimize var search monitor.
|
||||
OptimizeVar* const optimize = solver.MakeMinimize(objective_var, 1);
|
||||
monitors.push_back(optimize);
|
||||
|
||||
// Search log.
|
||||
SearchMonitor* const log = solver.MakeSearchLog(100000, optimize);
|
||||
monitors.push_back(log);
|
||||
|
||||
// Search limit.
|
||||
SearchLimit* const time_limit =
|
||||
solver.MakeLimit(FLAGS_time_limit_in_ms, kint64max, kint64max, kint64max);
|
||||
monitors.push_back(time_limit);
|
||||
|
||||
// And solve!
|
||||
solver.Solve(final_db, monitors);
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
// These constants comes directly from the dobble game.
|
||||
// There are actually 55 cards, but we can create up to 57 cards.
|
||||
const int kSymbolsPerCard = FLAGS_symbols_per_card;
|
||||
const int kCards = kSymbolsPerCard * (kSymbolsPerCard - 1) + 1;
|
||||
const int kSymbols = kCards;
|
||||
operations_research::SolveDobble(kCards,
|
||||
kSymbols,
|
||||
kSymbolsPerCard);
|
||||
return 0;
|
||||
}
|
||||
91
examples/cpp/flow_api.cc
Normal file
91
examples/cpp/flow_api.cc
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "graph/ebert_graph.h"
|
||||
#include "graph/max_flow.h"
|
||||
#include "graph/min_cost_flow.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// ----- Min Cost Flow -----
|
||||
|
||||
// Test on a 4x4 matrix. Example taken from
|
||||
// http://www.ee.oulu.fi/~mpa/matreng/eem1_2-1.htm
|
||||
void MinCostFlowOn4x4Matrix() {
|
||||
LOG(INFO) << "Min Cost Flow on 4x4 Matrix";
|
||||
const int kNumSources = 4;
|
||||
const int kNumTargets = 4;
|
||||
const CostValue kCost[kNumSources][kNumTargets] = {
|
||||
{ 90, 75, 75, 80 },
|
||||
{ 35, 85, 55, 65 },
|
||||
{ 125, 95, 90, 105 },
|
||||
{ 45, 110, 95, 115 }
|
||||
};
|
||||
const CostValue kExpectedCost = 275;
|
||||
StarGraph graph(kNumSources + kNumTargets, kNumSources * kNumTargets);
|
||||
MinCostFlow min_cost_flow(&graph);
|
||||
for (NodeIndex source = 0; source < kNumSources; ++source) {
|
||||
for (NodeIndex target = 0; target < kNumTargets; ++target) {
|
||||
ArcIndex arc = graph.AddArc(source, kNumSources + target);
|
||||
min_cost_flow.SetArcUnitCost(arc, kCost[source][target]);
|
||||
min_cost_flow.SetArcCapacity(arc, 1);
|
||||
}
|
||||
}
|
||||
for (NodeIndex source = 0; source < kNumSources; ++source) {
|
||||
min_cost_flow.SetNodeSupply(source, 1);
|
||||
}
|
||||
for (NodeIndex target = 0; target < kNumTargets; ++target) {
|
||||
min_cost_flow.SetNodeSupply(kNumSources + target, -1);
|
||||
}
|
||||
CHECK(min_cost_flow.Solve());
|
||||
CHECK_EQ(MinCostFlow::OPTIMAL, min_cost_flow.status());
|
||||
CostValue total_flow_cost = min_cost_flow.GetOptimalCost();
|
||||
CHECK_EQ(kExpectedCost, total_flow_cost);
|
||||
}
|
||||
|
||||
// ----- Max Flow -----
|
||||
|
||||
void MaxFeasibleFlow() {
|
||||
LOG(INFO) << "Max Feasible Flow";
|
||||
const int kNumNodes = 6;
|
||||
const int kNumArcs = 9;
|
||||
const NodeIndex kTail[kNumArcs] = { 0, 0, 0, 0, 1, 2, 3, 3, 4 };
|
||||
const NodeIndex kHead[kNumArcs] = { 1, 2, 3, 4, 3, 4, 4, 5, 5 };
|
||||
const FlowQuantity kCapacity[kNumArcs] = { 5, 8, 5, 3, 4, 5, 6, 6, 4 };
|
||||
const FlowQuantity kExpectedFlow[kNumArcs] = { 4, 4, 2, 0, 4, 4, 0, 6, 4 };
|
||||
const FlowQuantity kExpectedTotalFlow = 10;
|
||||
StarGraph graph(kNumNodes, kNumArcs);
|
||||
MaxFlow max_flow(&graph, 0, kNumNodes - 1);
|
||||
for (int i = 0; i < kNumArcs; ++i) {
|
||||
ArcIndex arc = graph.AddArc(kTail[i], kHead[i]);
|
||||
max_flow.SetArcCapacity(arc, kCapacity[i]);
|
||||
}
|
||||
CHECK(max_flow.Solve());
|
||||
CHECK_EQ(MaxFlow::OPTIMAL, max_flow.status());
|
||||
FlowQuantity total_flow = max_flow.GetOptimalFlow();
|
||||
CHECK_EQ(total_flow, kExpectedTotalFlow);
|
||||
for (int i = 0; i < kNumArcs; ++i) {
|
||||
CHECK_EQ(kExpectedFlow[i], max_flow.Flow(i)) << " i = " << i;
|
||||
}
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::MinCostFlowOn4x4Matrix();
|
||||
operations_research::MaxFeasibleFlow();
|
||||
return 0;
|
||||
}
|
||||
|
||||
582
examples/cpp/global_arith.cc
Normal file
582
examples/cpp/global_arith.cc
Normal file
@@ -0,0 +1,582 @@
|
||||
// Copyright 2010 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/stl_util.h"
|
||||
#include "constraint_solver/constraint_solveri.h"
|
||||
#include "examples/global_arith.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
class ArithmeticPropagator;
|
||||
|
||||
// ----- SubstitutionMap -----
|
||||
|
||||
class SubstitutionMap {
|
||||
public:
|
||||
struct Offset { // to_replace = var_index + offset
|
||||
Offset() : var_index(-1), offset(0) {}
|
||||
Offset(int v, int64 o) : var_index(v), offset(o) {}
|
||||
int var_index;
|
||||
int64 offset;
|
||||
};
|
||||
|
||||
void AddSubstitution(int left_var, int right_var, int64 right_offset) {
|
||||
// TODO(lperron) : Perform transitive closure.
|
||||
substitutions_[left_var] = Offset(right_var, right_offset);
|
||||
}
|
||||
|
||||
void ProcessAllSubstitutions(Callback3<int, int, int64>* const hook) {
|
||||
for (hash_map<int, Offset>::const_iterator it = substitutions_.begin();
|
||||
it != substitutions_.end();
|
||||
++it) {
|
||||
hook->Run(it->first, it->second.var_index, it->second.offset);
|
||||
}
|
||||
}
|
||||
private:
|
||||
hash_map<int, Offset> substitutions_;
|
||||
};
|
||||
|
||||
// ----- Bounds -----
|
||||
|
||||
struct Bounds {
|
||||
Bounds() : lb(kint64min), ub(kint64max) {}
|
||||
Bounds(int64 l, int64 u) : lb(l), ub(u) {}
|
||||
|
||||
void Intersect(int64 new_lb, int64 new_ub) {
|
||||
lb = std::max(lb, new_lb);
|
||||
ub = std::min(ub, new_ub);
|
||||
}
|
||||
|
||||
void Intersect(const Bounds& other) {
|
||||
Intersect(other.lb, other.ub);
|
||||
}
|
||||
|
||||
void Union(int64 new_lb, int64 new_ub) {
|
||||
lb = std::min(lb, new_lb);
|
||||
ub = std::max(ub, new_ub);
|
||||
}
|
||||
|
||||
void Union(const Bounds& other) {
|
||||
Union(other.lb, other.ub);
|
||||
}
|
||||
|
||||
bool IsEqual(const Bounds& other) {
|
||||
return (ub == other.ub && lb == other.lb);
|
||||
}
|
||||
|
||||
bool IsIncluded(const Bounds& other) {
|
||||
return (ub <= other.ub && lb >= other.lb);
|
||||
}
|
||||
|
||||
int64 lb;
|
||||
int64 ub;
|
||||
};
|
||||
|
||||
// ----- BoundsStore -----
|
||||
|
||||
class BoundsStore {
|
||||
public:
|
||||
BoundsStore(vector<Bounds>* initial_bounds)
|
||||
: initial_bounds_(initial_bounds) {}
|
||||
|
||||
void SetRange(int var_index, int64 lb, int64 ub) {
|
||||
hash_map<int, Bounds>::iterator it = modified_bounds_.find(var_index);
|
||||
if (it == modified_bounds_.end()) {
|
||||
Bounds new_bounds(lb, ub);
|
||||
const Bounds& initial = (*initial_bounds_)[var_index];
|
||||
new_bounds.Intersect(initial);
|
||||
if (!new_bounds.IsEqual(initial)) {
|
||||
modified_bounds_.insert(make_pair(var_index, new_bounds));
|
||||
}
|
||||
} else {
|
||||
it->second.Intersect(lb, ub);
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
modified_bounds_.clear();
|
||||
}
|
||||
|
||||
const hash_map<int, Bounds>& modified_bounds() const {
|
||||
return modified_bounds_;
|
||||
}
|
||||
|
||||
vector<Bounds>* initial_bounds() const { return initial_bounds_; }
|
||||
|
||||
void Apply() {
|
||||
for (hash_map<int, Bounds>::const_iterator it = modified_bounds_.begin();
|
||||
it != modified_bounds_.end();
|
||||
++it) {
|
||||
(*initial_bounds_)[it->first] = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
vector<Bounds>* initial_bounds_;
|
||||
hash_map<int, Bounds> modified_bounds_;
|
||||
};
|
||||
|
||||
// ----- ArithmeticConstraint -----
|
||||
|
||||
class ArithmeticConstraint {
|
||||
public:
|
||||
virtual ~ArithmeticConstraint() {}
|
||||
|
||||
const vector<int>& vars() const { return vars_; }
|
||||
|
||||
virtual bool Propagate(BoundsStore* const store) = 0;
|
||||
virtual void Replace(int to_replace, int var, int64 offset) = 0;
|
||||
virtual bool Deduce(ArithmeticPropagator* const propagator) const = 0;
|
||||
virtual string DebugString() const = 0;
|
||||
private:
|
||||
const vector<int> vars_;
|
||||
};
|
||||
|
||||
// ----- ArithmeticPropagator -----
|
||||
|
||||
class ArithmeticPropagator : PropagationBaseObject {
|
||||
public:
|
||||
ArithmeticPropagator(Solver* const solver, Demon* const demon)
|
||||
: PropagationBaseObject(solver), demon_(demon) {}
|
||||
|
||||
void ReduceProblem() {
|
||||
for (int constraint_index = 0;
|
||||
constraint_index < constraints_.size();
|
||||
++constraint_index) {
|
||||
if (constraints_[constraint_index]->Deduce(this)) {
|
||||
protected_constraints_.insert(constraint_index);
|
||||
}
|
||||
}
|
||||
scoped_ptr<Callback3<int, int, int64> > hook(
|
||||
NewPermanentCallback(this,
|
||||
&ArithmeticPropagator::ProcessOneSubstitution));
|
||||
substitution_map_.ProcessAllSubstitutions(hook.get());
|
||||
}
|
||||
|
||||
void Post() {
|
||||
for (int constraint_index = 0;
|
||||
constraint_index < constraints_.size();
|
||||
++constraint_index) {
|
||||
const vector<int>& vars = constraints_[constraint_index]->vars();
|
||||
for (int var_index = 0; var_index < vars.size(); ++var_index) {
|
||||
dependencies_[vars[var_index]].push_back(constraint_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitialPropagate() {
|
||||
|
||||
}
|
||||
|
||||
void Update(int var_index) {
|
||||
Enqueue(demon_);
|
||||
}
|
||||
|
||||
void AddConstraint(ArithmeticConstraint* const ct) {
|
||||
constraints_.push_back(ct);
|
||||
}
|
||||
|
||||
void AddVariable(int64 lb, int64 ub) {
|
||||
bounds_.push_back(Bounds(lb, ub));
|
||||
}
|
||||
|
||||
const vector<IntVar*> vars() const { return vars_; }
|
||||
|
||||
int VarIndex(IntVar* const var) {
|
||||
hash_map<IntVar*, int>::const_iterator it = var_map_.find(var);
|
||||
if (it == var_map_.end()) {
|
||||
const int index = var_map_.size();
|
||||
var_map_[var] = index;
|
||||
return index;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubstitution(int left_var, int right_var, int64 right_offset) {
|
||||
substitution_map_.AddSubstitution(left_var, right_var, right_offset);
|
||||
}
|
||||
|
||||
void AddNewBounds(int var_index, int64 lb, int64 ub) {
|
||||
bounds_[var_index].Intersect(lb, ub);
|
||||
}
|
||||
|
||||
void ProcessOneSubstitution(int left_var, int right_var, int64 right_offset) {
|
||||
for (int constraint_index = 0;
|
||||
constraint_index < constraints_.size();
|
||||
++constraint_index) {
|
||||
if (!ContainsKey(protected_constraints_, constraint_index)) {
|
||||
ArithmeticConstraint* const constraint = constraints_[constraint_index];
|
||||
constraint->Replace(left_var, right_var, right_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintModel() {
|
||||
LOG(INFO) << "Vars:";
|
||||
for (int i = 0; i < bounds_.size(); ++i) {
|
||||
LOG(INFO) << " var<" << i << "> = [" << bounds_[i].lb
|
||||
<< " .. " << bounds_[i].ub << "]";
|
||||
}
|
||||
LOG(INFO) << "Constraints";
|
||||
for (int i = 0; i < constraints_.size(); ++i) {
|
||||
LOG(INFO) << " " << constraints_[i]->DebugString();
|
||||
}
|
||||
}
|
||||
private:
|
||||
Demon* const demon_;
|
||||
vector<IntVar*> vars_;
|
||||
hash_map<IntVar*, int> var_map_;
|
||||
vector<ArithmeticConstraint*> constraints_;
|
||||
vector<Bounds> bounds_;
|
||||
vector<vector<int> > dependencies_; // from var indices to constraints.
|
||||
SubstitutionMap substitution_map_;
|
||||
hash_set<int> protected_constraints_;
|
||||
};
|
||||
|
||||
// ----- Custom Constraints -----
|
||||
|
||||
class RowConstraint : public ArithmeticConstraint {
|
||||
public:
|
||||
RowConstraint(int64 lb, int64 ub) : lb_(lb), ub_(ub) {}
|
||||
virtual ~RowConstraint() {}
|
||||
|
||||
void AddTerm(int var_index, int64 coefficient) {
|
||||
// TODO(lperron): Check not present.
|
||||
coefficients_[var_index] = coefficient;
|
||||
}
|
||||
|
||||
virtual bool Propagate(BoundsStore* const store) {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Replace(int to_replace, int var, int64 offset) {
|
||||
hash_map<int, int64>::iterator find_other = coefficients_.find(to_replace);
|
||||
if (find_other != coefficients_.end()) {
|
||||
hash_map<int, int64>::iterator find_var = coefficients_.find(var);
|
||||
const int64 other_coefficient = find_other->second;
|
||||
if (lb_ != kint64min) {
|
||||
lb_ += other_coefficient * offset;
|
||||
}
|
||||
if (ub_ != kint64max) {
|
||||
ub_ += other_coefficient * offset;
|
||||
}
|
||||
coefficients_.erase(find_other);
|
||||
if (find_var == coefficients_.end()) {
|
||||
coefficients_[var] = other_coefficient;
|
||||
} else {
|
||||
find_var->second += other_coefficient;
|
||||
if (find_var->second == 0) {
|
||||
coefficients_.erase(find_var);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool Deduce(ArithmeticPropagator* const propagator) const {
|
||||
// Deduce Simple translation from one var to another.
|
||||
if (lb_ == ub_ && coefficients_.size() == 2) {
|
||||
hash_map<int, int64>::const_iterator it = coefficients_.begin();
|
||||
const int var1 = it->first;
|
||||
const int64 coeff1 = it->second;
|
||||
++it;
|
||||
const int var2 = it->first;
|
||||
const int64 coeff2 = it->second;
|
||||
++it;
|
||||
CHECK(it == coefficients_.end());
|
||||
if (coeff1 == 1 && coeff2 == -1) {
|
||||
propagator->AddSubstitution(var1, var2, lb_);
|
||||
return true;
|
||||
} else if (coeff1 == -1 && coeff2 && 1) {
|
||||
propagator->AddSubstitution(var2, var1, lb_);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual string DebugString() const {
|
||||
string output = "(";
|
||||
bool first = true;
|
||||
for (hash_map<int, int64>::const_iterator it = coefficients_.begin();
|
||||
it != coefficients_.end();
|
||||
++it) {
|
||||
if (it->second != 0) {
|
||||
if (first) {
|
||||
first = false;
|
||||
if (it->second == 1) {
|
||||
output += StringPrintf("var<%d>", it->first);
|
||||
} else if (it->second == -1) {
|
||||
output += StringPrintf("-var<%d>", it->first);
|
||||
} else {
|
||||
output += StringPrintf("%lld*var<%d>", it->second, it->first);
|
||||
}
|
||||
} else if (it->second == 1) {
|
||||
output += StringPrintf(" + var<%d>", it->first);
|
||||
} else if (it->second == -1) {
|
||||
output += StringPrintf(" - var<%d>", it->first);
|
||||
} else if (it->second > 0) {
|
||||
output += StringPrintf(" + %lld*var<%d>", it->second, it->first);
|
||||
} else {
|
||||
output += StringPrintf(" - %lld*var<%d>", -it->second, it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lb_ == ub_) {
|
||||
output += StringPrintf(" == %lld)", ub_);
|
||||
} else if (lb_ == kint64min) {
|
||||
output += StringPrintf(" <= %lld)", ub_);
|
||||
} else if (ub_ == kint64max) {
|
||||
output += StringPrintf(" >= %lld)", lb_);
|
||||
} else {
|
||||
output += StringPrintf(" in [%lld .. %lld])", lb_, ub_);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
private:
|
||||
hash_map<int, int64> coefficients_;
|
||||
int64 lb_;
|
||||
int64 ub_;
|
||||
};
|
||||
|
||||
class OrConstraint : public ArithmeticConstraint {
|
||||
public:
|
||||
OrConstraint(ArithmeticConstraint* const left,
|
||||
ArithmeticConstraint* const right)
|
||||
: left_(left), right_(right) {}
|
||||
|
||||
virtual ~OrConstraint() {}
|
||||
|
||||
virtual bool Propagate(BoundsStore* const store) {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Replace(int to_replace, int var, int64 offset) {
|
||||
left_->Replace(to_replace, var, offset);
|
||||
right_->Replace(to_replace, var, offset);
|
||||
}
|
||||
|
||||
virtual bool Deduce(ArithmeticPropagator* const propagator) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual string DebugString() const {
|
||||
return StringPrintf("Or(%s, %s)",
|
||||
left_->DebugString().c_str(),
|
||||
right_->DebugString().c_str());
|
||||
}
|
||||
private:
|
||||
ArithmeticConstraint* const left_;
|
||||
ArithmeticConstraint* const right_;
|
||||
};
|
||||
|
||||
// ----- GlobalArithmeticConstraint -----
|
||||
|
||||
GlobalArithmeticConstraint::GlobalArithmeticConstraint(Solver* const solver)
|
||||
: Constraint(solver),
|
||||
propagator_(NULL) {
|
||||
propagator_.reset(new ArithmeticPropagator(
|
||||
solver,
|
||||
solver->MakeDelayedConstraintInitialPropagateCallback(this)));
|
||||
}
|
||||
GlobalArithmeticConstraint::~GlobalArithmeticConstraint() {
|
||||
STLDeleteElements(&constraints_);
|
||||
}
|
||||
|
||||
void GlobalArithmeticConstraint::Post() {
|
||||
const vector<IntVar*>& vars = propagator_->vars();
|
||||
for (int var_index = 0; var_index < vars.size(); ++var_index) {
|
||||
Demon* const demon =
|
||||
MakeConstraintDemon1(solver(),
|
||||
this,
|
||||
&GlobalArithmeticConstraint::Update,
|
||||
"Update",
|
||||
var_index);
|
||||
vars[var_index]->WhenRange(demon);
|
||||
}
|
||||
LOG(INFO) << "----- Before reduction -----";
|
||||
propagator_->PrintModel();
|
||||
LOG(INFO) << "----- After reduction -----";
|
||||
propagator_->ReduceProblem();
|
||||
propagator_->PrintModel();
|
||||
LOG(INFO) << "---------------------------";
|
||||
propagator_->Post();
|
||||
}
|
||||
|
||||
void GlobalArithmeticConstraint::InitialPropagate() {
|
||||
propagator_->InitialPropagate();
|
||||
}
|
||||
|
||||
void GlobalArithmeticConstraint::Update(int var_index) {
|
||||
propagator_->Update(var_index);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeScalProdGreaterOrEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(constant, kint64max);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), coefficients[index]);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeScalProdLessOrEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(kint64min, constant);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), coefficients[index]);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeScalProdEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(constant, constant);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), coefficients[index]);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeSumGreaterOrEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(constant, kint64max);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), 1);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeSumLessOrEqualConstant(
|
||||
const vector<IntVar*> vars, int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(kint64min, constant);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), 1);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeSumEqualConstant(
|
||||
const vector<IntVar*> vars, int64 constant) {
|
||||
RowConstraint* const constraint = new RowConstraint(constant, constant);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), 1);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeRowConstraint(
|
||||
int64 lb,
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 ub) {
|
||||
RowConstraint* const constraint = new RowConstraint(lb, ub);
|
||||
for (int index = 0; index < vars.size(); ++index) {
|
||||
constraint->AddTerm(VarIndex(vars[index]), coefficients[index]);
|
||||
}
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
int64 ub) {
|
||||
RowConstraint* const constraint = new RowConstraint(lb, ub);
|
||||
constraint->AddTerm(VarIndex(v1), coeff1);
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
int64 ub) {
|
||||
RowConstraint* const constraint = new RowConstraint(lb, ub);
|
||||
constraint->AddTerm(VarIndex(v1), coeff1);
|
||||
constraint->AddTerm(VarIndex(v2), coeff2);
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
IntVar* const v3,
|
||||
int64 coeff3,
|
||||
int64 ub) {
|
||||
RowConstraint* const constraint = new RowConstraint(lb, ub);
|
||||
constraint->AddTerm(VarIndex(v1), coeff1);
|
||||
constraint->AddTerm(VarIndex(v2), coeff2);
|
||||
constraint->AddTerm(VarIndex(v3), coeff3);
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
IntVar* const v3,
|
||||
int64 coeff3,
|
||||
IntVar* const v4,
|
||||
int64 coeff4,
|
||||
int64 ub) {
|
||||
RowConstraint* const constraint = new RowConstraint(lb, ub);
|
||||
constraint->AddTerm(VarIndex(v1), coeff1);
|
||||
constraint->AddTerm(VarIndex(v2), coeff2);
|
||||
constraint->AddTerm(VarIndex(v3), coeff3);
|
||||
constraint->AddTerm(VarIndex(v4), coeff4);
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::MakeOrConstraint(
|
||||
ConstraintRef left_ref,
|
||||
ConstraintRef right_ref) {
|
||||
OrConstraint* const constraint =
|
||||
new OrConstraint(constraints_[left_ref.index()],
|
||||
constraints_[right_ref.index()]);
|
||||
return Store(constraint);
|
||||
}
|
||||
|
||||
void GlobalArithmeticConstraint::Add(ConstraintRef ref) {
|
||||
propagator_->AddConstraint(constraints_[ref.index()]);
|
||||
}
|
||||
|
||||
int GlobalArithmeticConstraint::VarIndex(IntVar* const var) {
|
||||
hash_map<IntVar*, int>::const_iterator it = var_indices_.find(var);
|
||||
if (it == var_indices_.end()) {
|
||||
const int new_index = var_indices_.size();
|
||||
var_indices_.insert(make_pair(var, new_index));
|
||||
propagator_->AddVariable(var->Min(), var->Max());
|
||||
return new_index;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintRef GlobalArithmeticConstraint::Store(
|
||||
ArithmeticConstraint* const constraint) {
|
||||
const int constraint_index = constraints_.size();
|
||||
constraints_.push_back(constraint);
|
||||
return ConstraintRef(constraint_index);
|
||||
}
|
||||
} // namespace operations_research
|
||||
100
examples/cpp/global_arith.h
Normal file
100
examples/cpp/global_arith.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2010 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef EXAMPLES_GLOBAL_ARITH_H_
|
||||
#define EXAMPLES_GLOBAL_ARITH_H_
|
||||
namespace operations_research {
|
||||
class ArithmeticPropagator;
|
||||
class ArithmeticConstraint;
|
||||
|
||||
class ConstraintRef {
|
||||
public:
|
||||
ConstraintRef(int index) : index_(index) {}
|
||||
int index() const { return index_; }
|
||||
private:
|
||||
const int index_;
|
||||
};
|
||||
|
||||
class GlobalArithmeticConstraint : public Constraint {
|
||||
public:
|
||||
GlobalArithmeticConstraint(Solver* const solver);
|
||||
virtual ~GlobalArithmeticConstraint();
|
||||
|
||||
virtual void Post();
|
||||
virtual void InitialPropagate();
|
||||
void Update(int var_index);
|
||||
|
||||
ConstraintRef MakeScalProdGreaterOrEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant);
|
||||
ConstraintRef MakeScalProdLessOrEqualConstant(
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant);
|
||||
ConstraintRef MakeScalProdEqualConstant(const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 constant);
|
||||
ConstraintRef MakeSumGreaterOrEqualConstant(const vector<IntVar*> vars,
|
||||
int64 constant);
|
||||
ConstraintRef MakeSumLessOrEqualConstant(const vector<IntVar*> vars,
|
||||
int64 constant);
|
||||
ConstraintRef MakeSumEqualConstant(const vector<IntVar*> vars,
|
||||
int64 constant);
|
||||
ConstraintRef MakeRowConstraint(int64 lb,
|
||||
const vector<IntVar*> vars,
|
||||
const vector<int64> coefficients,
|
||||
int64 ub);
|
||||
ConstraintRef MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
int64 ub);
|
||||
ConstraintRef MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
int64 ub);
|
||||
ConstraintRef MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
IntVar* const v3,
|
||||
int64 coeff3,
|
||||
int64 ub);
|
||||
ConstraintRef MakeRowConstraint(int64 lb,
|
||||
IntVar* const v1,
|
||||
int64 coeff1,
|
||||
IntVar* const v2,
|
||||
int64 coeff2,
|
||||
IntVar* const v3,
|
||||
int64 coeff3,
|
||||
IntVar* const v4,
|
||||
int64 coeff4,
|
||||
int64 ub);
|
||||
ConstraintRef MakeOrConstraint(ConstraintRef left_constraint_index,
|
||||
ConstraintRef right_constraint_index);
|
||||
|
||||
void Add(ConstraintRef constraint_index);
|
||||
private:
|
||||
int VarIndex(IntVar* const var);
|
||||
ConstraintRef Store(ArithmeticConstraint* const constraint);
|
||||
|
||||
scoped_ptr<ArithmeticPropagator> propagator_;
|
||||
hash_map<IntVar*, int> var_indices_;
|
||||
vector<ArithmeticConstraint*> constraints_;
|
||||
};
|
||||
|
||||
} // namespace operations_research
|
||||
#endif // EXAMPLES_GLOBAL_ARITH_H_
|
||||
107
examples/cpp/golomb.cc
Normal file
107
examples/cpp/golomb.cc
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Golomb ruler problem
|
||||
//
|
||||
// find minimal ruler so that the differences between ticks are unique.
|
||||
//
|
||||
// First solutions:
|
||||
// 0, 1
|
||||
// 0, 1, 3
|
||||
// 0, 1, 4, 6
|
||||
// 0, 1, 4, 9, 11
|
||||
// 0, 1, 4, 10, 12, 17
|
||||
// 0, 1, 4, 10, 18, 23, 25
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
|
||||
DEFINE_bool(print, false, "If true, print the minimal solution.");
|
||||
DEFINE_int32(size, 0,
|
||||
"Size of the problem. If equal to 0, will test several increasing sizes.");
|
||||
|
||||
static const int kBestSolutions[] = {
|
||||
0, 1, 3, 6, 11, 17, 25, 34, 44, 55, 72, 85,
|
||||
// just for the optimistics ones, the rest:
|
||||
106, 127, 151, 177, 199, 216, 246
|
||||
};
|
||||
|
||||
static const int kKnownSolutions = 19;
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void GolombRuler(int size) {
|
||||
CHECK_GE(size, 1);
|
||||
Solver s("golomb");
|
||||
|
||||
// model
|
||||
std::vector<IntVar*> ticks(size);
|
||||
ticks[0] = s.MakeIntConst(0); // X(0) = 0
|
||||
const int64 max = 1 + size * size * size;
|
||||
for (int i = 1; i < size; ++i) {
|
||||
ticks[i] = s.MakeIntVar(1, max, StringPrintf("X%02d", i));
|
||||
}
|
||||
std::vector<IntVar*> diffs;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
for (int j = i + 1; j < size; ++j) {
|
||||
IntVar* const diff = s.MakeDifference(ticks[j], ticks[i])->Var();
|
||||
diffs.push_back(diff);
|
||||
diff->SetMin(1);
|
||||
}
|
||||
}
|
||||
s.AddConstraint(s.MakeAllDifferent(diffs));
|
||||
|
||||
OptimizeVar* const length = s.MakeMinimize(ticks[size-1], 1);
|
||||
SolutionCollector* const collector = s.MakeLastSolutionCollector();
|
||||
collector->Add(ticks);
|
||||
DecisionBuilder* const db = s.MakePhase(ticks,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
s.Solve(db, collector, length); // go!
|
||||
CHECK_EQ(collector->solution_count(), 1);
|
||||
const int64 result = collector->Value(0, ticks[size-1]);
|
||||
const int num_failures = collector->failures(0);
|
||||
printf("N = %d, optimal length = %d (fails:%d)\n",
|
||||
size, static_cast<int>(result), num_failures);
|
||||
if (size - 1 < kKnownSolutions) {
|
||||
CHECK_EQ(result, kBestSolutions[size - 1]);
|
||||
}
|
||||
if (FLAGS_print) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const int64 tick = collector->Value(0, ticks[i]);
|
||||
printf("%d ", static_cast<int>(tick));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_size != 0) {
|
||||
operations_research::GolombRuler(FLAGS_size);
|
||||
} else {
|
||||
for (int n = 1; n < 11; ++n) {
|
||||
operations_research::GolombRuler(n);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
80
examples/cpp/integer_programming.cc
Normal file
80
examples/cpp/integer_programming.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Integer programming example that shows how to use the API.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "linear_solver/linear_solver.h"
|
||||
|
||||
namespace operations_research {
|
||||
void RunIntegerProgrammingExample(
|
||||
MPSolver::OptimizationProblemType optimization_problem_type) {
|
||||
MPSolver solver("IntegerProgrammingExample", optimization_problem_type);
|
||||
const double infinity = solver.infinity();
|
||||
// x1 and x2 are integer non-negative variables.
|
||||
MPVariable* const x1 = solver.MakeIntVar(0.0, infinity, "x1");
|
||||
MPVariable* const x2 = solver.MakeIntVar(0.0, infinity, "x2");
|
||||
|
||||
// Minimize x1 + 2 * x2.
|
||||
solver.SetObjectiveCoefficient(x1, 1);
|
||||
solver.SetObjectiveCoefficient(x2, 2);
|
||||
|
||||
// 2 * x2 + 3 * x1 >= 17.
|
||||
MPConstraint* const c0 = solver.MakeRowConstraint(17, infinity);
|
||||
c0->SetCoefficient(x1, 3);
|
||||
c0->SetCoefficient(x2, 2);
|
||||
|
||||
const MPSolver::ResultStatus result_status = solver.Solve();
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (result_status != MPSolver::OPTIMAL) {
|
||||
LOG(FATAL) << "The problem does not have an optimal solution!";
|
||||
}
|
||||
|
||||
LOG(INFO) << "Problem solved in " << solver.wall_time() << " milliseconds";
|
||||
|
||||
// The objective value of the solution.
|
||||
LOG(INFO) << "Optimal objective value = " << solver.objective_value();
|
||||
|
||||
// The value of each variable in the solution.
|
||||
LOG(INFO) << "x1 = " << x1->solution_value();
|
||||
LOG(INFO) << "x2 = " << x2->solution_value();
|
||||
|
||||
LOG(INFO) << "Advanced usage:";
|
||||
LOG(INFO) << "Problem solved in " << solver.nodes()
|
||||
<< " branch-and-bound nodes";
|
||||
}
|
||||
|
||||
|
||||
void RunAllExamples() {
|
||||
#if defined(USE_GLPK)
|
||||
LOG(INFO) << "---- Integer programming example with GLPK ----";
|
||||
RunIntegerProgrammingExample(MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING);
|
||||
#endif
|
||||
#if defined(USE_CBC)
|
||||
LOG(INFO) << "---- Integer programming example with CBC ----";
|
||||
RunIntegerProgrammingExample(MPSolver::CBC_MIXED_INTEGER_PROGRAMMING);
|
||||
#endif
|
||||
#if defined(USE_SCIP)
|
||||
LOG(INFO) << "---- Integer programming example with SCIP ----";
|
||||
RunIntegerProgrammingExample(MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
|
||||
#endif
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::RunAllExamples();
|
||||
return 0;
|
||||
}
|
||||
189
examples/cpp/jobshop.cc
Normal file
189
examples/cpp/jobshop.cc
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This model implements a simple jobshop problem.
|
||||
//
|
||||
// A jobshop is a standard scheduling problem where you must schedule a
|
||||
// set of jobs on a set of machines. Each job is a sequence of tasks
|
||||
// (a task can only start when the preceding task finished), each of
|
||||
// which occupies a single specific machine during a specific
|
||||
// duration. Therefore, a job is simply given by a sequence of pairs
|
||||
// (machine id, duration).
|
||||
|
||||
// The objective is to minimize the 'makespan', which is the duration
|
||||
// between the start of the first task (across all machines) and the
|
||||
// completion of the last task (across all machines).
|
||||
//
|
||||
// This will be modelled by sets of intervals variables (see class
|
||||
// IntervalVar in constraint_solver/constraint_solver.h), one per
|
||||
// task, representing the [start_time, end_time] of the task. Tasks
|
||||
// in the same job will be linked by precedence constraints. Tasks on
|
||||
// the same machine will be covered by Sequence constraints.
|
||||
//
|
||||
// Search will then be applied on the sequence constraints.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
#include "cpp/jobshop.h"
|
||||
|
||||
DEFINE_string(
|
||||
data_file,
|
||||
"",
|
||||
"Required: input file description the scheduling problem to solve, "
|
||||
"in our jssp format:\n"
|
||||
" - the first line is \"instance <instance name>\"\n"
|
||||
" - the second line is \"<number of jobs> <number of machines>\"\n"
|
||||
" - then one line per job, with a single space-separated "
|
||||
"list of \"<machine index> <duration>\"\n"
|
||||
"note: jobs with one task are not supported");
|
||||
DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit.");
|
||||
|
||||
namespace operations_research {
|
||||
void Jobshop(const JobShopData& data) {
|
||||
Solver solver("jobshop");
|
||||
const int machine_count = data.machine_count();
|
||||
const int job_count = data.job_count();
|
||||
const int horizon = data.horizon();
|
||||
|
||||
// ----- Creates all Intervals and vars -----
|
||||
|
||||
// Stores all tasks attached interval variables per job.
|
||||
std::vector<std::vector<IntervalVar*> > jobs_to_tasks(job_count);
|
||||
// machines_to_tasks stores the same interval variables as above, but
|
||||
// grouped my machines instead of grouped by jobs.
|
||||
std::vector<std::vector<IntervalVar*> > machines_to_tasks(machine_count);
|
||||
|
||||
// Creates all individual interval variables.
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const std::vector<JobShopData::Task>& tasks = data.TasksOfJob(job_id);
|
||||
for (int task_index = 0; task_index < tasks.size(); ++task_index) {
|
||||
const JobShopData::Task& task = tasks[task_index];
|
||||
CHECK_EQ(job_id, task.job_id);
|
||||
const string name = StringPrintf("J%dM%dI%dD%d",
|
||||
task.job_id,
|
||||
task.machine_id,
|
||||
task_index,
|
||||
task.duration);
|
||||
IntervalVar* const one_task =
|
||||
solver.MakeFixedDurationIntervalVar(0,
|
||||
horizon,
|
||||
task.duration,
|
||||
false,
|
||||
name);
|
||||
jobs_to_tasks[task.job_id].push_back(one_task);
|
||||
machines_to_tasks[task.machine_id].push_back(one_task);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Creates model -----
|
||||
|
||||
// Creates precedences inside jobs.
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const int task_count = jobs_to_tasks[job_id].size();
|
||||
for (int task_index = 0; task_index < task_count - 1; ++task_index) {
|
||||
IntervalVar* const t1 = jobs_to_tasks[job_id][task_index];
|
||||
IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1];
|
||||
Constraint* const prec =
|
||||
solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1);
|
||||
solver.AddConstraint(prec);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds disjunctive constraints on unary resources.
|
||||
for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
|
||||
solver.AddConstraint(
|
||||
solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id]));
|
||||
}
|
||||
|
||||
// Creates sequences variables on machines. A sequence variable is a
|
||||
// dedicated variable whose job is to sequence interval variables.
|
||||
std::vector<SequenceVar*> all_sequences;
|
||||
for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
|
||||
const string name = StringPrintf("Machine_%d", machine_id);
|
||||
SequenceVar* const sequence =
|
||||
solver.MakeSequenceVar(machines_to_tasks[machine_id], name);
|
||||
all_sequences.push_back(sequence);
|
||||
}
|
||||
|
||||
// Creates array of end_times of jobs.
|
||||
std::vector<IntVar*> all_ends;
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const int task_count = jobs_to_tasks[job_id].size();
|
||||
IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1];
|
||||
all_ends.push_back(task->EndExpr()->Var());
|
||||
}
|
||||
|
||||
// Objective: minimize the makespan (maximum end times of all tasks)
|
||||
// of the problem.
|
||||
IntVar* const objective_var = solver.MakeMax(all_ends)->Var();
|
||||
OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);
|
||||
|
||||
// ----- Search monitors and decision builder -----
|
||||
|
||||
// This decision builder will rank all tasks on all machines.
|
||||
DecisionBuilder* const sequence_phase =
|
||||
solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT);
|
||||
|
||||
// After the ranking of tasks, the schedule is still loose and any
|
||||
// task can be postponed at will. But, because the problem is now a PERT
|
||||
// (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
|
||||
// we can schedule each task at its earliest start time. This is
|
||||
// conveniently done by fixing the objective variable to its
|
||||
// minimum value.
|
||||
DecisionBuilder* const obj_phase =
|
||||
solver.MakePhase(objective_var,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
|
||||
// The main decision builder (ranks all tasks, then fixes the
|
||||
// objective_variable).
|
||||
DecisionBuilder* const main_phase =
|
||||
solver.Compose(sequence_phase, obj_phase);
|
||||
|
||||
// Search log.
|
||||
const int kLogFrequency = 1000000;
|
||||
SearchMonitor* const search_log =
|
||||
solver.MakeSearchLog(kLogFrequency, objective_monitor);
|
||||
|
||||
SearchLimit* limit = NULL;
|
||||
if (FLAGS_time_limit_in_ms > 0) {
|
||||
limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
|
||||
}
|
||||
|
||||
// Search.
|
||||
solver.Solve(main_phase, search_log, objective_monitor, limit);
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
static const char kUsage[] =
|
||||
"Usage: see flags.\nThis program runs a simple job shop optimization "
|
||||
"output besides the debug LOGs of the solver.";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage(kUsage);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_data_file.empty()) {
|
||||
LOG(FATAL) << "Please supply a data file with --data_file=";
|
||||
}
|
||||
operations_research::JobShopData data;
|
||||
data.Load(FLAGS_data_file);
|
||||
operations_research::Jobshop(data);
|
||||
return 0;
|
||||
}
|
||||
219
examples/cpp/jobshop.h
Normal file
219
examples/cpp/jobshop.h
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// This model implements a simple jobshop problem.
|
||||
//
|
||||
// A jobshop is a standard scheduling problem where you must schedule a
|
||||
// set of jobs on a set of machines. Each job is a sequence of tasks
|
||||
// (a task can only start when the preceding task finished), each of
|
||||
// which occupies a single specific machine during a specific
|
||||
// duration. Therefore, a job is simply given by a sequence of pairs
|
||||
// (machine id, duration).
|
||||
|
||||
// The objective is to minimize the 'makespan', which is the duration
|
||||
// between the start of the first task (across all machines) and the
|
||||
// completion of the last task (across all machines).
|
||||
//
|
||||
// This will be modelled by sets of intervals variables (see class
|
||||
// IntervalVar in constraint_solver/constraint_solver.h), one per
|
||||
// task, representing the [start_time, end_time] of the task. Tasks
|
||||
// in the same job will be linked by precedence constraints. Tasks on
|
||||
// the same machine will be covered by Sequence constraints.
|
||||
|
||||
#ifndef OR_TOOLS_EXAMPLES_JOBSHOP_H_
|
||||
#define OR_TOOLS_EXAMPLES_JOBSHOP_H_
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "base/strtoint.h"
|
||||
#include "base/file.h"
|
||||
#include "base/filelinereader.h"
|
||||
#include "base/split.h"
|
||||
|
||||
namespace operations_research {
|
||||
// ----- JobShopData -----
|
||||
|
||||
// A JobShopData parses data files and stores all data internally for
|
||||
// easy retrieval.
|
||||
class JobShopData {
|
||||
public:
|
||||
// A task is the basic block of a jobshop.
|
||||
struct Task {
|
||||
Task(int j, int m, int d) : job_id(j), machine_id(m), duration(d) {}
|
||||
int job_id;
|
||||
int machine_id;
|
||||
int duration;
|
||||
};
|
||||
|
||||
enum ProblemType {
|
||||
UNDEFINED,
|
||||
JSSP,
|
||||
TAILLARD
|
||||
};
|
||||
|
||||
enum TaillardState {
|
||||
START,
|
||||
JOBS_READ,
|
||||
MACHINES_READ,
|
||||
SEED_READ,
|
||||
JOB_ID_READ,
|
||||
JOB_LENGTH_READ,
|
||||
JOB_READ
|
||||
};
|
||||
|
||||
JobShopData()
|
||||
: name_(""),
|
||||
machine_count_(0),
|
||||
job_count_(0),
|
||||
horizon_(0),
|
||||
current_job_index_(0),
|
||||
problem_type_(UNDEFINED),
|
||||
taillard_state_(START) {}
|
||||
|
||||
~JobShopData() {}
|
||||
|
||||
// Parses a file in jssp or taillard format and loads the model. See the flag
|
||||
// --data_file for a description of the format. Note that the format
|
||||
// is only partially checked: bad inputs might cause undefined
|
||||
// behavior.
|
||||
void Load(const string& filename) {
|
||||
FileLineReader reader(filename.c_str());
|
||||
reader.set_line_callback(NewPermanentCallback(
|
||||
this,
|
||||
&JobShopData::ProcessNewLine));
|
||||
reader.Reload();
|
||||
if (!reader.loaded_successfully()) {
|
||||
LOG(ERROR) << "Could not open jobshop file";
|
||||
}
|
||||
}
|
||||
|
||||
// The number of machines in the jobshop.
|
||||
int machine_count() const { return machine_count_; }
|
||||
|
||||
// The number of jobs in the jobshop.
|
||||
int job_count() const { return job_count_; }
|
||||
|
||||
// The name of the jobshop instance.
|
||||
const string& name() const { return name_; }
|
||||
|
||||
// The horizon of the workshop (the sum of all durations), which is
|
||||
// a trivial upper bound of the optimal make_span.
|
||||
int horizon() const { return horizon_; }
|
||||
|
||||
// Returns the tasks of a job, ordered by precedence.
|
||||
const std::vector<Task>& TasksOfJob(int job_id) const {
|
||||
return all_tasks_[job_id];
|
||||
}
|
||||
|
||||
private:
|
||||
void ProcessNewLine(char* const line) {
|
||||
// TODO(user): more robust logic to support single-task jobs.
|
||||
static const char kWordDelimiters[] = " ";
|
||||
std::vector<string> words;
|
||||
SplitStringUsing(line, kWordDelimiters, &words);
|
||||
switch (problem_type_) {
|
||||
case UNDEFINED: {
|
||||
if (words.size() == 2 && words[0] == "instance") {
|
||||
problem_type_ = JSSP;
|
||||
LOG(INFO) << "Reading jssp instance " << words[1];
|
||||
name_ = words[1];
|
||||
} else if (words.size() == 1 && atoi32(words[0]) > 0) {
|
||||
problem_type_ = TAILLARD;
|
||||
taillard_state_ = JOBS_READ;
|
||||
job_count_ = atoi32(words[0]);
|
||||
CHECK_GT(job_count_, 0);
|
||||
all_tasks_.resize(job_count_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JSSP: {
|
||||
if (words.size() == 2) {
|
||||
job_count_ = atoi32(words[0]);
|
||||
machine_count_ = atoi32(words[1]);
|
||||
CHECK_GT(machine_count_, 0);
|
||||
CHECK_GT(job_count_, 0);
|
||||
LOG(INFO) << machine_count_ << " machines and "
|
||||
<< job_count_ << " jobs";
|
||||
all_tasks_.resize(job_count_);
|
||||
}
|
||||
|
||||
if (words.size() > 2 && machine_count_ != 0) {
|
||||
CHECK_EQ(words.size(), machine_count_ * 2);
|
||||
for (int i = 0; i < machine_count_; ++i) {
|
||||
const int machine_id = atoi32(words[2 * i]);
|
||||
const int duration = atoi32(words[2 * i + 1]);
|
||||
AddTask(current_job_index_, machine_id, duration);
|
||||
}
|
||||
current_job_index_++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TAILLARD: {
|
||||
switch (taillard_state_) {
|
||||
case START: {
|
||||
LOG(FATAL) << "Should not be here";
|
||||
break;
|
||||
}
|
||||
case JOBS_READ: {
|
||||
CHECK_EQ(1, words.size());
|
||||
machine_count_ = atoi32(words[0]);
|
||||
CHECK_GT(machine_count_, 0);
|
||||
taillard_state_ = MACHINES_READ;
|
||||
break;
|
||||
}
|
||||
case MACHINES_READ: {
|
||||
CHECK_EQ(1, words.size());
|
||||
const int seed = atoi32(words[0]);
|
||||
LOG(INFO) << "Taillard instance with " << job_count_
|
||||
<< " jobs, and " << machine_count_
|
||||
<< " machines, generated with a seed of " << seed;
|
||||
taillard_state_ = SEED_READ;
|
||||
break;
|
||||
}
|
||||
case SEED_READ:
|
||||
case JOB_READ: {
|
||||
CHECK_EQ(1, words.size());
|
||||
current_job_index_ = atoi32(words[0]);
|
||||
taillard_state_ = JOB_ID_READ;
|
||||
break;
|
||||
}
|
||||
case JOB_ID_READ: {
|
||||
CHECK_EQ(1, words.size());
|
||||
taillard_state_ = JOB_LENGTH_READ;
|
||||
break;
|
||||
}
|
||||
case JOB_LENGTH_READ: {
|
||||
CHECK_EQ(machine_count_, words.size());
|
||||
for (int i = 0; i < machine_count_; ++i) {
|
||||
const int duration = atoi32(words[i]);
|
||||
AddTask(current_job_index_, i, duration);
|
||||
}
|
||||
taillard_state_ = JOB_READ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddTask(int job_id, int machine_id, int duration) {
|
||||
all_tasks_[job_id].push_back(Task(job_id, machine_id, duration));
|
||||
horizon_ += duration;
|
||||
}
|
||||
|
||||
string name_;
|
||||
int machine_count_;
|
||||
int job_count_;
|
||||
int horizon_;
|
||||
std::vector<std::vector<Task> > all_tasks_;
|
||||
int current_job_index_;
|
||||
ProblemType problem_type_;
|
||||
TaillardState taillard_state_;
|
||||
};
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_EXAMPLES_JOBSHOP_H_
|
||||
460
examples/cpp/jobshop_ls.cc
Normal file
460
examples/cpp/jobshop_ls.cc
Normal file
@@ -0,0 +1,460 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This model implements a simple jobshop problem.
|
||||
//
|
||||
// A jobshop is a standard scheduling problem where you must schedule a
|
||||
// set of jobs on a set of machines. Each job is a sequence of tasks
|
||||
// (a task can only start when the preceding task finished), each of
|
||||
// which occupies a single specific machine during a specific
|
||||
// duration. Therefore, a job is simply given by a sequence of pairs
|
||||
// (machine id, duration).
|
||||
|
||||
// The objective is to minimize the 'makespan', which is the duration
|
||||
// between the start of the first task (across all machines) and the
|
||||
// completion of the last task (across all machines).
|
||||
//
|
||||
// This will be modelled by sets of intervals variables (see class
|
||||
// IntervalVar in constraint_solver/constraint_solver.h), one per
|
||||
// task, representing the [start_time, end_time] of the task. Tasks
|
||||
// in the same job will be linked by precedence constraints. Tasks on
|
||||
// the same machine will be covered by Sequence constraints.
|
||||
//
|
||||
// Search will be implemented as local search on the sequence variables.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "base/bitmap.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
#include "constraint_solver/constraint_solveri.h"
|
||||
#include "cpp/jobshop.h"
|
||||
|
||||
DEFINE_string(
|
||||
data_file,
|
||||
"",
|
||||
"Required: input file description the scheduling problem to solve, "
|
||||
"in our jssp format:\n"
|
||||
" - the first line is \"instance <instance name>\"\n"
|
||||
" - the second line is \"<number of jobs> <number of machines>\"\n"
|
||||
" - then one line per job, with a single space-separated "
|
||||
"list of \"<machine index> <duration>\"\n"
|
||||
"note: jobs with one task are not supported");
|
||||
DEFINE_int32(time_limit_in_ms, 60000, "Time limit in ms, 0 means no limit.");
|
||||
DEFINE_int32(shuffle_length, 4, "Length of sub-sequences to shuffle LS.");
|
||||
DEFINE_int32(sub_sequence_length, 4,
|
||||
"Length of sub-sequences to relax in LNS.");
|
||||
DEFINE_int32(lns_seed, 1, "Seed of the LNS random search");
|
||||
DEFINE_int32(lns_limit, 30,
|
||||
"Limit the size of the search tree in a LNS fragment");
|
||||
|
||||
|
||||
namespace operations_research {
|
||||
// ----- Exchange 2 intervals on a sequence variable -----
|
||||
|
||||
class SwapIntervals : public SequenceVarLocalSearchOperator {
|
||||
public:
|
||||
SwapIntervals(const SequenceVar* const* vars, int size)
|
||||
: SequenceVarLocalSearchOperator(vars, size),
|
||||
current_var_(-1),
|
||||
current_first_(-1),
|
||||
current_second_(-1) {}
|
||||
|
||||
virtual ~SwapIntervals() {}
|
||||
|
||||
virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) {
|
||||
CHECK_NOTNULL(delta);
|
||||
while (true) {
|
||||
RevertChanges(true);
|
||||
if (!Increment()) {
|
||||
VLOG(1) << "finished neighborhood";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int> sequence = Sequence(current_var_);
|
||||
const int tmp = sequence[current_first_];
|
||||
sequence[current_first_] = sequence[current_second_];
|
||||
sequence[current_second_] = tmp;
|
||||
SetForwardSequence(current_var_, sequence);
|
||||
if (ApplyChanges(delta, deltadelta)) {
|
||||
VLOG(1) << "Delta = " << delta->DebugString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void OnStart() {
|
||||
VLOG(1) << "start neighborhood";
|
||||
current_var_ = 0;
|
||||
current_first_ = 0;
|
||||
current_second_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Increment() {
|
||||
const SequenceVar* const var = Var(current_var_);
|
||||
if (++current_second_ >= var->size()) {
|
||||
if (++current_first_ >= var->size() - 1) {
|
||||
current_var_++;
|
||||
current_first_ = 0;
|
||||
}
|
||||
current_second_ = current_first_ + 1;
|
||||
}
|
||||
return current_var_ < Size();
|
||||
}
|
||||
|
||||
int current_var_;
|
||||
int current_first_;
|
||||
int current_second_;
|
||||
};
|
||||
|
||||
// ----- Shuffle a fixed-length sub-sequence on one sequence variable -----
|
||||
|
||||
class ShuffleIntervals : public SequenceVarLocalSearchOperator {
|
||||
public:
|
||||
ShuffleIntervals(const SequenceVar* const* vars, int size, int max_length)
|
||||
: SequenceVarLocalSearchOperator(vars, size),
|
||||
max_length_(max_length),
|
||||
current_var_(-1),
|
||||
current_first_(-1),
|
||||
current_index_(-1),
|
||||
current_length_(-1) {}
|
||||
|
||||
virtual ~ShuffleIntervals() {}
|
||||
|
||||
virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) {
|
||||
CHECK_NOTNULL(delta);
|
||||
while (true) {
|
||||
RevertChanges(true);
|
||||
if (!Increment()) {
|
||||
VLOG(1) << "finished neighborhood";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int> sequence = Sequence(current_var_);
|
||||
std::vector<int> sequence_backup(current_length_);
|
||||
for (int i = 0; i < current_length_; ++i) {
|
||||
sequence_backup[i] = sequence[i + current_first_];
|
||||
}
|
||||
for (int i = 0; i < current_length_; ++i) {
|
||||
sequence[i + current_first_] =
|
||||
sequence_backup[current_permutation_[i]];
|
||||
}
|
||||
SetForwardSequence(current_var_, sequence);
|
||||
if (ApplyChanges(delta, deltadelta)) {
|
||||
VLOG(1) << "Delta = " << delta->DebugString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void OnStart() {
|
||||
VLOG(1) << "start neighborhood";
|
||||
current_var_ = 0;
|
||||
current_first_ = 0;
|
||||
current_index_ = -1;
|
||||
current_length_ = std::min(Var(current_var_)->size(), max_length_);
|
||||
current_permutation_.resize(current_length_);
|
||||
for (int i = 0; i < current_length_; ++i) {
|
||||
current_permutation_[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool Increment() {
|
||||
if (!std::next_permutation(current_permutation_.begin(),
|
||||
current_permutation_.end())) {
|
||||
if (++current_first_ >= Var(current_var_)->size() - current_length_) {
|
||||
if (++current_var_ >= Size()) {
|
||||
return false;
|
||||
}
|
||||
current_first_ = 0;
|
||||
current_length_ = std::min(Var(current_var_)->size(), max_length_);
|
||||
current_permutation_.resize(current_length_);
|
||||
}
|
||||
current_index_ = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const int max_length_;
|
||||
int current_var_;
|
||||
int current_first_;
|
||||
int current_index_;
|
||||
int current_length_;
|
||||
std::vector<int> current_permutation_;
|
||||
};
|
||||
|
||||
// ----- LNS Operator -----
|
||||
|
||||
class SequenceLns : public SequenceVarLocalSearchOperator {
|
||||
public:
|
||||
SequenceLns(const SequenceVar* const* vars,
|
||||
int size,
|
||||
int seed,
|
||||
int max_length)
|
||||
: SequenceVarLocalSearchOperator(vars, size),
|
||||
random_(seed),
|
||||
max_length_(max_length) {}
|
||||
|
||||
virtual ~SequenceLns() {}
|
||||
|
||||
virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) {
|
||||
CHECK_NOTNULL(delta);
|
||||
while (true) {
|
||||
RevertChanges(true);
|
||||
if (random_.Uniform(2) == 0) {
|
||||
FreeTimeWindow();
|
||||
} else {
|
||||
FreeTwoResources();
|
||||
}
|
||||
if (ApplyChanges(delta, deltadelta)) {
|
||||
VLOG(1) << "Delta = " << delta->DebugString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void FreeTimeWindow() {
|
||||
for (int i = 0; i < Size(); ++i) {
|
||||
std::vector<int> sequence = Sequence(i);
|
||||
const int current_length =
|
||||
std::min(static_cast<int>(sequence.size()), max_length_);
|
||||
const int start_position =
|
||||
random_.Uniform(sequence.size() - current_length);
|
||||
std::vector<int> forward;
|
||||
for (int j = 0; j < start_position; ++j) {
|
||||
forward.push_back(sequence[j]);
|
||||
}
|
||||
std::vector<int> backward;
|
||||
for (int j = sequence.size() - 1;
|
||||
j >= start_position + current_length;
|
||||
--j) {
|
||||
backward.push_back(sequence[j]);
|
||||
}
|
||||
SetForwardSequence(i, forward);
|
||||
SetBackwardSequence(i, backward);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeTwoResources() {
|
||||
std::vector<int> free_sequence;
|
||||
SetForwardSequence(random_.Uniform(Size()), free_sequence);
|
||||
SetForwardSequence(random_.Uniform(Size()), free_sequence);
|
||||
}
|
||||
|
||||
ACMRandom random_;
|
||||
const int max_length_;
|
||||
};
|
||||
|
||||
|
||||
// ----- Model and Solve -----
|
||||
|
||||
void JobshopLs(const JobShopData& data) {
|
||||
Solver solver("jobshop");
|
||||
const int machine_count = data.machine_count();
|
||||
const int job_count = data.job_count();
|
||||
const int horizon = data.horizon();
|
||||
|
||||
// ----- Creates all Intervals and vars -----
|
||||
|
||||
// Stores all tasks attached interval variables per job.
|
||||
std::vector<std::vector<IntervalVar*> > jobs_to_tasks(job_count);
|
||||
// machines_to_tasks stores the same interval variables as above, but
|
||||
// grouped my machines instead of grouped by jobs.
|
||||
std::vector<std::vector<IntervalVar*> > machines_to_tasks(machine_count);
|
||||
|
||||
// Creates all individual interval variables.
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const std::vector<JobShopData::Task>& tasks = data.TasksOfJob(job_id);
|
||||
for (int task_index = 0; task_index < tasks.size(); ++task_index) {
|
||||
const JobShopData::Task& task = tasks[task_index];
|
||||
CHECK_EQ(job_id, task.job_id);
|
||||
const string name = StringPrintf("J%dM%dI%dD%d",
|
||||
task.job_id,
|
||||
task.machine_id,
|
||||
task_index,
|
||||
task.duration);
|
||||
IntervalVar* const one_task =
|
||||
solver.MakeFixedDurationIntervalVar(0,
|
||||
horizon,
|
||||
task.duration,
|
||||
false,
|
||||
name);
|
||||
jobs_to_tasks[task.job_id].push_back(one_task);
|
||||
machines_to_tasks[task.machine_id].push_back(one_task);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Creates model -----
|
||||
|
||||
// Creates precedences inside jobs.
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const int task_count = jobs_to_tasks[job_id].size();
|
||||
for (int task_index = 0; task_index < task_count - 1; ++task_index) {
|
||||
IntervalVar* const t1 = jobs_to_tasks[job_id][task_index];
|
||||
IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1];
|
||||
Constraint* const prec =
|
||||
solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1);
|
||||
solver.AddConstraint(prec);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds disjunctive constraints on unary resources.
|
||||
for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
|
||||
solver.AddConstraint(
|
||||
solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id]));
|
||||
}
|
||||
|
||||
// Creates sequences variables on machines. A sequence variable is a
|
||||
// dedicated variable whose job is to sequence interval variables.
|
||||
std::vector<SequenceVar*> all_sequences;
|
||||
for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
|
||||
const string name = StringPrintf("Machine_%d", machine_id);
|
||||
SequenceVar* const sequence =
|
||||
solver.MakeSequenceVar(machines_to_tasks[machine_id], name);
|
||||
all_sequences.push_back(sequence);
|
||||
}
|
||||
|
||||
// Creates array of end_times of jobs.
|
||||
std::vector<IntVar*> all_ends;
|
||||
for (int job_id = 0; job_id < job_count; ++job_id) {
|
||||
const int task_count = jobs_to_tasks[job_id].size();
|
||||
IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1];
|
||||
all_ends.push_back(task->EndExpr()->Var());
|
||||
}
|
||||
|
||||
// Objective: minimize the makespan (maximum end times of all tasks)
|
||||
// of the problem.
|
||||
IntVar* const objective_var = solver.MakeMax(all_ends)->Var();
|
||||
|
||||
// ----- Search monitors and decision builder -----
|
||||
|
||||
// This decision builder will rank all tasks on all machines.
|
||||
DecisionBuilder* const sequence_phase =
|
||||
solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT);
|
||||
|
||||
// After the ranking of tasks, the schedule is still loose and any
|
||||
// task can be postponed at will. But, because the problem is now a PERT
|
||||
// (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
|
||||
// we can schedule each task at its earliest start time. This is
|
||||
// conveniently done by fixing the objective variable to its
|
||||
// minimum value.
|
||||
DecisionBuilder* const obj_phase =
|
||||
solver.MakePhase(objective_var,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
|
||||
Assignment* const first_solution = solver.MakeAssignment();
|
||||
first_solution->Add(all_sequences);
|
||||
first_solution->AddObjective(objective_var);
|
||||
// Store the first solution in the 'solution' object.
|
||||
DecisionBuilder* const store_db = solver.MakeStoreAssignment(first_solution);
|
||||
|
||||
// The main decision builder (ranks all tasks, then fixes the
|
||||
// objective_variable).
|
||||
DecisionBuilder* const first_solution_phase =
|
||||
solver.Compose(sequence_phase, obj_phase, store_db);
|
||||
|
||||
LOG(INFO) << "Looking for the first solution";
|
||||
const bool first_solution_found = solver.Solve(first_solution_phase);
|
||||
if (first_solution_found) {
|
||||
LOG(INFO) << "Solution found with makespan = "
|
||||
<< first_solution->ObjectiveValue();
|
||||
} else {
|
||||
LOG(INFO) << "No initial solution found!";
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Switching to local search";
|
||||
std::vector<LocalSearchOperator*> operators;
|
||||
LOG(INFO) << " - use swap operator";
|
||||
LocalSearchOperator* const swap_operator =
|
||||
solver.RevAlloc(new SwapIntervals(all_sequences.data(),
|
||||
all_sequences.size()));
|
||||
operators.push_back(swap_operator);
|
||||
LOG(INFO) << " - use shuffle operator with a max length of "
|
||||
<< FLAGS_shuffle_length;
|
||||
LocalSearchOperator* const shuffle_operator =
|
||||
solver.RevAlloc(new ShuffleIntervals(all_sequences.data(),
|
||||
all_sequences.size(),
|
||||
FLAGS_shuffle_length));
|
||||
operators.push_back(shuffle_operator);
|
||||
LOG(INFO) << " - use free sub sequences of length "
|
||||
<< FLAGS_sub_sequence_length << " lns operator";
|
||||
LocalSearchOperator* const lns_operator =
|
||||
solver.RevAlloc(new SequenceLns(all_sequences.data(),
|
||||
all_sequences.size(),
|
||||
FLAGS_lns_seed,
|
||||
FLAGS_sub_sequence_length));
|
||||
operators.push_back(lns_operator);
|
||||
|
||||
// Creates the local search decision builder.
|
||||
LocalSearchOperator* const concat =
|
||||
solver.ConcatenateOperators(operators, true);
|
||||
|
||||
SearchLimit* const ls_limit =
|
||||
solver.MakeLimit(kint64max, FLAGS_lns_limit, kint64max, kint64max);
|
||||
DecisionBuilder* const random_sequence_phase =
|
||||
solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD);
|
||||
DecisionBuilder* const ls_db =
|
||||
solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase),
|
||||
ls_limit);
|
||||
|
||||
LocalSearchPhaseParameters* const parameters =
|
||||
solver.MakeLocalSearchPhaseParameters(concat, ls_db);
|
||||
DecisionBuilder* const final_db =
|
||||
solver.MakeLocalSearchPhase(first_solution, parameters);
|
||||
|
||||
OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);
|
||||
|
||||
// Search log.
|
||||
const int kLogFrequency = 1000000;
|
||||
SearchMonitor* const search_log =
|
||||
solver.MakeSearchLog(kLogFrequency, objective_monitor);
|
||||
|
||||
SearchLimit* const limit = FLAGS_time_limit_in_ms > 0 ?
|
||||
solver.MakeTimeLimit(FLAGS_time_limit_in_ms) :
|
||||
NULL;
|
||||
|
||||
// Search.
|
||||
solver.Solve(final_db, search_log, objective_monitor, limit);
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
static const char kUsage[] =
|
||||
"Usage: see flags.\nThis program runs a simple job shop optimization "
|
||||
"output besides the debug LOGs of the solver.";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage(kUsage);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_data_file.empty()) {
|
||||
LOG(FATAL) << "Please supply a data file with --data_file=";
|
||||
}
|
||||
operations_research::JobShopData data;
|
||||
data.Load(FLAGS_data_file);
|
||||
operations_research::JobshopLs(data);
|
||||
return 0;
|
||||
}
|
||||
56
examples/cpp/linear_assignment_api.cc
Normal file
56
examples/cpp/linear_assignment_api.cc
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "graph/ebert_graph.h"
|
||||
#include "graph/linear_assignment.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// Test assignment on a 4x4 matrix. Example taken from
|
||||
// http://www.ee.oulu.fi/~mpa/matreng/eem1_2-1.htm with kCost[0][1]
|
||||
// modified so the optimum solution is unique.
|
||||
void AssignmentOn4x4Matrix() {
|
||||
LOG(INFO) << "Assignment on 4x4 Matrix";
|
||||
const int kNumSources = 4;
|
||||
const int kNumTargets = 4;
|
||||
const CostValue kCost[kNumSources][kNumTargets] = {
|
||||
{ 90, 76, 75, 80 },
|
||||
{ 35, 85, 55, 65 },
|
||||
{ 125, 95, 90, 105 },
|
||||
{ 45, 110, 95, 115 }
|
||||
};
|
||||
const CostValue kExpectedCost =
|
||||
kCost[0][3] + kCost[1][2] + kCost[2][1] + kCost[3][0];
|
||||
ForwardStarGraph graph(
|
||||
kNumSources + kNumTargets, kNumSources * kNumTargets);
|
||||
LinearSumAssignment<ForwardStarGraph> assignment(graph, kNumSources);
|
||||
for (NodeIndex source = 0; source < kNumSources; ++source) {
|
||||
for (NodeIndex target = 0; target < kNumTargets; ++target) {
|
||||
ArcIndex arc = graph.AddArc(source, kNumSources + target);
|
||||
assignment.SetArcCost(arc, kCost[source][target]);
|
||||
}
|
||||
}
|
||||
CHECK(assignment.ComputeAssignment());
|
||||
CostValue total_cost = assignment.GetCost();
|
||||
CHECK_EQ(kExpectedCost, total_cost);
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::AssignmentOn4x4Matrix();
|
||||
return 0;
|
||||
}
|
||||
105
examples/cpp/linear_programming.cc
Normal file
105
examples/cpp/linear_programming.cc
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Linear programming example that shows how to use the API.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "linear_solver/linear_solver.h"
|
||||
|
||||
namespace operations_research {
|
||||
void RunLinearProgrammingExample(
|
||||
MPSolver::OptimizationProblemType optimization_problem_type) {
|
||||
MPSolver solver("LinearProgrammingExample", optimization_problem_type);
|
||||
const double infinity = solver.infinity();
|
||||
// x1, x2 and x3 are continuous non-negative variables.
|
||||
MPVariable* const x1 = solver.MakeNumVar(0.0, infinity, "x1");
|
||||
MPVariable* const x2 = solver.MakeNumVar(0.0, infinity, "x2");
|
||||
MPVariable* const x3 = solver.MakeNumVar(0.0, infinity, "x3");
|
||||
|
||||
// Maximize 10 * x1 + 6 * x2 + 4 * x3.
|
||||
solver.SetObjectiveCoefficient(x1, 10);
|
||||
solver.SetObjectiveCoefficient(x2, 6);
|
||||
solver.SetObjectiveCoefficient(x3, 4);
|
||||
solver.SetMaximization();
|
||||
|
||||
// x1 + x2 + x3 <= 100.
|
||||
MPConstraint* const c0 = solver.MakeRowConstraint(-infinity, 100.0);
|
||||
c0->SetCoefficient(x1, 1);
|
||||
c0->SetCoefficient(x2, 1);
|
||||
c0->SetCoefficient(x3, 1);
|
||||
|
||||
// 10 * x1 + 4 * x2 + 5 * x3 <= 600.
|
||||
MPConstraint* const c1 = solver.MakeRowConstraint(-infinity, 600.0);
|
||||
c1->SetCoefficient(x1, 10);
|
||||
c1->SetCoefficient(x2, 4);
|
||||
c1->SetCoefficient(x3, 5);
|
||||
|
||||
// 2 * x1 + 2 * x2 + 6 * x3 <= 300.
|
||||
MPConstraint* const c2 = solver.MakeRowConstraint(-infinity, 300.0);
|
||||
c2->SetCoefficient(x1, 2);
|
||||
c2->SetCoefficient(x2, 2);
|
||||
c2->SetCoefficient(x3, 6);
|
||||
|
||||
// TODO(user): Change example to show = and >= constraints.
|
||||
|
||||
LOG(INFO) << "Number of variables = " << solver.NumVariables();
|
||||
LOG(INFO) << "Number of constraints = " << solver.NumConstraints();
|
||||
|
||||
const MPSolver::ResultStatus result_status = solver.Solve();
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (result_status != MPSolver::OPTIMAL) {
|
||||
LOG(FATAL) << "The problem does not have an optimal solution!";
|
||||
}
|
||||
|
||||
LOG(INFO) << "Problem solved in " << solver.wall_time() << " milliseconds";
|
||||
|
||||
// The objective value of the solution.
|
||||
LOG(INFO) << "Optimal objective value = " << solver.objective_value();
|
||||
|
||||
// The value of each variable in the solution.
|
||||
LOG(INFO) << "x1 = " << x1->solution_value();
|
||||
LOG(INFO) << "x2 = " << x2->solution_value();
|
||||
LOG(INFO) << "x3 = " << x3->solution_value();
|
||||
|
||||
LOG(INFO) << "Advanced usage:";
|
||||
LOG(INFO) << "Problem solved in " << solver.iterations() << " iterations";
|
||||
LOG(INFO) << "x1: reduced cost = " << x1->reduced_cost();
|
||||
LOG(INFO) << "x2: reduced cost = " << x2->reduced_cost();
|
||||
LOG(INFO) << "x3: reduced cost = " << x3->reduced_cost();
|
||||
LOG(INFO) << "c0: dual value = " << c0->dual_value()
|
||||
<< " activity = " << c0->activity();
|
||||
LOG(INFO) << "c1: dual value = " << c1->dual_value()
|
||||
<< " activity = " << c1->activity();
|
||||
LOG(INFO) << "c2: dual value = " << c2->dual_value()
|
||||
<< " activity = " << c2->activity();
|
||||
}
|
||||
|
||||
void RunAllExamples() {
|
||||
#if defined(USE_GLPK)
|
||||
LOG(INFO) << "---- Linear programming example with GLPK ----";
|
||||
RunLinearProgrammingExample(MPSolver::GLPK_LINEAR_PROGRAMMING);
|
||||
#endif // USE_GLPK
|
||||
#if defined(USE_CLP)
|
||||
LOG(INFO) << "---- Linear programming example with CLP ----";
|
||||
RunLinearProgrammingExample(MPSolver::CLP_LINEAR_PROGRAMMING);
|
||||
#endif // USE_CLP
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::RunAllExamples();
|
||||
return 0;
|
||||
}
|
||||
113
examples/cpp/linear_solver_protocol_buffers.cc
Normal file
113
examples/cpp/linear_solver_protocol_buffers.cc
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "linear_solver/linear_solver.h"
|
||||
#include "linear_solver/linear_solver.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
void BuildLinearProgrammingMaxExample(MPSolver::OptimizationProblemType type) {
|
||||
const double kObjCoef[] = {10.0, 6.0, 4.0};
|
||||
const string kVarName[] = {"x1", "x2", "x3"};
|
||||
const int numVars = 3;
|
||||
const int kNumConstraints = 3;
|
||||
const string kConstraintName[] = {"c1", "c2", "c3"};
|
||||
const double kConstraintCoef1[] = {1.0, 1.0, 1.0};
|
||||
const double kConstraintCoef2[] = {10.0, 4.0, 5.0};
|
||||
const double kConstraintCoef3[] = {2.0, 2.0, 6.0};
|
||||
const double* kConstraintCoef[] = {kConstraintCoef1,
|
||||
kConstraintCoef2,
|
||||
kConstraintCoef3};
|
||||
const double kConstraintUb[] = {100.0, 600.0, 300.0};
|
||||
|
||||
const double infinity = MPSolver::infinity();
|
||||
MPModelProto model_proto;
|
||||
model_proto.set_name("Max_Example");
|
||||
|
||||
// Create variables and objective function
|
||||
for (int j = 0; j < numVars; ++j) {
|
||||
MPVariableProto* x = model_proto.add_variables();
|
||||
x->set_id(kVarName[j]);
|
||||
x->set_lb(0.0);
|
||||
x->set_ub(infinity);
|
||||
x->set_integer(false);
|
||||
MPTermProto* obj_term = model_proto.add_objective_terms();
|
||||
obj_term->set_variable_id(kVarName[j]);
|
||||
obj_term->set_coefficient(kObjCoef[j]);
|
||||
}
|
||||
model_proto.set_maximize(true);
|
||||
|
||||
// Create constraints
|
||||
for (int i = 0; i < kNumConstraints; ++i) {
|
||||
MPConstraintProto* constraint_proto = model_proto.add_constraints();
|
||||
constraint_proto->set_id(kConstraintName[i]);
|
||||
constraint_proto->set_lb(-infinity);
|
||||
constraint_proto->set_ub(kConstraintUb[i]);
|
||||
for (int j = 0; j < numVars; ++j) {
|
||||
MPTermProto* term = constraint_proto->add_terms();
|
||||
term->set_variable_id(kVarName[j]);
|
||||
term->set_coefficient(kConstraintCoef[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
MPModelRequest model_request;
|
||||
model_request.mutable_model()->CopyFrom(model_proto);
|
||||
#if defined(USE_GLPK)
|
||||
if (type == MPSolver::GLPK_LINEAR_PROGRAMMING) {
|
||||
model_request.set_problem_type(MPModelRequest::GLPK_LINEAR_PROGRAMMING);
|
||||
}
|
||||
#endif // USE_GLPK
|
||||
#if defined(USE_CLP)
|
||||
if (type == MPSolver::CLP_LINEAR_PROGRAMMING) {
|
||||
model_request.set_problem_type(MPModelRequest::CLP_LINEAR_PROGRAMMING);
|
||||
}
|
||||
#endif // USE_CLP
|
||||
|
||||
MPSolutionResponse solution_response;
|
||||
MPSolver::SolveWithProtocolBuffers(model_request, &solution_response);
|
||||
|
||||
// The problem has an optimal solution.
|
||||
CHECK_EQ(MPSolutionResponse::OPTIMAL, solution_response.result_status());
|
||||
|
||||
LOG(INFO) << "objective = " << solution_response.objective_value();
|
||||
const int num_non_zeros = solution_response.solution_values_size();
|
||||
for (int j = 0; j < num_non_zeros; ++j) {
|
||||
MPSolutionValue solution_value = solution_response.solution_values(j);
|
||||
LOG(INFO) << solution_value.variable_id() << " = "
|
||||
<< solution_value.value();
|
||||
}
|
||||
if (num_non_zeros != numVars) {
|
||||
LOG(INFO) << "All other variables have zero value";
|
||||
}
|
||||
}
|
||||
|
||||
void RunAllExamples() {
|
||||
#if defined(USE_GLPK)
|
||||
LOG(INFO) << "----- Running Max Example with GLPK -----";
|
||||
BuildLinearProgrammingMaxExample(MPSolver::GLPK_LINEAR_PROGRAMMING);
|
||||
#endif // USE_GLPK
|
||||
#if defined(USE_CLP)
|
||||
LOG(INFO) << "----- Running Max Example with Coin LP -----";
|
||||
BuildLinearProgrammingMaxExample(MPSolver::CLP_LINEAR_PROGRAMMING);
|
||||
#endif // USE_CLP
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
operations_research::RunAllExamples();
|
||||
return 0;
|
||||
}
|
||||
163
examples/cpp/magic_square.cc
Normal file
163
examples/cpp/magic_square.cc
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Magic square problem
|
||||
//
|
||||
// Solves the problem where all numbers in an nxn array have to be different
|
||||
// while the sums on diagonals, rows, and columns have to be the same.
|
||||
// The problem is trivial for odd orders, but not for even orders.
|
||||
// We do not handle odd orders with the trivial method here.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
|
||||
DEFINE_int32(size, 0, "Size of the magic square.");
|
||||
DEFINE_bool(impact, false, "Use impact search.");
|
||||
DEFINE_int32(restart, -1, "Parameter for constant restart monitor.");
|
||||
DEFINE_bool(luby, false,
|
||||
"Use luby restart monitor instead of constant restart monitor.");
|
||||
DEFINE_bool(run_all_heuristics, false, "Run all heuristics.");
|
||||
DEFINE_int32(heuristics_period, 200, "Frequency to run all heuristics.");
|
||||
DEFINE_int32(choose_var_strategy, 0,
|
||||
"Selection strategy for variable: 0 = max sum impact, "
|
||||
"1 = max average impact, "
|
||||
"2 = max individual impact.");
|
||||
DEFINE_bool(select_max_impact_value, false,
|
||||
"Select the value with max impact instead of min impact.");
|
||||
DEFINE_double(restart_log_size, -1.0,
|
||||
"Threshold for automatic restarting the search in default"
|
||||
" phase.");
|
||||
DEFINE_bool(verbose_impact, false, "Verbose output of impact search.");
|
||||
DEFINE_bool(use_nogoods, false, "Use no goods in automatic restart.");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void MagicSquare(int grid_size) {
|
||||
Solver solver("magicsquare");
|
||||
const int total_size = grid_size * grid_size;
|
||||
const int sum = grid_size * (total_size + 1) / 2;
|
||||
// create the variables
|
||||
std::vector<IntVar*> vars;
|
||||
solver.MakeIntVarArray(total_size, 1, total_size, "", &vars);
|
||||
solver.AddConstraint(solver.MakeAllDifferent(vars));
|
||||
|
||||
// create the constraints
|
||||
std::vector<IntVar*> diag1(grid_size);
|
||||
std::vector<IntVar*> diag2(grid_size);
|
||||
for (int n = 0; n < grid_size; ++n) {
|
||||
std::vector<IntVar *> sub_set(grid_size);
|
||||
|
||||
for (int m = 0; m < grid_size; ++m) { // extract row indices
|
||||
sub_set[m] = vars[m + n * grid_size];
|
||||
}
|
||||
solver.AddConstraint(solver.MakeSumEquality(sub_set, sum));
|
||||
|
||||
for (int m = 0; m < grid_size; ++m) {
|
||||
sub_set[m] = vars[m * grid_size + n]; // extract column indices
|
||||
}
|
||||
solver.AddConstraint(solver.MakeSumEquality(sub_set, sum));
|
||||
diag1[n] = vars[n + n * grid_size]; // extract first diagonal indices
|
||||
diag2[n] = vars[(grid_size - 1 - n) + n * grid_size]; // second diagonal
|
||||
}
|
||||
solver.AddConstraint(solver.MakeSumEquality(diag1, sum));
|
||||
solver.AddConstraint(solver.MakeSumEquality(diag2, sum));
|
||||
|
||||
// To break a simple symmetry: the upper right corner
|
||||
// must be less than the lower left corner
|
||||
solver.AddConstraint(solver.MakeLess(vars[grid_size - 1],
|
||||
vars[(grid_size - 1) * grid_size]));
|
||||
// TODO(user) use local search
|
||||
|
||||
DefaultPhaseParameters parameters;
|
||||
parameters.run_all_heuristics = FLAGS_run_all_heuristics;
|
||||
parameters.heuristic_period = FLAGS_heuristics_period;
|
||||
parameters.restart_log_size = FLAGS_restart_log_size;
|
||||
parameters.display_level = FLAGS_verbose_impact ?
|
||||
DefaultPhaseParameters::VERBOSE :
|
||||
DefaultPhaseParameters::NORMAL;
|
||||
parameters.use_no_goods = FLAGS_use_nogoods;
|
||||
switch (FLAGS_choose_var_strategy) {
|
||||
case 0: {
|
||||
parameters.var_selection_schema =
|
||||
DefaultPhaseParameters::CHOOSE_MAX_SUM_IMPACT;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
parameters.var_selection_schema =
|
||||
DefaultPhaseParameters::CHOOSE_MAX_AVERAGE_IMPACT;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
parameters.var_selection_schema =
|
||||
DefaultPhaseParameters::CHOOSE_MAX_VALUE_IMPACT;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG(FATAL) << "Should not be here";
|
||||
}
|
||||
}
|
||||
parameters.value_selection_schema = FLAGS_select_max_impact_value?
|
||||
DefaultPhaseParameters::SELECT_MAX_IMPACT:
|
||||
DefaultPhaseParameters::SELECT_MIN_IMPACT;
|
||||
|
||||
DecisionBuilder* const db = FLAGS_impact?
|
||||
solver.MakeDefaultPhase(vars, parameters):
|
||||
solver.MakePhase(vars,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
|
||||
std::vector<SearchMonitor*> monitors;
|
||||
SearchMonitor* const log = solver.MakeSearchLog(100000);
|
||||
monitors.push_back(log);
|
||||
SearchMonitor* const restart = FLAGS_restart != -1?
|
||||
(FLAGS_luby?
|
||||
solver.MakeLubyRestart(FLAGS_restart):
|
||||
solver.MakeConstantRestart(FLAGS_restart)):
|
||||
NULL;
|
||||
if (restart) {
|
||||
monitors.push_back(restart);
|
||||
}
|
||||
solver.NewSearch(db, monitors);
|
||||
if (solver.NextSolution()) {
|
||||
for (int n = 0; n < grid_size; ++n) {
|
||||
string output;
|
||||
for (int m = 0; m < grid_size; ++m) { // extract row indices
|
||||
int64 v = vars[n * grid_size + m]->Value();
|
||||
StringAppendF(&output, "%3lld ", v);
|
||||
}
|
||||
LG << output;
|
||||
}
|
||||
LG << "";
|
||||
} else {
|
||||
LG << "No solution found!";
|
||||
}
|
||||
solver.EndSearch();
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_size != 0) {
|
||||
operations_research::MagicSquare(FLAGS_size);
|
||||
} else {
|
||||
for (int n = 3; n < 6; ++n) {
|
||||
operations_research::MagicSquare(n);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
424
examples/cpp/model_util.cc
Normal file
424
examples/cpp/model_util.cc
Normal file
@@ -0,0 +1,424 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/file.h"
|
||||
#include "base/recordio.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
#include "constraint_solver/model.pb.h"
|
||||
#include "util/graph_export.h"
|
||||
#include "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.
|
||||
string ExprLabel(int index) {
|
||||
return StringPrintf("expr_%i", index);
|
||||
}
|
||||
|
||||
string IntervalLabel(int index) {
|
||||
return StringPrintf("interval_%i", index);
|
||||
}
|
||||
|
||||
string SequenceLabel(int index) {
|
||||
return StringPrintf("sequence_%i", index);
|
||||
}
|
||||
|
||||
string ConstraintLabel(int index) {
|
||||
return StringPrintf("ct_%i", index);
|
||||
}
|
||||
|
||||
// Scans argument to add links in the graph.
|
||||
template <class T> void ExportLinks(const CPModelProto& model,
|
||||
const string& source,
|
||||
const T& proto,
|
||||
GraphExporter* const exporter) {
|
||||
const string& arg_name = model.tags(proto.argument_index());
|
||||
if (proto.has_integer_expression_index()) {
|
||||
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.has_interval_index()) {
|
||||
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.has_sequence_index()) {
|
||||
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 CPModelProto& model,
|
||||
const CPIntegerExpressionProto& proto,
|
||||
int64* const value) {
|
||||
CHECK_NOTNULL(value);
|
||||
const int expr_type = proto.type_index();
|
||||
if (model.tags(expr_type) != ModelVisitor::kIntegerVariable) {
|
||||
return false;
|
||||
}
|
||||
if (proto.arguments_size() != 2) {
|
||||
return false;
|
||||
}
|
||||
const CPArgumentProto& arg1_proto = proto.arguments(0);
|
||||
if (model.tags(arg1_proto.argument_index()) != ModelVisitor::kMinArgument) {
|
||||
return false;
|
||||
}
|
||||
const int64 value1 = arg1_proto.integer_value();
|
||||
const CPArgumentProto& 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 CPModelProto& proto,
|
||||
GraphExporter* const exporter) {
|
||||
const CPIntegerExpressionProto& expr = proto.expressions(index);
|
||||
const string label = ExprLabel(index);
|
||||
int64 value = 0;
|
||||
if (expr.has_name()) {
|
||||
exporter->WriteNode(label, expr.name(), "oval", kGreen1);
|
||||
} else if (GetValueIfConstant(proto, expr, &value)) {
|
||||
exporter->WriteNode(label,
|
||||
StringPrintf("%lld", value),
|
||||
"oval",
|
||||
kYellow);
|
||||
} else {
|
||||
const string& type = proto.tags(expr.type_index());
|
||||
exporter->WriteNode(label, type, "oval", kWhite);
|
||||
}
|
||||
}
|
||||
|
||||
void DeclareInterval(int index,
|
||||
const CPModelProto& proto,
|
||||
GraphExporter* const exporter) {
|
||||
const CPIntervalVariableProto& interval = proto.intervals(index);
|
||||
const string label = IntervalLabel(index);
|
||||
if (interval.has_name()) {
|
||||
exporter->WriteNode(label, interval.name(), "circle", kGreen2);
|
||||
} else {
|
||||
const string& type = proto.tags(interval.type_index());
|
||||
exporter->WriteNode(label, type, "circle", kWhite);
|
||||
}
|
||||
}
|
||||
|
||||
void DeclareSequence(int index,
|
||||
const CPModelProto& proto,
|
||||
GraphExporter* const exporter) {
|
||||
const CPSequenceVariableProto& sequence = proto.sequences(index);
|
||||
const string label = SequenceLabel(index);
|
||||
if (sequence.has_name()) {
|
||||
exporter->WriteNode(label, sequence.name(), "circle", kGreen3);
|
||||
} else {
|
||||
const string& type = proto.tags(sequence.type_index());
|
||||
exporter->WriteNode(label, type, "circle", kWhite);
|
||||
}
|
||||
}
|
||||
|
||||
void DeclareConstraint(int index,
|
||||
const CPModelProto& proto,
|
||||
GraphExporter* const exporter) {
|
||||
const CPConstraintProto& ct = proto.constraints(index);
|
||||
const string& type = proto.tags(ct.type_index());
|
||||
const string label = ConstraintLabel(index);
|
||||
exporter->WriteNode(label, type, "rectangle", kBlue);
|
||||
}
|
||||
|
||||
// Parses the proto and exports it to a graph file.
|
||||
void ExportToGraphFile(const CPModelProto& proto,
|
||||
File* const file,
|
||||
GraphExporter::GraphFormat format) {
|
||||
scoped_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 string name = proto.objective().maximize() ? "Maximize" : "Minimize";
|
||||
exporter->WriteNode(kObjLabel, name, "diamond", kRed);
|
||||
}
|
||||
|
||||
for (int i = 0; i < proto.expressions_size(); ++i) {
|
||||
const CPIntegerExpressionProto& expr = proto.expressions(i);
|
||||
const 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 CPIntervalVariableProto& interval = proto.intervals(i);
|
||||
const 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 CPSequenceVariableProto& sequence = proto.sequences(i);
|
||||
const 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 CPConstraintProto& ct = proto.constraints(i);
|
||||
const 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 CPObjectiveProto& obj = proto.objective();
|
||||
exporter->WriteLink(kObjLabel,
|
||||
ExprLabel(obj.objective_index()),
|
||||
ModelVisitor::kExpressionArgument);
|
||||
}
|
||||
exporter->WriteFooter();
|
||||
}
|
||||
|
||||
// ----- Main Method -----
|
||||
|
||||
int Run() {
|
||||
// ----- Load input file into protobuf -----
|
||||
|
||||
File::Init();
|
||||
File* const file = File::Open(FLAGS_input, "r");
|
||||
if (file == NULL) {
|
||||
LOG(WARNING) << "Cannot open " << FLAGS_input;
|
||||
return kProblem;
|
||||
}
|
||||
|
||||
CPModelProto model_proto;
|
||||
RecordReader reader(file);
|
||||
if (!(reader.ReadProtocolMessage(&model_proto) && reader.Close())) {
|
||||
LOG(INFO) << "No model found in " << file->CreateFileName();
|
||||
return kProblem;
|
||||
}
|
||||
|
||||
// ----- Display loaded protobuf -----
|
||||
|
||||
LOG(INFO) << "Read model " << model_proto.model();
|
||||
if (model_proto.has_license_text()) {
|
||||
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* const license = File::Open(FLAGS_insert_license, "rb");
|
||||
if (license == NULL) {
|
||||
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();
|
||||
}
|
||||
|
||||
// ----- 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.LoadModel(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 = "
|
||||
<< DebugStringVector(primary_integer_variables, ", ");
|
||||
LOG(INFO) << "Secondary integer variables = "
|
||||
<< DebugStringVector(secondary_integer_variables, ", ");
|
||||
LOG(INFO) << "Sequence variables = "
|
||||
<< DebugStringVector(sequence_variables, ", ");
|
||||
LOG(INFO) << "Interval variables = "
|
||||
<< DebugStringVector(interval_variables, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Output -----
|
||||
|
||||
if (!FLAGS_output.empty()) {
|
||||
File* const output = File::Open(FLAGS_output, "wb");
|
||||
if (output == NULL) {
|
||||
LOG(INFO) << "Cannot open " << FLAGS_output;
|
||||
return kProblem;
|
||||
}
|
||||
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* const dot_file = File::Open(FLAGS_dot_file, "w");
|
||||
if (dot_file == NULL) {
|
||||
LOG(INFO) << "Cannot open " << FLAGS_dot_file;
|
||||
return kProblem;
|
||||
}
|
||||
ExportToGraphFile(model_proto, dot_file, GraphExporter::DOT_FORMAT);
|
||||
dot_file->Close();
|
||||
}
|
||||
|
||||
if (!FLAGS_gml_file.empty()) {
|
||||
File* const gml_file = File::Open(FLAGS_gml_file, "w");
|
||||
if (gml_file == NULL) {
|
||||
LOG(INFO) << "Cannot open " << FLAGS_gml_file;
|
||||
return kProblem;
|
||||
}
|
||||
ExportToGraphFile(model_proto, gml_file, GraphExporter::GML_FORMAT);
|
||||
gml_file->Close();
|
||||
}
|
||||
return kOk;
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_input.empty()) {
|
||||
LOG(FATAL) << "Filename not specified";
|
||||
}
|
||||
return operations_research::Run();
|
||||
}
|
||||
333
examples/cpp/multidim_knapsack.cc
Normal file
333
examples/cpp/multidim_knapsack.cc
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This model implements a multidimensional knapsack problem.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "base/strtoint.h"
|
||||
#include "base/file.h"
|
||||
#include "base/filelinereader.h"
|
||||
#include "base/split.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
|
||||
DEFINE_string(
|
||||
data_file,
|
||||
"",
|
||||
"Required: input file description the muldi-dimensional knapsack problem\n "
|
||||
"to solve. It supports two file format as described in:\n"
|
||||
" - http://elib.zib.de/pub/Packages/mp-testdata/ip/sac94-suite/readme\n"
|
||||
" - http://hces.bus.olemiss.edu/tools.html\n");
|
||||
DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, <= 0 means no limit.");
|
||||
DEFINE_int32(simplex_frequency, 0, "Number of nodes explored between each"
|
||||
" call to the simplex optimizer.");
|
||||
DEFINE_bool(display_search_log, true, "Display search log.");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// ----- Data -----
|
||||
|
||||
class MultiDimKnapsackData {
|
||||
public:
|
||||
MultiDimKnapsackData()
|
||||
: name_(""), line_read_(0), mode_(0), num_dims_(-1), num_items_(-1),
|
||||
current_bin_(0), current_item_(0), optimal_value_(0),
|
||||
problem_type_(-1) {}
|
||||
|
||||
|
||||
void Load(const string& filename) {
|
||||
FileLineReader reader(filename.c_str());
|
||||
reader.set_line_callback(NewPermanentCallback(
|
||||
this,
|
||||
&MultiDimKnapsackData::ProcessNewLine));
|
||||
reader.Reload();
|
||||
if (!reader.loaded_successfully()) {
|
||||
LOG(ERROR) << "Could not open multi dimensional knapsack file";
|
||||
}
|
||||
if (optimal_value_ == 0) {
|
||||
LOG(INFO) << "Successfully loaded problem " << name_ << " with "
|
||||
<< items() << " items, " << dims() << " dimensions";
|
||||
} else {
|
||||
LOG(INFO) << "Successfully loaded problem " << name_ << " with "
|
||||
<< items() << " items, " << dims()
|
||||
<< " dimensions and an optimal value of " << optimal_value_;
|
||||
}
|
||||
}
|
||||
|
||||
// Number of items of the problem.
|
||||
int items() const { return num_items_; }
|
||||
|
||||
// Number of dimensions of the problem.
|
||||
int dims() const { return num_dims_; }
|
||||
|
||||
// Name of the problem.
|
||||
const string& name() const { return name_; }
|
||||
|
||||
int capacity(int i) const { return dims_[i]; }
|
||||
int profit(int j) const { return profit_[j]; }
|
||||
int weight(int i, int j) const { return weight_[i][j]; }
|
||||
int optimal_value() const { return optimal_value_; }
|
||||
|
||||
// Used internally.
|
||||
void ProcessNewLine(char* const line) {
|
||||
const char* const kWordDelimiters(" ");
|
||||
std::vector<string> words;
|
||||
SplitStringUsing(line, kWordDelimiters, &words);
|
||||
line_read_++;
|
||||
if (problem_type_ == -1) {
|
||||
if (words.size() == 1) {
|
||||
LOG(INFO) << "New data format";
|
||||
problem_type_ = 1;
|
||||
} else if (words.size() == 2) {
|
||||
LOG(INFO) << "Original data format";
|
||||
problem_type_ = 0;
|
||||
}
|
||||
}
|
||||
if (problem_type_ == 0) {
|
||||
// 0 = init
|
||||
// 1 = size passed
|
||||
// 2 = profit passed
|
||||
// 3 = capacity passed
|
||||
// 4 = constraint passed
|
||||
// 5 = optimum passed
|
||||
// 6 = name passed
|
||||
switch (mode_) {
|
||||
case 0: {
|
||||
if (words.size() != 0) {
|
||||
CHECK_EQ(2, words.size());
|
||||
num_dims_ = atoi32(words[0]);
|
||||
num_items_ = atoi32(words[1]);
|
||||
weight_.resize(num_dims_);
|
||||
mode_ = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
const int val = atoi32(words[i]);
|
||||
profit_.push_back(val);
|
||||
}
|
||||
CHECK_LE(profit_.size(), num_items_);
|
||||
if (profit_.size() == num_items_) {
|
||||
mode_ = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
const int val = atoi32(words[i]);
|
||||
dims_.push_back(val);
|
||||
}
|
||||
CHECK_LE(dims_.size(), num_dims_);
|
||||
if (dims_.size() == num_dims_) {
|
||||
mode_ = 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
const int val = atoi32(words[i]);
|
||||
weight_[current_bin_].push_back(val);
|
||||
if (weight_[current_bin_].size() == num_items_) {
|
||||
current_bin_++;
|
||||
}
|
||||
}
|
||||
if (current_bin_ == num_dims_) {
|
||||
mode_ = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (words.size() != 0) {
|
||||
CHECK_EQ(1, words.size());
|
||||
optimal_value_ = atoi32(words[0]);
|
||||
mode_ = 5;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
if (words.size() != 0) {
|
||||
name_ = line;
|
||||
mode_ = 6;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 0 = init
|
||||
// 1 = name passed
|
||||
// 2 = size passed
|
||||
// 3 = data passed
|
||||
// 4 = capacity passed
|
||||
switch (mode_) {
|
||||
case 0: {
|
||||
name_ = words[0];
|
||||
mode_ = 1;
|
||||
current_bin_ = -1;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
if (words.size() != 0) {
|
||||
CHECK_EQ(2, words.size());
|
||||
num_items_ = atoi32(words[0]);
|
||||
num_dims_ = atoi32(words[1]);
|
||||
weight_.resize(num_dims_);
|
||||
mode_ = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
const int val = atoi32(words[i]);
|
||||
if (current_bin_ == -1) {
|
||||
profit_.push_back(val);
|
||||
} else {
|
||||
weight_[current_bin_].push_back(val);
|
||||
CHECK_EQ(current_item_, weight_[current_bin_].size() - 1);
|
||||
}
|
||||
current_bin_++;
|
||||
if (current_bin_ == num_dims_) {
|
||||
current_bin_ = -1;
|
||||
current_item_++;
|
||||
}
|
||||
if (current_item_ == num_items_) {
|
||||
mode_ = 3;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
const int val = atoi32(words[i]);
|
||||
dims_.push_back(val);
|
||||
}
|
||||
CHECK_LE(dims_.size(), num_dims_);
|
||||
if (dims_.size() == num_dims_) {
|
||||
mode_ = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
string name_;
|
||||
std::vector<int> dims_;
|
||||
std::vector<int> profit_;
|
||||
std::vector<std::vector<int> > weight_;
|
||||
int line_read_;
|
||||
int mode_;
|
||||
int num_dims_;
|
||||
int num_items_;
|
||||
int current_bin_;
|
||||
int current_item_;
|
||||
int optimal_value_;
|
||||
int problem_type_; // -1 = undefined, 0 = original, 1 = new format
|
||||
};
|
||||
|
||||
int64 EvaluateItem(MultiDimKnapsackData* const data, int64 var, int64 val) {
|
||||
if (val == 0) {
|
||||
return 0LL;
|
||||
}
|
||||
const int profit = data->profit(var);
|
||||
int max_weight = 0;
|
||||
for (int i = 0; i < data->dims(); ++i) {
|
||||
const int weight = data->weight(i, var);
|
||||
if (weight > max_weight) {
|
||||
max_weight = weight;
|
||||
}
|
||||
}
|
||||
return -(profit * 100 / max_weight);
|
||||
}
|
||||
|
||||
void SolveKnapsack(MultiDimKnapsackData* const data) {
|
||||
Solver solver("MultiDim Knapsack");
|
||||
std::vector<IntVar*> assign;
|
||||
solver.MakeBoolVarArray(data->items(), "assign", &assign);
|
||||
for (int i = 0; i < data->dims(); ++i) {
|
||||
const int capacity = data->capacity(i);
|
||||
std::vector<int64> coefs;
|
||||
for (int j = 0; j < data->items(); ++j) {
|
||||
coefs.push_back(data->weight(i, j));
|
||||
}
|
||||
solver.AddConstraint(
|
||||
solver.MakeScalProdLessOrEqual(assign, coefs, capacity));
|
||||
}
|
||||
|
||||
// Build objective.
|
||||
std::vector<int64> profits;
|
||||
for (int i = 0; i < data->items(); ++i) {
|
||||
profits.push_back(data->profit(i));
|
||||
}
|
||||
|
||||
IntVar* const objective = solver.MakeScalProd(assign, profits)->Var();
|
||||
|
||||
std::vector<SearchMonitor*> monitors;
|
||||
OptimizeVar* const objective_monitor = solver.MakeMaximize(objective, 1);
|
||||
monitors.push_back(objective_monitor);
|
||||
if (FLAGS_display_search_log) {
|
||||
SearchMonitor* const search_log = solver.MakeSearchLog(1000000, objective);
|
||||
monitors.push_back(search_log);
|
||||
}
|
||||
DecisionBuilder* const db =
|
||||
solver.MakePhase(assign,
|
||||
NewPermanentCallback(&EvaluateItem, data),
|
||||
Solver::CHOOSE_STATIC_GLOBAL_BEST);
|
||||
if (FLAGS_time_limit_in_ms != 0) {
|
||||
LOG(INFO) << "adding time limit of " << FLAGS_time_limit_in_ms << " ms";
|
||||
SearchLimit* const limit = solver.MakeLimit(FLAGS_time_limit_in_ms,
|
||||
kint64max,
|
||||
kint64max,
|
||||
kint64max);
|
||||
monitors.push_back(limit);
|
||||
}
|
||||
|
||||
if (FLAGS_simplex_frequency > 0) {
|
||||
SearchMonitor* const simplex =
|
||||
solver.MakeSimplexConstraint(FLAGS_simplex_frequency);
|
||||
monitors.push_back(simplex);
|
||||
}
|
||||
|
||||
if (solver.Solve(db, monitors)) {
|
||||
LOG(INFO) << "Best solution found = " << objective_monitor->best();
|
||||
}
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
static const char kUsage[] =
|
||||
"Usage: see flags.\n"
|
||||
"This program runs a multi-dimensional knapsack problem.";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage(kUsage);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_data_file.empty()) {
|
||||
LOG(FATAL) << "Please supply a data file with --datafile=";
|
||||
}
|
||||
operations_research::MultiDimKnapsackData data;
|
||||
data.Load(FLAGS_data_file);
|
||||
operations_research::SolveKnapsack(&data);
|
||||
return 0;
|
||||
}
|
||||
1053
examples/cpp/network_routing.cc
Normal file
1053
examples/cpp/network_routing.cc
Normal file
File diff suppressed because it is too large
Load Diff
275
examples/cpp/nqueens.cc
Normal file
275
examples/cpp/nqueens.cc
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// N-queens problem
|
||||
//
|
||||
// unique solutions: http://www.research.att.com/~njas/sequences/A000170
|
||||
// distinct solutions: http://www.research.att.com/~njas/sequences/A002562
|
||||
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "base/map-util.h"
|
||||
#include "constraint_solver/constraint_solveri.h"
|
||||
|
||||
DEFINE_bool(print, false, "If true, print one of the solution.");
|
||||
DEFINE_bool(print_all, false, "If true, print all the solutions.");
|
||||
DEFINE_int32(nb_loops, 1,
|
||||
"Number of solving loops to perform, for performance timing.");
|
||||
DEFINE_int32(size, 0,
|
||||
"Size of the problem. If equal to 0, will test several increasing sizes.");
|
||||
DEFINE_bool(use_symmetry, false, "Use Symmetry Breaking methods");
|
||||
DECLARE_bool(cp_no_solve);
|
||||
|
||||
static const int kNumSolutions[] = {
|
||||
1, 0, 0, 2, 10, 4, 40, 92, 352, 724,
|
||||
2680, 14200, 73712, 365596, 2279184
|
||||
};
|
||||
static const int kKnownSolutions = 15;
|
||||
|
||||
static const int kNumUniqueSolutions[] = {
|
||||
1, 0, 0, 1, 2, 1, 6, 12, 46, 92, 341, 1787, 9233, 45752,
|
||||
285053, 1846955, 11977939, 83263591, 621012754
|
||||
};
|
||||
static const int kKnownUniqueSolutions = 19;
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
class NQueenSymmetry : public SymmetryBreaker {
|
||||
public:
|
||||
NQueenSymmetry(Solver* const s, const std::vector<IntVar*>& vars)
|
||||
: solver_(s), vars_(vars), size_(vars.size()) {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
indices_[vars[i]] = i;
|
||||
}
|
||||
}
|
||||
virtual ~NQueenSymmetry() {}
|
||||
|
||||
protected:
|
||||
int Index(IntVar* const var) const {
|
||||
return FindWithDefault(indices_, var, -1);
|
||||
}
|
||||
IntVar* Var(int index) const {
|
||||
DCHECK_GE(index, 0);
|
||||
DCHECK_LT(index, size_);
|
||||
return vars_[index];
|
||||
}
|
||||
int size() const { return size_; }
|
||||
int symmetric(int index) const { return size_ - 1 - index; }
|
||||
Solver* const solver() const { return solver_; }
|
||||
|
||||
private:
|
||||
Solver* const solver_;
|
||||
const std::vector<IntVar*> vars_;
|
||||
std::map<IntVar*, int> indices_;
|
||||
const int size_;
|
||||
};
|
||||
|
||||
// Symmetry vertical axis.
|
||||
class SX : public NQueenSymmetry {
|
||||
public:
|
||||
SX(Solver* const s, const std::vector<IntVar*>& vars) : NQueenSymmetry(s, vars) {}
|
||||
virtual ~SX() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(symmetric(index));
|
||||
AddIntegerVariableEqualValueClause(other_var, value);
|
||||
}
|
||||
};
|
||||
|
||||
// Symmetry horizontal axis.
|
||||
class SY : public NQueenSymmetry {
|
||||
public:
|
||||
SY(Solver* const s, const std::vector<IntVar*>& vars) : NQueenSymmetry(s, vars) {}
|
||||
virtual ~SY() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
AddIntegerVariableEqualValueClause(var, symmetric(value));
|
||||
}
|
||||
};
|
||||
|
||||
// Symmetry first diagonal axis.
|
||||
class SD1 : public NQueenSymmetry {
|
||||
public:
|
||||
SD1(Solver* const s, const std::vector<IntVar*>& vars) : NQueenSymmetry(s, vars) {}
|
||||
virtual ~SD1() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(value);
|
||||
AddIntegerVariableEqualValueClause(other_var, index);
|
||||
}
|
||||
};
|
||||
|
||||
// Symmetry second diagonal axis.
|
||||
class SD2 : public NQueenSymmetry {
|
||||
public:
|
||||
SD2(Solver* const s, const std::vector<IntVar*>& vars) : NQueenSymmetry(s, vars) {}
|
||||
virtual ~SD2() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(symmetric(value));
|
||||
AddIntegerVariableEqualValueClause(other_var, symmetric(index));
|
||||
}
|
||||
};
|
||||
|
||||
// Rotate 1/4 turn.
|
||||
class R90 : public NQueenSymmetry {
|
||||
public:
|
||||
R90(Solver* const s, const std::vector<IntVar*>& vars) : NQueenSymmetry(s, vars) {}
|
||||
virtual ~R90() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(value);
|
||||
AddIntegerVariableEqualValueClause(other_var, symmetric(index));
|
||||
}
|
||||
};
|
||||
|
||||
// Rotate 1/2 turn.
|
||||
class R180 : public NQueenSymmetry {
|
||||
public:
|
||||
R180(Solver* const s, const std::vector<IntVar*>& vars)
|
||||
: NQueenSymmetry(s, vars) {}
|
||||
virtual ~R180() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(symmetric(index));
|
||||
AddIntegerVariableEqualValueClause(other_var, symmetric(value));
|
||||
}
|
||||
};
|
||||
|
||||
// Rotate 3/4 turn.
|
||||
class R270 : public NQueenSymmetry {
|
||||
public:
|
||||
R270(Solver* const s, const std::vector<IntVar*>& vars)
|
||||
: NQueenSymmetry(s, vars) {}
|
||||
virtual ~R270() {}
|
||||
|
||||
virtual void VisitSetVariableValue(IntVar* const var, int64 value) {
|
||||
const int index = Index(var);
|
||||
IntVar* const other_var = Var(symmetric(value));
|
||||
AddIntegerVariableEqualValueClause(other_var, index);
|
||||
}
|
||||
};
|
||||
|
||||
void CheckNumberOfSolutions(int size, int num_solutions) {
|
||||
if (FLAGS_use_symmetry) {
|
||||
if (size - 1 < kKnownUniqueSolutions) {
|
||||
CHECK_EQ(num_solutions, kNumUniqueSolutions[size - 1]);
|
||||
} else if (!FLAGS_cp_no_solve) {
|
||||
CHECK_GT(num_solutions, 0);
|
||||
}
|
||||
} else {
|
||||
if (size - 1 < kKnownSolutions) {
|
||||
CHECK_EQ(num_solutions, kNumSolutions[size - 1]);
|
||||
} else if (!FLAGS_cp_no_solve) {
|
||||
CHECK_GT(num_solutions, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NQueens(int size) {
|
||||
CHECK_GE(size, 1);
|
||||
Solver s("nqueens");
|
||||
|
||||
// model
|
||||
std::vector<IntVar*> queens;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
queens.push_back(s.MakeIntVar(0, size - 1, StringPrintf("queen%04d", i)));
|
||||
}
|
||||
s.AddConstraint(s.MakeAllDifferent(queens));
|
||||
|
||||
std::vector<IntVar*> vars(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
vars[i] = s.MakeSum(queens[i], i)->Var();
|
||||
}
|
||||
s.AddConstraint(s.MakeAllDifferent(vars));
|
||||
for (int i = 0; i < size; ++i) {
|
||||
vars[i] = s.MakeSum(queens[i], -i)->Var();
|
||||
}
|
||||
s.AddConstraint(s.MakeAllDifferent(vars));
|
||||
|
||||
SolutionCollector* const solution_counter = s.MakeAllSolutionCollector(NULL);
|
||||
SolutionCollector* const collector = s.MakeFirstSolutionCollector();
|
||||
collector->Add(queens);
|
||||
std::vector<SearchMonitor*> monitors;
|
||||
monitors.push_back(solution_counter);
|
||||
monitors.push_back(collector);
|
||||
DecisionBuilder* const db = s.MakePhase(queens,
|
||||
Solver::CHOOSE_FIRST_UNBOUND,
|
||||
Solver::ASSIGN_MIN_VALUE);
|
||||
if (FLAGS_use_symmetry) {
|
||||
std::vector<SymmetryBreaker*> breakers;
|
||||
NQueenSymmetry* const sx = s.RevAlloc(new SX(&s, queens));
|
||||
breakers.push_back(sx);
|
||||
NQueenSymmetry* const sy = s.RevAlloc(new SY(&s, queens));
|
||||
breakers.push_back(sy);
|
||||
NQueenSymmetry* const sd1 = s.RevAlloc(new SD1(&s, queens));
|
||||
breakers.push_back(sd1);
|
||||
NQueenSymmetry* const sd2 = s.RevAlloc(new SD2(&s, queens));
|
||||
breakers.push_back(sd2);
|
||||
NQueenSymmetry* const r90 = s.RevAlloc(new R90(&s, queens));
|
||||
breakers.push_back(r90);
|
||||
NQueenSymmetry* const r180 = s.RevAlloc(new R180(&s, queens));
|
||||
breakers.push_back(r180);
|
||||
NQueenSymmetry* const r270 = s.RevAlloc(new R270(&s, queens));
|
||||
breakers.push_back(r270);
|
||||
SearchMonitor* const symmetry_manager = s.MakeSymmetryManager(breakers);
|
||||
monitors.push_back(symmetry_manager);
|
||||
}
|
||||
|
||||
for (int loop = 0; loop < FLAGS_nb_loops; ++loop) {
|
||||
s.Solve(db, monitors); // go!
|
||||
CheckNumberOfSolutions(size, solution_counter->solution_count());
|
||||
}
|
||||
|
||||
const int num_solutions = solution_counter->solution_count();
|
||||
if (num_solutions > 0 && size < kKnownSolutions) {
|
||||
int print_max = FLAGS_print_all ? num_solutions : FLAGS_print ? 1 : 0;
|
||||
for (int n = 0; n < print_max; ++n) {
|
||||
printf("--- solution #%d\n", n);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const int pos = static_cast<int>(collector->Value(n, queens[i]));
|
||||
for (int k = 0; k < pos; ++k) printf(" . ");
|
||||
printf("%2d ", i);
|
||||
for (int k = pos + 1; k < size; ++k) printf(" . ");
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("========= number of solutions:%d\n", num_solutions);
|
||||
printf(" number of failures: %lld\n", s.failures());
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_size != 0) {
|
||||
operations_research::NQueens(FLAGS_size);
|
||||
} else {
|
||||
for (int n = 1; n < 12; ++n) {
|
||||
operations_research::NQueens(n);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
252
examples/cpp/parse_dimacs_assignment.cc
Normal file
252
examples/cpp/parse_dimacs_assignment.cc
Normal file
@@ -0,0 +1,252 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cpp/parse_dimacs_assignment.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "graph/ebert_graph.h"
|
||||
#include "graph/linear_assignment.h"
|
||||
|
||||
DEFINE_bool(assignment_maximize_cost, false,
|
||||
"Negate costs so a max-cost assignment is found.");
|
||||
DEFINE_bool(assignment_optimize_layout, true,
|
||||
"Optimize graph layout for speed.");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
struct ParserState {
|
||||
ParserState()
|
||||
: bad(false),
|
||||
expect_last_line(false),
|
||||
nodes_described(false),
|
||||
reason(NULL),
|
||||
num_left_nodes(0) { }
|
||||
bool bad;
|
||||
bool expect_last_line;
|
||||
bool nodes_described;
|
||||
const char* reason;
|
||||
NodeIndex num_left_nodes;
|
||||
scoped_ptr<string> bad_line;
|
||||
};
|
||||
|
||||
static void ParseProblemLine(const char* line,
|
||||
ParserState* state,
|
||||
ForwardStarGraph** graph) {
|
||||
static const char* kIncorrectProblemLine =
|
||||
"Incorrect assignment problem line.";
|
||||
static const char* kAssignmentProblemType = "asn";
|
||||
char problem_type[4];
|
||||
NodeIndex num_nodes;
|
||||
ArcIndex num_arcs;
|
||||
|
||||
if ((sscanf(line, "%*c%3s%d%d", // NOLINT
|
||||
problem_type,
|
||||
&num_nodes,
|
||||
&num_arcs) != 3) ||
|
||||
(strncmp(kAssignmentProblemType,
|
||||
problem_type,
|
||||
strlen(kAssignmentProblemType)) != 0)) {
|
||||
state->bad = true;
|
||||
state->reason = kIncorrectProblemLine;
|
||||
state->bad_line.reset(new string(line));
|
||||
return;
|
||||
}
|
||||
|
||||
*graph = new ForwardStarGraph(num_nodes, num_arcs);
|
||||
}
|
||||
|
||||
static void ParseNodeLine(const char* line,
|
||||
ParserState* state) {
|
||||
NodeIndex node_id;
|
||||
if (sscanf(line, "%*c%d", &node_id) != 1) { // NOLINT
|
||||
state->bad = true;
|
||||
state->reason = "Syntax error in node desciption.";
|
||||
state->bad_line.reset(new string(line));
|
||||
return;
|
||||
}
|
||||
if (state->nodes_described) {
|
||||
state->bad = true;
|
||||
state->reason = "All node description must precede first arc description.";
|
||||
state->bad_line.reset(new string(line));
|
||||
return;
|
||||
}
|
||||
state->num_left_nodes = ::std::max(state->num_left_nodes, node_id);
|
||||
}
|
||||
|
||||
static void ParseArcLine(
|
||||
const char* line,
|
||||
ParserState* state,
|
||||
ForwardStarGraph* graph,
|
||||
LinearSumAssignment<ForwardStarGraph>** assignment) {
|
||||
if (graph == NULL) {
|
||||
state->bad = true;
|
||||
state->reason =
|
||||
"Problem specification line must precede any arc specification.";
|
||||
state->bad_line.reset(new string(line));
|
||||
return;
|
||||
}
|
||||
if (!state->nodes_described) {
|
||||
state->nodes_described = true;
|
||||
DCHECK(*assignment == NULL);
|
||||
*assignment = new LinearSumAssignment<ForwardStarGraph>(
|
||||
*graph, state->num_left_nodes);
|
||||
}
|
||||
NodeIndex tail;
|
||||
NodeIndex head;
|
||||
CostValue cost;
|
||||
if (sscanf(line, "%*c%d%d%lld", &tail, &head, &cost) != 3) { // NOLINT
|
||||
state->bad = true;
|
||||
state->reason = "Syntax error in arc descriptor.";
|
||||
state->bad_line.reset(new string(line));
|
||||
}
|
||||
ArcIndex arc = graph->AddArc(tail - 1, head - 1);
|
||||
(*assignment)->SetArcCost(arc, FLAGS_assignment_maximize_cost ? -cost : cost);
|
||||
}
|
||||
|
||||
// Parameters out of style-guide order because this function is used
|
||||
// as a callback that varies the input line.
|
||||
static void ParseOneLine(
|
||||
ParserState* state,
|
||||
ForwardStarGraph** graph,
|
||||
LinearSumAssignment<ForwardStarGraph>** assignment,
|
||||
char* line) {
|
||||
if (state->bad) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->expect_last_line) {
|
||||
state->bad = true;
|
||||
state->reason = "Input line is too long.";
|
||||
// state->bad_line was already set when we noticed the line
|
||||
// didn't end with '\n'.
|
||||
return;
|
||||
}
|
||||
|
||||
size_t length = strlen(line);
|
||||
// The final line might not end with newline. Any other line
|
||||
// that seems not to is actually a line that was too long
|
||||
// for our input buffer.
|
||||
if (line[length - 1] != '\n') {
|
||||
state->expect_last_line = true;
|
||||
// Prepare for the worst; we might need to inform the user of
|
||||
// an error on this line even though we can't detect the error
|
||||
// yet.
|
||||
state->bad_line.reset(new string(line));
|
||||
}
|
||||
|
||||
|
||||
switch (line[0]) {
|
||||
case 'p': {
|
||||
// Problem-specification line
|
||||
ParseProblemLine(line, state, graph);
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
// Comment; do nothing.
|
||||
return;
|
||||
}
|
||||
case 'n': {
|
||||
// Node line defining a node on the left side
|
||||
ParseNodeLine(line, state);
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
ParseArcLine(line, state, *graph, assignment);
|
||||
break;
|
||||
}
|
||||
case '0':
|
||||
case '\n':
|
||||
break;
|
||||
default: {
|
||||
state->bad = true;
|
||||
state->reason = "Unknown line type in the input.";
|
||||
state->bad_line.reset(new string(line));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseFileByLines(const string& filename,
|
||||
Callback1<char*>* line_parser) {
|
||||
FILE* fp = fopen(filename.c_str(), "r");
|
||||
const int kMaximumLineSize = 1024;
|
||||
char line[kMaximumLineSize];
|
||||
if (fp != NULL) {
|
||||
char* result;
|
||||
do {
|
||||
result = fgets(line, kMaximumLineSize, fp);
|
||||
if (result != NULL) {
|
||||
line_parser->Run(result);
|
||||
}
|
||||
} while (result != NULL);
|
||||
}
|
||||
delete line_parser;
|
||||
}
|
||||
|
||||
|
||||
// Reads an assignment problem description from the given file in
|
||||
// DIMACS format and returns a LinearSumAssignment object representing
|
||||
// the problem description. For a description of the format, see
|
||||
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
||||
//
|
||||
// Also returns an error message (empty if no error) and a handle on
|
||||
// the underlying graph representation. The error_message pointer must
|
||||
// not be NULL because we insist on returning an explanatory message
|
||||
// in the case of error. The graph_handle pointer must not be NULL
|
||||
// because unless we pass a non-const pointer to the graph
|
||||
// representation back to the caller, the caller lacks a good way to
|
||||
// free the underlying graph (which isn't owned by the
|
||||
// LinearAssignment instance).
|
||||
LinearSumAssignment<ForwardStarGraph>* ParseDimacsAssignment(
|
||||
const string& filename,
|
||||
string* error_message,
|
||||
ForwardStarGraph** graph_handle) {
|
||||
CHECK_NOTNULL(error_message);
|
||||
CHECK_NOTNULL(graph_handle);
|
||||
ForwardStarGraph* graph = NULL;
|
||||
LinearSumAssignment<ForwardStarGraph>* assignment = NULL;
|
||||
ParserState state;
|
||||
Callback1<char*>* cb =
|
||||
NewPermanentCallback(ParseOneLine, &state, &graph, &assignment);
|
||||
// ParseFileByLines takes ownership of cb and deletes it.
|
||||
ParseFileByLines(filename, cb);
|
||||
|
||||
if (state.bad) {
|
||||
*error_message = state.reason;
|
||||
*error_message = *error_message + ": \"" + *state.bad_line + "\"";
|
||||
return NULL;
|
||||
}
|
||||
if (graph == NULL) {
|
||||
*error_message = "empty graph description";
|
||||
return NULL;
|
||||
}
|
||||
if (FLAGS_assignment_optimize_layout) {
|
||||
assignment->OptimizeGraphLayout(graph);
|
||||
}
|
||||
*error_message = "";
|
||||
// Return a handle on the graph to the caller so the caller can free
|
||||
// the graph's memory, because the LinearSumAssignment object does
|
||||
// not take ownership of the graph and hence will not free it.
|
||||
*graph_handle = graph;
|
||||
return assignment;
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
38
examples/cpp/parse_dimacs_assignment.h
Normal file
38
examples/cpp/parse_dimacs_assignment.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Function for reading and parsing a file in DIMACS format:
|
||||
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
||||
//
|
||||
|
||||
#ifndef OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|
||||
#define OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "graph/ebert_graph.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
template <typename GraphType> class LinearSumAssignment;
|
||||
|
||||
// Reads an assignment problem description from the given file in
|
||||
// DIMACS format and returns a LinearSumAssignment object representing
|
||||
// the problem description. For a description of the format, see
|
||||
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
||||
//
|
||||
// Also returns an error message (empty if no error) and a handle on
|
||||
// the underlying graph representation. The error_message pointer must
|
||||
// not be NULL because we insist on returning an explanatory message
|
||||
// in the case of error. The graph_handle pointer must not be NULL
|
||||
// because unless we pass a non-const pointer to the graph
|
||||
// representation back to the caller, the caller lacks a good way to
|
||||
// free the underlying graph (which isn't owned by the
|
||||
// LinearAssignment instance).
|
||||
LinearSumAssignment<ForwardStarGraph>* ParseDimacsAssignment(
|
||||
const string& filename,
|
||||
string* error_message,
|
||||
ForwardStarGraph** graph);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|
||||
321
examples/cpp/pdptw.cc
Normal file
321
examples/cpp/pdptw.cc
Normal file
@@ -0,0 +1,321 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Pickup and Delivery Problem with Time Windows.
|
||||
// The overall objective is to minimize the length of the routes delivering
|
||||
// quantities of goods between pickup and delivery locations, taking into
|
||||
// account vehicle capacities and node time windows.
|
||||
// Given a set of pairs of pickup and delivery nodes, find the set of routes
|
||||
// visiting all the nodes, such that
|
||||
// - corresponding pickup and delivery nodes are visited on the same route,
|
||||
// - the pickup node is visited before the corresponding delivery node,
|
||||
// - the quantity picked up at the pickup node is the same as the quantity
|
||||
// delivered at the delivery node,
|
||||
// - the total quantity carried by a vehicle at any time is less than its
|
||||
// capacity,
|
||||
// - each node must be visited within its time window (time range during which
|
||||
// the node is accessible).
|
||||
// The maximum number of vehicles used (i.e. the number of routes used) is
|
||||
// specified in the data but can be overriden using the --pdp_force_vehicles
|
||||
// flag.
|
||||
//
|
||||
// A further description of the problem can be found here:
|
||||
// http://http://en.wikipedia.org/wiki/Vehicle_routing_problem
|
||||
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.123.9965&rep=rep1&type=pdf.
|
||||
// Reads data in the format defined by Li & Lim
|
||||
// (http://www.sintef.no/static/am/opti/projects/top/vrp/format_pdp.htm).
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/strtoint.h"
|
||||
#include "base/file.h"
|
||||
#include "base/split.h"
|
||||
#include "base/mathutil.h"
|
||||
#include "constraint_solver/routing.h"
|
||||
|
||||
DEFINE_string(pdp_file, "",
|
||||
"File containing the Pickup and Delivery Problem to solve.");
|
||||
DEFINE_int32(pdp_force_vehicles, 0,
|
||||
"Force the number of vehicles used (maximum number of routes.");
|
||||
DEFINE_bool(pdp_display_solution, false,
|
||||
"Displays the solution of the Pickup and Delivery Problem.");
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// Scaling factor used to scale up distances, allowing a bit more precision
|
||||
// from Euclidean distances.
|
||||
const int64 kScalingFactor = 1000;
|
||||
|
||||
// Vector of (x,y) node coordinates, *unscaled*, in some imaginary planar,
|
||||
// metric grid.
|
||||
typedef std::vector<std::pair<int, int> > Coordinates;
|
||||
|
||||
// Returns the scaled Euclidean distance between two nodes, coords holding the
|
||||
// coordinates of the nodes.
|
||||
int64 Travel(const Coordinates* const coords,
|
||||
RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) {
|
||||
DCHECK(coords != NULL);
|
||||
const int xd =
|
||||
coords->at(from.value()).first - coords->at(to.value()).first;
|
||||
const int yd =
|
||||
coords->at(from.value()).second - coords->at(to.value()).second;
|
||||
return static_cast<int64>(kScalingFactor * sqrt(1.0L * xd * xd + yd * yd));
|
||||
}
|
||||
|
||||
// Returns the scaled service time at a given node, service_times holding the
|
||||
// service times.
|
||||
int64 ServiceTime(const std::vector<int64>* const service_times,
|
||||
RoutingModel::NodeIndex node) {
|
||||
return kScalingFactor * service_times->at(node.value());
|
||||
}
|
||||
|
||||
// Returns the scaled (distance plus service time) between two nodes, coords
|
||||
// holding the coordinates of the nodes and service_times holding the service
|
||||
// times.
|
||||
// The service time is the time spent to execute a delivery or a pickup.
|
||||
int64 TravelPlusServiceTime(const Coordinates* const coords,
|
||||
const std::vector<int64>* const service_times,
|
||||
RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) {
|
||||
return ServiceTime(service_times, from) + Travel(coords, from, to);
|
||||
}
|
||||
|
||||
// Returns the demand (quantity picked up or delivered) of a node, demands
|
||||
// holds the demand of each node.
|
||||
int64 Demand(const std::vector<int64>* const demands,
|
||||
RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) {
|
||||
return demands->at(from.value());
|
||||
}
|
||||
|
||||
// Outputs a solution to the current model in a string.
|
||||
string VerboseOutput(const RoutingModel& routing,
|
||||
const Assignment& assignment,
|
||||
const Coordinates& coords,
|
||||
const std::vector<int64>& service_times) {
|
||||
string output;
|
||||
for (int i = 0; i < routing.vehicles(); ++i) {
|
||||
StringAppendF(&output, "Vehicle %d: ", i);
|
||||
int64 index = routing.Start(i);
|
||||
if (routing.IsEnd(assignment.Value(routing.NextVar(index)))) {
|
||||
StringAppendF(&output, "empty");
|
||||
} else {
|
||||
while (!routing.IsEnd(index)) {
|
||||
RoutingModel::NodeIndex real_node = routing.IndexToNode(index);
|
||||
StringAppendF(&output, "%d ", real_node.value());
|
||||
const IntVar* vehicle = routing.VehicleVar(index);
|
||||
StringAppendF(&output, "Vehicle(%lld) ", assignment.Value(vehicle));
|
||||
const IntVar* arrival = routing.CumulVar(index, "time");
|
||||
StringAppendF(&output, "Time(%lld..%lld) ",
|
||||
assignment.Min(arrival),
|
||||
assignment.Max(arrival));
|
||||
const IntVar* load = routing.CumulVar(index, "demand");
|
||||
StringAppendF(&output, "Load(%lld..%lld) ",
|
||||
assignment.Min(load),
|
||||
assignment.Max(load));
|
||||
index = assignment.Value(routing.NextVar(index));
|
||||
StringAppendF(&output, "Transit(%lld) ",
|
||||
TravelPlusServiceTime(&coords,
|
||||
&service_times,
|
||||
real_node,
|
||||
routing.IndexToNode(index)));
|
||||
}
|
||||
StringAppendF(&output, "Route end ");
|
||||
const IntVar* vehicle = routing.VehicleVar(index);
|
||||
StringAppendF(&output, "Vehicle(%lld) ", assignment.Value(vehicle));
|
||||
const IntVar* arrival = routing.CumulVar(index, "time");
|
||||
StringAppendF(&output, "Time(%lld..%lld) ",
|
||||
assignment.Min(arrival),
|
||||
assignment.Max(arrival));
|
||||
const IntVar* load = routing.CumulVar(index, "demand");
|
||||
StringAppendF(&output, "Load(%lld..%lld) ",
|
||||
assignment.Min(load),
|
||||
assignment.Max(load));
|
||||
}
|
||||
StringAppendF(&output, "\n");
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// An inefficient but convenient method to parse a whitespace-separated list
|
||||
// of integers. Returns true iff the input string was entirely valid and parsed.
|
||||
bool SafeParseInt64Array(const string& str, std::vector<int64>* parsed_int) {
|
||||
std::vector<string> items;
|
||||
static const char kWhiteSpaces[] = " \t\n\v\f\r";
|
||||
SplitStringUsing(str, kWhiteSpaces, &items);
|
||||
parsed_int->assign(items.size(), 0);
|
||||
for (int i = 0; i < items.size(); ++i) {
|
||||
const char* item = items[i].c_str();
|
||||
char *endptr = NULL;
|
||||
(*parsed_int)[i] = strto64(item, &endptr, 10); // NOLINT
|
||||
// The whole item should have been consumed.
|
||||
if (*endptr != '\0') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Builds and solves a model from a file in the format defined by Li & Lim
|
||||
// (http://www.sintef.no/static/am/opti/projects/top/vrp/format_pdp.htm).
|
||||
bool LoadAndSolve(const string& pdp_file) {
|
||||
// Load all the lines of the file in RAM (it shouldn't be too large anyway).
|
||||
std::vector<string> lines;
|
||||
{
|
||||
const int64 kMaxInputFileSize = 1 << 30; // 1GB
|
||||
File* data_file = File::OpenOrDie(pdp_file.c_str(), "r");
|
||||
string contents;
|
||||
data_file->ReadToString(&contents, kMaxInputFileSize);
|
||||
data_file->Close();
|
||||
if (contents.size() == kMaxInputFileSize) {
|
||||
LOG(WARNING)
|
||||
<< "Input file '" << pdp_file << "' is too large (>"
|
||||
<< kMaxInputFileSize << " bytes).";
|
||||
return false;
|
||||
}
|
||||
SplitStringUsing(contents, "\n", &lines);
|
||||
}
|
||||
// Reading header.
|
||||
if (lines.empty()) {
|
||||
LOG(WARNING) << "Empty file: " << pdp_file;
|
||||
return false;
|
||||
}
|
||||
// Parse file header.
|
||||
std::vector<int64> parsed_int;
|
||||
if (!SafeParseInt64Array(lines[0], &parsed_int)
|
||||
|| parsed_int.size() != 3
|
||||
|| parsed_int[0] < 0
|
||||
|| parsed_int[1] < 0
|
||||
|| parsed_int[2] < 0) {
|
||||
LOG(WARNING) << "Malformed header: " << lines[0];
|
||||
return false;
|
||||
}
|
||||
const int num_vehicles = FLAGS_pdp_force_vehicles > 0 ?
|
||||
FLAGS_pdp_force_vehicles : parsed_int[0];
|
||||
const int64 capacity = parsed_int[1];
|
||||
// We do not care about the 'speed' field, in third position.
|
||||
|
||||
// Parse order data.
|
||||
std::vector<int> customer_ids;
|
||||
std::vector<std::pair<int, int> > coords;
|
||||
std::vector<int64> demands;
|
||||
std::vector<int64> open_times;
|
||||
std::vector<int64> close_times;
|
||||
std::vector<int64> service_times;
|
||||
std::vector<RoutingModel::NodeIndex> pickups;
|
||||
std::vector<RoutingModel::NodeIndex> deliveries;
|
||||
int64 horizon = 0;
|
||||
for (int line_index = 1; line_index < lines.size(); ++line_index) {
|
||||
if (!SafeParseInt64Array(lines[line_index], &parsed_int)
|
||||
|| parsed_int.size() != 9
|
||||
|| parsed_int[0] < 0
|
||||
|| parsed_int[4] < 0
|
||||
|| parsed_int[5] < 0
|
||||
|| parsed_int[6] < 0
|
||||
|| parsed_int[7] < 0
|
||||
|| parsed_int[8] < 0) {
|
||||
LOG(WARNING)
|
||||
<< "Malformed line #" << line_index << ": " << lines[line_index];
|
||||
return false;
|
||||
}
|
||||
const int customer_id = parsed_int[0];
|
||||
const int x = parsed_int[1];
|
||||
const int y = parsed_int[2];
|
||||
const int delivery = parsed_int[8]; // Parse 'delivery' before 'demand'.
|
||||
const int64 demand = delivery == 0 ? -parsed_int[3] : parsed_int[3];
|
||||
const int64 open_time = parsed_int[4];
|
||||
const int64 close_time = parsed_int[5];
|
||||
const int64 service_time = parsed_int[6];
|
||||
const int pickup = parsed_int[7];
|
||||
customer_ids.push_back(customer_id);
|
||||
coords.push_back(std::make_pair(x, y));
|
||||
demands.push_back(demand);
|
||||
open_times.push_back(open_time);
|
||||
close_times.push_back(close_time);
|
||||
service_times.push_back(service_time);
|
||||
pickups.push_back(RoutingModel::NodeIndex(pickup));
|
||||
deliveries.push_back(RoutingModel::NodeIndex(delivery));
|
||||
horizon = std::max(horizon, close_time);
|
||||
}
|
||||
|
||||
// Build pickup and delivery model.
|
||||
const int num_nodes = customer_ids.size();
|
||||
RoutingModel routing(num_nodes, num_vehicles);
|
||||
routing.SetCost(NewPermanentCallback(
|
||||
Travel, const_cast<const Coordinates*>(&coords)));
|
||||
routing.AddDimension(
|
||||
NewPermanentCallback(&Demand, const_cast<const std::vector<int64>*>(&demands)),
|
||||
0, capacity, "demand");
|
||||
routing.AddDimension(
|
||||
NewPermanentCallback(&TravelPlusServiceTime,
|
||||
const_cast<const Coordinates*>(&coords),
|
||||
const_cast<const std::vector<int64>*>(&service_times)),
|
||||
kScalingFactor * horizon,
|
||||
kScalingFactor * horizon,
|
||||
"time");
|
||||
Solver* const solver = routing.solver();
|
||||
for (RoutingModel::NodeIndex i(0); i < num_nodes; ++i) {
|
||||
const int64 index = routing.NodeToIndex(i);
|
||||
if (pickups[i.value()] == 0) {
|
||||
if (deliveries[i.value()] == 0) {
|
||||
routing.SetDepot(i);
|
||||
} else {
|
||||
const int64 delivery_index = routing.NodeToIndex(deliveries[i.value()]);
|
||||
solver->AddConstraint(solver->MakeEquality(
|
||||
routing.VehicleVar(index),
|
||||
routing.VehicleVar(delivery_index)));
|
||||
solver->AddConstraint(solver->MakeLessOrEqual(
|
||||
routing.CumulVar(index, "time"),
|
||||
routing.CumulVar(delivery_index, "time")));
|
||||
routing.AddPickupAndDelivery(i, deliveries[i.value()]);
|
||||
}
|
||||
}
|
||||
IntVar* const cumul = routing.CumulVar(index, "time");
|
||||
cumul->SetMin(kScalingFactor * open_times[i.value()]);
|
||||
cumul->SetMax(kScalingFactor * close_times[i.value()]);
|
||||
}
|
||||
// Adding penalty costs to allow skipping orders.
|
||||
const int64 kPenalty = 10000000;
|
||||
for (RoutingModel::NodeIndex order(1);
|
||||
order < routing.nodes(); ++order) {
|
||||
std::vector<RoutingModel::NodeIndex> orders(1, order);
|
||||
routing.AddDisjunction(orders, kPenalty);
|
||||
}
|
||||
|
||||
// Set up search parameters.
|
||||
routing.set_first_solution_strategy(RoutingModel::ROUTING_ALL_UNPERFORMED);
|
||||
routing.SetCommandLineOption("routing_no_lns", "true");
|
||||
|
||||
// Solve pickup and delivery problem.
|
||||
const Assignment* assignment = routing.Solve(NULL);
|
||||
if (NULL != assignment) {
|
||||
LG << "Cost: " << assignment->ObjectiveValue();
|
||||
LG << VerboseOutput(routing, *assignment, coords, service_times);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (!operations_research::LoadAndSolve(FLAGS_pdp_file)) {
|
||||
LG << "Error solving " << FLAGS_pdp_file;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
74
examples/cpp/print_dimacs_assignment.cc
Normal file
74
examples/cpp/print_dimacs_assignment.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cpp/print_dimacs_assignment.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "graph/ebert_graph.h"
|
||||
#include "graph/linear_assignment.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
static void WriteOrDie(const char* buffer,
|
||||
size_t item_size,
|
||||
size_t buffer_length,
|
||||
FILE* fp) {
|
||||
size_t written = fwrite(buffer, item_size, buffer_length, fp);
|
||||
if (written != buffer_length) {
|
||||
fprintf(stderr, "Write failed.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrintDimacsAssignmentProblem(
|
||||
const LinearSumAssignment<ForwardStarGraph>& assignment,
|
||||
const TailArrayManager<ForwardStarGraph>& tail_array_manager,
|
||||
const string& output_filename) {
|
||||
FILE* output = fopen(output_filename.c_str(), "w");
|
||||
const ForwardStarGraph& graph(assignment.Graph());
|
||||
string output_line = StringPrintf("p asn %d %d\n",
|
||||
graph.num_nodes(),
|
||||
graph.num_arcs());
|
||||
WriteOrDie(output_line.c_str(), 1, output_line.length(),
|
||||
output);
|
||||
|
||||
for (LinearSumAssignment<ForwardStarGraph>::BipartiteLeftNodeIterator
|
||||
node_it(assignment);
|
||||
node_it.Ok();
|
||||
node_it.Next()) {
|
||||
output_line = StringPrintf("n %d\n", node_it.Index() + 1);
|
||||
WriteOrDie(output_line.c_str(), 1, output_line.length(),
|
||||
output);
|
||||
}
|
||||
|
||||
tail_array_manager.BuildTailArrayFromAdjacencyListsIfForwardGraph();
|
||||
|
||||
for (ForwardStarGraph::ArcIterator arc_it(assignment.Graph());
|
||||
arc_it.Ok();
|
||||
arc_it.Next()) {
|
||||
ArcIndex arc = arc_it.Index();
|
||||
output_line = StringPrintf("a %d %d %lld\n",
|
||||
graph.Tail(arc) + 1,
|
||||
graph.Head(arc) + 1,
|
||||
assignment.ArcCost(arc));
|
||||
WriteOrDie(output_line.c_str(), 1, output_line.length(),
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
28
examples/cpp/print_dimacs_assignment.h
Normal file
28
examples/cpp/print_dimacs_assignment.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Function for outputting an assignment problem in DIMACS format:
|
||||
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
||||
//
|
||||
#ifndef OR_TOOLS_EXAMPLES_PRINT_DIMACS_ASSIGNMENT_H_
|
||||
#define OR_TOOLS_EXAMPLES_PRINT_DIMACS_ASSIGNMENT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "graph/ebert_graph.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
template <typename GraphType> class LinearSumAssignment;
|
||||
|
||||
// Given a LinearSumAssigment object representing an assignment problem
|
||||
// description, outputs the problem in DIMACS format in the output file.
|
||||
// For a description of the format, see
|
||||
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
||||
void PrintDimacsAssignmentProblem(
|
||||
const LinearSumAssignment<ForwardStarGraph>& assignment,
|
||||
const TailArrayManager<ForwardStarGraph>& tail_array_manager,
|
||||
const string& output_filename);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_EXAMPLES_PRINT_DIMACS_ASSIGNMENT_H_
|
||||
427
examples/cpp/sports_scheduling.cc
Normal file
427
examples/cpp/sports_scheduling.cc
Normal file
@@ -0,0 +1,427 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// 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 "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "constraint_solver/constraint_solver.h"
|
||||
#include "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) {
|
||||
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) {
|
||||
google::SetUsageMessage(kUsage);
|
||||
google::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 0;
|
||||
}
|
||||
658
examples/cpp/strawberry_fields_with_column_generation.cc
Normal file
658
examples/cpp/strawberry_fields_with_column_generation.cc
Normal file
@@ -0,0 +1,658 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Demonstration of column generation using LP toolkit.
|
||||
//
|
||||
// Column generation is the technique of generating columns (aka
|
||||
// resource bundles aka variables) of the constraint matrix
|
||||
// incrementally guided by feedback from the constraint duals
|
||||
// (cost-of-resources). Frequently this lets one solve large problems
|
||||
// efficiently, e.g. problems where the number of potential columns is
|
||||
// exponentially large.
|
||||
//
|
||||
// Solves a covering problem taken from ITA Software recruiting web
|
||||
// site:
|
||||
//
|
||||
// "Strawberries are growing in the cells of a rectangular field
|
||||
// (grid). You want to build greenhouses to enclose the
|
||||
// strawberries. Greenhouses are rectangular, axis-aligned with the
|
||||
// field (i.e., not diagonal), and may not overlap. The cost of each
|
||||
// greenhouse is $10 plus $1 per unit of area covered."
|
||||
//
|
||||
// Variables:
|
||||
//
|
||||
// for each Box (greenhouse), continuous variable b{x1,y1,x2,y2} in [0,1]
|
||||
//
|
||||
// Constraints:
|
||||
//
|
||||
// box limit:
|
||||
// sum b{x1,y1,x2,y2) <= MAX_BOXES
|
||||
// non-overlap (for each cell x,y):
|
||||
// sum b{x1,y1,x2,y2} <= 1 (summed over containing x1<=x<=x2, y1<=y<=y2)
|
||||
// coverage (for each cell x,y with a strawberry):
|
||||
// sum b{x1,y1,x2,y2} = 1 (summed over containing x1<=x<=x2, y1<=y<=y2)
|
||||
//
|
||||
// Since the number of possible boxes is O(d^4) where d is the linear
|
||||
// dimension, starts from singleton column (box) covering entire grid,
|
||||
// ensuring solvability. Then iteratively the problem is solved and
|
||||
// the constraint duals (aka reduced costs) used to guide the
|
||||
// generation of a single new column (box), until convergence or a
|
||||
// maximum number of iterations.
|
||||
//
|
||||
// No attempt is made to force integrality.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h> // strlen
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/stringprintf.h"
|
||||
#include "linear_solver/linear_solver.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
DEFINE_bool(colgen_verbose, false, "print verbosely");
|
||||
DEFINE_bool(colgen_complete, false, "generate all columns initially");
|
||||
DEFINE_int32(colgen_max_iterations, 500, "max iterations");
|
||||
DEFINE_string(colgen_solver, "clp", "solver - glpk or clp (default)");
|
||||
DEFINE_int32(colgen_instance, -1, "Which instance to solve (0 - 9)");
|
||||
|
||||
namespace operations_research {
|
||||
// ---------- Data Instances ----------
|
||||
struct Instance {
|
||||
int max_boxes;
|
||||
int width;
|
||||
int height;
|
||||
const char* grid;
|
||||
};
|
||||
|
||||
Instance kInstances[] = {
|
||||
{ 4, 22, 6,
|
||||
"..@@@@@..............."
|
||||
"..@@@@@@........@@@..."
|
||||
".....@@@@@......@@@..."
|
||||
".......@@@@@@@@@@@@..."
|
||||
".........@@@@@........"
|
||||
".........@@@@@........"
|
||||
},
|
||||
{ 3, 13, 10,
|
||||
"............."
|
||||
"............."
|
||||
"............."
|
||||
"...@@@@......"
|
||||
"...@@@@......"
|
||||
"...@@@@......"
|
||||
".......@@@..."
|
||||
".......@@@..."
|
||||
".......@@@..."
|
||||
"............."
|
||||
},
|
||||
{ 4, 13, 9,
|
||||
"............."
|
||||
"..@.@.@......"
|
||||
"...@.@.@....."
|
||||
"..@.@.@......"
|
||||
"..@.@.@......"
|
||||
"...@.@.@....."
|
||||
"....@.@......"
|
||||
"..........@@@"
|
||||
"..........@@@"
|
||||
},
|
||||
{ 4, 13, 9,
|
||||
".........@..."
|
||||
".........@..."
|
||||
"@@@@@@@@@@..."
|
||||
"..@......@..."
|
||||
"..@......@..."
|
||||
"..@......@..."
|
||||
"..@@@@@@@@@@@"
|
||||
"..@.........."
|
||||
"..@.........."
|
||||
},
|
||||
{ 7, 25, 14,
|
||||
"........................."
|
||||
"..@@@@@@@@@@@@@@@@@@@@..."
|
||||
"..@@@@@@@@@@@@@@@@@@@@..."
|
||||
"..@@.................@..."
|
||||
"..@@.................@..."
|
||||
"..@@.......@@@.......@.@."
|
||||
"..@@.......@@@.......@..."
|
||||
"..@@...@@@@@@@@@@@@@@@..."
|
||||
"..@@...@@@@@@@@@@@@@@@..."
|
||||
"..@@.......@@@.......@..."
|
||||
"..@@.......@@@.......@..."
|
||||
"..@@.................@..."
|
||||
"..@@.................@..."
|
||||
"........................."
|
||||
},
|
||||
{ 6, 25, 16,
|
||||
"........................."
|
||||
"......@@@@@@@@@@@@@......"
|
||||
"........................."
|
||||
".....@..........@........"
|
||||
".....@..........@........"
|
||||
".....@......@............"
|
||||
".....@......@.@@@@@@@...."
|
||||
".....@......@............"
|
||||
".....@......@.@@@@@@@...."
|
||||
".....@......@............"
|
||||
"....@@@@....@............"
|
||||
"....@@@@....@............"
|
||||
"..@@@@@@....@............"
|
||||
"..@@@.......@............"
|
||||
"..@@@...................."
|
||||
"..@@@@@@@@@@@@@@@@@@@@@@@"
|
||||
},
|
||||
{ 5, 40, 18,
|
||||
"........................................"
|
||||
"........................................"
|
||||
"...@@@@@@..............................."
|
||||
"...@@@@@@..............................."
|
||||
"...@@@@@@..............................."
|
||||
"...@@@@@@.........@@@@@@@@@@............"
|
||||
"...@@@@@@.........@@@@@@@@@@............"
|
||||
"..................@@@@@@@@@@............"
|
||||
"..................@@@@@@@@@@............"
|
||||
".............@@@@@@@@@@@@@@@............"
|
||||
".............@@@@@@@@@@@@@@@............"
|
||||
"........@@@@@@@@@@@@...................."
|
||||
"........@@@@@@@@@@@@...................."
|
||||
"........@@@@@@.........................."
|
||||
"........@@@@@@.........................."
|
||||
"........................................"
|
||||
"........................................"
|
||||
"........................................"
|
||||
},
|
||||
{ 8, 40, 18,
|
||||
"........................................"
|
||||
"..@@.@.@.@.............................."
|
||||
"..@@.@.@.@...............@.............."
|
||||
"..@@.@.@.@............@................."
|
||||
"..@@.@.@.@.............................."
|
||||
"..@@.@.@.@.................@............"
|
||||
"..@@.@..................@..............."
|
||||
"..@@.@.................................."
|
||||
"..@@.@.................................."
|
||||
"..@@.@................@@@@.............."
|
||||
"..@@.@..............@@@@@@@@............"
|
||||
"..@@.@.................................."
|
||||
"..@@.@..............@@@@@@@@............"
|
||||
"..@@.@.................................."
|
||||
"..@@.@................@@@@.............."
|
||||
"..@@.@.................................."
|
||||
"..@@.@.................................."
|
||||
"........................................"
|
||||
},
|
||||
{ 10, 40, 19,
|
||||
"@@@@@..................................."
|
||||
"@@@@@..................................."
|
||||
"@@@@@..................................."
|
||||
"@@@@@..................................."
|
||||
"@@@@@..................................."
|
||||
"@@@@@...........@@@@@@@@@@@............."
|
||||
"@@@@@...........@@@@@@@@@@@............."
|
||||
"....................@@@@................"
|
||||
"....................@@@@................"
|
||||
"....................@@@@................"
|
||||
"....................@@@@................"
|
||||
"....................@@@@................"
|
||||
"...............@@@@@@@@@@@@@@..........."
|
||||
"...............@@@@@@@@@@@@@@..........."
|
||||
".......@@@@@@@@@@@@@@@@@@@@@@..........."
|
||||
".......@@@@@@@@@........................"
|
||||
"........................................"
|
||||
"........................................"
|
||||
"........................................"
|
||||
},
|
||||
{ 10, 40, 25,
|
||||
"...................@...................."
|
||||
"...............@@@@@@@@@................"
|
||||
"............@@@.........@@@............."
|
||||
"...........@...............@............"
|
||||
"..........@.................@..........."
|
||||
".........@...................@.........."
|
||||
".........@...................@.........."
|
||||
".........@.....@@......@@....@.........."
|
||||
"........@.....@@@@....@@@@....@........."
|
||||
"........@.....................@........."
|
||||
"........@.....................@........."
|
||||
"........@..........@@.........@........."
|
||||
".......@@..........@@.........@@........"
|
||||
"........@.....................@........."
|
||||
"........@.....................@........."
|
||||
"........@......@@@@@@@@@......@........."
|
||||
"........@......@@@@@@@@@......@........."
|
||||
".........@...................@.........."
|
||||
".........@...................@.........."
|
||||
".........@...................@.........."
|
||||
"..........@.................@..........."
|
||||
"...........@...............@............"
|
||||
"............@@@.........@@@............."
|
||||
"...............@@@@@@@@@................"
|
||||
"...................@...................."
|
||||
}
|
||||
};
|
||||
|
||||
const int kInstanceCount = 10;
|
||||
|
||||
// ---------- Box ---------
|
||||
|
||||
class Box {
|
||||
public:
|
||||
static const int kAreaCost = 1;
|
||||
static const int kFixedCost = 10;
|
||||
|
||||
Box() {}
|
||||
Box(int x_min, int x_max, int y_min, int y_max)
|
||||
: x_min_(x_min), x_max_(x_max), y_min_(y_min), y_max_(y_max) {
|
||||
CHECK_GE(x_max, x_min);
|
||||
CHECK_GE(y_max, y_min);
|
||||
}
|
||||
|
||||
int x_min() const { return x_min_; }
|
||||
int x_max() const { return x_max_; }
|
||||
int y_min() const { return y_min_; }
|
||||
int y_max() const { return y_max_; }
|
||||
|
||||
// Lexicographic order
|
||||
int Compare(const Box& box) const {
|
||||
int c;
|
||||
if ((c = (x_min() - box.x_min())) != 0) return c;
|
||||
if ((c = (x_max() - box.x_max())) != 0) return c;
|
||||
if ((c = (y_min() - box.y_min())) != 0) return c;
|
||||
return y_max() - box.y_max();
|
||||
}
|
||||
|
||||
bool Contains(int x, int y) const {
|
||||
return x >= x_min() && x <= x_max() && y >= y_min() && y <= y_max();
|
||||
}
|
||||
|
||||
int Cost() const {
|
||||
return kAreaCost * (x_max() - x_min() + 1) * (y_max() - y_min() + 1)
|
||||
+ kFixedCost;
|
||||
}
|
||||
|
||||
string DebugString() const {
|
||||
return StringPrintf("[%d,%dx%d,%d]c%d",
|
||||
x_min(), y_min(), x_max(), y_max(), Cost());
|
||||
}
|
||||
|
||||
private:
|
||||
int x_min_;
|
||||
int x_max_;
|
||||
int y_min_;
|
||||
int y_max_;
|
||||
};
|
||||
|
||||
struct BoxLessThan {
|
||||
bool operator()(const Box& b1, const Box& b2) const {
|
||||
return b1.Compare(b2) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------- Covering Problem ---------
|
||||
|
||||
class CoveringProblem {
|
||||
public:
|
||||
// Grid is a row-major string of length width*height with '@' for an
|
||||
// occupied cell (strawberry) and '.' for an empty cell. Solver is
|
||||
// not owned.
|
||||
CoveringProblem(MPSolver* const solver, const Instance& instance)
|
||||
: solver_(solver),
|
||||
max_boxes_(instance.max_boxes),
|
||||
width_(instance.width),
|
||||
height_(instance.height),
|
||||
grid_(instance.grid) {}
|
||||
|
||||
// Constructs initial variables and constraints. Initial column
|
||||
// (box) covers entire grid, ensuring feasibility.
|
||||
bool Init() {
|
||||
// Check consistency.
|
||||
int size = strlen(grid_);
|
||||
if (size != area()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < size; ++i) {
|
||||
char c = grid_[i];
|
||||
if ((c != '@') && (c != '.')) return false;
|
||||
}
|
||||
|
||||
AddCellConstraints(); // sum for every cell is <=1 or =1
|
||||
AddMaxBoxesConstraint(); // sum of box variables is <= max_boxes()
|
||||
if (!FLAGS_colgen_complete) {
|
||||
AddBox(Box(0, width()-1, 0, height()-1)); // grid-covering box
|
||||
} else {
|
||||
// Naive alternative to column generation - generate all boxes;
|
||||
// works fine for smaller problems, too slow for big.
|
||||
for (int y_min = 0; y_min < height(); ++y_min) {
|
||||
for (int y_max = y_min; y_max < height(); ++y_max) {
|
||||
for (int x_min = 0; x_min < width(); ++x_min) {
|
||||
for (int x_max = x_min; x_max < width(); ++x_max) {
|
||||
AddBox(Box(x_min, x_max, y_min, y_max));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
int area() const { return width() * height(); }
|
||||
int max_boxes() const { return max_boxes_; }
|
||||
|
||||
bool IsCellOccupied(int x, int y) const { return grid_[index(x, y)] == '@'; }
|
||||
|
||||
// Calculates reduced costs for each possible Box and if any is
|
||||
// negative (improves cost), returns reduced cost and set target to
|
||||
// the most-negative (steepest descent) one - otherwise returns 0..
|
||||
//
|
||||
// For a problem in standard form 'minimize c*x s.t. Ax<=b, x>=0'
|
||||
// the reduced cost vector is c - transp(y) * A where y is the dual
|
||||
// cost column vector.
|
||||
//
|
||||
// For this covering problem, in which all coefficients in A are 0
|
||||
// or 1, this reduces to:
|
||||
//
|
||||
// reduced_cost(box) =
|
||||
//
|
||||
// box.Cost() - sum_{enclosed cell} cell_constraint->dual_value()
|
||||
// - max_boxes_constraint_->dual_value()
|
||||
//
|
||||
// Since there are O(d^4) boxes, we don't also want O(d^2) sum for
|
||||
// each, so pre-calculate sums of cell duals for all rectangles with
|
||||
// upper-left at 0, 0, and use these to calculate the sum in
|
||||
// constant time using the standard inclusion-exclusion trick.
|
||||
double GetOptimalBox(Box* const target) {
|
||||
// Cost change threshold for new Box
|
||||
const double kCostChangeThreshold = -.01;
|
||||
|
||||
// Precomputes the sum of reduced costs for every upper-left
|
||||
// rectangle.
|
||||
std::vector<double> upper_left_sums(area());
|
||||
ComputeUpperLeftSums(&upper_left_sums);
|
||||
|
||||
const double max_boxes_dual = max_boxes_constraint_->dual_value();
|
||||
double best_reduced_cost = kCostChangeThreshold;
|
||||
Box best_box;
|
||||
for (int y_min = 0; y_min < height(); ++y_min) {
|
||||
for (int y_max = y_min; y_max < height(); ++y_max) {
|
||||
for (int x_min = 0; x_min < width(); ++x_min) {
|
||||
for (int x_max = x_min; x_max < width(); ++x_max) {
|
||||
Box box(x_min, x_max, y_min, y_max);
|
||||
const double cell_coverage_dual = // inclusion-exclusion
|
||||
+ zero_access(upper_left_sums, x_max, y_max)
|
||||
- zero_access(upper_left_sums, x_max, y_min - 1)
|
||||
- zero_access(upper_left_sums, x_min - 1, y_max)
|
||||
+ zero_access(upper_left_sums, x_min - 1, y_min - 1);
|
||||
|
||||
// All coefficients for new column are 1, so no need to
|
||||
// multiply constraint duals by any coefficients when
|
||||
// computing the reduced cost.
|
||||
const double reduced_cost = box.Cost()
|
||||
- (cell_coverage_dual + max_boxes_dual);
|
||||
|
||||
if (reduced_cost < best_reduced_cost) {
|
||||
// Even with negative reduced cost, the box may already
|
||||
// exist, and even be basic (part of solution)! This
|
||||
// counterintuitive situation is due to the problem's
|
||||
// many redundant linear equality constraints: many
|
||||
// steepest-edge pivot moves will be of zero-length.
|
||||
// Ideally one would want to check the length of the
|
||||
// move but that is difficult without access to the
|
||||
// internals of the solver (e.g., access to B^-1 in the
|
||||
// simplex algorithm).
|
||||
if (boxes_.find(box) == boxes_.end()) {
|
||||
best_reduced_cost = reduced_cost;
|
||||
best_box = box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_reduced_cost < kCostChangeThreshold) {
|
||||
if (target) {
|
||||
*target = best_box;
|
||||
}
|
||||
return best_reduced_cost;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add continuous [0,1] box variable with box.Cost() as objective
|
||||
// coefficient. Add to cell constraint of all enclosed cells.
|
||||
MPVariable* AddBox(const Box& box) {
|
||||
CHECK(boxes_.find(box) == boxes_.end());
|
||||
MPVariable* const var = solver_->MakeNumVar(0., 1., box.DebugString());
|
||||
solver_->SetObjectiveCoefficient(var, box.Cost());
|
||||
max_boxes_constraint_->SetCoefficient(var, 1.0);
|
||||
for (int y = box.y_min(); y <= box.y_max(); ++y) {
|
||||
for (int x = box.x_min(); x <= box.x_max(); ++x) {
|
||||
cell(x, y)->SetCoefficient(var, 1.0);
|
||||
}
|
||||
}
|
||||
boxes_[box] = var;
|
||||
return var;
|
||||
}
|
||||
|
||||
string PrintGrid() const {
|
||||
string output = StringPrintf("width = %d, height = %d, max_boxes = %d\n",
|
||||
width_,
|
||||
height_,
|
||||
max_boxes_);
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
StringAppendF(&output,
|
||||
"%s\n",
|
||||
string(grid_ + width_ * y, width_).c_str());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Prints covering - total cost, those variables with non-zero value,
|
||||
// and graphical depiction of covering using upper case letters for
|
||||
// integral coverage and lower case for coverage using combination
|
||||
// of fractional boxes.
|
||||
string PrintCovering() const {
|
||||
static const double kTolerance = 1e-5;
|
||||
string output = StringPrintf("cost = %lf\n", solver_->objective_value());
|
||||
scoped_array<char> display(new char[(width_ + 1) * height_ + 1]);
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
memcpy(display.get() + y * (width_ + 1),
|
||||
grid_ + width_ * y,
|
||||
width_); // Copy the original line.
|
||||
display[y * (width_ + 1) + width_] = '\n';
|
||||
}
|
||||
display[height_ * (width_ + 1)] = '\0';
|
||||
int active_box_index = 0;
|
||||
for (BoxTable::const_iterator i = boxes_.begin(); i != boxes_.end(); ++i) {
|
||||
const double value = i->second->solution_value();
|
||||
if (value > kTolerance) {
|
||||
const char box_character =
|
||||
(i->second->solution_value() >= (1. - kTolerance) ? 'A' : 'a') +
|
||||
active_box_index++;
|
||||
StringAppendF(&output, "%c: box %s with value %lf\n",
|
||||
box_character,
|
||||
i->first.DebugString().c_str(),
|
||||
value);
|
||||
const Box& box = i->first;
|
||||
for (int x = box.x_min(); x <= box.x_max(); ++x) {
|
||||
for (int y = box.y_min(); y <= box.y_max(); ++y) {
|
||||
display[x + y * (width_ + 1)] = box_character;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StringAppendF(&output, "%s", display.get());
|
||||
return output;
|
||||
}
|
||||
|
||||
protected:
|
||||
int index(int x, int y) const { return width_ * y + x; }
|
||||
MPConstraint* cell(int x, int y) { return cells_[index(x, y)]; }
|
||||
const MPConstraint* cell(int x, int y) const { return cells_[index(x, y)]; }
|
||||
|
||||
// Adds constraints that every cell is covered at most once, exactly
|
||||
// once if occupied.
|
||||
void AddCellConstraints() {
|
||||
cells_.resize(area());
|
||||
for (int y = 0; y < height(); ++y) {
|
||||
for (int x = 0; x < width(); ++x) {
|
||||
cells_[index(x, y)] = solver_->MakeRowConstraint
|
||||
(IsCellOccupied(x, y) ? 1. : 0., 1.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds constraint on maximum number of boxes used to cover.
|
||||
void AddMaxBoxesConstraint() {
|
||||
max_boxes_constraint_ = solver_->MakeRowConstraint(0., max_boxes());
|
||||
}
|
||||
|
||||
// Gets 2d array element, returning 0 if out-of-bounds.
|
||||
double zero_access(const std::vector<double>& array, int x, int y) const {
|
||||
if (x < 0 || y < 0) {
|
||||
return 0;
|
||||
}
|
||||
return array[index(x, y)];
|
||||
}
|
||||
|
||||
// Precomputes the sum of reduced costs for every upper-left
|
||||
// rectangle.
|
||||
void ComputeUpperLeftSums(std::vector<double>* upper_left_sums) const {
|
||||
for (int y = 0; y < height(); ++y) {
|
||||
for (int x = 0; x < width(); ++x) {
|
||||
upper_left_sums->operator[](index(x, y)) =
|
||||
cell(x, y)->dual_value()
|
||||
+ zero_access(*upper_left_sums, x - 1, y)
|
||||
+ zero_access(*upper_left_sums, x, y - 1)
|
||||
- zero_access(*upper_left_sums, x - 1, y - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::map<Box, MPVariable*, BoxLessThan> BoxTable;
|
||||
MPSolver* const solver_; // not owned
|
||||
const int max_boxes_;
|
||||
const int width_;
|
||||
const int height_;
|
||||
const char* const grid_;
|
||||
std::vector<MPConstraint*> cells_;
|
||||
BoxTable boxes_;
|
||||
MPConstraint* max_boxes_constraint_;
|
||||
};
|
||||
|
||||
// ---------- Main Solve Method ----------
|
||||
|
||||
// Solves iteratively using delayed column generation, up to maximum
|
||||
// number of steps.
|
||||
void SolveInstance(const Instance& instance,
|
||||
MPSolver::OptimizationProblemType solver_type) {
|
||||
// Prepares the solver.
|
||||
MPSolver solver("ColumnGeneration", solver_type);
|
||||
solver.SuppressOutput();
|
||||
solver.SetMinimization();
|
||||
|
||||
// Construct problem.
|
||||
CoveringProblem problem(&solver, instance);
|
||||
CHECK(problem.Init());
|
||||
LOG(INFO) << "Initial problem:\n" << problem.PrintGrid();
|
||||
|
||||
int step_number = 0;
|
||||
while (step_number < FLAGS_colgen_max_iterations) {
|
||||
if (FLAGS_colgen_verbose) {
|
||||
LOG(INFO) << "Step number " << step_number;
|
||||
}
|
||||
|
||||
// Solve with existing columns.
|
||||
CHECK_EQ(MPSolver::OPTIMAL, solver.Solve());
|
||||
if (FLAGS_colgen_verbose) {
|
||||
LOG(INFO) << problem.PrintCovering();
|
||||
}
|
||||
|
||||
// Find optimal new column to add, or stop if none.
|
||||
Box box;
|
||||
const double reduced_cost = problem.GetOptimalBox(&box);
|
||||
if (reduced_cost >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add new column to problem.
|
||||
if (FLAGS_colgen_verbose) {
|
||||
LOG(INFO) << "Adding " << box.DebugString()
|
||||
<< ", reduced_cost =" << reduced_cost;
|
||||
}
|
||||
problem.AddBox(box);
|
||||
|
||||
++step_number;
|
||||
}
|
||||
|
||||
if (step_number >= FLAGS_colgen_max_iterations) {
|
||||
// Solve one last time with all generated columns.
|
||||
CHECK_EQ(MPSolver::OPTIMAL, solver.Solve());
|
||||
}
|
||||
|
||||
LOG(INFO) << step_number << " columns added";
|
||||
LOG(INFO) << "Final coverage: " << problem.PrintCovering();
|
||||
}
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
string usage = "column_generation\n";
|
||||
usage += " --colgen_verbose print verbosely\n";
|
||||
usage += " --colgen_max_iterations <n> max columns to generate\n";
|
||||
usage += " --colgen_complete generate all columns at start\n";
|
||||
|
||||
|
||||
operations_research::MPSolver::OptimizationProblemType solver_type;
|
||||
bool found = false;
|
||||
#if defined(USE_CLP)
|
||||
if (FLAGS_colgen_solver == "clp") {
|
||||
solver_type = operations_research::MPSolver::CLP_LINEAR_PROGRAMMING;
|
||||
found = true;
|
||||
}
|
||||
#endif // USE_CLP
|
||||
#if defined(USE_GLPK)
|
||||
if (FLAGS_colgen_solver == "glpk") {
|
||||
solver_type = operations_research::MPSolver::GLPK_LINEAR_PROGRAMMING;
|
||||
found = true;
|
||||
}
|
||||
#endif // USE_GLPK
|
||||
if (!found) {
|
||||
LOG(ERROR) << "Unknown solver " << FLAGS_colgen_solver;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (FLAGS_colgen_instance == -1) {
|
||||
for (int i = 0; i < operations_research::kInstanceCount; ++i) {
|
||||
const operations_research::Instance& instance =
|
||||
operations_research::kInstances[i];
|
||||
operations_research::SolveInstance(instance, solver_type);
|
||||
}
|
||||
} else {
|
||||
CHECK_GE(FLAGS_colgen_instance, 0);
|
||||
CHECK_LT(FLAGS_colgen_instance, operations_research::kInstanceCount);
|
||||
const operations_research::Instance& instance =
|
||||
operations_research::kInstances[FLAGS_colgen_instance];
|
||||
operations_research::SolveInstance(instance, solver_type);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
160
examples/cpp/tsp.cc
Normal file
160
examples/cpp/tsp.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2010-2011 Google
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Traveling Salesman Sample.
|
||||
//
|
||||
// This is a sample using the routing library to solve a Traveling Salesman
|
||||
// Problem.
|
||||
// The description of the problem can be found here:
|
||||
// http://en.wikipedia.org/wiki/Travelling_salesman_problem.
|
||||
// For small problems one can use the hamiltonian path library directly (cf
|
||||
// graph/hamiltonian_path.h).
|
||||
// The optimization engine uses local search to improve solutions, first
|
||||
// solutions being generated using a cheapest addition heuristic.
|
||||
// Optionally one can randomly forbid a set of random connections between nodes
|
||||
// (forbidden arcs).
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/integral_types.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/join.h"
|
||||
#include "constraint_solver/routing.h"
|
||||
#include "base/random.h"
|
||||
|
||||
using operations_research::Assignment;
|
||||
using operations_research::RoutingModel;
|
||||
using operations_research::ACMRandom;
|
||||
using operations_research::StrCat;
|
||||
using operations_research::scoped_array;
|
||||
|
||||
|
||||
DEFINE_int32(tsp_size, 10, "Size of Traveling Salesman Problem instance.");
|
||||
DEFINE_bool(tsp_use_random_matrix, true, "Use random cost matrix.");
|
||||
DEFINE_int32(tsp_random_forbidden_connections, 0,
|
||||
"Number of random forbidden connections.");
|
||||
DEFINE_bool(tsp_use_deterministic_random_seed, false,
|
||||
"Use deterministic random seeds.");
|
||||
|
||||
// Random seed generator.
|
||||
int32 GetSeed() {
|
||||
if (FLAGS_tsp_use_deterministic_random_seed) {
|
||||
return ACMRandom::DeterministicSeed();
|
||||
} else {
|
||||
return ACMRandom::HostnamePidTimeSeed();
|
||||
}
|
||||
}
|
||||
|
||||
// Cost/distance functions.
|
||||
|
||||
// Sample function.
|
||||
int64 MyDistance(RoutingModel::NodeIndex from, RoutingModel::NodeIndex to) {
|
||||
// Put your distance code here.
|
||||
return (from + to).value(); // for instance
|
||||
}
|
||||
|
||||
// Random matrix.
|
||||
class RandomMatrix {
|
||||
public:
|
||||
explicit RandomMatrix(int size) : size_(size) {}
|
||||
void Initialize() {
|
||||
matrix_.reset(new int64[size_ * size_]);
|
||||
const int64 kDistanceMax = 100;
|
||||
ACMRandom randomizer(GetSeed());
|
||||
for (RoutingModel::NodeIndex from = RoutingModel::kFirstNode; from < size_;
|
||||
++from) {
|
||||
for (RoutingModel::NodeIndex to = RoutingModel::kFirstNode; to < size_;
|
||||
++to) {
|
||||
if (to != from) {
|
||||
matrix_[MatrixIndex(from, to)] = randomizer.Uniform(kDistanceMax);
|
||||
} else {
|
||||
matrix_[MatrixIndex(from, to)] = 0LL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int64 Distance(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return matrix_[MatrixIndex(from, to)];
|
||||
}
|
||||
|
||||
private:
|
||||
int64 MatrixIndex(RoutingModel::NodeIndex from,
|
||||
RoutingModel::NodeIndex to) const {
|
||||
return (from * size_ + to).value();
|
||||
}
|
||||
scoped_array<int64> matrix_;
|
||||
const int size_;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_tsp_size > 0) {
|
||||
// TSP of size FLAGS_tsp_size.
|
||||
// Second argument = 1 to build a single tour (it's a TSP).
|
||||
// Nodes are indexed from 0 to FLAGS_tsp_size - 1, by default the start of
|
||||
// the route is node 0.
|
||||
RoutingModel routing(FLAGS_tsp_size, 1);
|
||||
// Setting first solution heuristic (cheapest addition).
|
||||
routing.SetCommandLineOption("routing_first_solution", "PathCheapestArc");
|
||||
// Disabling Large Neighborhood Search, comment out to activate it.
|
||||
routing.SetCommandLineOption("routing_no_lns", "true");
|
||||
|
||||
// Setting the cost function.
|
||||
// Put a permanent callback to the distance accessor here. The callback
|
||||
// has the following signature: ResultCallback2<int64, int64, int64>.
|
||||
// The two arguments are the from and to node inidices.
|
||||
RandomMatrix matrix(FLAGS_tsp_size);
|
||||
if (FLAGS_tsp_use_random_matrix) {
|
||||
matrix.Initialize();
|
||||
routing.SetCost(NewPermanentCallback(&matrix, &RandomMatrix::Distance));
|
||||
} else {
|
||||
routing.SetCost(NewPermanentCallback(MyDistance));
|
||||
}
|
||||
// Forbid node connections (randomly).
|
||||
ACMRandom randomizer(GetSeed());
|
||||
int64 forbidden_connections = 0;
|
||||
while (forbidden_connections < FLAGS_tsp_random_forbidden_connections) {
|
||||
const int64 from = randomizer.Uniform(FLAGS_tsp_size - 1);
|
||||
const int64 to = randomizer.Uniform(FLAGS_tsp_size - 1) + 1;
|
||||
if (routing.NextVar(from)->Contains(to)) {
|
||||
LG << "Forbidding connection " << from << " -> " << to;
|
||||
routing.NextVar(from)->RemoveValue(to);
|
||||
++forbidden_connections;
|
||||
}
|
||||
}
|
||||
// Solve, returns a solution if any (owned by RoutingModel).
|
||||
const Assignment* solution = routing.Solve();
|
||||
if (solution != NULL) {
|
||||
// Solution cost.
|
||||
LG << "Cost " << solution->ObjectiveValue();
|
||||
// Inspect solution.
|
||||
// Only one route here; otherwise iterate from 0 to routing.vehicles() - 1
|
||||
const int route_number = 0;
|
||||
string route;
|
||||
for (int64 node = routing.Start(route_number);
|
||||
!routing.IsEnd(node);
|
||||
node = solution->Value(routing.NextVar(node))) {
|
||||
route = StrCat(route, StrCat(node, " -> "));
|
||||
}
|
||||
route = StrCat(route, "0");
|
||||
LG << route;
|
||||
} else {
|
||||
LG << "No solution found.";
|
||||
}
|
||||
} else {
|
||||
LG << "Specify an instance size greater than 0.";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user