diff --git a/bazel/python_deps.txt b/bazel/python_deps.txt index 579e326621..b2ec9c44b5 100644 --- a/bazel/python_deps.txt +++ b/bazel/python_deps.txt @@ -1,4 +1,8 @@ absl-py==1.4.0 numpy==1.24.1 +pandas==1.5.3 protobuf==4.21.12 +python_dateutil==2.8.2 +pytz==2022.7.1 scipy==1.10.0 +six==1.16 diff --git a/examples/python/BUILD.bazel b/examples/python/BUILD.bazel new file mode 100644 index 0000000000..e063791d5f --- /dev/null +++ b/examples/python/BUILD.bazel @@ -0,0 +1,90 @@ +# Copyright 2010-2022 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. + +# BUILD file to run python examples. + +load(":code_samples.bzl", "code_sample_compile_py", "code_sample_py") + +# code_sample_py("arc_flow_cutting_stock_sat") # using pywraplp + +code_sample_py("assignment2_sat") + +code_sample_py("assignment_with_constraints_sat") + +code_sample_py("balance_group_sat") + +code_sample_py("bus_driver_scheduling_flow_sat") + +code_sample_py("bus_driver_scheduling_sat") + +code_sample_py("chemical_balance_sat") + +code_sample_py("clustering_sat") + +code_sample_py("cover_rectangle_sat") + +code_sample_py("flexible_job_shop_sat") + +code_sample_py("gate_scheduling_sat") + +code_sample_py("golomb_sat") + +code_sample_py("hidato_sat") + +code_sample_py("jobshop_ft06_distance_sat") + +code_sample_py("jobshop_ft06_sat") + +code_sample_py("jobshop_with_maintenance_sat") + +code_sample_py("knapsack_2d_sat") + +code_sample_compile_py("line_balancing_sat") # no input + +code_sample_py("maze_escape_sat") + +code_sample_py("no_wait_baking_scheduling_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") # no input + +code_sample_py("reallocate_sat") + +code_sample_py("shift_scheduling_sat") + +code_sample_py("single_machine_scheduling_with_setup_release_due_dates_sat") + +# code_sample_py("steel_mill_slab_sat") # using pywraplp + +code_sample_py("sudoku_sat") + +code_sample_py("task_allocation_sat") + +code_sample_py("tasks_and_workers_assignment_sat") + +code_sample_py("tsp_sat") + +code_sample_py("vendor_scheduling_sat") + +code_sample_py("wedding_optimal_chart_sat") + +code_sample_py("weighted_latency_problem_sat") + +code_sample_py("worker_schedule_sat") + +code_sample_py("zebra_sat") diff --git a/examples/python/appointments.py b/examples/python/appointments.py index c11708dc90..4288cb0c6d 100755 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -100,14 +100,13 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections, Each collection may be selected more than one time. Args: - item_collections: a list of item collections. Each item collection is a - list of integers [#item0, ..., #itemN-1], where #itemK is the number - of times item #K appears in the collection, and N is the number of - distinct items. - max_num_collections: an integer, the maximum number of item collections - that may be selected (counting repetitions of the same collection). + item_collections: a list of item collections. Each item collection is a list + of integers [#item0, ..., #itemN-1], where #itemK is the number of times + item #K appears in the collection, and N is the number of distinct items. + max_num_collections: an integer, the maximum number of item collections that + may be selected (counting repetitions of the same collection). ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is - the ideal ratio of item #K in the whole aggregated selection. + the ideal ratio of item #K in the whole aggregated selection. Returns: A pair (objective value, list of pairs (item collection, num_selections)), @@ -184,10 +183,10 @@ def GetOptimalSchedule(demand): """Computes the optimal schedule for the installation input. Args: - demand: a list of "appointment types". Each "appointment type" is - a triple (ideal_ratio_pct, name, duration_minutes), where - ideal_ratio_pct is the ideal percentage (in [0..100.0]) of that - type of appointment among all appointments scheduled. + demand: a list of "appointment types". Each "appointment type" is a triple + (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the + ideal percentage (in [0..100.0]) of that type of appointment among all + appointments scheduled. Returns: The same output type as EnumerateAllKnapsacksWithRepetition. diff --git a/examples/python/code_samples.bzl b/examples/python/code_samples.bzl new file mode 100644 index 0000000000..b722d80fcb --- /dev/null +++ b/examples/python/code_samples.bzl @@ -0,0 +1,56 @@ +# Copyright 2010-2022 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("@ortools_deps//:requirements.bzl", "requirement") + +PYTHON_DEPS = [ + "//ortools/sat/python:cp_model", + "//ortools/sat/python:visualization", + requirement("absl-py"), + requirement("numpy"), + requirement("pandas"), + requirement("protobuf"), + requirement("python_dateutil"), + requirement("pytz"), + requirement("six"), +] + +def code_sample_compile_py(name): + native.py_binary( + name = name + "_py3", + srcs = [name + ".py"], + main = name + ".py", + deps = PYTHON_DEPS, + python_version = "PY3", + srcs_version = "PY3", + ) + +def code_sample_test_py(name): + native.py_test( + name = name + "_py_test", + size = "small", + srcs = [name + ".py"], + main = name + ".py", + data = [ + "//ortools/sat/python:cp_model", + ], + deps = PYTHON_DEPS, + python_version = "PY3", + srcs_version = "PY3", + ) + +def code_sample_py(name): + code_sample_compile_py(name) + code_sample_test_py(name) diff --git a/examples/python/knapsack_2d_sat.py b/examples/python/knapsack_2d_sat.py index eeb38caf2e..a3c5a9cc60 100755 --- a/examples/python/knapsack_2d_sat.py +++ b/examples/python/knapsack_2d_sat.py @@ -30,10 +30,9 @@ from ortools.sat.python import cp_model _OUTPUT_PROTO = flags.DEFINE_string( 'output_proto', '', 'Output file to write the cp_model proto to.') -_PARAMS = flags.DEFINE_string( - 'params', - 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:10.0', - 'Sat solver parameters.') +_PARAMS = flags.DEFINE_string('params', + 'num_search_workers:16,log_search_progress:true', + 'Sat solver parameters.') _MODEL = flags.DEFINE_string('model', 'rotation', '\'duplicate\' or \'rotation\' or \'optional\'') diff --git a/examples/python/line_balancing_sat.py b/examples/python/line_balancing_sat.py index 47d8531a6d..4c6f757757 100755 --- a/examples/python/line_balancing_sat.py +++ b/examples/python/line_balancing_sat.py @@ -33,6 +33,7 @@ from typing import Sequence from absl import app from absl import flags from google.protobuf import text_format + from ortools.sat.python import cp_model _INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.') @@ -259,14 +260,14 @@ def solve_boolean_model(model, hint): for t in all_tasks: model.AddHint(assign[t, hint[t]], 1) - if FLAGS.output_proto: - print(f'Writing proto to {FLAGS.output_proto}') - model.ExportToFile(FLAGS.output_proto) + if _OUTPUT_PROTO.value: + print(f'Writing proto to {_OUTPUT_PROTO.value}') + model.ExportToFile(_OUTPUT_PROTO.value) # Solve model. solver = cp_model.CpSolver() - if FLAGS.params: - text_format.Parse(FLAGS.params, solver.parameters) + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True solver.Solve(model) @@ -321,32 +322,31 @@ def solve_scheduling_model(model, hint): for t in all_tasks: model.AddHint(pods[t], hint[t]) - if FLAGS.output_proto: - print(f'Writing proto to{FLAGS.output_proto}') - model.ExportToFile(FLAGS.output_proto) + if _OUTPUT_PROTO.value: + print(f'Writing proto to{_OUTPUT_PROTO.value}') + model.ExportToFile(_OUTPUT_PROTO.value) # Solve model. solver = cp_model.CpSolver() - if FLAGS.params: - text_format.Parse(FLAGS.params, solver.parameters) + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.parameters.exploit_all_precedences = True # Helps with the lower bound. solver.Solve(model) def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') - if FLAGS.input == '': + if _INPUT.value == '': raise app.UsageError('Missing input file.') - model = read_model(FLAGS.input) + model = read_model(_INPUT.value) print_stats(model) greedy_solution = solve_model_greedily(model) - if FLAGS.model == 'boolean': + if _MODEL.value == 'boolean': solve_boolean_model(model, greedy_solution) - elif FLAGS.model == 'scheduling': + elif _MODEL.value == 'scheduling': solve_scheduling_model(model, greedy_solution) diff --git a/examples/python/maze_escape_sat.py b/examples/python/maze_escape_sat.py index 5f02f1d370..95f2119fdd 100755 --- a/examples/python/maze_escape_sat.py +++ b/examples/python/maze_escape_sat.py @@ -26,12 +26,11 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -FLAGS = flags.FLAGS - -flags.DEFINE_string('output_proto', '', - 'Output file to write the cp_model proto to.') -flags.DEFINE_string('params', 'num_search_workers:8,log_search_progress:true', - 'Sat solver parameters.') +_OUTPUT_PROTO = flags.DEFINE_string( + 'output_proto', '', 'Output file to write the cp_model proto to.') +_PARAMS = flags.DEFINE_string('params', + 'num_search_workers:8,log_search_progress:true', + 'Sat solver parameters.') def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, @@ -142,7 +141,7 @@ def escape_the_maze(params, output_proto): def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') - escape_the_maze(FLAGS.params, FLAGS.output_proto) + escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value) if __name__ == '__main__': diff --git a/examples/python/no_wait_baking_scheduling_sat.py b/examples/python/no_wait_baking_scheduling_sat.py index 5cd3f3744b..55310790a3 100755 --- a/examples/python/no_wait_baking_scheduling_sat.py +++ b/examples/python/no_wait_baking_scheduling_sat.py @@ -27,12 +27,11 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -FLAGS = flags.FLAGS - -flags.DEFINE_string('params', 'num_search_workers:16, max_time_in_seconds:30', - 'Sat solver parameters.') -flags.DEFINE_string('proto_file', '', - 'If not empty, output the proto to this file.') +_PARAMS = flags.DEFINE_string('params', + 'num_search_workers:16, max_time_in_seconds:30', + 'Sat solver parameters.') +_PROTO_FILE = flags.DEFINE_string( + 'proto_file', '', 'If not empty, output the proto to this file.') # Recipes CROISSANT = 'croissant' @@ -277,8 +276,8 @@ def solve_with_cp_sat(recipes, resources, orders): # Solve model. solver = cp_model.CpSolver() - if FLAGS.params: - text_format.Parse(FLAGS.params, solver.parameters) + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True status = solver.Solve(model) diff --git a/examples/python/qubo_sat.py b/examples/python/qubo_sat.py index e17bf4ec03..1eaf206341 100644 --- a/examples/python/qubo_sat.py +++ b/examples/python/qubo_sat.py @@ -575,6 +575,7 @@ def solve_qubo(): solver = cp_model.CpSolver() solver.parameters.num_search_workers = 16 solver.parameters.log_search_progress = True + solver.parameters.max_time_in_seconds = 30 solver.Solve(model) diff --git a/examples/python/shift_scheduling_sat.py b/examples/python/shift_scheduling_sat.py index 1d86aeb6ff..1cad5f2f9a 100755 --- a/examples/python/shift_scheduling_sat.py +++ b/examples/python/shift_scheduling_sat.py @@ -58,8 +58,8 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix): """Sequence constraint on true variables with soft and hard bounds. - This constraint looks at every maximal contiguous sequence of variables - assigned to true. It forbids sequence of length < hard_min or > hard_max. + This constraint look at every maximal contiguous sequence of variables + assigned to true. If forbids sequence of length < hard_min or > hard_max. Then it creates penalty terms if the length is < soft_min or > soft_max. Args: diff --git a/ortools/sat/python/BUILD.bazel b/ortools/sat/python/BUILD.bazel index 32f05ee591..01d0aaa668 100644 --- a/ortools/sat/python/BUILD.bazel +++ b/ortools/sat/python/BUILD.bazel @@ -88,3 +88,10 @@ py_test( requirement("absl-py"), ], ) + +py_library( + name = "visualization", + srcs = ["visualization.py"], + visibility = ["//visibility:public"], +) +