[bazel] Update examples
This commit is contained in:
committed by
Corentin Le Molgat
parent
1533e23537
commit
5e06a6c7e2
@@ -34,3 +34,12 @@ compile_pip_requirements(
|
||||
requirements_in = "notebook_requirements.in",
|
||||
requirements_txt = "notebook_requirements.txt",
|
||||
)
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "test_runner_template",
|
||||
testonly = 1,
|
||||
srcs = ["test_runner_template.sh"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
105
bazel/run_binary_test.bzl
Normal file
105
bazel/run_binary_test.bzl
Normal file
@@ -0,0 +1,105 @@
|
||||
# Copyright 2010-2025 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.
|
||||
|
||||
"""run_binary_test will run a xx_binary as test with the given args."""
|
||||
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
load("@rules_shell//shell:sh_test.bzl", "sh_test")
|
||||
|
||||
def parse_label(label):
|
||||
"""Parse a label into (package, name).
|
||||
|
||||
Args:
|
||||
label: string in relative or absolute form.
|
||||
|
||||
Returns:
|
||||
Pair of strings: package, relative_name
|
||||
|
||||
Raises:
|
||||
ValueError for malformed label (does not do an exhaustive validation)
|
||||
"""
|
||||
if label.startswith("//"):
|
||||
label = label[2:] # drop the leading //
|
||||
colon_split = label.split(":")
|
||||
if len(colon_split) == 1: # no ":" in label
|
||||
pkg = label
|
||||
_, _, target = label.rpartition("/")
|
||||
else:
|
||||
pkg, target = colon_split # fails if len(colon_split) != 2
|
||||
else:
|
||||
colon_split = label.split(":")
|
||||
if len(colon_split) == 1: # no ":" in label
|
||||
pkg, target = native.package_name(), label
|
||||
else:
|
||||
pkg2, target = colon_split # fails if len(colon_split) != 2
|
||||
pkg = native.package_name() + ("/" + pkg2 if pkg2 else "")
|
||||
return pkg, target
|
||||
|
||||
def get_check_contains_code(line):
|
||||
return """
|
||||
if ! grep -qF "{line}" "${{LOGFILE}}"; then
|
||||
cat "${{LOGFILE}}"
|
||||
echo "---------------------------------------------------------------"
|
||||
echo "FAILURE: string '{line}' was not found in the output."
|
||||
echo "---------------------------------------------------------------"
|
||||
exit 1
|
||||
fi
|
||||
""".format(line = line)
|
||||
|
||||
def run_binary_test(
|
||||
name,
|
||||
binary,
|
||||
template = "//bazel:test_runner_template",
|
||||
args = [],
|
||||
data = [],
|
||||
grep_lines = [],
|
||||
**kwargs):
|
||||
"""Create a sh_test to run the given binary as test.
|
||||
|
||||
Args:
|
||||
name: name of the test target.
|
||||
binary: name of the binary target to run.
|
||||
template: template file for executing the binary target.
|
||||
args: args to use to run the binary.
|
||||
data: data files required by this test.
|
||||
grep_lines: lines to grep for in the log file.
|
||||
**kwargs: other attributes that are applicable to tests, size, tags, etc.
|
||||
|
||||
"""
|
||||
shell_script = name + ".sh"
|
||||
|
||||
# Get the path to the binary we want to run.
|
||||
binary_pkg, binary_name = parse_label(binary)
|
||||
binary_path = "/".join([binary_pkg, binary_name])
|
||||
|
||||
# We would like to include args in the generated shell script, so that "blaze-bin/.../test" can
|
||||
# be run manually. Unfortunately `expand_template` does not resolve $(location) and other Make
|
||||
# variables so we only pass them in `sh_test` below.
|
||||
expand_template(
|
||||
name = name + "_gensh",
|
||||
template = template,
|
||||
out = shell_script,
|
||||
testonly = 1,
|
||||
substitutions = {
|
||||
"{{binary_path}}": binary_path,
|
||||
"{{post_script}}": "\n".join([get_check_contains_code(line) for line in grep_lines]),
|
||||
},
|
||||
)
|
||||
sh_test(
|
||||
name = name,
|
||||
testonly = 1,
|
||||
srcs = [shell_script],
|
||||
data = data + [binary],
|
||||
args = args,
|
||||
**kwargs
|
||||
)
|
||||
94
bazel/run_binary_test.bzl.orig
Normal file
94
bazel/run_binary_test.bzl.orig
Normal file
@@ -0,0 +1,94 @@
|
||||
# Copyright 2010-2025 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.
|
||||
|
||||
"""run_binary_test will run a xx_binary as test with the given args.
|
||||
"""
|
||||
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
load("@rules_shell//shell:sh_test.bzl", "sh_test")
|
||||
|
||||
def parse_label(label):
|
||||
"""Parse a label into (package, name).
|
||||
|
||||
Args:
|
||||
label: string in relative or absolute form.
|
||||
|
||||
Returns:
|
||||
Pair of strings: package, relative_name
|
||||
|
||||
Raises:
|
||||
ValueError for malformed label (does not do an exhaustive validation)
|
||||
"""
|
||||
if label.startswith("//"):
|
||||
label = label[2:] # drop the leading //
|
||||
colon_split = label.split(":")
|
||||
if len(colon_split) == 1: # no ":" in label
|
||||
pkg = label
|
||||
_, _, target = label.rpartition("/")
|
||||
else:
|
||||
pkg, target = colon_split # fails if len(colon_split) != 2
|
||||
else:
|
||||
colon_split = label.split(":")
|
||||
if len(colon_split) == 1: # no ":" in label
|
||||
pkg, target = native.package_name(), label
|
||||
else:
|
||||
pkg2, target = colon_split # fails if len(colon_split) != 2
|
||||
pkg = native.package_name() + ("/" + pkg2 if pkg2 else "")
|
||||
return pkg, target
|
||||
|
||||
def run_binary_test(
|
||||
name,
|
||||
binary,
|
||||
template = "//bazel:test_runner_template",
|
||||
args = [],
|
||||
data = [],
|
||||
**kwargs):
|
||||
"""Create a sh_test to run the given binary as test.
|
||||
|
||||
Args:
|
||||
name: name of the test target.
|
||||
binary: name of the binary target to run.
|
||||
template: template file for executing the binary target.
|
||||
args: args to use to run the binary.
|
||||
data: data files required by this test.
|
||||
**kwargs: other attributes that are applicable to tests, size, tags, etc.
|
||||
|
||||
"""
|
||||
shell_script = name + ".sh"
|
||||
|
||||
# Get the path to the binary we want to run.
|
||||
binary_pkg, binary_name = parse_label(binary)
|
||||
binary_path = "/".join([binary_pkg, binary_name])
|
||||
|
||||
# We would like to Include args in the generated shell script, so the "blaze-bin/.../test" can
|
||||
# be run manually. Unfortunately expand_template does not resolve $(location) and other Make
|
||||
# variables so we only pass them in `sh_test` below.
|
||||
expand_template(
|
||||
name = name + "_gensh",
|
||||
template = template,
|
||||
out = shell_script,
|
||||
testonly = 1,
|
||||
substitutions = {
|
||||
"{package_name}": native.package_name(),
|
||||
"{target}": name,
|
||||
"{binary_path}": binary_path,
|
||||
},
|
||||
)
|
||||
sh_test(
|
||||
name = name,
|
||||
testonly = 1,
|
||||
srcs = [shell_script],
|
||||
data = data + [binary],
|
||||
args = args,
|
||||
**kwargs
|
||||
)
|
||||
19
bazel/test_runner_template.sh
Normal file
19
bazel/test_runner_template.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2010-2025 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.
|
||||
|
||||
set -e # Fail on error
|
||||
|
||||
declare -r LOGFILE="${TEST_TMPDIR}/log.txt"
|
||||
{{binary_path}} "$@" > "${LOGFILE}"
|
||||
{{post_script}}
|
||||
File diff suppressed because it is too large
Load Diff
1029
examples/cpp/BUILD.bazel.orig
Normal file
1029
examples/cpp/BUILD.bazel.orig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
||||
// Copyright 2010-2025 Google LLC
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/log/globals.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/commandlineflags.h"
|
||||
#include "ortools/base/file.h"
|
||||
#include "ortools/base/helpers.h"
|
||||
#include "ortools/base/init_google.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/timer.h"
|
||||
#include "ortools/packing/arc_flow_builder.h"
|
||||
#include "ortools/packing/arc_flow_solver.h"
|
||||
#include "ortools/packing/vector_bin_packing.pb.h"
|
||||
#include "ortools/packing/vector_bin_packing_parser.h"
|
||||
|
||||
ABSL_FLAG(std::string, input, "", "Vector Bin Packing (.vpb) data file name.");
|
||||
ABSL_FLAG(std::string, params, "num_workers:16,max_time_in_seconds:10",
|
||||
"Parameters in solver specific text format.");
|
||||
ABSL_FLAG(std::string, solver, "sat", "Solver to use: sat, scip");
|
||||
ABSL_FLAG(double, time_limit, 900.0, "Time limit in seconds");
|
||||
ABSL_FLAG(int, threads, 1, "Number of threads");
|
||||
ABSL_FLAG(bool, display_proto, false, "Print the input protobuf");
|
||||
ABSL_FLAG(int, max_bins, -1,
|
||||
"Maximum number of bins: default = -1 meaning no limits");
|
||||
|
||||
namespace operations_research {
|
||||
void ParseAndSolve(const std::string& filename, absl::string_view solver,
|
||||
const std::string& params) {
|
||||
std::string problem_name = filename;
|
||||
const size_t found = problem_name.find_last_of("/\\");
|
||||
if (found != std::string::npos) {
|
||||
problem_name = problem_name.substr(found + 1);
|
||||
}
|
||||
if (absl::EndsWith(problem_name, ".vbp")) {
|
||||
// TODO(user): Move naming code to parser.
|
||||
problem_name.resize(problem_name.size() - 4);
|
||||
}
|
||||
|
||||
packing::vbp::VectorBinPackingProblem data;
|
||||
|
||||
packing::vbp::VbpParser parser;
|
||||
if (!parser.ParseFile(filename)) {
|
||||
LOG(FATAL) << "Cannot read " << filename;
|
||||
}
|
||||
data = parser.problem();
|
||||
data.set_name(problem_name);
|
||||
|
||||
if (data.max_bins() != 0) {
|
||||
LOG(WARNING)
|
||||
<< "Ignoring max_bins value. The feasibility problem is not supported.";
|
||||
}
|
||||
|
||||
LOG(INFO) << "Solving vector packing problem '" << data.name() << "' with "
|
||||
<< data.item_size() << " item types, and "
|
||||
<< data.resource_capacity_size() << " dimensions.";
|
||||
if (absl::GetFlag(FLAGS_display_proto)) {
|
||||
LOG(INFO) << data;
|
||||
}
|
||||
|
||||
// Build optimization model.
|
||||
MPSolver::OptimizationProblemType solver_type;
|
||||
MPSolver::ParseSolverType(solver, &solver_type);
|
||||
packing::vbp::VectorBinPackingSolution solution =
|
||||
packing::SolveVectorBinPackingWithArcFlow(
|
||||
data, solver_type, params, absl::GetFlag(FLAGS_time_limit),
|
||||
absl::GetFlag(FLAGS_threads), absl::GetFlag(FLAGS_max_bins));
|
||||
if (!solution.bins().empty()) {
|
||||
for (int b = 0; b < solution.bins_size(); ++b) {
|
||||
LOG(INFO) << "Bin " << b;
|
||||
const packing::vbp::VectorBinPackingOneBinInSolution& bin =
|
||||
solution.bins(b);
|
||||
for (int i = 0; i < bin.item_indices_size(); ++i) {
|
||||
LOG(INFO) << " - item: " << bin.item_indices(i)
|
||||
<< ", copies: " << bin.item_copies(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
|
||||
InitGoogle(argv[0], &argc, &argv, true);
|
||||
if (absl::GetFlag(FLAGS_input).empty()) {
|
||||
LOG(FATAL) << "Please supply a data file with --input=";
|
||||
}
|
||||
|
||||
operations_research::ParseAndSolve(absl::GetFlag(FLAGS_input),
|
||||
absl::GetFlag(FLAGS_solver),
|
||||
absl::GetFlag(FLAGS_params));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -11,103 +11,622 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# BUILD file to run python examples.
|
||||
load("@pip_deps//:requirements.bzl", "requirement")
|
||||
load("@rules_python//python:py_binary.bzl", "py_binary")
|
||||
load("//bazel:run_binary_test.bzl", "run_binary_test")
|
||||
|
||||
load(":code_samples.bzl", "code_sample_compile_py", "code_sample_py", "code_sample_test_arg_py")
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
code_sample_compile_py("arc_flow_cutting_stock_sat")
|
||||
py_binary(
|
||||
name = "arc_flow_cutting_stock_sat_py3",
|
||||
srcs = ["arc_flow_cutting_stock_sat.py"],
|
||||
main = "arc_flow_cutting_stock_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
requirement("numpy"),
|
||||
"//ortools/linear_solver/python:model_builder",
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("assignment_with_constraints_sat")
|
||||
py_binary(
|
||||
name = "assignment_with_constraints_sat_py3",
|
||||
srcs = ["assignment_with_constraints_sat.py"],
|
||||
main = "assignment_with_constraints_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("balance_group_sat")
|
||||
run_binary_test(
|
||||
name = "assignment_with_constraints_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":assignment_with_constraints_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("bus_driver_scheduling_sat")
|
||||
py_binary(
|
||||
name = "balance_group_sat_py3",
|
||||
srcs = ["balance_group_sat.py"],
|
||||
main = "balance_group_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("car_sequencing_optimization_sat")
|
||||
run_binary_test(
|
||||
name = "balance_group_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":balance_group_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("chemical_balance_sat")
|
||||
py_binary(
|
||||
name = "bus_driver_scheduling_sat_py3",
|
||||
srcs = ["bus_driver_scheduling_sat.py"],
|
||||
main = "bus_driver_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("clustering_sat")
|
||||
run_binary_test(
|
||||
name = "bus_driver_scheduling_sat_py_test",
|
||||
size = "medium",
|
||||
args = ["--params=max_time_in_seconds:40"],
|
||||
binary = ":bus_driver_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("cover_rectangle_sat")
|
||||
py_binary(
|
||||
name = "car_sequencing_optimization_sat_py3",
|
||||
srcs = ["car_sequencing_optimization_sat.py"],
|
||||
main = "car_sequencing_optimization_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("flexible_job_shop_sat")
|
||||
run_binary_test(
|
||||
name = "car_sequencing_optimization_sat_py_test",
|
||||
size = "small",
|
||||
binary = ":car_sequencing_optimization_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("gate_scheduling_sat")
|
||||
py_binary(
|
||||
name = "chemical_balance_sat_py3",
|
||||
srcs = ["chemical_balance_sat.py"],
|
||||
main = "chemical_balance_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("golomb_sat")
|
||||
run_binary_test(
|
||||
name = "chemical_balance_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":chemical_balance_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("hidato_sat")
|
||||
py_binary(
|
||||
name = "clustering_sat_py3",
|
||||
srcs = ["clustering_sat.py"],
|
||||
main = "clustering_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("jobshop_ft06_distance_sat")
|
||||
run_binary_test(
|
||||
name = "clustering_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":clustering_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("jobshop_ft06_sat")
|
||||
py_binary(
|
||||
name = "cover_rectangle_sat_py3",
|
||||
srcs = ["cover_rectangle_sat.py"],
|
||||
main = "cover_rectangle_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("jobshop_with_maintenance_sat")
|
||||
run_binary_test(
|
||||
name = "cover_rectangle_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":cover_rectangle_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("knapsack_2d_sat")
|
||||
py_binary(
|
||||
name = "flexible_job_shop_sat_py3",
|
||||
srcs = ["flexible_job_shop_sat.py"],
|
||||
main = "flexible_job_shop_sat.py",
|
||||
deps = ["//ortools/sat/python:cp_model"],
|
||||
)
|
||||
|
||||
code_sample_compile_py("line_balancing_sat")
|
||||
run_binary_test(
|
||||
name = "flexible_job_shop_sat_py_test",
|
||||
binary = ":flexible_job_shop_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_test_arg_py(
|
||||
name = "line_balancing_sat",
|
||||
args = ["--input $(rootpath //examples/python/testdata:salbp_20_1.alb)"],
|
||||
py_binary(
|
||||
name = "gate_scheduling_sat_py3",
|
||||
srcs = ["gate_scheduling_sat.py"],
|
||||
main = "gate_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/colab:visualization",
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "gate_scheduling_sat_py_test",
|
||||
binary = ":gate_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "golomb_sat_py3",
|
||||
srcs = ["golomb_sat.py"],
|
||||
main = "golomb_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "golomb_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":golomb_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "hidato_sat_py3",
|
||||
srcs = ["hidato_sat.py"],
|
||||
main = "hidato_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/colab:visualization",
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "hidato_sat_py_test",
|
||||
binary = ":hidato_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "jobshop_ft06_distance_sat_py3",
|
||||
srcs = ["jobshop_ft06_distance_sat.py"],
|
||||
main = "jobshop_ft06_distance_sat.py",
|
||||
deps = ["//ortools/sat/python:cp_model"],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "jobshop_ft06_distance_sat_py_test",
|
||||
binary = ":jobshop_ft06_distance_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "jobshop_ft06_sat_py3",
|
||||
srcs = ["jobshop_ft06_sat.py"],
|
||||
main = "jobshop_ft06_sat.py",
|
||||
deps = [
|
||||
"//ortools/sat/colab:visualization",
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "jobshop_ft06_sat_py_test",
|
||||
binary = ":jobshop_ft06_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "jobshop_with_maintenance_sat_py3",
|
||||
srcs = ["jobshop_with_maintenance_sat.py"],
|
||||
main = "jobshop_with_maintenance_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "jobshop_with_maintenance_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":jobshop_with_maintenance_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "knapsack_2d_sat_py3",
|
||||
srcs = ["knapsack_2d_sat.py"],
|
||||
main = "knapsack_2d_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
requirement("numpy"),
|
||||
requirement("pandas"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "knapsack_2d_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":knapsack_2d_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "line_balancing_sat_py3",
|
||||
srcs = ["line_balancing_sat.py"],
|
||||
main = "line_balancing_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "line_balancing_sat_salbp_20_1_py_test",
|
||||
args = ["--input=$(rootpath //examples/python/testdata:salbp_20_1.alb)"],
|
||||
binary = ":line_balancing_sat_py3",
|
||||
data = ["//examples/python/testdata:salbp_20_1.alb"],
|
||||
suffix = "salbp_20_1",
|
||||
grep_lines = ["objective: 3"],
|
||||
)
|
||||
|
||||
code_sample_py("maximize_combinations_sat")
|
||||
|
||||
code_sample_py("maze_escape_sat")
|
||||
|
||||
code_sample_py("no_wait_baking_scheduling_sat")
|
||||
|
||||
code_sample_py("pell_equation_sat")
|
||||
|
||||
code_sample_py("pentominoes_sat")
|
||||
|
||||
code_sample_py("prize_collecting_tsp_sat")
|
||||
|
||||
code_sample_py("prize_collecting_vrp_sat")
|
||||
|
||||
code_sample_py("qubo_sat")
|
||||
|
||||
code_sample_compile_py("rcpsp_sat")
|
||||
|
||||
code_sample_test_arg_py(
|
||||
name = "rcpsp_sat",
|
||||
args = ["--input $(rootpath //ortools/scheduling/testdata:j301_1.sm)"],
|
||||
data = ["//ortools/scheduling/testdata:j301_1.sm"],
|
||||
suffix = "j301_1",
|
||||
py_binary(
|
||||
name = "maximize_combinations_sat_py3",
|
||||
srcs = ["maximize_combinations_sat.py"],
|
||||
main = "maximize_combinations_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_test_arg_py(
|
||||
name = "rcpsp_sat",
|
||||
args = ["--input $(rootpath //ortools/scheduling/testdata:c1510_1.mm.txt)"],
|
||||
run_binary_test(
|
||||
name = "maximize_combinations_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":maximize_combinations_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "maze_escape_sat_py3",
|
||||
srcs = ["maze_escape_sat.py"],
|
||||
main = "maze_escape_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "maze_escape_sat_py_test",
|
||||
binary = ":maze_escape_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "music_playlist_sat_py3",
|
||||
srcs = ["music_playlist_sat.py"],
|
||||
main = "music_playlist_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "music_playlist_sat_py_test",
|
||||
binary = ":music_playlist_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "no_wait_baking_scheduling_sat_py3",
|
||||
srcs = ["no_wait_baking_scheduling_sat.py"],
|
||||
main = "no_wait_baking_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "no_wait_baking_scheduling_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":no_wait_baking_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "pell_equation_sat_py3",
|
||||
srcs = ["pell_equation_sat.py"],
|
||||
main = "pell_equation_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "pell_equation_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":pell_equation_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "pentominoes_sat_py3",
|
||||
srcs = ["pentominoes_sat.py"],
|
||||
main = "pentominoes_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "pentominoes_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":pentominoes_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "prize_collecting_tsp_sat_py3",
|
||||
srcs = ["prize_collecting_tsp_sat.py"],
|
||||
main = "prize_collecting_tsp_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "prize_collecting_tsp_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":prize_collecting_tsp_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "prize_collecting_vrp_sat_py3",
|
||||
srcs = ["prize_collecting_vrp_sat.py"],
|
||||
main = "prize_collecting_vrp_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "prize_collecting_vrp_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":prize_collecting_vrp_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "qubo_sat_py3",
|
||||
srcs = ["qubo_sat.py"],
|
||||
main = "qubo_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "qubo_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":qubo_sat_py3",
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "rcpsp_sat_c1510_1_py_test",
|
||||
args = ["--input=$(rootpath //ortools/scheduling/testdata:c1510_1.mm.txt)"],
|
||||
binary = ":rcpsp_sat_py3",
|
||||
data = ["//ortools/scheduling/testdata:c1510_1.mm.txt"],
|
||||
suffix = "c1510_1",
|
||||
grep_lines = ["objective: 21"],
|
||||
)
|
||||
|
||||
code_sample_py("shift_scheduling_sat")
|
||||
run_binary_test(
|
||||
name = "rcpsp_sat_j301_1_py_test",
|
||||
args = ["--input=$(rootpath //ortools/scheduling/testdata:j301_1.sm)"],
|
||||
binary = ":rcpsp_sat_py3",
|
||||
data = ["//ortools/scheduling/testdata:j301_1.sm"],
|
||||
grep_lines = ["objective: 43"],
|
||||
)
|
||||
|
||||
code_sample_py("single_machine_scheduling_with_setup_release_due_dates_sat")
|
||||
py_binary(
|
||||
name = "rcpsp_sat_py3",
|
||||
srcs = ["rcpsp_sat.py"],
|
||||
main = "rcpsp_sat.py",
|
||||
deps = [
|
||||
"//ortools/scheduling:rcpsp_py_pb2",
|
||||
"//ortools/scheduling/python:rcpsp",
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("spread_robots_sat")
|
||||
py_binary(
|
||||
name = "shift_scheduling_sat_py3",
|
||||
srcs = ["shift_scheduling_sat.py"],
|
||||
main = "shift_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("steel_mill_slab_sat")
|
||||
run_binary_test(
|
||||
name = "shift_scheduling_sat_py_test",
|
||||
args = ["--params=max_time_in_seconds:10"],
|
||||
binary = ":shift_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("sudoku_sat")
|
||||
py_binary(
|
||||
name = "single_machine_scheduling_with_setup_release_due_dates_sat_py3",
|
||||
srcs = ["single_machine_scheduling_with_setup_release_due_dates_sat.py"],
|
||||
main = "single_machine_scheduling_with_setup_release_due_dates_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("task_allocation_sat")
|
||||
run_binary_test(
|
||||
name = "single_machine_scheduling_with_setup_release_due_dates_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":single_machine_scheduling_with_setup_release_due_dates_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("tasks_and_workers_assignment_sat")
|
||||
py_binary(
|
||||
name = "spread_robots_sat_py3",
|
||||
srcs = ["spread_robots_sat.py"],
|
||||
main = "spread_robots_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("test_scheduling_sat")
|
||||
run_binary_test(
|
||||
name = "spread_robots_sat_py_test",
|
||||
binary = ":spread_robots_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("tsp_sat")
|
||||
py_binary(
|
||||
name = "steel_mill_slab_sat_py3",
|
||||
srcs = ["steel_mill_slab_sat.py"],
|
||||
main = "steel_mill_slab_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
code_sample_py("vendor_scheduling_sat")
|
||||
run_binary_test(
|
||||
name = "steel_mill_slab_sat_py_test",
|
||||
binary = ":steel_mill_slab_sat_py3",
|
||||
)
|
||||
|
||||
code_sample_py("wedding_optimal_chart_sat")
|
||||
py_binary(
|
||||
name = "sudoku_sat_py3",
|
||||
srcs = ["sudoku_sat.py"],
|
||||
main = "sudoku_sat.py",
|
||||
deps = ["//ortools/sat/python:cp_model"],
|
||||
)
|
||||
|
||||
code_sample_py("zebra_sat")
|
||||
run_binary_test(
|
||||
name = "sudoku_sat_py_test",
|
||||
binary = ":sudoku_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "task_allocation_sat_py3",
|
||||
srcs = ["task_allocation_sat.py"],
|
||||
main = "task_allocation_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "task_allocation_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":task_allocation_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "tasks_and_workers_assignment_sat_py3",
|
||||
srcs = ["tasks_and_workers_assignment_sat.py"],
|
||||
main = "tasks_and_workers_assignment_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "tasks_and_workers_assignment_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":tasks_and_workers_assignment_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "test_scheduling_sat_py3",
|
||||
srcs = ["test_scheduling_sat.py"],
|
||||
main = "test_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
requirement("pandas"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "test_scheduling_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":test_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "tsp_sat_py3",
|
||||
srcs = ["tsp_sat.py"],
|
||||
main = "tsp_sat.py",
|
||||
deps = ["//ortools/sat/python:cp_model"],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "tsp_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":tsp_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "vendor_scheduling_sat_py3",
|
||||
srcs = ["vendor_scheduling_sat.py"],
|
||||
main = "vendor_scheduling_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "vendor_scheduling_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":vendor_scheduling_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "wedding_optimal_chart_sat_py3",
|
||||
srcs = ["wedding_optimal_chart_sat.py"],
|
||||
main = "wedding_optimal_chart_sat.py",
|
||||
deps = [
|
||||
requirement("absl-py"),
|
||||
"//ortools/sat/python:cp_model",
|
||||
],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "wedding_optimal_chart_sat_py_test",
|
||||
size = "medium",
|
||||
binary = ":wedding_optimal_chart_sat_py3",
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "zebra_sat_py3",
|
||||
srcs = ["zebra_sat.py"],
|
||||
main = "zebra_sat.py",
|
||||
deps = ["//ortools/sat/python:cp_model"],
|
||||
)
|
||||
|
||||
run_binary_test(
|
||||
name = "zebra_sat_py_test",
|
||||
binary = ":zebra_sat_py3",
|
||||
)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright 2010-2025 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.
|
||||
|
||||
"""Helper macro to compile and test code samples."""
|
||||
|
||||
load("@pip_deps//:requirements.bzl", "requirement")
|
||||
load("@rules_python//python:py_binary.bzl", "py_binary")
|
||||
load("@rules_python//python:py_test.bzl", "py_test")
|
||||
|
||||
PYTHON_DEPS = [
|
||||
"//ortools/init/python:init",
|
||||
"//ortools/linear_solver/python:model_builder",
|
||||
"//ortools/sat/python:cp_model",
|
||||
"//ortools/sat/colab:visualization",
|
||||
"//ortools/scheduling:rcpsp_py_proto",
|
||||
"//ortools/scheduling/python:rcpsp",
|
||||
requirement("absl-py"),
|
||||
requirement("numpy"),
|
||||
requirement("pandas"),
|
||||
requirement("protobuf"),
|
||||
requirement("python_dateutil"),
|
||||
requirement("pytz"),
|
||||
requirement("six"),
|
||||
]
|
||||
|
||||
def code_sample_compile_py(name):
|
||||
py_binary(
|
||||
name = name + "_py3",
|
||||
srcs = [name + ".py"],
|
||||
main = name + ".py",
|
||||
deps = PYTHON_DEPS,
|
||||
)
|
||||
|
||||
def code_sample_test_py(name):
|
||||
py_test(
|
||||
name = name + "_py_test",
|
||||
size = "medium",
|
||||
srcs = [name + ".py"],
|
||||
main = name + ".py",
|
||||
deps = PYTHON_DEPS,
|
||||
)
|
||||
|
||||
def code_sample_test_arg_py(name, suffix, args, data):
|
||||
py_test(
|
||||
name = name + "_" + suffix + "_py_test",
|
||||
size = "medium",
|
||||
srcs = [name + ".py"],
|
||||
main = name + ".py",
|
||||
data = data,
|
||||
args = args,
|
||||
deps = PYTHON_DEPS,
|
||||
)
|
||||
|
||||
def code_sample_py(name):
|
||||
code_sample_compile_py(name)
|
||||
code_sample_test_py(name)
|
||||
309
examples/python/music_playlist_sat.py
Normal file
309
examples/python/music_playlist_sat.py
Normal file
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2010-2025 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.
|
||||
|
||||
"""Create a balanced music playlist.
|
||||
|
||||
Create a music playlist by selecting tunes from a list of tunes.
|
||||
|
||||
Each tune has a duration in seconds and a music genre (e.g. Rock, Disco, Techno,
|
||||
etc).
|
||||
|
||||
The total playlist duration must be as close as possible to a given total
|
||||
duration. Each tune can appear at most once in the playlist. All existing
|
||||
genres must appear at least once in the playlist. Two consecutive tunes must be
|
||||
of different genres. There is a positive cost to go from a genre to another, and
|
||||
the playlist must minimize this cost overall.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from absl import app
|
||||
from ortools.sat.python import cp_model
|
||||
|
||||
|
||||
def Solve():
|
||||
"""Solves the music playlist problem."""
|
||||
|
||||
# --------------------
|
||||
# 1. Data
|
||||
# --------------------
|
||||
tunes = [
|
||||
("Song 01", 202, "Pop"),
|
||||
("Song 02", 233, "Techno"),
|
||||
("Song 03", 108, "Disco"),
|
||||
("Song 04", 281, "Disco"),
|
||||
("Song 05", 129, "Techno"),
|
||||
("Song 06", 122, "Techno"),
|
||||
("Song 07", 244, "Pop"),
|
||||
("Song 08", 178, "Techno"),
|
||||
("Song 09", 213, "Techno"),
|
||||
("Song 10", 124, "Rock"),
|
||||
("Song 11", 120, "Disco"),
|
||||
("Song 12", 196, "Rock"),
|
||||
("Song 13", 249, "Disco"),
|
||||
("Song 14", 294, "Disco"),
|
||||
("Song 15", 103, "Techno"),
|
||||
("Song 16", 179, "Disco"),
|
||||
("Song 17", 146, "Disco"),
|
||||
("Song 18", 126, "Techno"),
|
||||
("Song 19", 100, "Pop"),
|
||||
("Song 20", 122, "Disco"),
|
||||
("Song 21", 190, "Disco"),
|
||||
("Song 22", 181, "Techno"),
|
||||
("Song 23", 273, "Pop"),
|
||||
("Song 24", 121, "Disco"),
|
||||
("Song 25", 159, "Pop"),
|
||||
("Song 26", 234, "Rock"),
|
||||
("Song 27", 169, "Rock"),
|
||||
("Song 28", 151, "Rock"),
|
||||
("Song 29", 142, "Techno"),
|
||||
("Song 30", 245, "Pop"),
|
||||
("Song 31", 281, "Techno"),
|
||||
("Song 32", 154, "Rock"),
|
||||
("Song 33", 148, "Disco"),
|
||||
("Song 34", 120, "Pop"),
|
||||
("Song 35", 163, "Disco"),
|
||||
("Song 36", 158, "Pop"),
|
||||
("Song 37", 235, "Rock"),
|
||||
("Song 38", 106, "Techno"),
|
||||
("Song 39", 117, "Disco"),
|
||||
("Song 40", 110, "Pop"),
|
||||
("Song 41", 144, "Rock"),
|
||||
("Song 42", 156, "Disco"),
|
||||
("Song 43", 204, "Rock"),
|
||||
("Song 44", 108, "Pop"),
|
||||
("Song 45", 255, "Pop"),
|
||||
("Song 46", 165, "Rock"),
|
||||
("Song 47", 290, "Disco"),
|
||||
("Song 48", 242, "Pop"),
|
||||
("Song 49", 272, "Rock"),
|
||||
("Song 50", 212, "Pop"),
|
||||
]
|
||||
|
||||
# Genre transition costs. A higher cost means a less desirable transition.
|
||||
genre_transition_costs = {
|
||||
"Rock": {"Pop": 3, "Disco": 5, "Techno": 7},
|
||||
"Pop": {"Rock": 3, "Disco": 6, "Techno": 8},
|
||||
"Disco": {"Rock": 5, "Pop": 6, "Techno": 9},
|
||||
"Techno": {"Rock": 7, "Pop": 8, "Disco": 9},
|
||||
}
|
||||
|
||||
num_tunes = len(tunes)
|
||||
all_tunes = range(num_tunes)
|
||||
|
||||
# Playlist target duration in seconds.
|
||||
target_duration = 60 * 60 # 1 hour
|
||||
|
||||
# We use a circuit constraint to model the playlist. In the circuit constraint
|
||||
# graph, each node is a tune, and each arc represents a pair of consecutive
|
||||
# tunes in the playlist. We introduce a dummy node to represent the start and
|
||||
# the end of the playlist.
|
||||
#
|
||||
# The constraint that two consecutive tunes must be of different genres is
|
||||
# encoded by not creating an arc between two tunes that are of the same genre.
|
||||
# This is crucial in the modelisation of this problem: it reduces the number
|
||||
# of variables in the model, and it avoids additional constraints to ensure
|
||||
# two consecutive tunes are of different genres.
|
||||
|
||||
# Dummy node representing the start and end of the playlist.
|
||||
dummy_node = num_tunes
|
||||
|
||||
# `possible_successors[i]` contains the list of nodes that can be reached
|
||||
# after node `i`.
|
||||
possible_successors = {}
|
||||
possible_successors[dummy_node] = [dummy_node]
|
||||
for i in all_tunes:
|
||||
# Any node can be the first tune in the playlist.
|
||||
possible_successors[dummy_node].append(i)
|
||||
# Any node can be the last tune in the playlist.
|
||||
possible_successors[i] = [dummy_node]
|
||||
genre_i = tunes[i][2]
|
||||
for j in all_tunes:
|
||||
genre_j = tunes[j][2]
|
||||
# If `i` and `j` are of different genres, we can go from `i` to `j`.
|
||||
if genre_i != genre_j:
|
||||
possible_successors[i].append(j)
|
||||
|
||||
# --------------------
|
||||
# 2. Model
|
||||
# --------------------
|
||||
model = cp_model.CpModel()
|
||||
|
||||
# --------------------
|
||||
# 3. Decision Variables
|
||||
# --------------------
|
||||
# `literals[i][j]` is true if tune `j` follows tune `i` in the playlist.
|
||||
literals = {}
|
||||
|
||||
# --------------------
|
||||
# 4. Constraints
|
||||
# --------------------
|
||||
|
||||
# 4.1 Two consecutive tunes must be of different genres.
|
||||
# This is encoded in possible_successors, which doesn't contain any arcs
|
||||
# between two tunes that are of the same genre. Now we just have to add a
|
||||
# circuit constraint.
|
||||
|
||||
# `arcs` contains the list of possible arcs in the circuit graph, each arc
|
||||
# is a tuple (i, j, literals[i][j]).
|
||||
arcs = []
|
||||
|
||||
def AddArc(i, j):
|
||||
literals[(i, j)] = model.new_bool_var(f"lit_{i}_{j}")
|
||||
arcs.append((i, j, literals[(i, j)]))
|
||||
|
||||
# Add all possible arcs between different nodes.
|
||||
for i, successors in possible_successors.items():
|
||||
for j in successors:
|
||||
AddArc(i, j)
|
||||
|
||||
# Add self-arcs to let tunes not be in the playlist.
|
||||
for i in all_tunes:
|
||||
AddArc(i, i)
|
||||
|
||||
# Add a circuit constraint with the arcs.
|
||||
model.add_circuit(arcs)
|
||||
|
||||
# 4.2 All genres must appear at least once.
|
||||
# This is encoded by adding a constraint that the sum of all literals for a
|
||||
# given genre is at least 1.
|
||||
|
||||
# `is_active[i]` is true iff tune `i` is in the playlist, i.e. if its self-arc
|
||||
# is not active in the circuit.
|
||||
is_active = {}
|
||||
for i in all_tunes:
|
||||
is_active[i] = literals[(i, i)].Not()
|
||||
|
||||
# `genre_tunes[genre]` contains the list of tunes that are of genre `genre`.
|
||||
genre_tunes = {}
|
||||
for genre in genre_transition_costs:
|
||||
genre_tunes[genre] = []
|
||||
for i in all_tunes:
|
||||
genre_tunes[tunes[i][2]].append(i)
|
||||
|
||||
# For each genre, at least one tune must be active: the sum of all literals
|
||||
# for this genre is at least 1.
|
||||
for t in genre_tunes.values():
|
||||
model.add(sum(is_active[i] for i in t) >= 1)
|
||||
|
||||
# --------------------
|
||||
# 5. Objective
|
||||
# --------------------
|
||||
|
||||
# 5.1. Minimize genre transition costs.
|
||||
|
||||
# Add a total_transition_cost variable representing the sum of all transition
|
||||
# costs in the playlist.
|
||||
max_transition_cost = 0
|
||||
for genre_costs in genre_transition_costs.values():
|
||||
for cost in genre_costs.values():
|
||||
max_transition_cost = max(cost, max_transition_cost)
|
||||
total_transition_cost_upper_bound = (num_tunes - 1) * max_transition_cost
|
||||
total_transition_cost = model.new_int_var(
|
||||
0, total_transition_cost_upper_bound, "total_transition_cost"
|
||||
)
|
||||
|
||||
transition_cost_terms = []
|
||||
for i, successors in possible_successors.items():
|
||||
if i == dummy_node:
|
||||
continue
|
||||
genre_i = tunes[i][2]
|
||||
for j in successors:
|
||||
if j == dummy_node:
|
||||
continue
|
||||
genre_j = tunes[j][2]
|
||||
cost = genre_transition_costs[genre_i][genre_j]
|
||||
transition_cost_terms.append(cost * literals[(i, j)])
|
||||
model.add(total_transition_cost == sum(transition_cost_terms))
|
||||
|
||||
# 5.2. Minimize the deviation between the target duration and the actual total
|
||||
# duration.
|
||||
|
||||
# Add a total_duration variable representing the duration of all active tunes.
|
||||
total_duration_upper_bound = sum([t[1] for t in tunes])
|
||||
total_duration = model.new_int_var(0, total_duration_upper_bound, "total_duration")
|
||||
model.add(total_duration == sum(tunes[i][1] * is_active[i] for i in all_tunes))
|
||||
|
||||
# Minimize the absolute difference from the target duration.
|
||||
deviation = model.new_int_var(0, target_duration, "deviation")
|
||||
model.add_abs_equality(deviation, total_duration - target_duration)
|
||||
|
||||
# 5.3 Combine the objectives.
|
||||
#
|
||||
# You can add a weight to prioritize one over the other.
|
||||
# For example, `model.minimize(10 * total_transition_cost + deviation)`
|
||||
model.minimize(total_transition_cost + deviation)
|
||||
|
||||
# --------------------
|
||||
# 6. Solve
|
||||
# --------------------
|
||||
solver = cp_model.CpSolver()
|
||||
# Set a time limit for the solver
|
||||
solver.parameters.max_time_in_seconds = 30.0
|
||||
status = solver.solve(model)
|
||||
|
||||
# -----------------------
|
||||
# 7. Print the solution
|
||||
# -----------------------
|
||||
if status == cp_model.OPTIMAL:
|
||||
print("Found Optimal Playlist:")
|
||||
elif status == cp_model.FEASIBLE:
|
||||
print("Found Feasible Playlist:")
|
||||
else:
|
||||
print("No solution found.")
|
||||
return
|
||||
|
||||
print(f" Total Transition Cost: {solver.value(total_transition_cost)}")
|
||||
print(
|
||||
f" Playlist Duration: {solver.value(total_duration)} seconds "
|
||||
f"({solver.value(total_duration) / 60:.2f} minutes)"
|
||||
)
|
||||
print(
|
||||
f" Deviation from target duration ({target_duration}):"
|
||||
f" {solver.value(deviation)} seconds"
|
||||
)
|
||||
print("-" * 30)
|
||||
|
||||
# Reconstruct the playlist sequence by starting from the dummy node.
|
||||
playlist = []
|
||||
current_node = dummy_node
|
||||
while True:
|
||||
# Find the successor of the current node.
|
||||
next_node = dummy_node
|
||||
for next_node in possible_successors[current_node]:
|
||||
if solver.value(literals[(current_node, next_node)]):
|
||||
break
|
||||
|
||||
if next_node == dummy_node:
|
||||
break # We've completed the loop back to the start.
|
||||
|
||||
playlist.append(next_node)
|
||||
current_node = next_node
|
||||
|
||||
if not playlist:
|
||||
print("Empty playlist.")
|
||||
else:
|
||||
for i in playlist:
|
||||
(name, duration, genre) = tunes[i]
|
||||
print(f"{i+1}. {name} ({genre}) - {duration}s")
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError("Too many command-line arguments.")
|
||||
Solve()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(main)
|
||||
@@ -12,13 +12,5 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
source gbash.sh || exit
|
||||
source module gbash_unit.sh
|
||||
|
||||
function test::operations_research_examples::cvrptw() {
|
||||
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
|
||||
EXPECT_SUCCEED '${DIR}/cvrptw --vrp_use_deterministic_random_seed'
|
||||
}
|
||||
|
||||
gbash::unit::main "$@"
|
||||
set -x
|
||||
exec {binary_path} "$@"
|
||||
|
||||
Reference in New Issue
Block a user