Files
ortools-clone/examples/cpp/strawberry_fields_with_column_generation.cc

661 lines
26 KiB
C++
Raw Normal View History

2025-01-10 11:35:44 +01:00
// Copyright 2010-2025 Google LLC
2011-11-03 10:30:57 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 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 <cstdlib>
2020-10-22 23:36:58 +02:00
#include <cstring> // strlen
2011-11-03 10:30:57 +00:00
#include <map>
#include <memory>
2022-09-09 16:49:24 +02:00
#include <ostream>
2011-11-03 10:30:57 +00:00
#include <string>
#include <utility>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/log/check.h"
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
#include "absl/strings/str_format.h"
2023-10-27 14:01:38 +02:00
#include "absl/types/span.h"
#include "ortools/base/commandlineflags.h"
2022-02-25 09:47:52 +01:00
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "ortools/linear_solver/linear_solver.h"
2011-11-03 10:30:57 +00:00
2020-10-23 11:50:14 +02:00
ABSL_FLAG(bool, colgen_verbose, false, "print verbosely");
ABSL_FLAG(bool, colgen_complete, false, "generate all columns initially");
ABSL_FLAG(int, colgen_max_iterations, 500, "max iterations");
ABSL_FLAG(std::string, colgen_solver, "glop", "solver - glop (default) or clp");
ABSL_FLAG(int, colgen_instance, -1, "Which instance to solve (0 - 9)");
2011-11-03 10:30:57 +00:00
namespace operations_research {
// ---------- Data Instances ----------
struct Instance {
int max_boxes;
int width;
int height;
2020-10-29 14:25:39 +01:00
const char* grid;
2011-11-03 10:30:57 +00:00
};
2020-10-22 23:36:58 +02:00
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,
"...................@...................."
"...............@@@@@@@@@................"
"............@@@.........@@@............."
"...........@...............@............"
"..........@.................@..........."
".........@...................@.........."
".........@...................@.........."
".........@.....@@......@@....@.........."
"........@.....@@@@....@@@@....@........."
"........@.....................@........."
"........@.....................@........."
"........@..........@@.........@........."
".......@@..........@@.........@@........"
"........@.....................@........."
"........@.....................@........."
"........@......@@@@@@@@@......@........."
"........@......@@@@@@@@@......@........."
".........@...................@.........."
".........@...................@.........."
".........@...................@.........."
"..........@.................@..........."
"...........@...............@............"
"............@@@.........@@@............."
"...............@@@@@@@@@................"
"...................@...................."}};
2011-11-03 10:30:57 +00:00
const int kInstanceCount = 10;
// ---------- Box ---------
class Box {
2020-10-22 23:36:58 +02:00
public:
2021-01-14 10:48:19 +01:00
static constexpr int kAreaCost = 1;
static constexpr int kFixedCost = 10;
2011-11-03 10:30:57 +00:00
Box() = default;
2011-11-03 10:30:57 +00:00
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
2020-10-29 14:25:39 +01:00
int Compare(const Box& box) const {
2011-11-03 10:30:57 +00:00
int c;
2020-10-22 23:36:58 +02:00
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;
2011-11-03 10:30:57 +00:00
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 {
2014-01-08 12:01:58 +00:00
return kAreaCost * (x_max() - x_min() + 1) * (y_max() - y_min() + 1) +
kFixedCost;
2011-11-03 10:30:57 +00:00
}
std::string DebugString() const {
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
return absl::StrFormat("[%d,%dx%d,%d]c%d", x_min(), y_min(), x_max(),
y_max(), Cost());
2011-11-03 10:30:57 +00:00
}
2020-10-22 23:36:58 +02:00
private:
2011-11-03 10:30:57 +00:00
int x_min_;
int x_max_;
int y_min_;
int y_max_;
};
struct BoxLessThan {
2020-10-29 14:25:39 +01:00
bool operator()(const Box& b1, const Box& b2) const {
2011-11-03 10:30:57 +00:00
return b1.Compare(b2) < 0;
}
};
// ---------- Covering Problem ---------
class CoveringProblem {
2020-10-22 23:36:58 +02:00
public:
2019-11-22 15:17:10 +01:00
// Grid is a row-major string of length width*height with '@' for an
2011-11-03 10:30:57 +00:00
// occupied cell (strawberry) and '.' for an empty cell. Solver is
// not owned.
2023-10-27 14:01:38 +02:00
CoveringProblem(MPSolver* const solver, const Instance& instance)
2020-10-22 23:36:58 +02:00
: solver_(solver),
max_boxes_(instance.max_boxes),
width_(instance.width),
height_(instance.height),
grid_(instance.grid) {}
2011-11-03 10:30:57 +00:00
// 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];
2020-10-22 23:36:58 +02:00
if ((c != '@') && (c != '.')) return false;
2011-11-03 10:30:57 +00:00
}
2020-10-22 23:36:58 +02:00
AddCellConstraints(); // sum for every cell is <=1 or =1
AddMaxBoxesConstraint(); // sum of box variables is <= max_boxes()
if (!absl::GetFlag(FLAGS_colgen_complete)) {
2020-10-22 23:36:58 +02:00
AddBox(Box(0, width() - 1, 0, height() - 1)); // grid-covering box
2011-11-03 10:30:57 +00:00
} 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.
2020-10-29 14:25:39 +01:00
double GetOptimalBox(Box* const target) {
2011-11-03 10:30:57 +00:00
// 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);
2020-10-22 23:36:58 +02:00
const double cell_coverage_dual = // inclusion-exclusion
2014-01-08 12:01:58 +00:00
+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);
2011-11-03 10:30:57 +00:00
// All coefficients for new column are 1, so no need to
// multiply constraint duals by any coefficients when
// computing the reduced cost.
2014-01-08 12:01:58 +00:00
const double reduced_cost =
box.Cost() - (cell_coverage_dual + max_boxes_dual);
2011-11-03 10:30:57 +00:00
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.
2020-10-29 14:25:39 +01:00
MPVariable* AddBox(const Box& box) {
2011-11-03 10:30:57 +00:00
CHECK(boxes_.find(box) == boxes_.end());
2020-10-29 14:25:39 +01:00
MPVariable* const var = solver_->MakeNumVar(0., 1., box.DebugString());
2014-01-08 12:01:58 +00:00
solver_->MutableObjective()->SetCoefficient(var, box.Cost());
max_boxes_constraint_->SetCoefficient(var, 1.0);
2011-11-03 10:30:57 +00:00
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);
2011-11-03 10:30:57 +00:00
}
}
boxes_[box] = var;
return var;
}
std::string PrintGrid() const {
2018-06-08 16:40:43 +02:00
std::string output =
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
absl::StrFormat("width = %d, height = %d, max_boxes = %d\n", width_,
height_, max_boxes_);
2011-11-03 10:30:57 +00:00
for (int y = 0; y < height_; ++y) {
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
absl::StrAppendFormat(&output, "%s\n",
std::string(grid_ + width_ * y, width_));
2011-11-03 10:30:57 +00:00
}
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.
std::string PrintCovering() const {
2011-11-03 10:30:57 +00:00
static const double kTolerance = 1e-5;
2018-06-08 16:40:43 +02:00
std::string output =
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
absl::StrFormat("cost = %f\n", solver_->Objective().Value());
std::unique_ptr<char[]> display(new char[(width_ + 1) * height_ + 1]);
2011-11-03 10:30:57 +00:00
for (int y = 0; y < height_; ++y) {
2014-01-08 12:01:58 +00:00
memcpy(display.get() + y * (width_ + 1), grid_ + width_ * y,
2020-10-22 23:36:58 +02:00
width_); // Copy the original line.
2011-11-03 10:30:57 +00:00
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++;
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
absl::StrAppendFormat(&output, "%c: box %s with value %f\n",
box_character, i->first.DebugString(), value);
2020-10-29 14:25:39 +01:00
const Box& box = i->first;
2011-11-03 10:30:57 +00:00
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;
}
}
}
}
output.append(display.get());
2011-11-03 10:30:57 +00:00
return output;
}
2020-10-22 23:36:58 +02:00
protected:
2011-11-03 10:30:57 +00:00
int index(int x, int y) const { return width_ * y + x; }
2020-10-29 14:25:39 +01:00
MPConstraint* cell(int x, int y) { return cells_[index(x, y)]; }
const MPConstraint* cell(int x, int y) const { return cells_[index(x, y)]; }
2011-11-03 10:30:57 +00:00
// 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) {
2014-01-08 12:01:58 +00:00
cells_[index(x, y)] =
solver_->MakeRowConstraint(IsCellOccupied(x, y) ? 1. : 0., 1.);
2011-11-03 10:30:57 +00:00
}
}
}
// 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.
2023-10-27 14:01:38 +02:00
double zero_access(absl::Span<const double> array, int x, int y) const {
2011-11-03 10:30:57 +00:00
if (x < 0 || y < 0) {
return 0;
}
return array[index(x, y)];
}
// Precomputes the sum of reduced costs for every upper-left
// rectangle.
2020-10-29 14:25:39 +01:00
void ComputeUpperLeftSums(std::vector<double>* upper_left_sums) const {
2011-11-03 10:30:57 +00:00
for (int y = 0; y < height(); ++y) {
for (int x = 0; x < width(); ++x) {
upper_left_sums->operator[](index(x, y)) =
2014-01-08 12:01:58 +00:00
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);
2011-11-03 10:30:57 +00:00
}
}
}
2020-10-29 14:25:39 +01:00
typedef std::map<Box, MPVariable*, BoxLessThan> BoxTable;
MPSolver* const solver_; // not owned
2011-11-03 10:30:57 +00:00
const int max_boxes_;
const int width_;
const int height_;
2020-10-29 14:25:39 +01:00
const char* const grid_;
std::vector<MPConstraint*> cells_;
2011-11-03 10:30:57 +00:00
BoxTable boxes_;
2020-10-29 14:25:39 +01:00
MPConstraint* max_boxes_constraint_;
2011-11-03 10:30:57 +00:00
};
// ---------- Main Solve Method ----------
// Solves iteratively using delayed column generation, up to maximum
// number of steps.
2023-10-27 14:01:38 +02:00
void SolveInstance(const Instance& instance,
MPSolver::OptimizationProblemType solver_type) {
// Prepares the solver.
MPSolver solver("ColumnGeneration", solver_type);
solver.SuppressOutput();
solver.MutableObjective()->SetMinimization();
2011-11-03 10:30:57 +00:00
// Construct problem.
2023-10-27 14:01:38 +02:00
CoveringProblem problem(&solver, instance);
2011-11-03 10:30:57 +00:00
CHECK(problem.Init());
LOG(INFO) << "Initial problem:\n" << problem.PrintGrid();
int step_number = 0;
while (step_number < absl::GetFlag(FLAGS_colgen_max_iterations)) {
if (absl::GetFlag(FLAGS_colgen_verbose)) {
2011-11-03 10:30:57 +00:00
LOG(INFO) << "Step number " << step_number;
}
// Solve with existing columns.
2023-10-27 14:01:38 +02:00
CHECK_EQ(MPSolver::OPTIMAL, solver.Solve());
if (absl::GetFlag(FLAGS_colgen_verbose)) {
2011-11-03 10:30:57 +00:00
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 (absl::GetFlag(FLAGS_colgen_verbose)) {
2011-11-03 10:30:57 +00:00
LOG(INFO) << "Adding " << box.DebugString()
<< ", reduced_cost =" << reduced_cost;
}
problem.AddBox(box);
++step_number;
}
if (step_number >= absl::GetFlag(FLAGS_colgen_max_iterations)) {
2011-11-03 10:30:57 +00:00
// Solve one last time with all generated columns.
2023-10-27 14:01:38 +02:00
CHECK_EQ(MPSolver::OPTIMAL, solver.Solve());
2011-11-03 10:30:57 +00:00
}
LOG(INFO) << step_number << " columns added";
LOG(INFO) << "Final coverage: " << problem.PrintCovering();
}
2020-10-22 23:36:58 +02:00
} // namespace operations_research
2011-11-03 10:30:57 +00:00
2020-10-29 14:25:39 +01:00
int main(int argc, char** argv) {
std::string usage = "column_generation\n";
2011-11-03 10:30:57 +00:00
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";
2023-08-09 22:34:09 -07:00
absl::SetFlag(&FLAGS_stderrthreshold, 0);
2022-02-25 09:47:52 +01:00
InitGoogle(usage.c_str(), &argc, &argv, true);
2023-10-27 14:01:38 +02:00
operations_research::MPSolver::OptimizationProblemType solver_type;
bool found = false;
#if defined(USE_CLP)
if (absl::GetFlag(FLAGS_colgen_solver) == "clp") {
solver_type = operations_research::MPSolver::CLP_LINEAR_PROGRAMMING;
found = true;
}
#endif // USE_CLP
if (absl::GetFlag(FLAGS_colgen_solver) == "glop") {
solver_type = operations_research::MPSolver::GLOP_LINEAR_PROGRAMMING;
found = true;
}
#if defined(USE_XPRESS)
if (absl::GetFlag(FLAGS_colgen_solver) == "xpress") {
solver_type = operations_research::MPSolver::XPRESS_LINEAR_PROGRAMMING;
// solver_type = operations_research::MPSolver::CPLEX_LINEAR_PROGRAMMING;
found = true;
}
#endif
#if defined(USE_CPLEX)
if (absl::GetFlag(FLAGS_colgen_solver) == "cplex") {
solver_type = operations_research::MPSolver::CPLEX_LINEAR_PROGRAMMING;
found = true;
}
#endif
if (!found) {
LOG(ERROR) << "Unknown solver " << absl::GetFlag(FLAGS_colgen_solver);
return 1;
}
LOG(INFO) << "Chosen solver: " << absl::GetFlag(FLAGS_colgen_solver)
<< std::endl;
2019-08-06 18:21:29 +02:00
if (absl::GetFlag(FLAGS_colgen_instance) == -1) {
2011-11-03 10:30:57 +00:00
for (int i = 0; i < operations_research::kInstanceCount; ++i) {
2020-10-29 14:25:39 +01:00
const operations_research::Instance& instance =
2011-11-03 10:30:57 +00:00
operations_research::kInstances[i];
2023-10-27 14:01:38 +02:00
operations_research::SolveInstance(instance, solver_type);
2011-11-03 10:30:57 +00:00
}
} else {
CHECK_GE(absl::GetFlag(FLAGS_colgen_instance), 0);
CHECK_LT(absl::GetFlag(FLAGS_colgen_instance),
operations_research::kInstanceCount);
2020-10-29 14:25:39 +01:00
const operations_research::Instance& instance =
operations_research::kInstances[absl::GetFlag(FLAGS_colgen_instance)];
2023-10-27 14:01:38 +02:00
operations_research::SolveInstance(instance, solver_type);
2011-11-03 10:30:57 +00:00
}
2018-11-07 09:52:37 +01:00
return EXIT_SUCCESS;
2011-11-03 10:30:57 +00:00
}