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

263 lines
10 KiB
C++
Raw Normal View History

2024-01-04 13:43:15 +01:00
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2022-09-09 16:49:24 +02:00
#include <algorithm>
2021-04-23 14:55:27 +02:00
#include <cstdint>
#include <numeric>
#include <string>
#include <vector>
#include "absl/flags/flag.h"
2024-03-25 11:59:02 +01:00
#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/numbers.h"
2024-03-25 11:59:02 +01:00
#include "absl/strings/str_cat.h"
dotnet: Remove reference to dotnet release command - Currently not implemented... Add abseil patch - Add patches/absl-config.cmake Makefile: Add abseil-cpp on unix - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake Makefile: Add abseil-cpp on windows - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake CMake: Add abseil-cpp - Force abseil-cpp SHA1 to 45221cc note: Just before the PR #136 which break all CMake port to absl: C++ Part - Fix warning with the use of ABSL_MUST_USE_RESULT > The macro must appear as the very first part of a function declaration or definition: ... Note: past advice was to place the macro after the argument list. src: dependencies/sources/abseil-cpp-master/absl/base/attributes.h:418 - Rename enum after windows clash - Remove non compact table constraints - Change index type from int64 to int in routing library - Fix file_nonport compilation on windows - Fix another naming conflict with windows (NO_ERROR is a macro) - Cleanup hash containers; work on sat internals - Add optional_boolean sub-proto Sync cpp examples with internal code - reenable issue173 after reducing number of loops port to absl: Python Part - Add back cp_model.INT32_MIN|MAX for examples Update Python examples - Add random_tsp.py - Run words_square example - Run magic_square in python tests port to absl: Java Part - Fix compilation of the new routing parameters in java - Protect some code from SWIG parsing Update Java Examples port to absl: .Net Part Update .Net examples work on sat internals; Add C++ CP-SAT CpModelBuilder API; update sample code and recipes to use the new API; sync with internal code Remove VS 2015 in Appveyor-CI - abseil-cpp does not support VS 2015... improve tables upgrade C++ sat examples to use the new API; work on sat internals update license dates rewrite jobshop_ft06_distance.py to use the CP-SAT solver rename last example revert last commit more work on SAT internals fix
2018-10-31 16:18:18 +01:00
#include "absl/strings/str_split.h"
#include "absl/types/span.h"
#include "ortools/base/init_google.h"
2018-06-08 16:40:43 +02:00
#include "ortools/base/logging.h"
#include "ortools/sat/cp_model.h"
2024-03-25 11:59:02 +01:00
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/sat/model.h"
2024-03-25 11:59:02 +01:00
#include "ortools/sat/sat_parameters.pb.h"
2022-01-19 10:32:55 +01:00
#include "ortools/util/filelineiter.h"
2024-03-25 11:59:02 +01:00
#include "ortools/util/sorted_interval_list.h"
ABSL_FLAG(std::string, input, "examples/cpp/wt40.txt", "wt data file name.");
2020-10-23 11:50:14 +02:00
ABSL_FLAG(int, size, 40, "Size of the problem in the wt file.");
ABSL_FLAG(int, n, 28, "1-based instance number in the wt file.");
ABSL_FLAG(std::string, params, "", "Sat parameters in text proto format.");
ABSL_FLAG(int, upper_bound, -1, "If positive, look for a solution <= this.");
namespace operations_research {
namespace sat {
// Solve a single machine problem with weighted tardiness cost.
void Solve(absl::Span<const int64_t> durations,
absl::Span<const int64_t> due_dates,
absl::Span<const int64_t> weights) {
const int num_tasks = durations.size();
CHECK_EQ(due_dates.size(), num_tasks);
CHECK_EQ(weights.size(), num_tasks);
// Display some statistics.
int horizon = 0;
for (int i = 0; i < num_tasks; ++i) {
horizon += durations[i];
LOG(INFO) << "#" << i << " duration:" << durations[i]
<< " due_date:" << due_dates[i] << " weight:" << weights[i];
}
2016-09-12 13:42:16 +02:00
// An simple heuristic solution: We choose the tasks from last to first, and
// always take the one with smallest cost.
std::vector<bool> is_taken(num_tasks, false);
2021-04-02 14:58:16 +02:00
int64_t heuristic_bound = 0;
int64_t end = horizon;
for (int i = 0; i < num_tasks; ++i) {
2016-09-12 13:42:16 +02:00
int next_task = -1;
2021-04-02 14:58:16 +02:00
int64_t next_cost;
2016-09-12 13:42:16 +02:00
for (int j = 0; j < num_tasks; ++j) {
2020-10-22 23:36:58 +02:00
if (is_taken[j]) continue;
2021-04-23 14:55:27 +02:00
const int64_t cost =
weights[j] * std::max<int64_t>(0, end - due_dates[j]);
2016-09-12 13:42:16 +02:00
if (next_task == -1 || cost < next_cost) {
next_task = j;
next_cost = cost;
}
}
CHECK_NE(-1, next_task);
is_taken[next_task] = true;
end -= durations[next_task];
heuristic_bound += next_cost;
}
LOG(INFO) << "num_tasks: " << num_tasks;
LOG(INFO) << "The time horizon is " << horizon;
2016-09-12 13:42:16 +02:00
LOG(INFO) << "Trival cost bound = " << heuristic_bound;
// Create the model.
CpModelBuilder cp_model;
std::vector<IntervalVar> task_intervals(num_tasks);
std::vector<IntVar> task_starts(num_tasks);
std::vector<LinearExpr> tardiness_expressions(num_tasks);
LinearExpr objective;
for (int i = 0; i < num_tasks; ++i) {
task_starts[i] = cp_model.NewIntVar(Domain(0, horizon - durations[i]));
task_intervals[i] =
cp_model.NewFixedSizeIntervalVar(task_starts[i], durations[i]);
if (due_dates[i] == 0) {
tardiness_expressions[i] = task_starts[i] + durations[i];
} else {
tardiness_expressions[i] = cp_model.NewIntVar(
2021-04-02 14:58:16 +02:00
Domain(0, std::max<int64_t>(0, horizon - due_dates[i])));
// tardiness_vars >= end - due_date
cp_model.AddGreaterOrEqual(tardiness_expressions[i],
task_starts[i] + durations[i] - due_dates[i]);
}
objective += weights[i] * tardiness_expressions[i];
}
// Decision heuristic. Note that we don't instantiate all the variables. As a
// consequence, in the values returned by the solution observer for the
// non-fully instantiated variable will be the variable lower bounds after
// propagation.
cp_model.AddDecisionStrategy(task_starts,
DecisionStrategyProto::CHOOSE_HIGHEST_MAX,
DecisionStrategyProto::SELECT_MAX_VALUE);
cp_model.AddNoOverlap(task_intervals);
2016-09-12 13:42:16 +02:00
// TODO(user): We can't set an objective upper bound with the current cp_model
// interface, so we can't use heuristic or absl::GetFlag(FLAGS_upper_bound)
// here. The best is probably to provide a "solution hint" instead.
//
2016-09-12 13:42:16 +02:00
// Set a known upper bound (or use the flag). This has a bigger impact than
// can be expected at first:
// - It avoid spending time finding not so good solution.
// - More importantly, because we lazily create the associated Boolean
// variables, we end up creating less of them, and that speed up the search
// for the optimal and the proof of optimality.
//
// Note however than for big problem, this will drastically augment the time
// to get a first feasible solution (but then the heuristic gave one to us).
cp_model.Minimize(objective);
// Optional preprocessing: add precedences that don't change the optimal
// solution value.
//
// Proof: in any schedule, if such precedence between task A and B is not
// satisfied, then it is always better (or the same) to swap A and B. This is
// because the tasks between A and B will be completed earlier (because the
// duration of A is smaller), and the cost of the swap itself is also smaller.
int num_added_precedences = 0;
for (int i = 0; i < num_tasks; ++i) {
for (int j = 0; j < num_tasks; ++j) {
2020-10-22 23:36:58 +02:00
if (i == j) continue;
if (due_dates[i] <= due_dates[j] && durations[i] <= durations[j] &&
weights[i] >= weights[j]) {
2016-09-12 13:42:16 +02:00
// If two jobs have exactly the same specs, we don't add both
// precedences!
if (due_dates[i] == due_dates[j] && durations[i] == durations[j] &&
weights[i] == weights[j] && i > j) {
continue;
}
++num_added_precedences;
cp_model.AddLessOrEqual(task_starts[i] + durations[i], task_starts[j]);
}
}
}
LOG(INFO) << "Added " << num_added_precedences
<< " precedences that will not affect the optimal solution value.";
// Solve it.
2017-03-28 16:13:30 +02:00
//
// Note that we only fully instantiate the start/end and only look at the
2017-03-28 16:13:30 +02:00
// lower bound for the objective and the tardiness variables.
Model model;
2021-06-29 17:20:38 +02:00
model.Add(NewSatParameters(absl::GetFlag(FLAGS_params)));
model.GetOrCreate<SatParameters>()->set_log_search_progress(true);
model.Add(NewFeasibleSolutionObserver([&, due_dates, durations,
weights](const CpSolverResponse& r) {
2018-06-08 13:36:19 +02:00
// Note that we compute the "real" cost here and do not use the tardiness
// variables. This is because in the core based approach, the tardiness
// variable might be fixed before the end date, and we just have a >=
// relation.
2018-06-08 13:36:19 +02:00
2021-04-02 14:58:16 +02:00
int64_t objective = 0;
for (int i = 0; i < num_tasks; ++i) {
const int64_t end =
SolutionIntegerValue(r, task_starts[i]) + durations[i];
objective += weights[i] * std::max<int64_t>(0, end - due_dates[i]);
}
LOG(INFO) << "Cost " << objective;
// Print the current solution.
std::vector<int> sorted_tasks(num_tasks);
std::iota(sorted_tasks.begin(), sorted_tasks.end(), 0);
std::sort(sorted_tasks.begin(), sorted_tasks.end(), [&](int v1, int v2) {
2021-10-13 17:12:18 +02:00
return SolutionIntegerValue(r, task_starts[v1]) <
SolutionIntegerValue(r, task_starts[v2]);
});
std::string solution = "0";
int end = 0;
for (const int i : sorted_tasks) {
2021-04-23 14:55:27 +02:00
const int64_t cost =
weights[i] * SolutionIntegerValue(r, tardiness_expressions[i]);
absl::StrAppend(&solution, "| #", i, " ");
if (cost > 0) {
// Display the cost in red.
absl::StrAppend(&solution, "\033[1;31m(+", cost, ") \033[0m");
}
absl::StrAppend(&solution, "|",
SolutionIntegerValue(r, task_starts[i]) + durations[i]);
end += durations[i];
}
LOG(INFO) << "solution: " << solution;
}));
// Solve.
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
}
void ParseAndSolve() {
std::vector<int> numbers;
std::vector<std::string> entries;
2020-10-29 14:25:39 +01:00
for (const std::string& line : FileLines(absl::GetFlag(FLAGS_input))) {
entries = absl::StrSplit(line, ' ', absl::SkipEmpty());
2020-10-29 14:25:39 +01:00
for (const std::string& entry : entries) {
2018-11-28 11:32:45 +01:00
numbers.push_back(0);
CHECK(absl::SimpleAtoi(entry, &numbers.back()));
}
}
const int instance_size = absl::GetFlag(FLAGS_size) * 3;
LOG(INFO) << numbers.size() << " numbers in '" << absl::GetFlag(FLAGS_input)
<< "'.";
LOG(INFO) << "This correspond to " << numbers.size() / instance_size
<< " instances of size " << absl::GetFlag(FLAGS_size);
LOG(INFO) << "Loading instance #" << absl::GetFlag(FLAGS_n);
CHECK_GE(absl::GetFlag(FLAGS_n), 0);
CHECK_LE(absl::GetFlag(FLAGS_n) * instance_size, numbers.size());
// The order in a wt file is: duration, tardiness weights and then due_dates.
int index = (absl::GetFlag(FLAGS_n) - 1) * instance_size;
2021-04-02 14:58:16 +02:00
std::vector<int64_t> durations;
for (int j = 0; j < absl::GetFlag(FLAGS_size); ++j)
durations.push_back(numbers[index++]);
2021-04-02 14:58:16 +02:00
std::vector<int64_t> weights;
for (int j = 0; j < absl::GetFlag(FLAGS_size); ++j)
weights.push_back(numbers[index++]);
2021-04-02 14:58:16 +02:00
std::vector<int64_t> due_dates;
for (int j = 0; j < absl::GetFlag(FLAGS_size); ++j)
due_dates.push_back(numbers[index++]);
Solve(durations, due_dates, weights);
}
2020-10-22 23:36:58 +02:00
} // namespace sat
} // namespace operations_research
2020-10-29 14:25:39 +01:00
int main(int argc, char** argv) {
2023-02-17 15:17:12 +01:00
absl::SetFlag(&FLAGS_stderrthreshold, 0);
InitGoogle(argv[0], &argc, &argv, true);
if (absl::GetFlag(FLAGS_input).empty()) {
LOG(FATAL) << "Please supply a data file with --input=";
}
operations_research::sat::ParseAndSolve();
return EXIT_SUCCESS;
}