From 7f48cd9a9af95d7b2440f9af7a597f448d4c3472 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 16 Nov 2023 13:14:11 +0100 Subject: [PATCH 1/3] reformat --- ortools/sat/colab/flags.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ortools/sat/colab/flags.py b/ortools/sat/colab/flags.py index ec31be8e240..3cc51b04641 100644 --- a/ortools/sat/colab/flags.py +++ b/ortools/sat/colab/flags.py @@ -13,9 +13,11 @@ """Collection of helpers to manage absl flags in colab.""" + class NotebookStringFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:str, doc:str): + + def __init__(self, name: str, value: str, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -31,13 +33,14 @@ def name(self) -> str: return self.__name -def define_string(name:str, value:str, doc:str): +def define_string(name: str, value: str, doc: str): return NotebookStringFlag(name, value, doc) class NotebookIntFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:int, doc:str): + + def __init__(self, name: str, value: int, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -53,13 +56,14 @@ def name(self) -> str: return self.__name -def define_integer(name:str, value:int, doc:str): - return NotebookIntFlag(name, value, doc) +def define_integer(name: str, value: int, doc: str): + return NotebookIntFlag(name, value, doc) class NotebookFloatFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:float, doc:str): + + def __init__(self, name: str, value: float, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -75,13 +79,14 @@ def name(self) -> str: return self.__name -def define_float(name:str, value:bool, doc:str): - return NotebookFloatFlag(name, value, doc) +def define_float(name: str, value: bool, doc: str): + return NotebookFloatFlag(name, value, doc) class NotebookBoolFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:bool, doc:str): + + def __init__(self, name: str, value: bool, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -96,6 +101,6 @@ def name(self) -> str: """Returns the name of the parameter.""" return self.__name -def define_bool(name:str, value:bool, doc:str): - return NotebookBoolFlag(name, value, doc) +def define_bool(name: str, value: bool, doc: str): + return NotebookBoolFlag(name, value, doc) From 857c353f710f1390e6af8a35336c7db87fdf8b93 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Thu, 16 Nov 2023 18:17:12 +0100 Subject: [PATCH 2/3] tools: add --platform arg when possible make script more resilient/cross-platform --- tools/docker/Makefile | 2 +- tools/release/build_delivery_manylinux_amd64.sh | 7 ++++--- tools/release/publish_delivery_manylinux_amd64.sh | 2 +- tools/release/publish_delivery_manylinux_arm64.sh | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/docker/Makefile b/tools/docker/Makefile index dfde496a29a..8b8b3ff281a 100644 --- a/tools/docker/Makefile +++ b/tools/docker/Makefile @@ -212,7 +212,7 @@ define manylinux_inner = .PHONY: python_$1_manylinux_cp$2_$3 python_$1_manylinux_cp$2_$3: python/$1/manylinux.Dockerfile export/python/manylinux/build-manylinux.sh @docker image rm -f ${IMAGE}:$$@ 2>/dev/null - ${DOCKER_BUILD_CMD} \ + ${DOCKER_BUILDX_CMD} --platform linux/$1 \ --tag ${IMAGE}:$$@ \ --build-arg GIT_BRANCH=${OR_TOOLS_BRANCH} \ --build-arg GIT_SHA1=${OR_TOOLS_SHA1} \ diff --git a/tools/release/build_delivery_manylinux_amd64.sh b/tools/release/build_delivery_manylinux_amd64.sh index 5c49e443fea..735b69a018f 100755 --- a/tools/release/build_delivery_manylinux_amd64.sh +++ b/tools/release/build_delivery_manylinux_amd64.sh @@ -73,8 +73,9 @@ function build_delivery() { cd "${RELEASE_DIR}" || exit 2 # Build env + # https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63 echo -n "Build ${ORTOOLS_IMG}:env..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":env \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ @@ -84,7 +85,7 @@ function build_delivery() { # Build devel echo -n "Build ${ORTOOLS_IMG}:devel..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":devel \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ @@ -94,7 +95,7 @@ function build_delivery() { # Build delivery echo -n "Build ${ORTOOLS_IMG}:${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":"${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ diff --git a/tools/release/publish_delivery_manylinux_amd64.sh b/tools/release/publish_delivery_manylinux_amd64.sh index bb694ed92ac..a80d2e95988 100755 --- a/tools/release/publish_delivery_manylinux_amd64.sh +++ b/tools/release/publish_delivery_manylinux_amd64.sh @@ -70,7 +70,7 @@ function publish_delivery() { # Publish delivery echo -n "Build ${ORTOOLS_IMG}:publish_${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/publish.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":"publish_${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ diff --git a/tools/release/publish_delivery_manylinux_arm64.sh b/tools/release/publish_delivery_manylinux_arm64.sh index 653b0b89a43..3c5f6488534 100755 --- a/tools/release/publish_delivery_manylinux_arm64.sh +++ b/tools/release/publish_delivery_manylinux_arm64.sh @@ -70,7 +70,7 @@ function publish_delivery() { # Publish delivery echo -n "Build ${ORTOOLS_IMG}:publish_${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/publish.log" - docker buildx build \ + docker buildx build --platform linux/arm64 \ --tag "${ORTOOLS_IMG}":"publish_${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ From 5b6c803db3f1461c7ebb7defd39f5aad4d3e73fe Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 16 Nov 2023 19:46:56 +0100 Subject: [PATCH 3/3] [CP-SAT] convert to PEP8 convention --- examples/python/appointments.py | 47 +- .../python/assignment_with_constraints_sat.py | 35 +- examples/python/balance_group_sat.py | 42 +- examples/python/bus_driver_scheduling_sat.py | 142 +- examples/python/chemical_balance_sat.py | 23 +- examples/python/clustering_sat.py | 16 +- examples/python/cover_rectangle_sat.py | 55 +- examples/python/cryptarithm_sat.py | 58 +- examples/python/flexible_job_shop_sat.py | 56 +- examples/python/gate_scheduling_sat.py | 66 +- examples/python/golomb_sat.py | 32 +- examples/python/hidato_sat.py | 43 +- examples/python/jobshop_ft06_distance_sat.py | 36 +- examples/python/jobshop_ft06_sat.py | 28 +- .../python/jobshop_with_maintenance_sat.py | 39 +- examples/python/knapsack_2d_sat.py | 167 +- examples/python/line_balancing_sat.py | 72 +- examples/python/maze_escape_sat.py | 20 +- .../memory_layout_and_infeasibility_sat.py | 179 ++ .../python/no_wait_baking_scheduling_sat.py | 24 +- examples/python/nqueens_sat.py | 39 +- examples/python/prize_collecting_tsp_sat.py | 31 +- examples/python/prize_collecting_vrp_sat.py | 36 +- examples/python/proto_solve.py | 4 +- examples/python/qubo_sat.py | 18 +- examples/python/rcpsp_sat.py | 186 +- examples/python/shift_scheduling_sat.py | 130 +- ...duling_with_setup_release_due_dates_sat.py | 36 +- examples/python/spread_robots_sat.py | 38 +- examples/python/steel_mill_slab_sat.py | 100 +- examples/python/sudoku_sat.py | 16 +- examples/python/task_allocation_sat.py | 34 +- .../tasks_and_workers_assignment_sat.py | 46 +- examples/python/tsp_sat.py | 12 +- examples/python/vendor_scheduling_sat.py | 34 +- examples/python/wedding_optimal_chart_sat.py | 42 +- .../python/weighted_latency_problem_sat.py | 24 +- examples/python/zebra_sat.py | 130 +- ortools/sat/BUILD.bazel | 874 ++++++--- ortools/sat/clause.cc | 9 +- ortools/sat/cp_model_checker.cc | 7 +- ortools/sat/cp_model_expand.cc | 30 +- ortools/sat/cp_model_presolve.cc | 36 +- ortools/sat/cp_model_presolve.h | 8 +- ortools/sat/cp_model_solver.cc | 11 + ortools/sat/docs/README.md | 16 +- ortools/sat/docs/boolean_logic.md | 40 +- ortools/sat/docs/channeling.md | 57 +- ortools/sat/docs/integer_arithmetic.md | 88 +- ortools/sat/docs/model.md | 48 +- ortools/sat/docs/scheduling.md | 320 ++-- ortools/sat/docs/solver.md | 91 +- ortools/sat/docs/troubleshooting.md | 28 +- ortools/sat/feasibility_jump.cc | 12 +- ortools/sat/lp_utils.cc | 9 + ortools/sat/presolve_context.cc | 10 +- ortools/sat/presolve_context.h | 1 - ortools/sat/python/cp_model.py | 1576 ++++++++++------- ortools/sat/python/cp_model_test.py | 1370 +++++++------- ortools/sat/python/swig_helper.cc | 25 +- ortools/sat/python/swig_helper_test.py | 31 +- ortools/sat/samples/assignment_groups_sat.py | 26 +- ortools/sat/samples/assignment_sat.py | 17 +- .../sat/samples/assignment_task_sizes_sat.py | 16 +- ortools/sat/samples/assignment_teams_sat.py | 20 +- ortools/sat/samples/assumptions_sample_sat.py | 28 +- ortools/sat/samples/bin_packing_sat.py | 23 +- ortools/sat/samples/binpacking_problem_sat.py | 28 +- ortools/sat/samples/bool_or_sample_sat.py | 6 +- .../sat/samples/boolean_product_sample_sat.py | 14 +- ortools/sat/samples/channeling_sample_sat.py | 29 +- ortools/sat/samples/clone_model_sample_sat.py | 28 +- ortools/sat/samples/cp_is_fun_sat.py | 47 +- ortools/sat/samples/cp_sat_example.py | 32 +- .../cumulative_variable_profile_sample_sat.py | 21 +- .../earliness_tardiness_cost_sample_sat.py | 29 +- ortools/sat/samples/interval_sample_sat.py | 10 +- ortools/sat/samples/literal_sample_sat.py | 4 +- ortools/sat/samples/minimal_jobshop_sat.py | 32 +- ortools/sat/samples/multiple_knapsack_sat.py | 20 +- ortools/sat/samples/no_overlap_sample_sat.py | 42 +- ortools/sat/samples/non_linear_sat.py | 22 +- ortools/sat/samples/nqueens_sat.py | 25 +- ortools/sat/samples/nurses_sat.py | 26 +- .../samples/optional_interval_sample_sat.py | 12 +- .../overlapping_intervals_sample_sat.py | 53 +- .../sat/samples/rabbits_and_pheasants_sat.py | 12 +- .../sat/samples/ranking_circuit_sample_sat.py | 61 +- ortools/sat/samples/ranking_sample_sat.py | 80 +- ortools/sat/samples/reified_sample_sat.py | 16 +- ortools/sat/samples/schedule_requests_sat.py | 25 +- .../scheduling_with_calendar_sample_sat.py | 41 +- .../search_for_all_solutions_sample_sat.py | 23 +- ortools/sat/samples/simple_sat_program.py | 16 +- .../samples/solution_hinting_sample_sat.py | 20 +- ...print_intermediate_solutions_sample_sat.py | 27 +- .../solve_with_time_limit_sample_sat.py | 16 +- .../sat/samples/step_function_sample_sat.py | 47 +- .../stop_after_n_solutions_sample_sat.py | 25 +- ortools/util/fp_utils.cc | 7 + ortools/util/python/sorted_interval_list.cc | 46 +- .../util/python/sorted_interval_list_test.py | 72 +- ortools/util/zvector.h | 11 +- 103 files changed, 4603 insertions(+), 3385 deletions(-) mode change 100755 => 100644 examples/python/appointments.py mode change 100755 => 100644 examples/python/maze_escape_sat.py create mode 100644 examples/python/memory_layout_and_infeasibility_sat.py mode change 100755 => 100644 examples/python/no_wait_baking_scheduling_sat.py mode change 100755 => 100644 examples/python/prize_collecting_tsp_sat.py mode change 100755 => 100644 examples/python/prize_collecting_vrp_sat.py mode change 100755 => 100644 examples/python/rcpsp_sat.py mode change 100755 => 100644 examples/python/steel_mill_slab_sat.py mode change 100644 => 100755 examples/python/sudoku_sat.py mode change 100644 => 100755 examples/python/zebra_sat.py mode change 100755 => 100644 ortools/sat/samples/assumptions_sample_sat.py mode change 100755 => 100644 ortools/sat/samples/rabbits_and_pheasants_sat.py diff --git a/examples/python/appointments.py b/examples/python/appointments.py old mode 100755 new mode 100644 index e88571070de..017e8c90099 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -44,17 +44,19 @@ def __init__(self, variables): self.__variables = variables self.__collect = [] - def on_solution_callback(self): + def on_solution_callback(self) -> None: """Collect a new combination.""" - combination = [self.Value(v) for v in self.__variables] + combination = [self.value(v) for v in self.__variables] self.__collect.append(combination) - def combinations(self): + def combinations(self) -> list[list[int]]: """Returns all collected combinations.""" return self.__collect -def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max): +def EnumerateAllKnapsacksWithRepetition( + item_sizes: list[int], total_size_min: int, total_size_max: int +) -> list[list[int]]: """Enumerate all possible knapsacks with total size in the given range. Args: @@ -68,22 +70,26 @@ def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_m nonnegative integer: the number of times we put item #K in the knapsack. """ model = cp_model.CpModel() - variables = [model.NewIntVar(0, total_size_max // size, "") for size in item_sizes] + variables = [ + model.new_int_var(0, total_size_max // size, "") for size in item_sizes + ] load = sum(variables[i] * size for i, size in enumerate(item_sizes)) - model.AddLinearConstraint(load, total_size_min, total_size_max) + model.add_linear_constraint(load, total_size_min, total_size_max) solver = cp_model.CpSolver() solution_collector = AllSolutionCollector(variables) # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True - # Solve - solver.Solve(model, solution_collector) + # solve + solver.solve(model, solution_collector) return solution_collector.combinations() def AggregateItemCollectionsOptimally( - item_collections, max_num_collections, ideal_item_ratios -): + item_collections: list[list[int]], + max_num_collections: int, + ideal_item_ratios: list[float], +) -> list[int]: """Selects a set (with repetition) of combination of items optimally. Given a set of collections of N possible items (in each collection, an item @@ -173,7 +179,9 @@ def AggregateItemCollectionsOptimally( return [] -def GetOptimalSchedule(demand): +def GetOptimalSchedule( + demand: list[tuple[float, str, int]] +) -> list[tuple[int, list[tuple[int, str]]]]: """Computes the optimal schedule for the installation input. Args: @@ -186,7 +194,9 @@ def GetOptimalSchedule(demand): The same output type as EnumerateAllKnapsacksWithRepetition. """ combinations = EnumerateAllKnapsacksWithRepetition( - [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value + [a[2] + _COMMUTE_TIME.value for a in demand], + _LOAD_MIN.value, + _LOAD_MAX.value, ) print( ( @@ -199,14 +209,14 @@ def GetOptimalSchedule(demand): combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand] ) output = [] - for i in range(len(selection)): - if selection[i] != 0: + for i, s in enumerate(selection): + if s != 0: output.append( ( - selection[i], + s, [ - (combinations[i][t], demand[t][1]) - for t in range(len(demand)) + (combinations[i][t], d[1]) + for t, d in enumerate(demand) if combinations[i][t] != 0 ], ) @@ -252,7 +262,8 @@ def main(_): per_type = installed_per_type[name] if installed != 0: print( - f" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned" + f" {per_type} ({per_type * 100.0 / installed}%) installations of" + f" type {name} planned" ) else: print(f" {per_type} installations of type {name} planned") diff --git a/examples/python/assignment_with_constraints_sat.py b/examples/python/assignment_with_constraints_sat.py index b0dc5b5db6c..3a805787a12 100644 --- a/examples/python/assignment_with_constraints_sat.py +++ b/examples/python/assignment_with_constraints_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve an assignment problem with combination constraints on workers.""" +"""solve an assignment problem with combination constraints on workers.""" from typing import Sequence from absl import app @@ -20,7 +20,7 @@ def solve_assignment(): - """Solve the assignment problem.""" + """solve the assignment problem.""" # Data. cost = [ [90, 76, 75, 70, 50, 74], @@ -73,44 +73,45 @@ def solve_assignment(): model = cp_model.CpModel() # Variables selected = [ - [model.NewBoolVar("x[%i,%i]" % (i, j)) for j in all_tasks] for i in all_workers + [model.new_bool_var("x[%i,%i]" % (i, j)) for j in all_tasks] + for i in all_workers ] - works = [model.NewBoolVar("works[%i]" % i) for i in all_workers] + works = [model.new_bool_var("works[%i]" % i) for i in all_workers] # Constraints # Link selected and workers. for i in range(num_workers): - model.AddMaxEquality(works[i], selected[i]) + model.add_max_equality(works[i], selected[i]) # Each task is assigned to at least one worker. for j in all_tasks: - model.Add(sum(selected[i][j] for i in all_workers) >= 1) + model.add(sum(selected[i][j] for i in all_workers) >= 1) # Total task size for each worker is at most total_size_max for i in all_workers: - model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max) + model.add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max) # Group constraints. - model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1) - model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2) - model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3) + model.add_allowed_assignments([works[0], works[1], works[2], works[3]], group1) + model.add_allowed_assignments([works[4], works[5], works[6], works[7]], group2) + model.add_allowed_assignments([works[8], works[9], works[10], works[11]], group3) # Objective - model.Minimize( + model.minimize( sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers) ) # Solve and output solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print("Total cost = %i" % solver.ObjectiveValue()) + print("Total cost = %i" % solver.objective_value) print() for i in all_workers: for j in all_tasks: - if solver.BooleanValue(selected[i][j]): + if solver.boolean_value(selected[i][j]): print( "Worker ", i, " assigned to task ", j, " Cost = ", cost[i][j] ) @@ -118,9 +119,9 @@ def solve_assignment(): print() print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/balance_group_sat.py b/examples/python/balance_group_sat.py index b11fb8fe11f..87093264c72 100644 --- a/examples/python/balance_group_sat.py +++ b/examples/python/balance_group_sat.py @@ -41,14 +41,14 @@ def on_solution_callback(self): print("Solution %i" % self.__solution_count) self.__solution_count += 1 - print(" objective value = %i" % self.ObjectiveValue()) + print(" objective value = %i" % self.objective_value) groups = {} sums = {} for g in self.__all_groups: groups[g] = [] sums[g] = 0 for item in self.__all_items: - if self.BooleanValue(self.__item_in_group[(item, g)]): + if self.boolean_value(self.__item_in_group[(item, g)]): groups[g].append(item) sums[g] += self.__values[item] @@ -77,7 +77,7 @@ def main(argv: Sequence[str]) -> None: all_items = range(num_items) all_colors = range(num_colors) - # Values for each items. + # values for each items. values = [1 + i + (i * i // 200) for i in all_items] # Color for each item (simple modulo). colors = [i % num_colors for i in all_items] @@ -108,26 +108,26 @@ def main(argv: Sequence[str]) -> None: item_in_group = {} for i in all_items: for g in all_groups: - item_in_group[(i, g)] = model.NewBoolVar("item %d in group %d" % (i, g)) + item_in_group[(i, g)] = model.new_bool_var("item %d in group %d" % (i, g)) # Each group must have the same size. for g in all_groups: - model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group) + model.add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group) # One item must belong to exactly one group. for i in all_items: - model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1) + model.add(sum(item_in_group[(i, g)] for g in all_groups) == 1) # The deviation of the sum of each items in a group against the average. - e = model.NewIntVar(0, 550, "epsilon") + e = model.new_int_var(0, 550, "epsilon") # Constrain the sum of values in one group around the average sum per group. for g in all_groups: - model.Add( + model.add( sum(item_in_group[(i, g)] * values[i] for i in all_items) <= average_sum_per_group + e ) - model.Add( + model.add( sum(item_in_group[(i, g)] * values[i] for i in all_items) >= average_sum_per_group - e ) @@ -136,24 +136,24 @@ def main(argv: Sequence[str]) -> None: color_in_group = {} for g in all_groups: for c in all_colors: - color_in_group[(c, g)] = model.NewBoolVar( + color_in_group[(c, g)] = model.new_bool_var( "color %d is in group %d" % (c, g) ) # Item is in a group implies its color is in that group. for i in all_items: for g in all_groups: - model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)]) + model.add_implication(item_in_group[(i, g)], color_in_group[(colors[i], g)]) # If a color is in a group, it must contains at least # min_items_of_same_color_per_group items from that color. for c in all_colors: for g in all_groups: literal = color_in_group[(c, g)] - model.Add( + model.add( sum(item_in_group[(i, g)] for i in items_per_color[c]) >= min_items_of_same_color_per_group - ).OnlyEnforceIf(literal) + ).only_enforce_if(literal) # Compute the maximum number of colors in a group. max_color = num_items_per_group // min_items_of_same_color_per_group @@ -161,10 +161,10 @@ def main(argv: Sequence[str]) -> None: # Redundant constraint, it helps with solving time. if max_color < num_colors: for g in all_groups: - model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color) + model.add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color) - # Minimize epsilon - model.Minimize(e) + # minimize epsilon + model.minimize(e) solver = cp_model.CpSolver() # solver.parameters.log_search_progress = True @@ -172,14 +172,14 @@ def main(argv: Sequence[str]) -> None: solution_printer = SolutionPrinter( values, colors, all_groups, all_items, item_in_group ) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) if status == cp_model.OPTIMAL: - print("Optimal epsilon: %i" % solver.ObjectiveValue()) + print("Optimal epsilon: %i" % solver.objective_value) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) else: print("No solution found") diff --git a/examples/python/bus_driver_scheduling_sat.py b/examples/python/bus_driver_scheduling_sat.py index 18d5713f97a..a1972edc1f1 100644 --- a/examples/python/bus_driver_scheduling_sat.py +++ b/examples/python/bus_driver_scheduling_sat.py @@ -1708,7 +1708,7 @@ ] # yapf:disable -def bus_driver_scheduling(minimize_drivers, max_num_drivers): +def bus_driver_scheduling(minimize_drivers: bool, max_num_drivers: int) -> int: """Optimize the bus driver scheduling problem. This model has two modes. @@ -1806,14 +1806,14 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): for d in range(num_drivers): start_times.append( - model.NewIntVar(min_start_time - setup_time, max_end_time, "start_%i" % d) + model.new_int_var(min_start_time - setup_time, max_end_time, "start_%i" % d) ) end_times.append( - model.NewIntVar(min_start_time, max_end_time + cleanup_time, "end_%i" % d) + model.new_int_var(min_start_time, max_end_time + cleanup_time, "end_%i" % d) ) - driving_times.append(model.NewIntVar(0, max_driving_time, "driving_%i" % d)) + driving_times.append(model.new_int_var(0, max_driving_time, "driving_%i" % d)) working_times.append( - model.NewIntVar(0, max_working_time, "working_times_%i" % d) + model.new_int_var(0, max_working_time, "working_times_%i" % d) ) incoming_literals = collections.defaultdict(list) @@ -1824,13 +1824,13 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Create all the shift variables before iterating on the transitions # between these shifts. for s in range(num_shifts): - total_driving[d, s] = model.NewIntVar( + total_driving[d, s] = model.new_int_var( 0, max_driving_time, "dr_%i_%i" % (d, s) ) - no_break_driving[d, s] = model.NewIntVar( + no_break_driving[d, s] = model.new_int_var( 0, max_driving_time_without_pauses, "mdr_%i_%i" % (d, s) ) - performed[d, s] = model.NewBoolVar("performed_%i_%i" % (d, s)) + performed[d, s] = model.new_bool_var("performed_%i_%i" % (d, s)) for s in range(num_shifts): shift = shifts[s] @@ -1839,42 +1839,48 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Arc from source to shift. # - set the start time of the driver # - increase driving time and driving time since break - source_lit = model.NewBoolVar("%i from source to %i" % (d, s)) + source_lit = model.new_bool_var("%i from source to %i" % (d, s)) outgoing_source_literals.append(source_lit) incoming_literals[s].append(source_lit) shared_incoming_literals[s].append(source_lit) - model.Add(start_times[d] == shift[3] - setup_time).OnlyEnforceIf(source_lit) - model.Add(total_driving[d, s] == duration).OnlyEnforceIf(source_lit) - model.Add(no_break_driving[d, s] == duration).OnlyEnforceIf(source_lit) + model.add(start_times[d] == shift[3] - setup_time).only_enforce_if( + source_lit + ) + model.add(total_driving[d, s] == duration).only_enforce_if(source_lit) + model.add(no_break_driving[d, s] == duration).only_enforce_if(source_lit) starting_shifts[d, s] = source_lit # Arc from shift to sink # - set the end time of the driver # - set the driving times of the driver - sink_lit = model.NewBoolVar("%i from %i to sink" % (d, s)) + sink_lit = model.new_bool_var("%i from %i to sink" % (d, s)) outgoing_literals[s].append(sink_lit) shared_outgoing_literals[s].append(sink_lit) incoming_sink_literals.append(sink_lit) - model.Add(end_times[d] == shift[4] + cleanup_time).OnlyEnforceIf(sink_lit) - model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(sink_lit) + model.add(end_times[d] == shift[4] + cleanup_time).only_enforce_if(sink_lit) + model.add(driving_times[d] == total_driving[d, s]).only_enforce_if(sink_lit) # Node not performed # - set both driving times to 0 # - add a looping arc on the node - model.Add(total_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not()) - model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not()) - incoming_literals[s].append(performed[d, s].Not()) - outgoing_literals[s].append(performed[d, s].Not()) - # Not adding to the shared lists, because, globally, each node will have - # one incoming literal, and one outgoing literal. + model.add(total_driving[d, s] == 0).only_enforce_if( + performed[d, s].negated() + ) + model.add(no_break_driving[d, s] == 0).only_enforce_if( + performed[d, s].negated() + ) + incoming_literals[s].append(performed[d, s].negated()) + outgoing_literals[s].append(performed[d, s].negated()) + # negated adding to the shared lists, because, globally, each node will + # have one incoming literal, and one outgoing literal. # Node performed: # - add upper bound on start_time # - add lower bound on end_times - model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf( + model.add(start_times[d] <= shift[3] - setup_time).only_enforce_if( performed[d, s] ) - model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf( + model.add(end_times[d] >= shift[4] + cleanup_time).only_enforce_if( performed[d, s] ) @@ -1883,22 +1889,22 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): delay = other[3] - shift[4] if delay < min_delay_between_shifts: continue - lit = model.NewBoolVar("%i from %i to %i" % (d, s, o)) + lit = model.new_bool_var("%i from %i to %i" % (d, s, o)) # Increase driving time - model.Add( + model.add( total_driving[d, o] == total_driving[d, s] + other[5] - ).OnlyEnforceIf(lit) + ).only_enforce_if(lit) # Increase no_break_driving or reset it to 0 depending on the delay if delay >= min_pause_after_4h: - model.Add(no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit) + model.add(no_break_driving[d, o] == other[5]).only_enforce_if(lit) else: - model.Add( + model.add( no_break_driving[d, o] == no_break_driving[d, s] + other[5] - ).OnlyEnforceIf(lit) + ).only_enforce_if(lit) - # Add arc + # add arc outgoing_literals[s].append(lit) shared_outgoing_literals[s].append(lit) incoming_literals[o].append(lit) @@ -1908,68 +1914,72 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): delay_literals.append(lit) delay_weights.append(delay) - model.Add(working_times[d] == end_times[d] - start_times[d]) + model.add(working_times[d] == end_times[d] - start_times[d]) if minimize_drivers: # Driver is not working. - working = model.NewBoolVar("working_%i" % d) - model.Add(start_times[d] == min_start_time).OnlyEnforceIf(working.Not()) - model.Add(end_times[d] == min_start_time).OnlyEnforceIf(working.Not()) - model.Add(driving_times[d] == 0).OnlyEnforceIf(working.Not()) + working = model.new_bool_var("working_%i" % d) + model.add(start_times[d] == min_start_time).only_enforce_if( + working.negated() + ) + model.add(end_times[d] == min_start_time).only_enforce_if(working.negated()) + model.add(driving_times[d] == 0).only_enforce_if(working.negated()) working_drivers.append(working) - outgoing_source_literals.append(working.Not()) - incoming_sink_literals.append(working.Not()) + outgoing_source_literals.append(working.negated()) + incoming_sink_literals.append(working.negated()) # Conditional working time constraints - model.Add(working_times[d] >= min_working_time).OnlyEnforceIf(working) - model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not()) + model.add(working_times[d] >= min_working_time).only_enforce_if(working) + model.add(working_times[d] == 0).only_enforce_if(working.negated()) else: # Working time constraints - model.Add(working_times[d] >= min_working_time) + model.add(working_times[d] >= min_working_time) # Create circuit constraint. - model.AddExactlyOne(outgoing_source_literals) + model.add_exactly_one(outgoing_source_literals) for s in range(num_shifts): - model.AddExactlyOne(outgoing_literals[s]) - model.AddExactlyOne(incoming_literals[s]) - model.AddExactlyOne(incoming_sink_literals) + model.add_exactly_one(outgoing_literals[s]) + model.add_exactly_one(incoming_literals[s]) + model.add_exactly_one(incoming_sink_literals) # Each shift is covered. for s in range(num_shifts): - model.AddExactlyOne(performed[d, s] for d in range(num_drivers)) + model.add_exactly_one(performed[d, s] for d in range(num_drivers)) # Globally, each node has one incoming and one outgoing literal - model.AddExactlyOne(shared_incoming_literals[s]) - model.AddExactlyOne(shared_outgoing_literals[s]) + model.add_exactly_one(shared_incoming_literals[s]) + model.add_exactly_one(shared_outgoing_literals[s]) # Symmetry breaking # The first 3 shifts must be performed by 3 different drivers. # Let's assign them to the first 3 drivers in sequence - model.Add(starting_shifts[0, 0] == 1) - model.Add(starting_shifts[1, 1] == 1) - model.Add(starting_shifts[2, 2] == 1) + model.add(starting_shifts[0, 0] == 1) + model.add(starting_shifts[1, 1] == 1) + model.add(starting_shifts[2, 2] == 1) if minimize_drivers: # Push non working drivers to the end for d in range(num_drivers - 1): - model.AddImplication(working_drivers[d].Not(), working_drivers[d + 1].Not()) + model.add_implication( + working_drivers[d].negated(), working_drivers[d + 1].negated() + ) # Redundant constraints: sum of driving times = sum of shift driving times - model.Add(cp_model.LinearExpr.Sum(driving_times) == total_driving_time) + model.add(cp_model.LinearExpr.sum(driving_times) == total_driving_time) if not minimize_drivers: - model.Add( - cp_model.LinearExpr.Sum(working_times) + model.add( + cp_model.LinearExpr.sum(working_times) == total_driving_time + num_drivers * (setup_time + cleanup_time) - + cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights) + + cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights) ) if minimize_drivers: - # Minimize the number of working drivers - model.Minimize(cp_model.LinearExpr.Sum(working_drivers)) + # minimize the number of working drivers + model.minimize(cp_model.LinearExpr.sum(working_drivers)) else: - # Minimize the sum of delays between tasks, which in turns minimize the + # minimize the sum of delays between tasks, which in turns minimize the # sum of working times as the total driving time is fixed - model.Minimize(cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights)) + model.minimize(cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights)) if not minimize_drivers and _OUTPUT_PROTO.value: print("Writing proto to %s" % _OUTPUT_PROTO.value) @@ -1981,41 +1991,41 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE: return -1 # Display solution if minimize_drivers: - max_num_drivers = int(solver.ObjectiveValue()) + max_num_drivers = int(solver.objective_value) print("minimal number of drivers =", max_num_drivers) return max_num_drivers for d in range(num_drivers): print("Driver %i: " % (d + 1)) - print(" total driving time =", solver.Value(driving_times[d])) + print(" total driving time =", solver.value(driving_times[d])) print( " working time =", - solver.Value(working_times[d]) + setup_time + cleanup_time, + solver.value(working_times[d]) + setup_time + cleanup_time, ) first = True for s in range(num_shifts): shift = shifts[s] - if not solver.BooleanValue(performed[d, s]): + if not solver.boolean_value(performed[d, s]): continue # Hack to detect if the waiting time between the last shift and # this one exceeds 30 minutes. For this, we look at the # no_break_driving which was reinitialized in that case. - if solver.Value(no_break_driving[d, s]) == shift[5] and not first: + if solver.value(no_break_driving[d, s]) == shift[5] and not first: print(" **break**") print(" shift ", shift[0], ":", shift[1], "-", shift[2]) first = False - return int(solver.ObjectiveValue()) + return int(solver.objective_value) def main(_): diff --git a/examples/python/chemical_balance_sat.py b/examples/python/chemical_balance_sat.py index 6585e7d12ce..d3604c6bfdb 100644 --- a/examples/python/chemical_balance_sat.py +++ b/examples/python/chemical_balance_sat.py @@ -70,37 +70,40 @@ def chemical_balance(): for s in all_sets ] - set_vars = [model.NewIntVar(0, max_set[s], f"set_{s}") for s in all_sets] + set_vars = [model.new_int_var(0, max_set[s], f"set_{s}") for s in all_sets] - epsilon = model.NewIntVar(0, 10000000, "epsilon") + epsilon = model.new_int_var(0, 10000000, "epsilon") for p in all_products: - model.Add( + model.add( sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets) <= int(max_quantities[p][1] * 10000) ) - model.Add( + model.add( sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets) >= int(max_quantities[p][1] * 10000) - epsilon ) - model.Minimize(epsilon) + model.minimize(epsilon) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Status = {solver.StatusName(status)}") + status = solver.solve(model) + print(f"Status = {solver.status_name(status)}") # The objective value of the solution. - print(f"Optimal objective value = {solver.ObjectiveValue() / 10000.0}") + print(f"Optimal objective value = {solver.objective_value / 10000.0}") for s in all_sets: - print(f" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}", end=" ") + print( + f" {chemical_set[s][0]} = {solver.value(set_vars[s]) / 1000.0}", + end=" ", + ) print() for p in all_products: name = max_quantities[p][0] max_quantity = max_quantities[p][1] quantity = sum( - solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1] + solver.value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1] for s in all_sets ) print(f"{name}: {quantity} out of {max_quantity}") diff --git a/examples/python/clustering_sat.py b/examples/python/clustering_sat.py index e4acc1809f4..9cfb60cf0d0 100644 --- a/examples/python/clustering_sat.py +++ b/examples/python/clustering_sat.py @@ -83,14 +83,14 @@ def clustering_sat(): obj_coeffs = [] for n1 in range(num_nodes - 1): for n2 in range(n1 + 1, num_nodes): - same = model.NewBoolVar("neighbors_%i_%i" % (n1, n2)) + same = model.new_bool_var("neighbors_%i_%i" % (n1, n2)) neighbors[n1, n2] = same obj_vars.append(same) obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1]) # Number of neighborss: for n in range(num_nodes): - model.Add( + model.add( sum(neighbors[m, n] for m in range(n)) + sum(neighbors[n, m] for m in range(n + 1, num_nodes)) == group_size - 1 @@ -100,23 +100,23 @@ def clustering_sat(): for n1 in range(num_nodes - 2): for n2 in range(n1 + 1, num_nodes - 1): for n3 in range(n2 + 1, num_nodes): - model.Add( + model.add( neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2 ) # Redundant constraints on total sum of neighborss. - model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2) + model.add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2) # Minimize weighted sum of arcs. - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.num_search_workers = 8 - status = solver.Solve(model) - print(solver.ResponseStats()) + status = solver.solve(model) + print(solver.response_stats()) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: visited = set() @@ -126,7 +126,7 @@ def clustering_sat(): visited.add(n) output = str(n) for o in range(n + 1, num_nodes): - if solver.BooleanValue(neighbors[n, o]): + if solver.boolean_value(neighbors[n, o]): visited.add(o) output += " " + str(o) print("Group", g, ":", output) diff --git a/examples/python/cover_rectangle_sat.py b/examples/python/cover_rectangle_sat.py index dc4dcd640ed..2beb09ce0a4 100644 --- a/examples/python/cover_rectangle_sat.py +++ b/examples/python/cover_rectangle_sat.py @@ -19,7 +19,7 @@ from ortools.sat.python import cp_model -def cover_rectangle(num_squares): +def cover_rectangle(num_squares: int) -> bool: """Try to fill the rectangle with a given number of squares.""" size_x = 60 size_y = 50 @@ -35,17 +35,17 @@ def cover_rectangle(num_squares): # Creates intervals for the NoOverlap2D and size variables. for i in range(num_squares): - size = model.NewIntVar(1, size_y, "size_%i" % i) - start_x = model.NewIntVar(0, size_x, "sx_%i" % i) - end_x = model.NewIntVar(0, size_x, "ex_%i" % i) - start_y = model.NewIntVar(0, size_y, "sy_%i" % i) - end_y = model.NewIntVar(0, size_y, "ey_%i" % i) + size = model.new_int_var(1, size_y, "size_%i" % i) + start_x = model.new_int_var(0, size_x, "sx_%i" % i) + end_x = model.new_int_var(0, size_x, "ex_%i" % i) + start_y = model.new_int_var(0, size_y, "sy_%i" % i) + end_y = model.new_int_var(0, size_y, "ey_%i" % i) - interval_x = model.NewIntervalVar(start_x, size, end_x, "ix_%i" % i) - interval_y = model.NewIntervalVar(start_y, size, end_y, "iy_%i" % i) + interval_x = model.new_interval_var(start_x, size, end_x, "ix_%i" % i) + interval_y = model.new_interval_var(start_y, size, end_y, "iy_%i" % i) - area = model.NewIntVar(1, size_y * size_y, "area_%i" % i) - model.AddMultiplicationEquality(area, [size, size]) + area = model.new_int_var(1, size_y * size_y, "area_%i" % i) + model.add_multiplication_equality(area, [size, size]) areas.append(area) x_intervals.append(interval_x) @@ -55,47 +55,46 @@ def cover_rectangle(num_squares): y_starts.append(start_y) # Main constraint. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) # Redundant constraints. - model.AddCumulative(x_intervals, sizes, size_y) - model.AddCumulative(y_intervals, sizes, size_x) + model.add_cumulative(x_intervals, sizes, size_y) + model.add_cumulative(y_intervals, sizes, size_x) # Forces the rectangle to be exactly covered. - model.Add(sum(areas) == size_x * size_y) + model.add(sum(areas) == size_x * size_y) # Symmetry breaking 1: sizes are ordered. for i in range(num_squares - 1): - model.Add(sizes[i] <= sizes[i + 1]) + model.add(sizes[i] <= sizes[i + 1]) # Define same to be true iff sizes[i] == sizes[i + 1] - same = model.NewBoolVar("") - model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same) - model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not()) + same = model.new_bool_var("") + model.add(sizes[i] == sizes[i + 1]).only_enforce_if(same) + model.add(sizes[i] < sizes[i + 1]).only_enforce_if(same.negated()) # Tie break with starts. - model.Add(x_starts[i] <= x_starts[i + 1]).OnlyEnforceIf(same) + model.add(x_starts[i] <= x_starts[i + 1]).only_enforce_if(same) # Symmetry breaking 2: first square in one quadrant. - model.Add(x_starts[0] < (size_x + 1) // 2) - model.Add(y_starts[0] < (size_y + 1) // 2) + model.add(x_starts[0] < (size_x + 1) // 2) + model.add(y_starts[0] < (size_y + 1) // 2) # Creates a solver and solves. solver = cp_model.CpSolver() - solver.parameters.num_workers = 16 - # solver.parameters.log_search_progress = True + solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) - print("%s found in %0.2fs" % (solver.StatusName(status), solver.WallTime())) + status = solver.solve(model) + print("%s found in %0.2fs" % (solver.status_name(status), solver.wall_time)) # Prints solution. solution_found = status == cp_model.OPTIMAL or status == cp_model.FEASIBLE if solution_found: display = [[" " for _ in range(size_x)] for _ in range(size_y)] for i in range(num_squares): - sol_x = solver.Value(x_starts[i]) - sol_y = solver.Value(y_starts[i]) - sol_s = solver.Value(sizes[i]) + sol_x = solver.value(x_starts[i]) + sol_y = solver.value(y_starts[i]) + sol_s = solver.value(sizes[i]) char = format(i, "01x") for j in range(sol_s): for k in range(sol_s): diff --git a/examples/python/cryptarithm_sat.py b/examples/python/cryptarithm_sat.py index d96b5c6ad37..c519d990e0f 100644 --- a/examples/python/cryptarithm_sat.py +++ b/examples/python/cryptarithm_sat.py @@ -20,58 +20,58 @@ def send_more_money(): - """Solve the cryptarithmic puzzle SEND+MORE=MONEY.""" + """solve the cryptarithmic puzzle SEND+MORE=MONEY.""" model = cp_model.CpModel() # Create variables. # Since s is a leading digit, it can't be 0. - s = model.NewIntVar(1, 9, "s") - e = model.NewIntVar(0, 9, "e") - n = model.NewIntVar(0, 9, "n") - d = model.NewIntVar(0, 9, "d") + s = model.new_int_var(1, 9, "s") + e = model.new_int_var(0, 9, "e") + n = model.new_int_var(0, 9, "n") + d = model.new_int_var(0, 9, "d") # Since m is a leading digit, it can't be 0. - m = model.NewIntVar(1, 9, "m") - o = model.NewIntVar(0, 9, "o") - r = model.NewIntVar(0, 9, "r") - y = model.NewIntVar(0, 9, "y") + m = model.new_int_var(1, 9, "m") + o = model.new_int_var(0, 9, "o") + r = model.new_int_var(0, 9, "r") + y = model.new_int_var(0, 9, "y") # Create carry variables. c0 is true if the first column of addends carries # a 1, c2 is true if the second column carries a 1, and so on. - c0 = model.NewBoolVar("c0") - c1 = model.NewBoolVar("c1") - c2 = model.NewBoolVar("c2") - c3 = model.NewBoolVar("c3") + c0 = model.new_bool_var("c0") + c1 = model.new_bool_var("c1") + c2 = model.new_bool_var("c2") + c3 = model.new_bool_var("c3") # Force all letters to take on different values. - model.AddAllDifferent(s, e, n, d, m, o, r, y) + model.add_all_different(s, e, n, d, m, o, r, y) # Column 0: - model.Add(c0 == m) + model.add(c0 == m) # Column 1: - model.Add(c1 + s + m == o + 10 * c0) + model.add(c1 + s + m == o + 10 * c0) # Column 2: - model.Add(c2 + e + o == n + 10 * c1) + model.add(c2 + e + o == n + 10 * c1) # Column 3: - model.Add(c3 + n + r == e + 10 * c2) + model.add(c3 + n + r == e + 10 * c2) # Column 4: - model.Add(d + e == y + 10 * c3) + model.add(d + e == y + 10 * c3) - # Solve model. + # solve model. solver = cp_model.CpSolver() - if solver.Solve(model) == cp_model.OPTIMAL: + if solver.solve(model) == cp_model.OPTIMAL: print("Optimal solution found!") - print("s:", solver.Value(s)) - print("e:", solver.Value(e)) - print("n:", solver.Value(n)) - print("d:", solver.Value(d)) - print("m:", solver.Value(m)) - print("o:", solver.Value(o)) - print("r:", solver.Value(r)) - print("y:", solver.Value(y)) + print("s:", solver.value(s)) + print("e:", solver.value(e)) + print("n:", solver.value(n)) + print("d:", solver.value(d)) + print("m:", solver.value(m)) + print("o:", solver.value(o)) + print("r:", solver.value(r)) + print("y:", solver.value(y)) def main(_): diff --git a/examples/python/flexible_job_shop_sat.py b/examples/python/flexible_job_shop_sat.py index 9eea9d24f41..f216ae9b243 100644 --- a/examples/python/flexible_job_shop_sat.py +++ b/examples/python/flexible_job_shop_sat.py @@ -41,13 +41,13 @@ def on_solution_callback(self): """Called at each new solution.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 def flexible_jobshop(): - """Solve a small flexible jobshop problem.""" + """solve a small flexible jobshop problem.""" # Data part. jobs = [ # task = (processing_time, machine_id) [ # Job 0 @@ -113,12 +113,12 @@ def flexible_jobshop(): # Create main interval for the task. suffix_name = "_j%i_t%i" % (job_id, task_id) - start = model.NewIntVar(0, horizon, "start" + suffix_name) - duration = model.NewIntVar( + start = model.new_int_var(0, horizon, "start" + suffix_name) + duration = model.new_int_var( min_duration, max_duration, "duration" + suffix_name ) - end = model.NewIntVar(0, horizon, "end" + suffix_name) - interval = model.NewIntervalVar( + end = model.new_int_var(0, horizon, "end" + suffix_name) + interval = model.new_interval_var( start, duration, end, "interval" + suffix_name ) @@ -127,7 +127,7 @@ def flexible_jobshop(): # Add precedence with previous task in the same job. if previous_end is not None: - model.Add(start >= previous_end) + model.add(start >= previous_end) previous_end = end # Create alternative intervals. @@ -135,19 +135,19 @@ def flexible_jobshop(): l_presences = [] for alt_id in all_alternatives: alt_suffix = "_j%i_t%i_a%i" % (job_id, task_id, alt_id) - l_presence = model.NewBoolVar("presence" + alt_suffix) - l_start = model.NewIntVar(0, horizon, "start" + alt_suffix) + l_presence = model.new_bool_var("presence" + alt_suffix) + l_start = model.new_int_var(0, horizon, "start" + alt_suffix) l_duration = task[alt_id][0] - l_end = model.NewIntVar(0, horizon, "end" + alt_suffix) - l_interval = model.NewOptionalIntervalVar( + l_end = model.new_int_var(0, horizon, "end" + alt_suffix) + l_interval = model.new_optional_interval_var( l_start, l_duration, l_end, l_presence, "interval" + alt_suffix ) l_presences.append(l_presence) # Link the primary/global variables with the local ones. - model.Add(start == l_start).OnlyEnforceIf(l_presence) - model.Add(duration == l_duration).OnlyEnforceIf(l_presence) - model.Add(end == l_end).OnlyEnforceIf(l_presence) + model.add(start == l_start).only_enforce_if(l_presence) + model.add(duration == l_duration).only_enforce_if(l_presence) + model.add(end == l_end).only_enforce_if(l_presence) # Add the local interval to the right machine. intervals_per_resources[task[alt_id][1]].append(l_interval) @@ -156,10 +156,10 @@ def flexible_jobshop(): presences[(job_id, task_id, alt_id)] = l_presence # Select exactly one presence variable. - model.AddExactlyOne(l_presences) + model.add_exactly_one(l_presences) else: intervals_per_resources[task[0][1]].append(interval) - presences[(job_id, task_id, 0)] = model.NewConstant(1) + presences[(job_id, task_id, 0)] = model.new_constant(1) job_ends.append(previous_end) @@ -167,28 +167,28 @@ def flexible_jobshop(): for machine_id in all_machines: intervals = intervals_per_resources[machine_id] if len(intervals) > 1: - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Makespan objective - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, job_ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, job_ends) + model.minimize(makespan) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print final solution. for job_id in all_jobs: print("Job %i:" % job_id) for task_id in range(len(jobs[job_id])): - start_value = solver.Value(starts[(job_id, task_id)]) + start_value = solver.value(starts[(job_id, task_id)]) machine = -1 duration = -1 selected = -1 for alt_id in range(len(jobs[job_id][task_id])): - if solver.Value(presences[(job_id, task_id, alt_id)]): + if solver.value(presences[(job_id, task_id, alt_id)]): duration = jobs[job_id][task_id][alt_id][0] machine = jobs[job_id][task_id][alt_id][1] selected = alt_id @@ -197,12 +197,12 @@ def flexible_jobshop(): % (job_id, task_id, start_value, selected, machine, duration) ) - print("Solve status: %s" % solver.StatusName(status)) - print("Optimal objective value: %i" % solver.ObjectiveValue()) + print("solve status: %s" % solver.status_name(status)) + print("Optimal objective value: %i" % solver.objective_value) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) flexible_jobshop() diff --git a/examples/python/gate_scheduling_sat.py b/examples/python/gate_scheduling_sat.py index 00aebdeebd6..67790f42dcc 100644 --- a/examples/python/gate_scheduling_sat.py +++ b/examples/python/gate_scheduling_sat.py @@ -67,66 +67,70 @@ def main(_): for i in all_jobs: # Create main interval. - start = model.NewIntVar(0, horizon, "start_%i" % i) + start = model.new_int_var(0, horizon, "start_%i" % i) duration = jobs[i][0] - end = model.NewIntVar(0, horizon, "end_%i" % i) - interval = model.NewIntervalVar(start, duration, end, "interval_%i" % i) + end = model.new_int_var(0, horizon, "end_%i" % i) + interval = model.new_interval_var(start, duration, end, "interval_%i" % i) starts.append(start) intervals.append(interval) ends.append(end) demands.append(jobs[i][1]) # Create an optional copy of interval to be executed on machine 0. - performed_on_m0 = model.NewBoolVar("perform_%i_on_m0" % i) + performed_on_m0 = model.new_bool_var("perform_%i_on_m0" % i) performed.append(performed_on_m0) - start0 = model.NewIntVar(0, horizon, "start_%i_on_m0" % i) - end0 = model.NewIntVar(0, horizon, "end_%i_on_m0" % i) - interval0 = model.NewOptionalIntervalVar( + start0 = model.new_int_var(0, horizon, "start_%i_on_m0" % i) + end0 = model.new_int_var(0, horizon, "end_%i_on_m0" % i) + interval0 = model.new_optional_interval_var( start0, duration, end0, performed_on_m0, "interval_%i_on_m0" % i ) intervals0.append(interval0) # Create an optional copy of interval to be executed on machine 1. - start1 = model.NewIntVar(0, horizon, "start_%i_on_m1" % i) - end1 = model.NewIntVar(0, horizon, "end_%i_on_m1" % i) - interval1 = model.NewOptionalIntervalVar( - start1, duration, end1, performed_on_m0.Not(), "interval_%i_on_m1" % i + start1 = model.new_int_var(0, horizon, "start_%i_on_m1" % i) + end1 = model.new_int_var(0, horizon, "end_%i_on_m1" % i) + interval1 = model.new_optional_interval_var( + start1, + duration, + end1, + performed_on_m0.negated(), + "interval_%i_on_m1" % i, ) intervals1.append(interval1) # We only propagate the constraint if the tasks is performed on the machine. - model.Add(start0 == start).OnlyEnforceIf(performed_on_m0) - model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not()) + model.add(start0 == start).only_enforce_if(performed_on_m0) + model.add(start1 == start).only_enforce_if(performed_on_m0.negated()) # Width constraint (modeled as a cumulative) - model.AddCumulative(intervals, demands, max_width) + model.add_cumulative(intervals, demands, max_width) # Choose which machine to perform the jobs on. - model.AddNoOverlap(intervals0) - model.AddNoOverlap(intervals1) + model.add_no_overlap(intervals0) + model.add_no_overlap(intervals1) # Objective variable. - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, ends) + model.minimize(makespan) # Symmetry breaking. - model.Add(performed[0] == 0) + model.add(performed[0] == 0) # Solve model. solver = cp_model.CpSolver() - solver.Solve(model) + solver.solve(model) # Output solution. if visualization.RunFromIPython(): - output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0) - output.AddTitle("Makespan = %i" % solver.ObjectiveValue()) + output = visualization.SvgWrapper(solver.objective_value, max_width, 40.0) + output.AddTitle("Makespan = %i" % solver.objective_value) color_manager = visualization.ColorManager() color_manager.SeedRandomColor(0) for i in all_jobs: - performed_machine = 1 - solver.Value(performed[i]) - start = solver.Value(starts[i]) + performed_machine = 1 - solver.value(performed[i]) + start = solver.value(starts[i]) d_x = jobs[i][0] d_y = jobs[i][1] s_y = performed_machine * (max_width - d_y) @@ -139,17 +143,17 @@ def main(_): output.Display() else: print("Solution") - print(" - makespan = %i" % solver.ObjectiveValue()) + print(" - makespan = %i" % solver.objective_value) for i in all_jobs: - performed_machine = 1 - solver.Value(performed[i]) - start = solver.Value(starts[i]) + performed_machine = 1 - solver.value(performed[i]) + start = solver.value(starts[i]) print( " - Job %i starts at %i on machine %i" % (i, start, performed_machine) ) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) if __name__ == "__main__": diff --git a/examples/python/golomb_sat.py b/examples/python/golomb_sat.py index c23a11a2b8f..561cf44fdd9 100644 --- a/examples/python/golomb_sat.py +++ b/examples/python/golomb_sat.py @@ -38,7 +38,7 @@ ) -def solve_golomb_ruler(order, params): +def solve_golomb_ruler(order: int, params: str): """Solve the Golomb ruler problem.""" # Create the model. model = cp_model.CpModel() @@ -46,26 +46,26 @@ def solve_golomb_ruler(order, params): var_max = order * order all_vars = list(range(0, order)) - marks = [model.NewIntVar(0, var_max, f"marks_{i}") for i in all_vars] + marks = [model.new_int_var(0, var_max, f"marks_{i}") for i in all_vars] - model.Add(marks[0] == 0) + model.add(marks[0] == 0) for i in range(order - 2): - model.Add(marks[i + 1] > marks[i]) + model.add(marks[i + 1] > marks[i]) diffs = [] for i in range(order - 1): for j in range(i + 1, order): - diff = model.NewIntVar(0, var_max, f"diff [{j},{i}]") - model.Add(diff == marks[j] - marks[i]) + diff = model.new_int_var(0, var_max, f"diff [{j},{i}]") + model.add(diff == marks[j] - marks[i]) diffs.append(diff) - model.AddAllDifferent(diffs) + model.add_all_different(diffs) # symmetry breaking if order > 2: - model.Add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0]) + model.add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0]) # Objective - model.Minimize(marks[order - 1]) + model.minimize(marks[order - 1]) # Solve the model. solver = cp_model.CpSolver() @@ -73,21 +73,21 @@ def solve_golomb_ruler(order, params): text_format.Parse(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() print(f"Golomb ruler(order={order})") - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print solution. - print(f"status: {solver.StatusName(status)}") + print(f"status: {solver.status_name(status)}") if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): for idx, var in enumerate(marks): - print(f"mark[{idx}]: {solver.Value(var)}") - intervals = [solver.Value(diff) for diff in diffs] + print(f"mark[{idx}]: {solver.value(var)}") + intervals = [solver.value(diff) for diff in diffs] intervals.sort() print(f"intervals: {intervals}") print("Statistics:") - print(f"- conflicts: {solver.NumConflicts()}") - print(f"- branches : {solver.NumBranches()}") - print(f"- wall time: {solver.WallTime()}s\n") + print(f"- conflicts: {solver.num_conflicts}") + print(f"- branches : {solver.num_branches}") + print(f"- wall time: {solver.wall_time}s\n") def main(argv: Sequence[str]) -> None: diff --git a/examples/python/hidato_sat.py b/examples/python/hidato_sat.py index 02fb4a9d9c7..eb84aa6cc6d 100755 --- a/examples/python/hidato_sat.py +++ b/examples/python/hidato_sat.py @@ -14,12 +14,13 @@ """Solves the Hidato problem with the CP-SAT solver.""" +from typing import Union from absl import app from ortools.sat.colab import visualization from ortools.sat.python import cp_model -def build_pairs(rows, cols): +def build_pairs(rows: int, cols: int) -> list[tuple[int, int]]: """Build closeness pairs for consecutive numbers. Build set of allowed pairs such that two consecutive numbers touch @@ -48,7 +49,7 @@ def build_pairs(rows, cols): return result -def print_solution(positions, rows, cols): +def print_solution(positions: list[int], rows: int, cols: int): """Print a current solution.""" # Create empty board. board = [] @@ -63,7 +64,7 @@ def print_solution(positions, rows, cols): print_matrix(board) -def print_matrix(game): +def print_matrix(game: list[list[int]]) -> None: """Pretty print of a matrix.""" rows = len(game) cols = len(game[0]) @@ -77,7 +78,7 @@ def print_matrix(game): print(line) -def build_puzzle(problem): +def build_puzzle(problem: int) -> Union[None, list[list[int]]]: """Build the problem from its index.""" # # models, a 0 indicates an open cell which number is not yet known. @@ -146,8 +147,8 @@ def build_puzzle(problem): return puzzle -def solve_hidato(puzzle, index): - """Solve the given hidato table.""" +def solve_hidato(puzzle: list[list[int]], index: int): + """solve the given hidato table.""" # Create the model. model = cp_model.CpModel() @@ -161,58 +162,58 @@ def solve_hidato(puzzle, index): print_matrix(puzzle) # - # declare variables + # Declare variables. # - positions = [model.NewIntVar(0, r * c - 1, "p[%i]" % i) for i in range(r * c)] + positions = [model.new_int_var(0, r * c - 1, "p[%i]" % i) for i in range(r * c)] # - # constraints + # Constraints. # - model.AddAllDifferent(positions) + model.add_all_different(positions) # - # Fill in the clues + # Fill in the clues. # for i in range(r): for j in range(c): if puzzle[i][j] > 0: - model.Add(positions[puzzle[i][j] - 1] == i * c + j) + model.add(positions[puzzle[i][j] - 1] == i * c + j) # Consecutive numbers much touch each other in the grid. # We use an allowed assignment constraint to model it. close_tuples = build_pairs(r, c) for k in range(0, r * c - 1): - model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples) + model.add_allowed_assignments([positions[k], positions[k + 1]], close_tuples) # - # solution and search + # Solution and search. # solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: if visualization.RunFromIPython(): output = visualization.SvgWrapper(10, r, 40.0) for i, var in enumerate(positions): - val = solver.Value(var) + val = solver.value(var) x = val % c y = val // c color = "white" if puzzle[y][x] == 0 else "lightgreen" output.AddRectangle(x, r - y - 1, 1, 1, color, "black", str(i + 1)) - output.AddTitle("Puzzle %i solved in %f s" % (index, solver.WallTime())) + output.AddTitle("Puzzle %i solved in %f s" % (index, solver.wall_time)) output.Display() else: print_solution( - [solver.Value(x) for x in positions], + [solver.value(x) for x in positions], r, c, ) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(_): diff --git a/examples/python/jobshop_ft06_distance_sat.py b/examples/python/jobshop_ft06_distance_sat.py index a0edaa02bd4..dbea74de66e 100755 --- a/examples/python/jobshop_ft06_distance_sat.py +++ b/examples/python/jobshop_ft06_distance_sat.py @@ -31,7 +31,7 @@ from ortools.sat.python import cp_model -def distance_between_jobs(x, y): +def distance_between_jobs(x: int, y: int) -> int: """Returns the distance between tasks of job x and tasks of job y.""" return abs(x - y) @@ -73,10 +73,10 @@ def jobshop_ft06_distance(): all_tasks = {} for i in all_jobs: for j in all_machines: - start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j)) + start_var = model.new_int_var(0, horizon, "start_%i_%i" % (i, j)) duration = durations[i][j] - end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) - interval_var = model.NewIntervalVar( + end_var = model.new_int_var(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval_%i_%i" % (i, j) ) all_tasks[(i, j)] = task_type( @@ -96,51 +96,51 @@ def jobshop_ft06_distance(): job_indices.append(j) job_starts.append(all_tasks[(j, k)].start) job_ends.append(all_tasks[(j, k)].end) - model.AddNoOverlap(job_intervals) + model.add_no_overlap(job_intervals) arcs = [] for j1 in range(len(job_intervals)): # Initial arc from the dummy node (0) to a task. - start_lit = model.NewBoolVar("%i is first job" % j1) + start_lit = model.new_bool_var("%i is first job" % j1) arcs.append((0, j1 + 1, start_lit)) # Final arc from an arc to the dummy node. - arcs.append((j1 + 1, 0, model.NewBoolVar("%i is last job" % j1))) + arcs.append((j1 + 1, 0, model.new_bool_var("%i is last job" % j1))) for j2 in range(len(job_intervals)): if j1 == j2: continue - lit = model.NewBoolVar("%i follows %i" % (j2, j1)) + lit = model.new_bool_var("%i follows %i" % (j2, j1)) arcs.append((j1 + 1, j2 + 1, lit)) # We add the reified precedence to link the literal with the # times of the two tasks. min_distance = distance_between_jobs(j1, j2) - model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf( - lit - ) + model.add( + job_starts[j2] >= job_ends[j1] + min_distance + ).only_enforce_if(lit) - model.AddCircuit(arcs) + model.add_circuit(arcs) # Precedences inside a job. for i in all_jobs: for j in range(0, machines_count - 1): - model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) + model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] ) - model.Minimize(obj_var) + model.minimize(obj_var) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # Output solution. if status == cp_model.OPTIMAL: - print("Optimal makespan: %i" % solver.ObjectiveValue()) + print("Optimal makespan: %i" % solver.objective_value) jobshop_ft06_distance() diff --git a/examples/python/jobshop_ft06_sat.py b/examples/python/jobshop_ft06_sat.py index 7de58ff9340..24bc31ca267 100755 --- a/examples/python/jobshop_ft06_sat.py +++ b/examples/python/jobshop_ft06_sat.py @@ -29,7 +29,7 @@ from ortools.sat.python import cp_model -def jobshop_ft06(): +def jobshop_ft06() -> None: """Solves the ft06 jobshop.""" # Creates the solver. model = cp_model.CpModel() @@ -66,10 +66,10 @@ def jobshop_ft06(): all_tasks = {} for i in all_jobs: for j in all_machines: - start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j)) + start_var = model.new_int_var(0, horizon, "start_%i_%i" % (i, j)) duration = durations[i][j] - end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) - interval_var = model.NewIntervalVar( + end_var = model.new_int_var(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval_%i_%i" % (i, j) ) all_tasks[(i, j)] = task_type( @@ -85,35 +85,35 @@ def jobshop_ft06(): if machines[j][k] == i: machines_jobs.append(all_tasks[(j, k)].interval) machine_to_jobs[i] = machines_jobs - model.AddNoOverlap(machines_jobs) + model.add_no_overlap(machines_jobs) # Precedences inside a job. for i in all_jobs: for j in range(0, machines_count - 1): - model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) + model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] ) - model.Minimize(obj_var) + model.minimize(obj_var) - # Solve model. + # Solve the model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) - # Output solution. + # Output the solution. if status == cp_model.OPTIMAL: if visualization.RunFromIPython(): starts = [ - [solver.Value(all_tasks[(i, j)][0]) for j in all_machines] + [solver.value(all_tasks[(i, j)][0]) for j in all_machines] for i in all_jobs ] visualization.DisplayJobshop(starts, durations, machines, "FT06") else: - print("Optimal makespan: %i" % solver.ObjectiveValue()) + print("Optimal makespan: %i" % solver.objective_value) jobshop_ft06() diff --git a/examples/python/jobshop_with_maintenance_sat.py b/examples/python/jobshop_with_maintenance_sat.py index 953ad9380d6..d9a7e76a89d 100644 --- a/examples/python/jobshop_with_maintenance_sat.py +++ b/examples/python/jobshop_with_maintenance_sat.py @@ -31,7 +31,7 @@ def on_solution_callback(self): """Called at each new solution.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 @@ -65,13 +65,14 @@ def jobshop_with_maintenance(): machine_to_intervals = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): - for task_id, task in enumerate(job): + for entry in enumerate(job): + task_id, task = entry machine = task[0] duration = task[1] suffix = "_%i_%i" % (job_id, task_id) - start_var = model.NewIntVar(0, horizon, "start" + suffix) - end_var = model.NewIntVar(0, horizon, "end" + suffix) - interval_var = model.NewIntervalVar( + start_var = model.new_int_var(0, horizon, "start" + suffix) + end_var = model.new_int_var(0, horizon, "end" + suffix) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval" + suffix ) all_tasks[job_id, task_id] = task_type( @@ -80,31 +81,31 @@ def jobshop_with_maintenance(): machine_to_intervals[machine].append(interval_var) # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}). - machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, "weekend_0")) + machine_to_intervals[0].append(model.new_interval_var(4, 4, 8, "weekend_0")) # Create and add disjunctive constraints. for machine in all_machines: - model.AddNoOverlap(machine_to_intervals[machine]) + model.add_no_overlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): - model.Add( + model.add( all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end ) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)], ) - model.Minimize(obj_var) + model.minimize(obj_var) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Output solution. if status == cp_model.OPTIMAL: @@ -115,7 +116,7 @@ def jobshop_with_maintenance(): machine = task[0] assigned_jobs[machine].append( assigned_task_type( - start=solver.Value(all_tasks[job_id, task_id].start), + start=solver.value(all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1], @@ -132,13 +133,13 @@ def jobshop_with_maintenance(): for assigned_task in assigned_jobs[machine]: name = "job_%i_%i" % (assigned_task.job, assigned_task.index) - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line_tasks += "%-10s" % name start = assigned_task.start duration = assigned_task.duration sol_tmp = "[%i,%i]" % (start, start + duration) - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line += "%-10s" % sol_tmp sol_line += "\n" @@ -147,12 +148,12 @@ def jobshop_with_maintenance(): output += sol_line # Finally print the solution found. - print("Optimal Schedule Length: %i" % solver.ObjectiveValue()) + print("Optimal Schedule Length: %i" % solver.objective_value) print(output) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/knapsack_2d_sat.py b/examples/python/knapsack_2d_sat.py index 3972a772671..3400f349659 100644 --- a/examples/python/knapsack_2d_sat.py +++ b/examples/python/knapsack_2d_sat.py @@ -29,6 +29,7 @@ from ortools.sat.python import cp_model + _OUTPUT_PROTO = flags.DEFINE_string( "output_proto", "", "Output file to write the cp_model proto to." ) @@ -42,7 +43,7 @@ ) -def build_data(): +def build_data() -> tuple[pd.Series, int, int]: """Build the data frame.""" data = """ item width height available value color @@ -70,8 +71,8 @@ def build_data(): return (data, max_height, max_width) -def solve_with_duplicate_items(data, max_height, max_width): - """Solve the problem by building 2 items (rotated or not) for each item.""" +def solve_with_duplicate_items(data: pd.Series, max_height: int, max_width: int): + """solve the problem by building 2 items (rotated or not) for each item.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -105,41 +106,47 @@ def solve_with_duplicate_items(data, max_height, max_width): for i in range(num_items): ## Is the item used? - is_used.append(model.NewBoolVar(f"is_used{i}")) + is_used.append(model.new_bool_var(f"is_used{i}")) ## Item coordinates. - x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}")) - x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}")) - y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}")) - y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}")) + x_starts.append(model.new_int_var(0, max_width, f"x_start{i}")) + x_ends.append(model.new_int_var(0, max_width, f"x_end{i}")) + y_starts.append(model.new_int_var(0, max_height, f"y_start{i}")) + y_ends.append(model.new_int_var(0, max_height, f"y_end{i}")) ## Interval variables. x_intervals.append( - model.NewIntervalVar( - x_starts[i], item_widths[i] * is_used[i], x_ends[i], f"x_interval{i}" + model.new_interval_var( + x_starts[i], + item_widths[i] * is_used[i], + x_ends[i], + f"x_interval{i}", ) ) y_intervals.append( - model.NewIntervalVar( - y_starts[i], item_heights[i] * is_used[i], y_ends[i], f"y_interval{i}" + model.new_interval_var( + y_starts[i], + item_heights[i] * is_used[i], + y_ends[i], + f"y_interval{i}", ) ) # Unused boxes are fixed at (0.0). - model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) - model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) + model.add(x_starts[i] == 0).only_enforce_if(is_used[i].negated()) + model.add(y_starts[i] == 0).only_enforce_if(is_used[i].negated()) # Constraints. ## Only one of non-rotated/rotated pair can be used. for i in range(num_data_items): - model.Add(is_used[i] + is_used[i + num_data_items] <= 1) + model.add(is_used[i] + is_used[i + num_data_items] <= 1) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) ## Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -152,27 +159,29 @@ def solve_with_duplicate_items(data, max_height, max_width): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], "item_width": [item_widths[i] for i in used], "item_height": [item_heights[i] for i in used], - "x_end": [solver.Value(x_ends[i]) for i in used], - "y_end": [solver.Value(y_ends[i]) for i in used], + "x_end": [solver.value(x_ends[i]) for i in used], + "y_end": [solver.value(y_ends[i]) for i in used], "item_value": [item_values[i] for i in used], } ) print(data) -def solve_with_duplicate_optional_items(data, max_height, max_width): - """Solve the problem by building 2 optional items (rotated or not) for each item.""" +def solve_with_duplicate_optional_items( + data: pd.Series, max_height: int, max_width: int +): + """solve the problem by building 2 optional items (rotated or not) for each item.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -204,42 +213,42 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): for i in range(num_items): ## Is the item used? - is_used.append(model.NewBoolVar(f"is_used{i}")) + is_used.append(model.new_bool_var(f"is_used{i}")) ## Item coordinates. x_starts.append( - model.NewIntVar(0, max_width - int(item_widths[i]), f"x_start{i}") + model.new_int_var(0, max_width - int(item_widths[i]), f"x_start{i}") ) y_starts.append( - model.NewIntVar(0, max_height - int(item_heights[i]), f"y_start{i}") + model.new_int_var(0, max_height - int(item_heights[i]), f"y_start{i}") ) ## Interval variables. x_intervals.append( - model.NewOptionalFixedSizeIntervalVar( + model.new_optional_fixed_size_interval_var( x_starts[i], item_widths[i], is_used[i], f"x_interval{i}" ) ) y_intervals.append( - model.NewOptionalFixedSizeIntervalVar( + model.new_optional_fixed_size_interval_var( y_starts[i], item_heights[i], is_used[i], f"y_interval{i}" ) ) # Unused boxes are fixed at (0.0). - model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) - model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) + model.add(x_starts[i] == 0).only_enforce_if(is_used[i].negated()) + model.add(y_starts[i] == 0).only_enforce_if(is_used[i].negated()) # Constraints. ## Only one of non-rotated/rotated pair can be used. for i in range(num_data_items): - model.Add(is_used[i] + is_used[i + num_data_items] <= 1) + model.add(is_used[i] + is_used[i + num_data_items] <= 1) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) ## Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -247,32 +256,32 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], "item_width": [item_widths[i] for i in used], "item_height": [item_heights[i] for i in used], - "x_end": [solver.Value(x_starts[i]) + item_widths[i] for i in used], - "y_end": [solver.Value(y_starts[i]) + item_heights[i] for i in used], + "x_end": [solver.value(x_starts[i]) + item_widths[i] for i in used], + "y_end": [solver.value(y_starts[i]) + item_heights[i] for i in used], "item_value": [item_values[i] for i in used], } ) print(data) -def solve_with_rotations(data, max_height, max_width): - """Solve the problem by rotating items.""" +def solve_with_rotations(data: pd.Series, max_height: int, max_width: int): + """solve the problem by rotating items.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -301,25 +310,29 @@ def solve_with_rotations(data, max_height, max_width): for i in range(num_items): sizes = [0, int(item_widths[i]), int(item_heights[i])] # X coordinates. - x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}")) + x_starts.append(model.new_int_var(0, max_width, f"x_start{i}")) x_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"x_size{i}") + model.new_int_var_from_domain( + cp_model.Domain.FromValues(sizes), f"x_size{i}" + ) ) - x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}")) + x_ends.append(model.new_int_var(0, max_width, f"x_end{i}")) # Y coordinates. - y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}")) + y_starts.append(model.new_int_var(0, max_height, f"y_start{i}")) y_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"y_size{i}") + model.new_int_var_from_domain( + cp_model.Domain.FromValues(sizes), f"y_size{i}" + ) ) - y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}")) + y_ends.append(model.new_int_var(0, max_height, f"y_end{i}")) ## Interval variables x_intervals.append( - model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i], f"x_interval{i}") + model.new_interval_var(x_starts[i], x_sizes[i], x_ends[i], f"x_interval{i}") ) y_intervals.append( - model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i], f"y_interval{i}") + model.new_interval_var(y_starts[i], y_sizes[i], y_ends[i], f"y_interval{i}") ) # is_used[i] == True if and only if item i is selected. @@ -329,34 +342,34 @@ def solve_with_rotations(data, max_height, max_width): ## for each item, decide is unselected, no_rotation, rotated. for i in range(num_items): - not_selected = model.NewBoolVar(f"not_selected_{i}") - no_rotation = model.NewBoolVar(f"no_rotation_{i}") - rotated = model.NewBoolVar(f"rotated_{i}") + not_selected = model.new_bool_var(f"not_selected_{i}") + no_rotation = model.new_bool_var(f"no_rotation_{i}") + rotated = model.new_bool_var(f"rotated_{i}") ### Exactly one state must be chosen. - model.AddExactlyOne(not_selected, no_rotation, rotated) + model.add_exactly_one(not_selected, no_rotation, rotated) ### Define height and width according to the state. dim1 = item_widths[i] dim2 = item_heights[i] # Unused boxes are fixed at (0.0). - model.Add(x_sizes[i] == 0).OnlyEnforceIf(not_selected) - model.Add(y_sizes[i] == 0).OnlyEnforceIf(not_selected) - model.Add(x_starts[i] == 0).OnlyEnforceIf(not_selected) - model.Add(y_starts[i] == 0).OnlyEnforceIf(not_selected) + model.add(x_sizes[i] == 0).only_enforce_if(not_selected) + model.add(y_sizes[i] == 0).only_enforce_if(not_selected) + model.add(x_starts[i] == 0).only_enforce_if(not_selected) + model.add(y_starts[i] == 0).only_enforce_if(not_selected) # Sizes are fixed by the rotation. - model.Add(x_sizes[i] == dim1).OnlyEnforceIf(no_rotation) - model.Add(y_sizes[i] == dim2).OnlyEnforceIf(no_rotation) - model.Add(x_sizes[i] == dim2).OnlyEnforceIf(rotated) - model.Add(y_sizes[i] == dim1).OnlyEnforceIf(rotated) + model.add(x_sizes[i] == dim1).only_enforce_if(no_rotation) + model.add(y_sizes[i] == dim2).only_enforce_if(no_rotation) + model.add(x_sizes[i] == dim2).only_enforce_if(rotated) + model.add(y_sizes[i] == dim1).only_enforce_if(rotated) - is_used.append(not_selected.Not()) + is_used.append(not_selected.negated()) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) # Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -364,24 +377,24 @@ def solve_with_rotations(data, max_height, max_width): with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], - "item_width": [solver.Value(x_sizes[i]) for i in used], - "item_height": [solver.Value(y_sizes[i]) for i in used], - "x_end": [solver.Value(x_ends[i]) for i in used], - "y_end": [solver.Value(y_ends[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], + "item_width": [solver.value(x_sizes[i]) for i in used], + "item_height": [solver.value(y_sizes[i]) for i in used], + "x_end": [solver.value(x_ends[i]) for i in used], + "y_end": [solver.value(y_ends[i]) for i in used], "item_value": [item_values[i] for i in used], } ) @@ -389,7 +402,7 @@ def solve_with_rotations(data, max_height, max_width): def main(_): - """Solve the problem with all models.""" + """solve the problem with all models.""" data, max_height, max_width = build_data() if _MODEL.value == "duplicate": solve_with_duplicate_items(data, max_height, max_width) diff --git a/examples/python/line_balancing_sat.py b/examples/python/line_balancing_sat.py index b2999ebfee1..47bd9506708 100644 --- a/examples/python/line_balancing_sat.py +++ b/examples/python/line_balancing_sat.py @@ -184,7 +184,7 @@ def solve_model_greedily(model): def solve_boolean_model(model, hint): - """Solve the given model.""" + """solve the given model.""" print("Solving using the Boolean model") # Model data @@ -207,73 +207,73 @@ def solve_boolean_model(model, hint): # Create the variables for t in all_tasks: for p in all_pods: - assign[t, p] = model.NewBoolVar(f"assign_{t}_{p}") - possible[t, p] = model.NewBoolVar(f"possible_{t}_{p}") + assign[t, p] = model.new_bool_var(f"assign_{t}_{p}") + possible[t, p] = model.new_bool_var(f"possible_{t}_{p}") # active[p] indicates if pod p is active. - active = [model.NewBoolVar(f"active_{p}") for p in all_pods] + active = [model.new_bool_var(f"active_{p}") for p in all_pods] # Each task is done on exactly one pod. for t in all_tasks: - model.AddExactlyOne([assign[t, p] for p in all_pods]) + model.add_exactly_one([assign[t, p] for p in all_pods]) # Total tasks assigned to one pod cannot exceed cycle time. for p in all_pods: - model.Add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time) + model.add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time) # Maintain the possible variables: # possible at pod p -> possible at any pod after p for t in all_tasks: for p in range(num_pods - 1): - model.AddImplication(possible[t, p], possible[t, p + 1]) + model.add_implication(possible[t, p], possible[t, p + 1]) # Link possible and active variables. for t in all_tasks: for p in all_pods: - model.AddImplication(assign[t, p], possible[t, p]) + model.add_implication(assign[t, p], possible[t, p]) if p > 1: - model.AddImplication(assign[t, p], possible[t, p - 1].Not()) + model.add_implication(assign[t, p], possible[t, p - 1].negated()) # Precedences. for before, after in precedences: for p in range(1, num_pods): - model.AddImplication(assign[before, p], possible[after, p - 1].Not()) + model.add_implication(assign[before, p], possible[after, p - 1].negated()) # Link active variables with the assign one. for p in all_pods: all_assign_vars = [assign[t, p] for t in all_tasks] for a in all_assign_vars: - model.AddImplication(a, active[p]) - model.AddBoolOr(all_assign_vars + [active[p].Not()]) + model.add_implication(a, active[p]) + model.add_bool_or(all_assign_vars + [active[p].negated()]) # Force pods to be contiguous. This is critical to get good lower bounds # on the objective, even if it makes feasibility harder. for p in range(1, num_pods): - model.AddImplication(active[p - 1].Not(), active[p].Not()) + model.add_implication(active[p - 1].negated(), active[p].negated()) for t in all_tasks: - model.AddImplication(active[p].Not(), possible[t, p - 1]) + model.add_implication(active[p].negated(), possible[t, p - 1]) # Objective. - model.Minimize(sum(active)) + model.minimize(sum(active)) - # Add search hinting from the greedy solution. + # add search hinting from the greedy solution. for t in all_tasks: - model.AddHint(assign[t, hint[t]], 1) + model.add_hint(assign[t, hint[t]], 1) if _OUTPUT_PROTO.value: print(f"Writing proto to {_OUTPUT_PROTO.value}") - model.ExportToFile(_OUTPUT_PROTO.value) + model.export_to_file(_OUTPUT_PROTO.value) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def solve_scheduling_model(model, hint): - """Solve the given model using a cumutive model.""" + """solve the given model using a cumutive model.""" print("Solving using the scheduling model") # Model data @@ -290,47 +290,49 @@ def solve_scheduling_model(model, hint): # pod[t] indicates on which pod the task is performed. pods = {} for t in all_tasks: - pods[t] = model.NewIntVar(0, num_pods - 1, f"pod_{t}") + pods[t] = model.new_int_var(0, num_pods - 1, f"pod_{t}") # Create the variables intervals = [] demands = [] for t in all_tasks: - interval = model.NewFixedSizeIntervalVar(pods[t], 1, "") + interval = model.new_fixed_size_interval_var(pods[t], 1, "") intervals.append(interval) demands.append(durations[t]) - # Add terminating interval as the objective. - obj_var = model.NewIntVar(1, num_pods, "obj_var") - obj_size = model.NewIntVar(1, num_pods, "obj_duration") - obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1, "obj_interval") + # add terminating interval as the objective. + obj_var = model.new_int_var(1, num_pods, "obj_var") + obj_size = model.new_int_var(1, num_pods, "obj_duration") + obj_interval = model.new_interval_var( + obj_var, obj_size, num_pods + 1, "obj_interval" + ) intervals.append(obj_interval) demands.append(cycle_time) # Cumulative constraint. - model.AddCumulative(intervals, demands, cycle_time) + model.add_cumulative(intervals, demands, cycle_time) # Precedences. for before, after in precedences: - model.Add(pods[after] >= pods[before]) + model.add(pods[after] >= pods[before]) # Objective. - model.Minimize(obj_var) + model.minimize(obj_var) - # Add search hinting from the greedy solution. + # add search hinting from the greedy solution. for t in all_tasks: - model.AddHint(pods[t], hint[t]) + model.add_hint(pods[t], hint[t]) if _OUTPUT_PROTO.value: print(f"Writing proto to{_OUTPUT_PROTO.value}") - model.ExportToFile(_OUTPUT_PROTO.value) + model.export_to_file(_OUTPUT_PROTO.value) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/maze_escape_sat.py b/examples/python/maze_escape_sat.py old mode 100755 new mode 100644 index cbd123bd70d..e01948b9c6d --- a/examples/python/maze_escape_sat.py +++ b/examples/python/maze_escape_sat.py @@ -50,8 +50,8 @@ def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, before_rank = position_to_rank[(x, y, z)] after_index = index_map[(x + dx, y + dy, z + dz)] after_rank = position_to_rank[(x + dx, y + dy, z + dz)] - move_literal = model.NewBoolVar("") - model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal) + move_literal = model.new_bool_var("") + model.add(after_rank == before_rank + 1).only_enforce_if(move_literal) arcs.append((before_index, after_index, move_literal)) @@ -79,13 +79,13 @@ def escape_the_maze(params, output_proto): position_to_rank = {} for coord in reverse_map: - position_to_rank[coord] = model.NewIntVar(0, counter - 1, f"rank_{coord}") + position_to_rank[coord] = model.new_int_var(0, counter - 1, f"rank_{coord}") # Path constraints. - model.Add(position_to_rank[start] == 0) - model.Add(position_to_rank[end] == counter - 1) + model.add(position_to_rank[start] == 0) + model.add(position_to_rank[end] == counter - 1) for i in range(len(boxes) - 1): - model.Add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]]) + model.add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]]) # Circuit constraint: visit all blocks exactly once, and maintains the rank # of each block. @@ -116,18 +116,18 @@ def escape_the_maze(params, output_proto): arcs.append((index_map[end], index_map[start], True)) # Adds the circuit (hamiltonian path) constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) # Exports the model if required. if output_proto: - model.ExportToFile(output_proto) + model.export_to_file(output_proto) # Solve model. solver = cp_model.CpSolver() if params: text_format.Parse(params, solver.parameters) solver.parameters.log_search_progress = True - result = solver.Solve(model) + result = solver.solve(model) # Prints solution. if result == cp_model.OPTIMAL: @@ -136,7 +136,7 @@ def escape_the_maze(params, output_proto): for y in range(size): for z in range(size): position = (x, y, z) - rank = solver.Value(position_to_rank[position]) + rank = solver.value(position_to_rank[position]) msg = f"({x}, {y}, {z})" if position == start: msg += " [start]" diff --git a/examples/python/memory_layout_and_infeasibility_sat.py b/examples/python/memory_layout_and_infeasibility_sat.py new file mode 100644 index 00000000000..51b970f7259 --- /dev/null +++ b/examples/python/memory_layout_and_infeasibility_sat.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# 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. + +"""Solves the memory allocation problem, and returns a minimal set of demands to explain infeasibility.""" + +from collections.abc import Sequence +from typing import List + +from absl import app +from absl import flags + +from google.protobuf import text_format +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_workers:1,linearization_level:2", "Sat solver parameters." +) + + +# Input of the problem. +DEMANDS = [ + [1578, 1583, 43008, 1], + [1588, 1589, 11264, 1], + [1590, 1595, 43008, 1], + [1583, 1588, 47872, 1], + [1589, 1590, 22848, 1], + [1586, 1590, 22848, 1], + [1591, 1594, 43008, 1], +] +CAPACITY = 98304 + + +def solve_hard_model(output_proto: str, params: str) -> bool: + """Solves the hard assignment model.""" + print("Solving the hard assignment model") + model = cp_model.CpModel() + + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + x_interval = model.new_fixed_size_interval_var(start, end - start + 1, "") + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_fixed_size_interval_var(y_start, demand, "") + + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + + if output_proto: + model.export_to_file(output_proto) + + solver = cp_model.CpSolver() + if params: + text_format.Parse(params, solver.parameters) + status = solver.solve(model) + print(solver.response_stats()) + + if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: + for index, start in enumerate(y_starts): + print(f"task {index} buffer starts at {solver.value(start)}") + + return status != cp_model.INFEASIBLE + + +def solve_soft_model_with_assumptions() -> None: + """Solves the soft model using assumptions.""" + print("Solving the soft model using assumptions") + + model = cp_model.CpModel() + + presences: List[cp_model.IntVar] = [] + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + presence = model.new_bool_var("") + x_interval = model.new_optional_fixed_size_interval_var( + start, end - start + 1, presence, "" + ) + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_optional_fixed_size_interval_var( + y_start, demand, presence, "" + ) + + presences.append(presence) + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + model.add_assumptions(presences) + + solver = cp_model.CpSolver() + status = solver.solve(model) + print(solver.response_stats()) + if status == cp_model.INFEASIBLE: + # The list actually contains the indices of the variables sufficient to + # explain infeasibility. + infeasible_variable_indices = solver.sufficient_assumptions_for_infeasibility() + infeasible_variable_indices_set = set(infeasible_variable_indices) + + for index, presence in enumerate(presences): + if presence.index in infeasible_variable_indices_set: + print(f"using task {index} is sufficient to explain infeasibility") + + +def solve_soft_model_with_maximization(params: str) -> None: + """Solves the soft model using maximization.""" + print("Solving the soft model using minimization") + + model = cp_model.CpModel() + + presences: List[cp_model.IntVar] = [] + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + presence = model.new_bool_var("") + x_interval = model.new_optional_fixed_size_interval_var( + start, end - start + 1, presence, "" + ) + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_optional_fixed_size_interval_var( + y_start, demand, presence, "" + ) + + presences.append(presence) + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + + model.maximize(sum(presences)) + + solver = cp_model.CpSolver() + if params: + text_format.Parse(params, solver.parameters) + status = solver.solve(model) + print(solver.response_stats()) + if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: + for index, presence in enumerate(presences): + if not solver.boolean_value(presence): + print(f"task {index} does not fit") + else: + print(f"task {index} buffer starts at {solver.value(y_starts[index])}") + + +def main(argv: Sequence[str]) -> None: + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + if not solve_hard_model(_OUTPUT_PROTO.value, _PARAMS.value): + solve_soft_model_with_assumptions() + solve_soft_model_with_maximization(_PARAMS.value) + + +if __name__ == "__main__": + app.run(main) diff --git a/examples/python/no_wait_baking_scheduling_sat.py b/examples/python/no_wait_baking_scheduling_sat.py old mode 100755 new mode 100644 index 4cd2ab09717..1c970b1aad1 --- a/examples/python/no_wait_baking_scheduling_sat.py +++ b/examples/python/no_wait_baking_scheduling_sat.py @@ -232,68 +232,68 @@ def solve_with_cp_sat(recipes, resources, orders): start = None if previous_end is None: - start = model.NewIntVar(start_work, horizon, f"start{suffix}") + start = model.new_int_var(start_work, horizon, f"start{suffix}") orders_sequence_of_events[order_id].append( (start, f"start{suffix}") ) else: start = previous_end - size = model.NewIntVar( + size = model.new_int_var( task.min_duration, task.max_duration, f"size{suffix}" ) end = None if task == recipe.tasks[-1]: # The order must end after the due_date. Ideally, exactly at the # due_date. - tardiness = model.NewIntVar(0, horizon - due_date, f"end{suffix}") + tardiness = model.new_int_var(0, horizon - due_date, f"end{suffix}") end = tardiness + due_date # Store the end_var for the objective. tardiness_vars.append(tardiness) else: - end = model.NewIntVar(start_work, horizon, f"end{suffix}") + end = model.new_int_var(start_work, horizon, f"end{suffix}") orders_sequence_of_events[order_id].append((end, f"end{suffix}")) previous_end = end # Per resource copy. presence_literals = [] for resource in resource_list_by_skill_name[skill_name]: - presence = model.NewBoolVar(f"presence{suffix}_{resource.name}") - copy = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence{suffix}_{resource.name}") + copy = model.new_optional_interval_var( start, size, end, presence, f"interval{suffix}_{resource.name}" ) interval_list_by_resource_name[resource.name].append(copy) presence_literals.append(presence) # Only one copy will be performed. - model.AddExactlyOne(presence_literals) + model.add_exactly_one(presence_literals) # Create resource constraints. for resource in resources: intervals = interval_list_by_resource_name[resource.name] if resource.capacity == 1: - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) else: - model.AddCumulative(intervals, [1] * len(intervals), resource.capacity) + model.add_cumulative(intervals, [1] * len(intervals), resource.capacity) # The objective is to minimize the sum of the tardiness values of each jobs. # The tardiness is difference between the end time of an order and its # due date. - model.Minimize(sum(tardiness_vars)) + model.minimize(sum(tardiness_vars)) # Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for order_id in sorted_orders: print(f"{order_id}:") for time_expr, event_id in orders_sequence_of_events[order_id]: - time = solver.Value(time_expr) + time = solver.value(time_expr) print(f" {event_id} at {time // 60}:{time % 60:02}") diff --git a/examples/python/nqueens_sat.py b/examples/python/nqueens_sat.py index fb93eebb9d2..a2c32d87c28 100644 --- a/examples/python/nqueens_sat.py +++ b/examples/python/nqueens_sat.py @@ -26,16 +26,17 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, queens): + def __init__(self, queens: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__queens = queens self.__solution_count = 0 self.__start_time = time.time() - def SolutionCount(self): + @property + def solution_count(self) -> int: return self.__solution_count - def on_solution_callback(self): + def on_solution_callback(self) -> None: current_time = time.time() print( "Solution %i, time = %f s" @@ -46,7 +47,7 @@ def on_solution_callback(self): all_queens = range(len(self.__queens)) for i in all_queens: for j in all_queens: - if self.Value(self.__queens[j]) == i: + if self.value(self.__queens[j]) == i: # There is a queen in column j, row i. print("Q", end=" ") else: @@ -63,41 +64,43 @@ def main(_): ### Creates the variables. # The array index is the column, and the value is the row. - queens = [model.NewIntVar(0, board_size - 1, "x%i" % i) for i in range(board_size)] + queens = [ + model.new_int_var(0, board_size - 1, "x%i" % i) for i in range(board_size) + ] ### Creates the constraints. # All columns must be different because the indices of queens are all # different, so we just add the all different constraint on the rows. - model.AddAllDifferent(queens) + model.add_all_different(queens) # No two queens can be on the same diagonal. diag1 = [] diag2 = [] for i in range(board_size): - q1 = model.NewIntVar(0, 2 * board_size, "diag1_%i" % i) - q2 = model.NewIntVar(-board_size, board_size, "diag2_%i" % i) + q1 = model.new_int_var(0, 2 * board_size, "diag1_%i" % i) + q2 = model.new_int_var(-board_size, board_size, "diag2_%i" % i) diag1.append(q1) diag2.append(q2) - model.Add(q1 == queens[i] + i) - model.Add(q2 == queens[i] - i) - model.AddAllDifferent(diag1) - model.AddAllDifferent(diag2) + model.add(q1 == queens[i] + i) + model.add(q2 == queens[i] - i) + model.add_all_different(diag1) + model.add_all_different(diag2) ### Solve model. solver = cp_model.CpSolver() solution_printer = NQueenSolutionPrinter(queens) # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True - # Solve. - solver.Solve(model, solution_printer) + # solve. + solver.solve(model, solution_printer) print() print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) - print(" - solutions found : %i" % solution_printer.SolutionCount()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) + print(" - solutions found : %i" % solution_printer.solution_count) if __name__ == "__main__": diff --git a/examples/python/prize_collecting_tsp_sat.py b/examples/python/prize_collecting_tsp_sat.py old mode 100755 new mode 100644 index a82e4657411..bdb363f3b26 --- a/examples/python/prize_collecting_tsp_sat.py +++ b/examples/python/prize_collecting_tsp_sat.py @@ -71,14 +71,19 @@ # Create a console solution printer. -def print_solution(solver, visited_nodes, used_arcs, num_nodes): +def print_solution( + solver: cp_model.CpSolver, + visited_nodes: list[cp_model.IntVar], + used_arcs: dict[tuple[int, int], cp_model.IntVar], + num_nodes: int, +) -> None: """Prints solution on console.""" # Display dropped nodes. dropped_nodes = "Dropped nodes:" for i in range(num_nodes): if i == 0: continue - if not solver.BooleanValue(visited_nodes[i]): + if not solver.boolean_value(visited_nodes[i]): dropped_nodes += f" {i}({VISIT_VALUES[i]})" print(dropped_nodes) # Display routes @@ -94,7 +99,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes): for node in range(num_nodes): if node == current_node: continue - if solver.BooleanValue(used_arcs[current_node, node]): + if solver.boolean_value(used_arcs[current_node, node]): route_distance += DISTANCE_MATRIX[current_node][node] current_node = node if current_node == 0: @@ -102,7 +107,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes): break plan_output += f" {current_node}\n" plan_output += f"Distance of the route: {route_distance}m\n" - plan_output += f"Value collected: {value_collected}/{sum(VISIT_VALUES)}\n" + plan_output += f"value collected: {value_collected}/{sum(VISIT_VALUES)}\n" print(plan_output) @@ -123,8 +128,8 @@ def prize_collecting_tsp(): # Create the circuit constraint. arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f"{i} is visited") - arcs.append((i, i, is_visited.Not())) + is_visited = model.new_bool_var(f"{i} is visited") + arcs.append((i, i, is_visited.negated())) obj_vars.append(is_visited) obj_coeffs.append(VISIT_VALUES[i]) @@ -132,22 +137,22 @@ def prize_collecting_tsp(): for j in all_nodes: if i == j: - used_arcs[i, j] = is_visited.Not() + used_arcs[i, j] = is_visited.negated() continue - arc_is_used = model.NewBoolVar(f"{j} follows {i}") + arc_is_used = model.new_bool_var(f"{j} follows {i}") arcs.append((i, j, arc_is_used)) obj_vars.append(arc_is_used) obj_coeffs.append(-DISTANCE_MATRIX[i][j]) used_arcs[i, j] = arc_is_used - model.AddCircuit(arcs) + model.add_circuit(arcs) # Node 0 must be visited. - model.Add(visited_nodes[0] == 1) + model.add(visited_nodes[0] == 1) # limit the route distance - model.Add( + model.add( sum( used_arcs[i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes @@ -157,7 +162,7 @@ def prize_collecting_tsp(): ) # Maximize visited node values minus the travelled distance. - model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -166,7 +171,7 @@ def prize_collecting_tsp(): solver.parameters.num_search_workers = 8 solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print_solution(solver, visited_nodes, used_arcs, num_nodes) diff --git a/examples/python/prize_collecting_vrp_sat.py b/examples/python/prize_collecting_vrp_sat.py old mode 100755 new mode 100644 index 73873232500..d0aee475ff3 --- a/examples/python/prize_collecting_vrp_sat.py +++ b/examples/python/prize_collecting_vrp_sat.py @@ -71,7 +71,13 @@ # Create a console solution printer. -def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): +def print_solution( + solver: cp_model.CpSolver, + visited_nodes: dict[int, list[cp_model.IntVar]], + used_arcs: dict[int, dict[tuple[int, int], cp_model.IntVar]], + num_nodes: int, + num_vehicles: int, +) -> None: """Prints solution on console.""" # Display dropped nodes. dropped_nodes = "Dropped nodes:" @@ -79,7 +85,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): if node == 0: continue is_visited = sum( - [solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)] + [solver.boolean_value(visited_nodes[v][node]) for v in range(num_vehicles)] ) if not is_visited: dropped_nodes += f" {node}({VISIT_VALUES[node]})" @@ -100,7 +106,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): for node in range(num_nodes): if node == current_node: continue - if solver.BooleanValue(used_arcs[v][current_node, node]): + if solver.boolean_value(used_arcs[v][current_node, node]): route_distance += DISTANCE_MATRIX[current_node][node] current_node = node if current_node == 0: @@ -108,12 +114,12 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): break plan_output += f" {current_node}\n" plan_output += f"Distance of the route: {route_distance}m\n" - plan_output += f"Value collected: {value_collected}\n" + plan_output += f"value collected: {value_collected}\n" print(plan_output) total_distance += route_distance total_value_collected += value_collected print(f"Total Distance: {total_distance}m") - print(f"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}") + print(f"Total value collected: {total_value_collected}/{sum(VISIT_VALUES)}") def prize_collecting_vrp(): @@ -137,8 +143,8 @@ def prize_collecting_vrp(): used_arcs[v] = {} arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f"{i} is visited") - arcs.append((i, i, is_visited.Not())) + is_visited = model.new_bool_var(f"{i} is visited") + arcs.append((i, i, is_visited.negated())) obj_vars.append(is_visited) obj_coeffs.append(VISIT_VALUES[i]) @@ -146,22 +152,22 @@ def prize_collecting_vrp(): for j in all_nodes: if i == j: - used_arcs[v][i, j] = is_visited.Not() + used_arcs[v][i, j] = is_visited.negated() continue - arc_is_used = model.NewBoolVar(f"{j} follows {i}") + arc_is_used = model.new_bool_var(f"{j} follows {i}") arcs.append((i, j, arc_is_used)) obj_vars.append(arc_is_used) obj_coeffs.append(-DISTANCE_MATRIX[i][j]) used_arcs[v][i, j] = arc_is_used - model.AddCircuit(arcs) + model.add_circuit(arcs) # Node 0 must be visited. - model.Add(visited_nodes[v][0] == 1) + model.add(visited_nodes[v][0] == 1) # limit the route distance - model.Add( + model.add( sum( used_arcs[v][i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes @@ -172,10 +178,10 @@ def prize_collecting_vrp(): # Each node is visited at most once for node in range(1, num_nodes): - model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)]) + model.add_at_most_one([visited_nodes[v][node] for v in range(num_vehicles)]) # Maximize visited node values minus the travelled distance. - model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -183,7 +189,7 @@ def prize_collecting_vrp(): solver.parameters.max_time_in_seconds = 15.0 solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles) diff --git a/examples/python/proto_solve.py b/examples/python/proto_solve.py index 4fb965cbbd3..523d2f2c202 100644 --- a/examples/python/proto_solve.py +++ b/examples/python/proto_solve.py @@ -20,7 +20,7 @@ def main(_): # Create solver. solver = model_builder.ModelSolver(_SOLVER.value) - if not solver: + if not solver.solver_is_supported(): print(f'Cannot create solver with name \'{_SOLVER.value}\'') return @@ -36,4 +36,4 @@ def main(_): if __name__ == '__main__': - app.run(main) \ No newline at end of file + app.run(main) diff --git a/examples/python/qubo_sat.py b/examples/python/qubo_sat.py index 3d7364f837a..9af9e02c2ab 100644 --- a/examples/python/qubo_sat.py +++ b/examples/python/qubo_sat.py @@ -653,14 +653,14 @@ def solve_qubo(): - """Solve the Qubo problem.""" + """solve the Qubo problem.""" - # Constraint programming engine + # Build the model. model = cp_model.CpModel() num_vars = len(RAW_DATA) all_vars = range(num_vars) - variables = [model.NewBoolVar("x_%i" % i) for i in all_vars] + variables = [model.new_bool_var("x_%i" % i) for i in all_vars] obj_vars = [] obj_coeffs = [] @@ -672,10 +672,10 @@ def solve_qubo(): if coeff == 0.0: continue x_j = variables[j] - var = model.NewBoolVar("") - model.AddBoolOr([x_i.Not(), x_j.Not(), var]) - model.AddImplication(var, x_i) - model.AddImplication(var, x_j) + var = model.new_bool_var("") + model.add_bool_or([x_i.negated(), x_j.negated(), var]) + model.add_implication(var, x_i) + model.add_implication(var, x_j) obj_vars.append(var) obj_coeffs.append(coeff) @@ -685,14 +685,14 @@ def solve_qubo(): obj_vars.append(variables[i]) obj_coeffs.append(self_coeff) - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) ### Solve model. 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) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/rcpsp_sat.py b/examples/python/rcpsp_sat.py old mode 100755 new mode 100644 index 1cd34e69328..e698ebd6092 --- a/examples/python/rcpsp_sat.py +++ b/examples/python/rcpsp_sat.py @@ -19,10 +19,10 @@ Data use in flags: http://www.om-db.wi.tum.de/psplib/data.html - """ import collections +from typing import Optional from absl import app from absl import flags @@ -46,7 +46,7 @@ _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS = flags.DEFINE_bool( "add_redundant_energetic_constraints", False, - "Add redundant energetic constraints on the pairs of tasks extracted from" + "add redundant energetic constraints on the pairs of tasks extracted from" + " precedence graph.", ) _DELAY_TIME_LIMIT = flags.DEFINE_float( @@ -63,7 +63,7 @@ ) -def PrintProblemStatistics(problem): +def print_problem_statistics(problem: rcpsp_pb2.RcpspProblem): """Display various statistics on the problem.""" # Determine problem type. @@ -108,7 +108,9 @@ def PrintProblemStatistics(problem): print(f" - {tasks_with_delay} tasks with successor delays") -def AnalyseDependencyGraph(problem): +def analyse_dependency_graph( + problem: rcpsp_pb2.RcpspProblem, +) -> tuple[list[tuple[int, int, list[int]]], dict[int, list[int]]]: """Analyses the dependency graph to improve the model. Args: @@ -144,7 +146,7 @@ def AnalyseDependencyGraph(problem): # Search for pair of tasks, containing at least two parallel branch between # them in the precedence graph. num_candidates = 0 - result = [] + result: list[tuple[int, int, list[int]]] = [] for source, start_outs in outs.items(): if len(start_outs) <= 1: # Starting with the unique successor of source will be as good. @@ -177,27 +179,27 @@ def AnalyseDependencyGraph(problem): result.append((source, sink, common)) # Sort entries lexicographically by (len(common), source, sink) - def Price(entry): + def price(entry): return num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] + entry[1] - result.sort(key=Price) + result.sort(key=price) print(f" - created {len(result)} pairs of nodes to examine", flush=True) return result, after -def SolveRcpsp( - problem, - proto_file, - params, - active_tasks, - source, - sink, - intervals_of_tasks, - delays, - in_main_solve=False, - initial_solution=None, - lower_bound=0, -): +def solve_rcpsp( + problem: rcpsp_pb2.RcpspProblem, + proto_file: str, + params: str, + active_tasks: set[int], + source: int, + sink: int, + intervals_of_tasks: list[tuple[int, int, list[int]]], + delays: dict[tuple[int, int], tuple[int, int]], + in_main_solve: bool = False, + initial_solution: Optional[rcpsp_pb2.RcpspAssignment] = None, + lower_bound: int = 0, +) -> tuple[int, int, Optional[rcpsp_pb2.RcpspAssignment]]: """Parse and solve a given RCPSP problem in proto format. The model will only look at the tasks {source} + {sink} + active_tasks, and @@ -224,7 +226,7 @@ def SolveRcpsp( """ # Create the model. model = cp_model.CpModel() - model.SetName(problem.name) + model.name = problem.name num_resources = len(problem.resources) @@ -269,16 +271,16 @@ def SolveRcpsp( num_recipes = len(task.recipes) all_recipes = range(num_recipes) - start_var = model.NewIntVar(0, horizon, f"start_of_task_{t}") - end_var = model.NewIntVar(0, horizon, f"end_of_task_{t}") + start_var = model.new_int_var(0, horizon, f"start_of_task_{t}") + end_var = model.new_int_var(0, horizon, f"end_of_task_{t}") literals = [] if num_recipes > 1: # Create one literal per recipe. - literals = [model.NewBoolVar(f"is_present_{t}_{r}") for r in all_recipes] + literals = [model.new_bool_var(f"is_present_{t}_{r}") for r in all_recipes] # Exactly one recipe must be performed. - model.AddExactlyOne(literals) + model.add_exactly_one(literals) else: literals = [1] @@ -293,19 +295,19 @@ def SolveRcpsp( demand_matrix[(resource, recipe_index)] = demand # Create the duration variable from the accumulated durations. - duration_var = model.NewIntVarFromDomain( - cp_model.Domain.FromValues(task_to_recipe_durations[t]), + duration_var = model.new_int_var_from_domain( + cp_model.Domain.from_values(task_to_recipe_durations[t]), f"duration_of_task_{t}", ) # Link the recipe literals and the duration_var. for r in range(num_recipes): - model.Add(duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf( + model.add(duration_var == task_to_recipe_durations[t][r]).only_enforce_if( literals[r] ) # Create the interval of the task. - task_interval = model.NewIntervalVar( + task_interval = model.new_interval_var( start_var, duration_var, end_var, f"task_interval_{t}" ) @@ -320,14 +322,14 @@ def SolveRcpsp( for res in all_resources: demands = [demand_matrix[(res, recipe)] for recipe in all_recipes] task_resource_to_fixed_demands[(t, res)] = demands - demand_var = model.NewIntVarFromDomain( - cp_model.Domain.FromValues(demands), f"demand_{t}_{res}" + demand_var = model.new_int_var_from_domain( + cp_model.Domain.from_values(demands), f"demand_{t}_{res}" ) task_to_resource_demands[t].append(demand_var) # Link the recipe literals and the demand_var. for r in all_recipes: - model.Add(demand_var == demand_matrix[(res, r)]).OnlyEnforceIf( + model.add(demand_var == demand_matrix[(res, r)]).only_enforce_if( literals[r] ) @@ -348,10 +350,13 @@ def SolveRcpsp( ) # Create makespan variable - makespan = model.NewIntVar(lower_bound, horizon, "makespan") - makespan_size = model.NewIntVar(1, horizon, "interval_makespan_size") - interval_makespan = model.NewIntervalVar( - makespan, makespan_size, model.NewConstant(horizon + 1), "interval_makespan" + makespan = model.new_int_var(lower_bound, horizon, "makespan") + makespan_size = model.new_int_var(1, horizon, "interval_makespan_size") + interval_makespan = model.new_interval_var( + makespan, + makespan_size, + model.new_constant(horizon + 1), + "interval_makespan", ) # Add precedences. @@ -370,21 +375,21 @@ def SolveRcpsp( p1 = task_to_presence_literals[task_id][m1] if next_id == sink: delay = delay_matrix.recipe_delays[m1].min_delays[0] - model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1) + model.add(s1 + delay <= makespan).only_enforce_if(p1) else: for m2 in range(num_next_modes): delay = delay_matrix.recipe_delays[m1].min_delays[m2] s2 = task_starts[next_id] p2 = task_to_presence_literals[next_id][m2] - model.Add(s1 + delay <= s2).OnlyEnforceIf([p1, p2]) + model.add(s1 + delay <= s2).only_enforce_if([p1, p2]) else: # Normal dependencies (task ends before the start of successors). for t in all_active_tasks: for n in problem.tasks[t].successors: if n == sink: - model.Add(task_ends[t] <= makespan) + model.add(task_ends[t] <= makespan) elif n in active_tasks: - model.Add(task_ends[t] <= task_starts[n]) + model.add(task_ends[t] <= task_starts[n]) # Containers for resource investment problems. capacities = [] # Capacity variables for all resources. @@ -404,8 +409,8 @@ def SolveRcpsp( demands = [task_to_resource_demands[t][res] for t in all_active_tasks] if problem.is_resource_investment: - capacity = model.NewIntVar(0, c, f"capacity_of_{res}") - model.AddCumulative(intervals, demands, capacity) + capacity = model.new_int_var(0, c, f"capacity_of_{res}") + model.add_cumulative(intervals, demands, capacity) capacities.append(capacity) max_cost += c * resource.unit_cost else: # Standard renewable resource. @@ -413,7 +418,7 @@ def SolveRcpsp( intervals.append(interval_makespan) demands.append(c) - model.AddCumulative(intervals, demands, c) + model.add_cumulative(intervals, demands, c) else: # Non empty non renewable resource. (single mode only) if problem.is_consumer_producer: reservoir_starts = [] @@ -424,15 +429,15 @@ def SolveRcpsp( reservoir_demands.append( task_resource_to_fixed_demands[(t, res)][0] ) - model.AddReservoirConstraint( + model.add_reservoir_constraint( reservoir_starts, reservoir_demands, resource.min_capacity, resource.max_capacity, ) else: # No producer-consumer. We just sum the demands. - model.Add( - cp_model.LinearExpr.Sum( + model.add( + cp_model.LinearExpr.sum( [task_to_resource_demands[t][res] for t in all_active_tasks] ) <= c @@ -440,8 +445,8 @@ def SolveRcpsp( # Objective. if problem.is_resource_investment: - objective = model.NewIntVar(0, max_cost, "capacity_costs") - model.Add( + objective = model.new_int_var(0, max_cost, "capacity_costs") + model.add( objective == sum( problem.resources[i].unit_cost * capacities[i] @@ -451,17 +456,17 @@ def SolveRcpsp( else: objective = makespan - model.Minimize(objective) + model.minimize(objective) # Add min delay constraints. if delays is not None: for (local_start, local_end), (min_delay, _) in delays.items(): if local_start == source and local_end in active_tasks: - model.Add(task_starts[local_end] >= min_delay) + model.add(task_starts[local_end] >= min_delay) elif local_start in active_tasks and local_end == sink: - model.Add(makespan >= task_ends[local_start] + min_delay) + model.add(makespan >= task_ends[local_start] + min_delay) elif local_start in active_tasks and local_end in active_tasks: - model.Add(task_starts[local_end] >= task_ends[local_start] + min_delay) + model.add(task_starts[local_end] >= task_ends[local_start] + min_delay) problem_is_single_mode = True for t in all_active_tasks: @@ -504,7 +509,7 @@ def SolveRcpsp( if sum_of_max_energies <= c * min_delay: ignored_constraits += 1 continue - model.Add( + model.add( c * (task_starts[local_end] - task_ends[local_start]) >= sum(task_resource_to_energy[(t, res)] for t in common) ) @@ -518,15 +523,15 @@ def SolveRcpsp( # Add solution hint. if initial_solution: for t in all_active_tasks: - model.AddHint(task_starts[t], initial_solution.start_of_task[t]) + model.add_hint(task_starts[t], initial_solution.start_of_task[t]) if len(task_to_presence_literals[t]) > 1: selected = initial_solution.selected_recipe_of_task[t] - model.AddHint(task_to_presence_literals[t][selected], 1) + model.add_hint(task_to_presence_literals[t][selected], 1) # Write model to file. if proto_file: print(f"Writing proto to{proto_file}") - model.ExportToFile(proto_file) + model.export_to_file(proto_file) # Solve model. solver = cp_model.CpSolver() @@ -548,28 +553,35 @@ def SolveRcpsp( if in_main_solve: solver.parameters.log_search_progress = True # - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: assignment = rcpsp_pb2.RcpspAssignment() for t, _ in enumerate(problem.tasks): if t in task_starts: - assignment.start_of_task.append(solver.Value(task_starts[t])) + assignment.start_of_task.append(solver.value(task_starts[t])) for r, recipe_literal in enumerate(task_to_presence_literals[t]): - if solver.BooleanValue(recipe_literal): + if solver.boolean_value(recipe_literal): assignment.selected_recipe_of_task.append(r) break else: # t is not an active task. assignment.start_of_task.append(0) assignment.selected_recipe_of_task.append(0) return ( - int(solver.BestObjectiveBound()), - int(solver.ObjectiveValue()), + int(solver.best_objective_bound), + int(solver.objective_value), assignment, ) return -1, -1, None -def ComputeDelaysBetweenNodes(problem, task_intervals): +def compute_delays_between_nodes( + problem: rcpsp_pb2.RcpspProblem, + task_intervals: list[tuple[int, int, list[int]]], +) -> tuple[ + dict[tuple[int, int], tuple[int, int]], + Optional[rcpsp_pb2.RcpspAssignment], + bool, +]: """Computes the min delays between all pairs of tasks in 'task_intervals'. Args: @@ -594,11 +606,11 @@ def ComputeDelaysBetweenNodes(problem, task_intervals): num_delays_not_found = 0 optimal_found = True for start_task, end_task, active_tasks in task_intervals: - min_delay, feasible_delay, assignment = SolveRcpsp( + min_delay, feasible_delay, assignment = solve_rcpsp( problem, "", f"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}", - active_tasks, + set(active_tasks), start_task, end_task, [], @@ -623,7 +635,13 @@ def ComputeDelaysBetweenNodes(problem, task_intervals): return delays, complete_problem_assignment, optimal_found -def AcceptNewCandidate(problem, after, demand_map, current, candidate): +def accept_new_candidate( + problem: rcpsp_pb2.RcpspProblem, + after: dict[int, list[int]], + demand_map: dict[tuple[int, int], int], + current: list[int], + candidate: int, +) -> bool: """Check if candidate is compatible with the tasks in current.""" for c in current: if candidate in after[c] or c in after[candidate]: @@ -643,7 +661,11 @@ def AcceptNewCandidate(problem, after, demand_map, current, candidate): return True -def ComputePreemptiveLowerBound(problem, after, lower_bound): +def compute_preemptive_lower_bound( + problem: rcpsp_pb2.RcpspProblem, + after: dict[int, list[int]], + lower_bound: int, +) -> int: """Computes a preemtive lower bound for the makespan statically. For this, it breaks all intervals into a set of intervals of size one. @@ -696,7 +718,7 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): new_combinations = [[t]] for c in all_combinations: - if AcceptNewCandidate(problem, after, demand_map, c, t): + if accept_new_candidate(problem, after, demand_map, c, t): new_combinations.append(c + [t]) all_combinations.extend(new_combinations) @@ -705,14 +727,14 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): if len(all_combinations) > 5000000: return lower_bound # Abort if too large. - # Solve the selection model. + # solve the selection model. # TODO(user): a few possible improvements: # 1/ use "dominating" columns, i.e. if you can add a task to a column, then # do not use that column. # 2/ Merge all task with exactly same demands into one. model = cp_model.CpModel() - model.SetName(f"lower_bound_{problem.name}") + model.name = f"lower_bound_{problem.name}" vars_per_task = collections.defaultdict(list) all_vars = [] @@ -720,29 +742,29 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): min_duration = max_duration for t in c: min_duration = min(min_duration, duration_map[t]) - count = model.NewIntVar(0, min_duration, f"count_{c}") + count = model.new_int_var(0, min_duration, f"count_{c}") all_vars.append(count) for t in c: vars_per_task[t].append(count) # Each task must be performed. for t in all_active_tasks: - model.Add(sum(vars_per_task[t]) >= duration_map[t]) + model.add(sum(vars_per_task[t]) >= duration_map[t]) # Objective - objective_var = model.NewIntVar(lower_bound, sum_of_demands, "objective_var") - model.Add(objective_var == sum(all_vars)) + objective_var = model.new_int_var(lower_bound, sum_of_demands, "objective_var") + model.add(objective_var == sum(all_vars)) - model.Minimize(objective_var) + model.minimize(objective_var) - # Solve model. + # solve model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 16 solver.parameters.max_time_in_seconds = _PREEMPTIVE_LB_TIME_LIMIT.value - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: status_str = "optimal" if status == cp_model.OPTIMAL else "" - lower_bound = max(lower_bound, int(solver.BestObjectiveBound())) + lower_bound = max(lower_bound, int(solver.best_objective_bound)) print(f" - {status_str} static lower bound = {lower_bound}", flush=True) return lower_bound @@ -753,10 +775,10 @@ def main(_): rcpsp_parser.parse_file(_INPUT.value) problem = rcpsp_parser.problem() - PrintProblemStatistics(problem) + print_problem_statistics(problem) - intervals_of_tasks, after = AnalyseDependencyGraph(problem) - delays, initial_solution, optimal_found = ComputeDelaysBetweenNodes( + intervals_of_tasks, after = analyse_dependency_graph(problem) + delays, initial_solution, optimal_found = compute_delays_between_nodes( problem, intervals_of_tasks ) @@ -764,9 +786,9 @@ def main(_): key = (0, last_task) lower_bound = delays[key][0] if key in delays else 0 if not optimal_found and _PREEMPTIVE_LB_TIME_LIMIT.value > 0.0: - lower_bound = ComputePreemptiveLowerBound(problem, after, lower_bound) + lower_bound = compute_preemptive_lower_bound(problem, after, lower_bound) - SolveRcpsp( + solve_rcpsp( problem=problem, proto_file=_OUTPUT_PROTO.value, params=_PARAMS.value, diff --git a/examples/python/shift_scheduling_sat.py b/examples/python/shift_scheduling_sat.py index 96b36c80e68..c681f46ceef 100644 --- a/examples/python/shift_scheduling_sat.py +++ b/examples/python/shift_scheduling_sat.py @@ -17,8 +17,8 @@ from absl import app from absl import flags -from ortools.sat.python import cp_model from google.protobuf import text_format +from ortools.sat.python import cp_model _OUTPUT_PROTO = flags.DEFINE_string( "output_proto", "", "Output file to write the cp_model proto to." @@ -28,7 +28,9 @@ ) -def negated_bounded_span(works, start, length): +def negated_bounded_span( + works: list[cp_model.BoolVarT], start: int, length: int +) -> list[cp_model.BoolVarT]: """Filters an isolated sub-sequence of variables assined to True. Extract the span of Boolean variables [start, start + length), negate them, @@ -46,20 +48,28 @@ def negated_bounded_span(works, start, length): or by the start or end of works. """ sequence = [] - # Left border (start of works, or works[start - 1]) + # left border (start of works, or works[start - 1]) if start > 0: sequence.append(works[start - 1]) for i in range(length): - sequence.append(works[start + i].Not()) - # Right border (end of works or works[start + length]) + sequence.append(works[start + i].negated()) + # right border (end of works or works[start + length]) if start + length < len(works): sequence.append(works[start + length]) return sequence def add_soft_sequence_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix -): + model: cp_model.CpModel, + works: list[cp_model.BoolVarT], + hard_min: int, + soft_min: int, + min_cost: int, + soft_max: int, + hard_max: int, + max_cost: int, + prefix: str, +) -> tuple[list[cp_model.BoolVarT], list[int]]: """Sequence constraint on true variables with soft and hard bounds. This constraint look at every maximal contiguous sequence of variables @@ -93,7 +103,7 @@ def add_soft_sequence_constraint( # Forbid sequences that are too short. for length in range(1, hard_min): for start in range(len(works) - length + 1): - model.AddBoolOr(negated_bounded_span(works, start, length)) + model.add_bool_or(negated_bounded_span(works, start, length)) # Penalize sequences that are below the soft limit. if min_cost > 0: @@ -101,9 +111,9 @@ def add_soft_sequence_constraint( for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) name = ": under_span(start=%i, length=%i)" % (start, length) - lit = model.NewBoolVar(prefix + name) + lit = model.new_bool_var(prefix + name) span.append(lit) - model.AddBoolOr(span) + model.add_bool_or(span) cost_literals.append(lit) # We filter exactly the sequence with a short length. # The penalty is proportional to the delta with soft_min. @@ -115,23 +125,33 @@ def add_soft_sequence_constraint( for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) name = ": over_span(start=%i, length=%i)" % (start, length) - lit = model.NewBoolVar(prefix + name) + lit = model.new_bool_var(prefix + name) span.append(lit) - model.AddBoolOr(span) + model.add_bool_or(span) cost_literals.append(lit) # Cost paid is max_cost * excess length. cost_coefficients.append(max_cost * (length - soft_max)) # Just forbid any sequence of true variables with length hard_max + 1 for start in range(len(works) - hard_max): - model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)]) + model.add_bool_or( + [works[i].negated() for i in range(start, start + hard_max + 1)] + ) return cost_literals, cost_coefficients def add_soft_sum_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix -): - """Sum constraint with soft and hard bounds. + model: cp_model.CpModel, + works: list[cp_model.BoolVarT], + hard_min: int, + soft_min: int, + min_cost: int, + soft_max: int, + hard_max: int, + max_cost: int, + prefix: str, +) -> tuple[list[cp_model.IntVar], list[int]]: + """sum constraint with soft and hard bounds. This constraint counts the variables assigned to true from works. If forbids sum < hard_min or > hard_max. @@ -160,33 +180,33 @@ def add_soft_sum_constraint( """ cost_variables = [] cost_coefficients = [] - sum_var = model.NewIntVar(hard_min, hard_max, "") + sum_var = model.new_int_var(hard_min, hard_max, "") # This adds the hard constraints on the sum. - model.Add(sum_var == sum(works)) + model.add(sum_var == sum(works)) # Penalize sums below the soft_min target. if soft_min > hard_min and min_cost > 0: - delta = model.NewIntVar(-len(works), len(works), "") - model.Add(delta == soft_min - sum_var) + delta = model.new_int_var(-len(works), len(works), "") + model.add(delta == soft_min - sum_var) # TODO(user): Compare efficiency with only excess >= soft_min - sum_var. - excess = model.NewIntVar(0, 7, prefix + ": under_sum") - model.AddMaxEquality(excess, [delta, 0]) + excess = model.new_int_var(0, 7, prefix + ": under_sum") + model.add_max_equality(excess, [delta, 0]) cost_variables.append(excess) cost_coefficients.append(min_cost) # Penalize sums above the soft_max target. if soft_max < hard_max and max_cost > 0: - delta = model.NewIntVar(-7, 7, "") - model.Add(delta == sum_var - soft_max) - excess = model.NewIntVar(0, 7, prefix + ": over_sum") - model.AddMaxEquality(excess, [delta, 0]) + delta = model.new_int_var(-7, 7, "") + model.add(delta == sum_var - soft_max) + excess = model.new_int_var(0, 7, prefix + ": over_sum") + model.add_max_equality(excess, [delta, 0]) cost_variables.append(excess) cost_coefficients.append(max_cost) return cost_variables, cost_coefficients -def solve_shift_scheduling(params, output_proto): +def solve_shift_scheduling(params: str, output_proto: str): """Solves the shift scheduling problem.""" # Data num_employees = 8 @@ -281,22 +301,22 @@ def solve_shift_scheduling(params, output_proto): for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): - work[e, s, d] = model.NewBoolVar("work%i_%i_%i" % (e, s, d)) + work[e, s, d] = model.new_bool_var("work%i_%i_%i" % (e, s, d)) # Linear terms of the objective in a minimization context. - obj_int_vars = [] - obj_int_coeffs = [] - obj_bool_vars = [] - obj_bool_coeffs = [] + obj_int_vars: list[cp_model.IntVar] = [] + obj_int_coeffs: list[int] = [] + obj_bool_vars: list[cp_model.BoolVarT] = [] + obj_bool_coeffs: list[int] = [] # Exactly one shift per day. for e in range(num_employees): for d in range(num_days): - model.AddExactlyOne(work[e, s, d] for s in range(num_shifts)) + model.add_exactly_one(work[e, s, d] for s in range(num_shifts)) # Fixed assignments. for e, s, d in fixed_assignments: - model.Add(work[e, s, d] == 1) + model.add(work[e, s, d] == 1) # Employee requests for e, s, d, w in requests: @@ -348,17 +368,17 @@ def solve_shift_scheduling(params, output_proto): for e in range(num_employees): for d in range(num_days - 1): transition = [ - work[e, previous_shift, d].Not(), - work[e, next_shift, d + 1].Not(), + work[e, previous_shift, d].negated(), + work[e, next_shift, d + 1].negated(), ] if cost == 0: - model.AddBoolOr(transition) + model.add_bool_or(transition) else: - trans_var = model.NewBoolVar( + trans_var = model.new_bool_var( "transition (employee=%i, day=%i)" % (e, d) ) transition.append(trans_var) - model.AddBoolOr(transition) + model.add_bool_or(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) @@ -369,18 +389,18 @@ def solve_shift_scheduling(params, output_proto): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] - worked = model.NewIntVar(min_demand, num_employees, "") - model.Add(worked == sum(works)) + worked = model.new_int_var(min_demand, num_employees, "") + model.add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = "excess_demand(shift=%i, week=%i, day=%i)" % (s, w, d) - excess = model.NewIntVar(0, num_employees - min_demand, name) - model.Add(excess == worked - min_demand) + excess = model.new_int_var(0, num_employees - min_demand, name) + model.add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective - model.Minimize( + model.minimize( sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars))) + sum(obj_int_vars[i] * obj_int_coeffs[i] for i in range(len(obj_int_vars))) ) @@ -395,7 +415,7 @@ def solve_shift_scheduling(params, output_proto): if params: text_format.Parse(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: @@ -408,32 +428,32 @@ def solve_shift_scheduling(params, output_proto): schedule = "" for d in range(num_days): for s in range(num_shifts): - if solver.BooleanValue(work[e, s, d]): + if solver.boolean_value(work[e, s, d]): schedule += shifts[s] + " " print("worker %i: %s" % (e, schedule)) print() print("Penalties:") for i, var in enumerate(obj_bool_vars): - if solver.BooleanValue(var): + if solver.boolean_value(var): penalty = obj_bool_coeffs[i] if penalty > 0: - print(" %s violated, penalty=%i" % (var.Name(), penalty)) + print(f" {var.name} violated, penalty={penalty}") else: - print(" %s fulfilled, gain=%i" % (var.Name(), -penalty)) + print(f" {var.name} fulfilled, gain={-penalty}") for i, var in enumerate(obj_int_vars): - if solver.Value(var) > 0: + if solver.value(var) > 0: print( " %s violated by %i, linear penalty=%i" - % (var.Name(), solver.Value(var), obj_int_coeffs[i]) + % (var.name, solver.value(var), obj_int_coeffs[i]) ) print() print("Statistics") - print(" - status : %s" % solver.StatusName(status)) - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - status : %s" % solver.status_name(status)) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(_): diff --git a/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py index 2381de7831f..fa496da34e6 100644 --- a/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py +++ b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py @@ -48,7 +48,7 @@ def on_solution_callback(self): """Called after each new solution found.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 @@ -433,34 +433,34 @@ def single_machine_scheduling(): % (job_id, release_date, duration, due_date) ) name_suffix = "_%i" % job_id - start = model.NewIntVar(release_date, due_date, "s" + name_suffix) - end = model.NewIntVar(release_date, due_date, "e" + name_suffix) - interval = model.NewIntervalVar(start, duration, end, "i" + name_suffix) + start = model.new_int_var(release_date, due_date, "s" + name_suffix) + end = model.new_int_var(release_date, due_date, "e" + name_suffix) + interval = model.new_interval_var(start, duration, end, "i" + name_suffix) starts.append(start) ends.append(end) intervals.append(interval) # No overlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # ---------------------------------------------------------------------------- # Transition times using a circuit constraint. arcs = [] for i in all_jobs: # Initial arc from the dummy node (0) to a task. - start_lit = model.NewBoolVar("") + start_lit = model.new_bool_var("") arcs.append((0, i + 1, start_lit)) # If this task is the first, set to minimum starting time. min_start_time = max(release_dates[i], setup_times[0][i]) - model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit) + model.add(starts[i] == min_start_time).only_enforce_if(start_lit) # Final arc from an arc to the dummy node. - arcs.append((i + 1, 0, model.NewBoolVar(""))) + arcs.append((i + 1, 0, model.new_bool_var(""))) for j in all_jobs: if i == j: continue - lit = model.NewBoolVar("%i follows %i" % (j, i)) + lit = model.new_bool_var("%i follows %i" % (j, i)) arcs.append((i + 1, j + 1, lit)) # We add the reified precedence to link the literal with the times of the @@ -468,27 +468,27 @@ def single_machine_scheduling(): # If release_dates[j] == 0, we can strenghten this precedence into an # equality as we are minimizing the makespan. if release_dates[j] == 0: - model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + model.add(starts[j] == ends[i] + setup_times[i + 1][j]).only_enforce_if( lit ) else: - model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + model.add(starts[j] >= ends[i] + setup_times[i + 1][j]).only_enforce_if( lit ) - model.AddCircuit(arcs) + model.add_circuit(arcs) # ---------------------------------------------------------------------------- # Precedences. for before, after in precedences: print("job %i is after job %i" % (after, before)) - model.Add(ends[before] <= starts[after]) + model.add(ends[before] <= starts[after]) # ---------------------------------------------------------------------------- # Objective. - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, ends) + model.minimize(makespan) # ---------------------------------------------------------------------------- # Write problem to file. @@ -503,11 +503,11 @@ def single_machine_scheduling(): if parameters: text_format.Parse(parameters, solver.parameters) solution_printer = SolutionPrinter() - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) for job_id in all_jobs: print( "job %i starts at %i end ends at %i" - % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id])) + % (job_id, solver.value(starts[job_id]), solver.value(ends[job_id])) ) diff --git a/examples/python/spread_robots_sat.py b/examples/python/spread_robots_sat.py index fb5011b3169..d85fc1afa80 100644 --- a/examples/python/spread_robots_sat.py +++ b/examples/python/spread_robots_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Maximize the minimum of pairwise distances between n robots in a square space.""" +"""maximize the minimum of pairwise distances between n robots in a square space.""" import math from typing import Sequence @@ -38,8 +38,8 @@ def spread_robots(num_robots: int, room_size: int, params: str): model = cp_model.CpModel() # Create the list of coordinates (x, y) for each robot. - x = [model.NewIntVar(1, room_size, f"x_{i}") for i in range(num_robots)] - y = [model.NewIntVar(1, room_size, f"y_{i}") for i in range(num_robots)] + x = [model.new_int_var(1, room_size, f"x_{i}") for i in range(num_robots)] + y = [model.new_int_var(1, room_size, f"y_{i}") for i in range(num_robots)] # The specification of the problem is to maximize the minimum euclidian # distance between any two robots. Unfortunately, the euclidian distance @@ -58,7 +58,7 @@ def spread_robots(num_robots: int, room_size: int, params: str): # forall i: # scaled_min_square_distance <= scaling * (x_diff_sq[i] + y_diff_sq[i]) scaling = 1000 - scaled_min_square_distance = model.NewIntVar( + scaled_min_square_distance = model.new_int_var( 0, 2 * scaling * room_size**2, "scaled_min_square_distance" ) @@ -67,45 +67,45 @@ def spread_robots(num_robots: int, room_size: int, params: str): for i in range(num_robots - 1): for j in range(i + 1, num_robots): # Compute the distance on each dimension between robot i and robot j. - x_diff = model.NewIntVar(-room_size, room_size, f"x_diff{i}") - y_diff = model.NewIntVar(-room_size, room_size, f"y_diff{i}") - model.Add(x_diff == x[i] - x[j]) - model.Add(y_diff == y[i] - y[j]) + x_diff = model.new_int_var(-room_size, room_size, f"x_diff{i}") + y_diff = model.new_int_var(-room_size, room_size, f"y_diff{i}") + model.add(x_diff == x[i] - x[j]) + model.add(y_diff == y[i] - y[j]) # Compute the square of the previous differences. - x_diff_sq = model.NewIntVar(0, room_size**2, f"x_diff_sq{i}") - y_diff_sq = model.NewIntVar(0, room_size**2, f"y_diff_sq{i}") - model.AddMultiplicationEquality(x_diff_sq, x_diff, x_diff) - model.AddMultiplicationEquality(y_diff_sq, y_diff, y_diff) + x_diff_sq = model.new_int_var(0, room_size**2, f"x_diff_sq{i}") + y_diff_sq = model.new_int_var(0, room_size**2, f"y_diff_sq{i}") + model.add_multiplication_equality(x_diff_sq, x_diff, x_diff) + model.add_multiplication_equality(y_diff_sq, y_diff, y_diff) # We just need to be <= to the scaled square distance as we are # maximizing the min distance, which is equivalent as maximizing the min # square distance. - model.Add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq)) + model.add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq)) # Naive symmetry breaking. for i in range(1, num_robots): - model.Add(x[0] <= x[i]) - model.Add(y[0] <= y[i]) + model.add(x[0] <= x[i]) + model.add(y[0] <= y[i]) # Objective - model.Maximize(scaled_min_square_distance) + model.maximize(scaled_min_square_distance) # Creates a solver and solves the model. solver = cp_model.CpSolver() if params: text_format.Parse(params, solver.parameters) solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) # Prints the solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print( f"Spread {num_robots} with a min pairwise distance of" - f" {math.sqrt(solver.ObjectiveValue() / scaling)}" + f" {math.sqrt(solver.objective_value / scaling)}" ) for i in range(num_robots): - print(f"robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}") + print(f"robot {i}: x={solver.value(x[i])} y={solver.value(y[i])}") else: print("No solution found.") diff --git a/examples/python/steel_mill_slab_sat.py b/examples/python/steel_mill_slab_sat.py old mode 100755 new mode 100644 index f119ff3c262..667f0217541 --- a/examples/python/steel_mill_slab_sat.py +++ b/examples/python/steel_mill_slab_sat.py @@ -131,22 +131,22 @@ def __init__(self, orders, assign, load, loss): def on_solution_callback(self): """Called on each new solution.""" current_time = time.time() - objective = sum(self.Value(l) for l in self.__loss) + objective = sum(self.value(l) for l in self.__loss) print( "Solution %i, time = %f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, objective) ) self.__solution_count += 1 orders_in_slab = [ - [o for o in self.__all_orders if self.Value(self.__assign[o][s])] + [o for o in self.__all_orders if self.value(self.__assign[o][s])] for s in self.__all_slabs ] for s in self.__all_slabs: if orders_in_slab[s]: line = " - slab %i, load = %i, loss = %i, orders = [" % ( s, - self.Value(self.__load[s]), - self.Value(self.__loss[s]), + self.value(self.__load[s]), + self.value(self.__loss[s]), ) for o in orders_in_slab[s]: line += "#%i(w%i, c%i) " % ( @@ -193,44 +193,48 @@ def steel_mill_slab(problem, break_symmetries): # Create the model and the decision variables. model = cp_model.CpModel() assign = [ - [model.NewBoolVar("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] + [model.new_bool_var("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] for o in all_orders ] - loads = [model.NewIntVar(0, max_capacity, "load_of_slab_%i" % s) for s in all_slabs] + loads = [ + model.new_int_var(0, max_capacity, "load_of_slab_%i" % s) for s in all_slabs + ] color_is_in_slab = [ - [model.NewBoolVar("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors] + [model.new_bool_var("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors] for s in all_slabs ] # Compute load of all slabs. for s in all_slabs: - model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s]) + model.add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s]) # Orders are assigned to one slab. for o in all_orders: - model.AddExactlyOne(assign[o]) + model.add_exactly_one(assign[o]) # Redundant constraint (sum of loads == sum of widths). - model.Add(sum(loads) == sum(widths)) + model.add(sum(loads) == sum(widths)) # Link present_colors and assign. for c in all_colors: for s in all_slabs: for o in orders_per_color[c]: - model.AddImplication(assign[o][s], color_is_in_slab[s][c]) - model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not()) + model.add_implication(assign[o][s], color_is_in_slab[s][c]) + model.add_implication( + color_is_in_slab[s][c].negated(), assign[o][s].negated() + ) # At most two colors per slab. for s in all_slabs: - model.Add(sum(color_is_in_slab[s]) <= 2) + model.add(sum(color_is_in_slab[s]) <= 2) # Project previous constraint on unique_color_orders for s in all_slabs: - model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2) + model.add(sum(assign[o][s] for o in unique_color_orders) <= 2) # Symmetry breaking. for s in range(num_slabs - 1): - model.Add(loads[s] >= loads[s + 1]) + model.add(loads[s] >= loads[s + 1]) # Collect equivalent orders. width_to_unique_color_order = {} @@ -271,38 +275,38 @@ def steel_mill_slab(problem, break_symmetries): positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: - positions[p[0]] = model.NewIntVar( + positions[p[0]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[0] ) - model.AddMapDomain(positions[p[0]], assign[p[0]]) + model.add_map_domain(positions[p[0]], assign[p[0]]) if p[1] not in positions: - positions[p[1]] = model.NewIntVar( + positions[p[1]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[1] ) - model.AddMapDomain(positions[p[1]], assign[p[1]]) + model.add_map_domain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. - model.Add(positions[p[0]] <= positions[p[1]]) + model.add(positions[p[0]] <= positions[p[1]]) # Objective. - obj = model.NewIntVar(0, num_slabs * max_loss, "obj") - losses = [model.NewIntVar(0, max_loss, "loss_%i" % s) for s in all_slabs] + obj = model.new_int_var(0, num_slabs * max_loss, "obj") + losses = [model.new_int_var(0, max_loss, "loss_%i" % s) for s in all_slabs] for s in all_slabs: - model.AddElement(loads[s], loss_array, losses[s]) - model.Add(obj == sum(losses)) - model.Minimize(obj) + model.add_element(loads[s], loss_array, losses[s]) + model.add(obj == sum(losses)) + model.minimize(obj) ### Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) objective_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, objective_printer) + status = solver.solve(model, objective_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print( "Loss = %i, time = %f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") @@ -381,11 +385,11 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): # Create the model and the decision variables. model = cp_model.CpModel() assign = [ - [model.NewBoolVar("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] + [model.new_bool_var("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] for o in all_orders ] - loads = [model.NewIntVar(0, max_capacity, "load_%i" % s) for s in all_slabs] - losses = [model.NewIntVar(0, max_loss, "loss_%i" % s) for s in all_slabs] + loads = [model.new_int_var(0, max_capacity, "load_%i" % s) for s in all_slabs] + losses = [model.new_int_var(0, max_loss, "loss_%i" % s) for s in all_slabs] unsorted_valid_slabs = collect_valid_slabs_dp( capacities, colors, widths, loss_array @@ -394,20 +398,20 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2]) for s in all_slabs: - model.AddAllowedAssignments( + model.add_allowed_assignments( [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs ) # Orders are assigned to one slab. for o in all_orders: - model.AddExactlyOne(assign[o]) + model.add_exactly_one(assign[o]) # Redundant constraint (sum of loads == sum of widths). - model.Add(sum(loads) == sum(widths)) + model.add(sum(loads) == sum(widths)) # Symmetry breaking. for s in range(num_slabs - 1): - model.Add(loads[s] >= loads[s + 1]) + model.add(loads[s] >= loads[s + 1]) # Collect equivalent orders. if break_symmetries: @@ -453,20 +457,20 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: - positions[p[0]] = model.NewIntVar( + positions[p[0]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[0] ) - model.AddMapDomain(positions[p[0]], assign[p[0]]) + model.add_map_domain(positions[p[0]], assign[p[0]]) if p[1] not in positions: - positions[p[1]] = model.NewIntVar( + positions[p[1]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[1] ) - model.AddMapDomain(positions[p[1]], assign[p[1]]) + model.add_map_domain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. - model.Add(positions[p[0]] <= positions[p[1]]) + model.add(positions[p[0]] <= positions[p[1]]) # Objective. - model.Minimize(sum(losses)) + model.minimize(sum(losses)) print("Model created") @@ -476,13 +480,13 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): text_format.Parse(_PARAMS.value, solver.parameters) solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) ### Output the solution. if status == cp_model.OPTIMAL: print( "Loss = %i, time = %.2f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") @@ -522,21 +526,21 @@ def steel_mill_slab_with_column_generation(problem): # create model and decision variables. model = cp_model.CpModel() - selected = [model.NewBoolVar("selected_%i" % i) for i in all_valid_slabs] + selected = [model.new_bool_var("selected_%i" % i) for i in all_valid_slabs] for order_id in all_orders: - model.Add( + model.add( sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id]) == 1 ) # Redundant constraint (sum of loads == sum of widths). - model.Add( + model.add( sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths) ) # Objective. - model.Minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs)) + model.minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs)) print("Model created") @@ -545,13 +549,13 @@ def steel_mill_slab_with_column_generation(problem): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print( "Loss = %i, time = %.2f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") diff --git a/examples/python/sudoku_sat.py b/examples/python/sudoku_sat.py old mode 100644 new mode 100755 index bf4b391ca96..732ee74a97a --- a/examples/python/sudoku_sat.py +++ b/examples/python/sudoku_sat.py @@ -42,15 +42,15 @@ def solve_sudoku(): grid = {} for i in line: for j in line: - grid[(i, j)] = model.NewIntVar(1, line_size, "grid %i %i" % (i, j)) + grid[(i, j)] = model.new_int_var(1, line_size, "grid %i %i" % (i, j)) # AllDifferent on rows. for i in line: - model.AddAllDifferent(grid[(i, j)] for j in line) + model.add_all_different(grid[(i, j)] for j in line) # AllDifferent on columns. for j in line: - model.AddAllDifferent(grid[(i, j)] for i in line) + model.add_all_different(grid[(i, j)] for i in line) # AllDifferent on cells. for i in cell: @@ -60,20 +60,20 @@ def solve_sudoku(): for dj in cell: one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)]) - model.AddAllDifferent(one_cell) + model.add_all_different(one_cell) # Initial values. for i in line: for j in line: if initial_grid[i][j]: - model.Add(grid[(i, j)] == initial_grid[i][j]) + model.add(grid[(i, j)] == initial_grid[i][j]) - # Solve and print out the solution. + # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: for i in line: - print([int(solver.Value(grid[(i, j)])) for j in line]) + print([int(solver.value(grid[(i, j)])) for j in line]) solve_sudoku() diff --git a/examples/python/task_allocation_sat.py b/examples/python/task_allocation_sat.py index 0027f533bee..04009582aac 100644 --- a/examples/python/task_allocation_sat.py +++ b/examples/python/task_allocation_sat.py @@ -246,12 +246,12 @@ def task_allocation_sat(): assign = {} for task in all_tasks: for slot in all_slots: - assign[(task, slot)] = model.NewBoolVar("x[%i][%i]" % (task, slot)) - count = model.NewIntVar(0, nslots, "count") - slot_used = [model.NewBoolVar("slot_used[%i]" % s) for s in all_slots] + assign[(task, slot)] = model.new_bool_var("x[%i][%i]" % (task, slot)) + count = model.new_int_var(0, nslots, "count") + slot_used = [model.new_bool_var("slot_used[%i]" % s) for s in all_slots] for task in all_tasks: - model.Add( + model.add( sum( assign[(task, slot)] for slot in all_slots if available[task][slot] == 1 ) @@ -259,38 +259,40 @@ def task_allocation_sat(): ) for slot in all_slots: - model.Add( + model.add( sum( assign[(task, slot)] for task in all_tasks if available[task][slot] == 1 ) <= capacity ) - model.AddBoolOr( + model.add_bool_or( [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1] - ).OnlyEnforceIf(slot_used[slot]) + ).only_enforce_if(slot_used[slot]) for task in all_tasks: if available[task][slot] == 1: - model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not()) + model.add_implication( + slot_used[slot].negated(), assign[(task, slot)].negated() + ) else: - model.Add(assign[(task, slot)] == 0) + model.add(assign[(task, slot)] == 0) - model.Add(count == sum(slot_used)) + model.add(count == sum(slot_used)) # Redundant constraint. This instance is easier if we add this constraint. - # model.Add(count >= (nslots + capacity - 1) // capacity) + # model.add(count >= (nslots + capacity - 1) // capacity) - model.Minimize(count) + model.minimize(count) # Create a solver and solve the problem. solver = cp_model.CpSolver() # Uses the portfolion of heuristics. solver.parameters.log_search_progress = True solver.parameters.num_search_workers = 16 - status = solver.Solve(model) + status = solver.solve(model) print("Statistics") - print(" - status =", solver.StatusName(status)) - print(" - optimal solution =", solver.ObjectiveValue()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - status =", solver.status_name(status)) + print(" - optimal solution =", solver.objective_value) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/tasks_and_workers_assignment_sat.py b/examples/python/tasks_and_workers_assignment_sat.py index 0960f59c5c7..83f2d62b320 100644 --- a/examples/python/tasks_and_workers_assignment_sat.py +++ b/examples/python/tasks_and_workers_assignment_sat.py @@ -29,13 +29,13 @@ def __init__(self): def on_solution_callback(self): print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 def tasks_and_workers_assignment_sat(): - """Solve the assignment problem.""" + """solve the assignment problem.""" model = cp_model.CpModel() # CP-SAT solver is integer only. @@ -53,71 +53,71 @@ def tasks_and_workers_assignment_sat(): x = {} for i in all_workers: for j in all_groups: - x[i, j] = model.NewBoolVar("x[%i,%i]" % (i, j)) + x[i, j] = model.new_bool_var("x[%i,%i]" % (i, j)) ## y_kj is 1 if task k is assigned to group j y = {} for k in all_tasks: for j in all_groups: - y[k, j] = model.NewBoolVar("x[%i,%i]" % (k, j)) + y[k, j] = model.new_bool_var("x[%i,%i]" % (k, j)) # Constraints # Each task k is assigned to a group and only one. for k in all_tasks: - model.Add(sum(y[k, j] for j in all_groups) == 1) + model.add(sum(y[k, j] for j in all_groups) == 1) # Each worker i is assigned to a group and only one. for i in all_workers: - model.Add(sum(x[i, j] for j in all_groups) == 1) + model.add(sum(x[i, j] for j in all_groups) == 1) - # cost per group + # Cost per group sum_of_costs = sum(task_cost) averages = [] num_workers_in_group = [] scaled_sum_of_costs_in_group = [] scaling = 1000 # We introduce scaling to deal with floating point average. for j in all_groups: - n = model.NewIntVar(1, num_workers, "num_workers_in_group_%i" % j) - model.Add(n == sum(x[i, j] for i in all_workers)) - c = model.NewIntVar(0, sum_of_costs * scaling, "sum_of_costs_of_group_%i" % j) - model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks)) - a = model.NewIntVar(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j) - model.AddDivisionEquality(a, c, n) + n = model.new_int_var(1, num_workers, "num_workers_in_group_%i" % j) + model.add(n == sum(x[i, j] for i in all_workers)) + c = model.new_int_var(0, sum_of_costs * scaling, "sum_of_costs_of_group_%i" % j) + model.add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks)) + a = model.new_int_var(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j) + model.add_division_equality(a, c, n) averages.append(a) num_workers_in_group.append(n) scaled_sum_of_costs_in_group.append(c) # All workers are assigned. - model.Add(sum(num_workers_in_group) == num_workers) + model.add(sum(num_workers_in_group) == num_workers) # Objective. - obj = model.NewIntVar(0, sum_of_costs * scaling, "obj") - model.AddMaxEquality(obj, averages) - model.Minimize(obj) + obj = model.new_int_var(0, sum_of_costs * scaling, "obj") + model.add_max_equality(obj, averages) + model.minimize(obj) # Solve and print out the solution. solver = cp_model.CpSolver() solver.parameters.max_time_in_seconds = 60 * 60 * 2 objective_printer = ObjectivePrinter() - status = solver.Solve(model, objective_printer) - print(solver.ResponseStats()) + status = solver.solve(model, objective_printer) + print(solver.response_stats()) if status == cp_model.OPTIMAL: for j in all_groups: print("Group %i" % j) for i in all_workers: - if solver.BooleanValue(x[i, j]): + if solver.boolean_value(x[i, j]): print(" - worker %i" % i) for k in all_tasks: - if solver.BooleanValue(y[k, j]): + if solver.boolean_value(y[k, j]): print(" - task %i with cost %i" % (k, task_cost[k])) print( " - sum_of_costs = %i" - % (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling) + % (solver.value(scaled_sum_of_costs_in_group[j]) // scaling) ) - print(" - average cost = %f" % (solver.Value(averages[j]) * 1.0 / scaling)) + print(" - average cost = %f" % (solver.value(averages[j]) * 1.0 / scaling)) tasks_and_workers_assignment_sat() diff --git a/examples/python/tsp_sat.py b/examples/python/tsp_sat.py index f0ef4f916e1..02b149377fd 100644 --- a/examples/python/tsp_sat.py +++ b/examples/python/tsp_sat.py @@ -82,17 +82,17 @@ def main(): if i == j: continue - lit = model.NewBoolVar("%i follows %i" % (j, i)) + lit = model.new_bool_var("%i follows %i" % (j, i)) arcs.append((i, j, lit)) arc_literals[i, j] = lit obj_vars.append(lit) obj_coeffs.append(DISTANCE_MATRIX[i][j]) - model.AddCircuit(arcs) + model.add_circuit(arcs) # Minimize weighted sum of arcs. Because this s - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -100,8 +100,8 @@ def main(): # To benefit from the linearization of the circuit constraint. solver.parameters.linearization_level = 2 - solver.Solve(model) - print(solver.ResponseStats()) + solver.solve(model) + print(solver.response_stats()) current_node = 0 str_route = "%i" % current_node @@ -111,7 +111,7 @@ def main(): for i in all_nodes: if i == current_node: continue - if solver.BooleanValue(arc_literals[current_node, i]): + if solver.boolean_value(arc_literals[current_node, i]): str_route += " -> %i" % i route_distance += DISTANCE_MATRIX[current_node][i] current_node = i diff --git a/examples/python/vendor_scheduling_sat.py b/examples/python/vendor_scheduling_sat.py index ac65975e476..60957851ef9 100644 --- a/examples/python/vendor_scheduling_sat.py +++ b/examples/python/vendor_scheduling_sat.py @@ -48,13 +48,13 @@ def on_solution_callback(self): for i in range(self.__num_vendors): print( " - vendor %i: " % i, - self.__possible_schedules[self.Value(self.__selected_schedules[i])], + self.__possible_schedules[self.value(self.__selected_schedules[i])], ) print() for j in range(self.__num_hours): print(" - # workers on day%2i: " % j, end=" ") - print(self.Value(self.__hours_stat[j]), end=" ") + print(self.value(self.__hours_stat[j]), end=" ") print() print() @@ -101,38 +101,40 @@ def vendor_scheduling_sat(): all_hours = range(num_hours) # - # declare variables + # Declare variables # x = {} for v in all_vendors: tmp = [] for h in all_hours: - x[v, h] = model.NewIntVar(0, num_work_types, "x[%i,%i]" % (v, h)) + x[v, h] = model.new_int_var(0, num_work_types, "x[%i,%i]" % (v, h)) tmp.append(x[v, h]) - selected_schedule = model.NewIntVar(0, num_possible_schedules - 1, "s[%i]" % v) - hours = model.NewIntVar(0, num_hours, "h[%i]" % v) + selected_schedule = model.new_int_var( + 0, num_possible_schedules - 1, "s[%i]" % v + ) + hours = model.new_int_var(0, num_hours, "h[%i]" % v) selected_schedules.append(selected_schedule) vendors_stat.append(hours) tmp.append(selected_schedule) tmp.append(hours) - model.AddAllowedAssignments(tmp, possible_schedules) + model.add_allowed_assignments(tmp, possible_schedules) # # Statistics and constraints for each hour # for h in all_hours: - workers = model.NewIntVar(0, 1000, "workers[%i]" % h) - model.Add(workers == sum(x[v, h] for v in all_vendors)) + workers = model.new_int_var(0, 1000, "workers[%i]" % h) + model.add(workers == sum(x[v, h] for v in all_vendors)) hours_stat.append(workers) - model.Add(workers * max_traffic_per_vendor >= traffic[h]) + model.add(workers * max_traffic_per_vendor >= traffic[h]) # # Redundant constraint: sort selected_schedules # for v in range(num_vendors - 1): - model.Add(selected_schedules[v] <= selected_schedules[v + 1]) + model.add(selected_schedules[v] <= selected_schedules[v + 1]) # Solve model. solver = cp_model.CpSolver() @@ -145,13 +147,13 @@ def vendor_scheduling_sat(): hours_stat, min_vendors, ) - status = solver.Solve(model, solution_printer) - print("Status = %s" % solver.StatusName(status)) + status = solver.solve(model, solution_printer) + print("Status = %s" % solver.status_name(status)) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) print(" - number of solutions found: %i" % solution_printer.solution_count()) diff --git a/examples/python/wedding_optimal_chart_sat.py b/examples/python/wedding_optimal_chart_sat.py index a9c0cf8521d..e23803a0642 100644 --- a/examples/python/wedding_optimal_chart_sat.py +++ b/examples/python/wedding_optimal_chart_sat.py @@ -56,7 +56,7 @@ def __init__(self, seats, names, num_tables, num_guests): def on_solution_callback(self): current_time = time.time() - objective = self.ObjectiveValue() + objective = self.objective_value print( "Solution %i, time = %f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, objective) @@ -66,10 +66,10 @@ def on_solution_callback(self): for t in range(self.__num_tables): print("Table %d: " % t) for g in range(self.__num_guests): - if self.Value(self.__seats[(t, g)]): + if self.value(self.__seats[(t, g)]): print(" " + self.__names[g]) - def num_solutions(self): + def num_solutions(self) -> int: return self.__solution_count @@ -148,12 +148,12 @@ def solve_with_discrete_model(): seats = {} for t in all_tables: for g in all_guests: - seats[(t, g)] = model.NewBoolVar("guest %i seats on table %i" % (g, t)) + seats[(t, g)] = model.new_bool_var("guest %i seats on table %i" % (g, t)) colocated = {} for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): - colocated[(g1, g2)] = model.NewBoolVar( + colocated[(g1, g2)] = model.new_bool_var( "guest %i seats with guest %i" % (g1, g2) ) @@ -161,12 +161,12 @@ def solve_with_discrete_model(): for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): for t in all_tables: - same_table[(g1, g2, t)] = model.NewBoolVar( + same_table[(g1, g2, t)] = model.new_bool_var( "guest %i seats with guest %i on table %i" % (g1, g2, t) ) # Objective - model.Maximize( + model.maximize( sum( connections[g1][g2] * colocated[g1, g2] for g1 in range(num_guests - 1) @@ -181,35 +181,35 @@ def solve_with_discrete_model(): # Everybody seats at one table. for g in all_guests: - model.Add(sum(seats[(t, g)] for t in all_tables) == 1) + model.add(sum(seats[(t, g)] for t in all_tables) == 1) # Tables have a max capacity. for t in all_tables: - model.Add(sum(seats[(t, g)] for g in all_guests) <= table_capacity) + model.add(sum(seats[(t, g)] for g in all_guests) <= table_capacity) # Link colocated with seats for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): for t in all_tables: # Link same_table and seats. - model.AddBoolOr( + model.add_bool_or( [ - seats[(t, g1)].Not(), - seats[(t, g2)].Not(), + seats[(t, g1)].negated(), + seats[(t, g2)].negated(), same_table[(g1, g2, t)], ] ) - model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)]) - model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)]) + model.add_implication(same_table[(g1, g2, t)], seats[(t, g1)]) + model.add_implication(same_table[(g1, g2, t)], seats[(t, g2)]) # Link colocated and same_table. - model.Add( + model.add( sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1, g2)] ) # Min known neighbors rule. for g in all_guests: - model.Add( + model.add( sum( same_table[(g, g2, t)] for g2 in range(g + 1, num_guests) @@ -226,17 +226,17 @@ def solve_with_discrete_model(): ) # Symmetry breaking. First guest seats on the first table. - model.Add(seats[(0, 0)] == 1) + model.add(seats[(0, 0)] == 1) ### Solve model. solver = cp_model.CpSolver() solution_printer = WeddingChartPrinter(seats, names, num_tables, num_guests) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) print(" - num solutions: %i" % solution_printer.num_solutions()) diff --git a/examples/python/weighted_latency_problem_sat.py b/examples/python/weighted_latency_problem_sat.py index 3db149a3855..8acb20d911e 100644 --- a/examples/python/weighted_latency_problem_sat.py +++ b/examples/python/weighted_latency_problem_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a random Weighted Latency problem with the CP-SAT solver.""" +"""solve a random Weighted Latency problem with the CP-SAT solver.""" import random from typing import Sequence @@ -61,10 +61,12 @@ def solve_with_cp_sat(x, y, profits): # because of the manhattan distance, the sum of distances is bounded by this. horizon = _GRID_SIZE.value * 2 * _NUM_NODES.value - times = [model.NewIntVar(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1)] + times = [ + model.new_int_var(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1) + ] # Node 0 is the start node. - model.Add(times[0] == 0) + model.add(times[0] == 0) # Create the circuit constraint. arcs = [] @@ -74,29 +76,29 @@ def solve_with_cp_sat(x, y, profits): continue # We use a manhattan distance between nodes. distance = abs(x[i] - x[j]) + abs(y[i] - y[j]) - lit = model.NewBoolVar(f"{i}_to_{j}") + lit = model.new_bool_var(f"{i}_to_{j}") arcs.append((i, j, lit)) - # Add transitions between nodes. + # add transitions between nodes. if i == 0: # Initial transition - model.Add(times[j] == distance).OnlyEnforceIf(lit) + model.add(times[j] == distance).only_enforce_if(lit) elif j != 0: # We do not care for the last transition. - model.Add(times[j] == times[i] + distance).OnlyEnforceIf(lit) - model.AddCircuit(arcs) + model.add(times[j] == times[i] + distance).only_enforce_if(lit) + model.add_circuit(arcs) - model.Minimize(cp_model.LinearExpr.WeightedSum(times, profits)) + model.minimize(cp_model.LinearExpr.weighted_sum(times, profits)) if _PROTO_FILE.value: - model.ExportToFile(_PROTO_FILE.value) + model.export_to_file(_PROTO_FILE.value) # Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/zebra_sat.py b/examples/python/zebra_sat.py old mode 100644 new mode 100755 index b0e4ba01e87..24a5afceb04 --- a/examples/python/zebra_sat.py +++ b/examples/python/zebra_sat.py @@ -44,77 +44,77 @@ def solve_zebra(): # Create the model. model = cp_model.CpModel() - red = model.NewIntVar(1, 5, "red") - green = model.NewIntVar(1, 5, "green") - yellow = model.NewIntVar(1, 5, "yellow") - blue = model.NewIntVar(1, 5, "blue") - ivory = model.NewIntVar(1, 5, "ivory") - - englishman = model.NewIntVar(1, 5, "englishman") - spaniard = model.NewIntVar(1, 5, "spaniard") - japanese = model.NewIntVar(1, 5, "japanese") - ukrainian = model.NewIntVar(1, 5, "ukrainian") - norwegian = model.NewIntVar(1, 5, "norwegian") - - dog = model.NewIntVar(1, 5, "dog") - snails = model.NewIntVar(1, 5, "snails") - fox = model.NewIntVar(1, 5, "fox") - zebra = model.NewIntVar(1, 5, "zebra") - horse = model.NewIntVar(1, 5, "horse") - - tea = model.NewIntVar(1, 5, "tea") - coffee = model.NewIntVar(1, 5, "coffee") - water = model.NewIntVar(1, 5, "water") - milk = model.NewIntVar(1, 5, "milk") - fruit_juice = model.NewIntVar(1, 5, "fruit juice") - - old_gold = model.NewIntVar(1, 5, "old gold") - kools = model.NewIntVar(1, 5, "kools") - chesterfields = model.NewIntVar(1, 5, "chesterfields") - lucky_strike = model.NewIntVar(1, 5, "lucky strike") - parliaments = model.NewIntVar(1, 5, "parliaments") - - model.AddAllDifferent(red, green, yellow, blue, ivory) - model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian) - model.AddAllDifferent(dog, snails, fox, zebra, horse) - model.AddAllDifferent(tea, coffee, water, milk, fruit_juice) - model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold) - - model.Add(englishman == red) - model.Add(spaniard == dog) - model.Add(coffee == green) - model.Add(ukrainian == tea) - model.Add(green == ivory + 1) - model.Add(old_gold == snails) - model.Add(kools == yellow) - model.Add(milk == 3) - model.Add(norwegian == 1) - - diff_fox_chesterfields = model.NewIntVar(-4, 4, "diff_fox_chesterfields") - model.Add(diff_fox_chesterfields == fox - chesterfields) - model.AddAbsEquality(1, diff_fox_chesterfields) - - diff_horse_kools = model.NewIntVar(-4, 4, "diff_horse_kools") - model.Add(diff_horse_kools == horse - kools) - model.AddAbsEquality(1, diff_horse_kools) - - model.Add(lucky_strike == fruit_juice) - model.Add(japanese == parliaments) - - diff_norwegian_blue = model.NewIntVar(-4, 4, "diff_norwegian_blue") - model.Add(diff_norwegian_blue == norwegian - blue) - model.AddAbsEquality(1, diff_norwegian_blue) + red = model.new_int_var(1, 5, "red") + green = model.new_int_var(1, 5, "green") + yellow = model.new_int_var(1, 5, "yellow") + blue = model.new_int_var(1, 5, "blue") + ivory = model.new_int_var(1, 5, "ivory") + + englishman = model.new_int_var(1, 5, "englishman") + spaniard = model.new_int_var(1, 5, "spaniard") + japanese = model.new_int_var(1, 5, "japanese") + ukrainian = model.new_int_var(1, 5, "ukrainian") + norwegian = model.new_int_var(1, 5, "norwegian") + + dog = model.new_int_var(1, 5, "dog") + snails = model.new_int_var(1, 5, "snails") + fox = model.new_int_var(1, 5, "fox") + zebra = model.new_int_var(1, 5, "zebra") + horse = model.new_int_var(1, 5, "horse") + + tea = model.new_int_var(1, 5, "tea") + coffee = model.new_int_var(1, 5, "coffee") + water = model.new_int_var(1, 5, "water") + milk = model.new_int_var(1, 5, "milk") + fruit_juice = model.new_int_var(1, 5, "fruit juice") + + old_gold = model.new_int_var(1, 5, "old gold") + kools = model.new_int_var(1, 5, "kools") + chesterfields = model.new_int_var(1, 5, "chesterfields") + lucky_strike = model.new_int_var(1, 5, "lucky strike") + parliaments = model.new_int_var(1, 5, "parliaments") + + model.add_all_different(red, green, yellow, blue, ivory) + model.add_all_different(englishman, spaniard, japanese, ukrainian, norwegian) + model.add_all_different(dog, snails, fox, zebra, horse) + model.add_all_different(tea, coffee, water, milk, fruit_juice) + model.add_all_different(parliaments, kools, chesterfields, lucky_strike, old_gold) + + model.add(englishman == red) + model.add(spaniard == dog) + model.add(coffee == green) + model.add(ukrainian == tea) + model.add(green == ivory + 1) + model.add(old_gold == snails) + model.add(kools == yellow) + model.add(milk == 3) + model.add(norwegian == 1) + + diff_fox_chesterfields = model.new_int_var(-4, 4, "diff_fox_chesterfields") + model.add(diff_fox_chesterfields == fox - chesterfields) + model.add_abs_equality(1, diff_fox_chesterfields) + + diff_horse_kools = model.new_int_var(-4, 4, "diff_horse_kools") + model.add(diff_horse_kools == horse - kools) + model.add_abs_equality(1, diff_horse_kools) + + model.add(lucky_strike == fruit_juice) + model.add(japanese == parliaments) + + diff_norwegian_blue = model.new_int_var(-4, 4, "diff_norwegian_blue") + model.add(diff_norwegian_blue == norwegian - blue) + model.add_abs_equality(1, diff_norwegian_blue) # Solve and print out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: people = [englishman, spaniard, japanese, ukrainian, norwegian] - water_drinker = [p for p in people if solver.Value(p) == solver.Value(water)][0] - zebra_owner = [p for p in people if solver.Value(p) == solver.Value(zebra)][0] - print("The", water_drinker.Name(), "drinks water.") - print("The", zebra_owner.Name(), "owns the zebra.") + water_drinker = [p for p in people if solver.value(p) == solver.value(water)][0] + zebra_owner = [p for p in people if solver.value(p) == solver.value(zebra)][0] + print("The", water_drinker.name, "drinks water.") + print("The", zebra_owner.name, "owns the zebra.") else: print("No solutions to the zebra problem, this is unusual!") diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index daef72cf5e5..633b0bcab46 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -12,15 +12,43 @@ # limitations under the License. # Home of CP/SAT solver (which includes SAT, max-SAT and PB problems). -# -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") -load("@rules_java//java:defs.bzl", "java_proto_library") load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_java//java:defs.bzl", "java_proto_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") load("@rules_python//python:proto.bzl", "py_proto_library") -package( - default_visibility = ["//visibility:public"], +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "cp_model", + srcs = ["cp_model.cc"], + hdrs = ["cp_model.h"], + deps = [ + ":cp_model_cc_proto", + ":cp_model_solver", + ":cp_model_utils", + ":model", + ":sat_parameters_cc_proto", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "model", + hdrs = ["model.h"], + deps = [ + "//ortools/base", + "//ortools/base:typeid", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + ], ) proto_library( @@ -43,16 +71,6 @@ java_proto_library( deps = [":sat_parameters_proto"], ) -proto_library( - name = "boolean_problem_proto", - srcs = ["boolean_problem.proto"], -) - -cc_proto_library( - name = "boolean_problem_cc_proto", - deps = [":boolean_problem_proto"], -) - proto_library( name = "cp_model_proto", srcs = ["cp_model.proto"], @@ -70,55 +88,27 @@ py_proto_library( java_proto_library( name = "cp_model_java_proto", - visibility = ["//visibility:public"], deps = [":cp_model_proto"], ) -cc_library( - name = "cp_model", - srcs = ["cp_model.cc"], - hdrs = ["cp_model.h"], - visibility = ["//visibility:public"], - deps = [ - ":cp_model_cc_proto", - ":cp_model_solver", - ":cp_model_utils", - ":model", - ":sat_parameters_cc_proto", - "//ortools/util:sorted_interval_list", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:span", - ], -) - -cc_library( - name = "model", - hdrs = ["model.h"], - visibility = ["//visibility:public"], - deps = [ - "//ortools/base", - "//ortools/base:typeid", - "@com_google_absl//absl/container:flat_hash_map", - ], -) - cc_library( name = "cp_model_utils", srcs = ["cp_model_utils.cc"], hdrs = ["cp_model_utils.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", - "//ortools/base", "//ortools/base:file", "//ortools/base:hash", "//ortools/base:stl_util", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "@com_google_protobuf//:protobuf", ], ) @@ -126,20 +116,39 @@ cc_library( name = "synchronization", srcs = ["synchronization.cc"], hdrs = ["synchronization.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":integer", ":model", ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":util", "//ortools/base", + "//ortools/base:status_macros", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", ], ) @@ -147,20 +156,22 @@ cc_library( name = "cp_model_checker", srcs = ["cp_model_checker.cc"], hdrs = ["cp_model_checker.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":sat_parameters_cc_proto", "//ortools/base", - "//ortools/base:hash", + "//ortools/base:types", "//ortools/port:proto_utils", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -168,21 +179,19 @@ cc_library( name = "constraint_violation", srcs = ["constraint_violation.cc"], hdrs = ["constraint_violation.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":sat_parameters_cc_proto", ":util", - "//ortools/base", - "//ortools/base:hash", + "//ortools/base:stl_util", "//ortools/graph:strongly_connected_components", - "//ortools/port:proto_utils", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -191,7 +200,6 @@ cc_library( name = "feasibility_jump", srcs = ["feasibility_jump.cc"], hdrs = ["feasibility_jump.h"], - visibility = ["//visibility:public"], deps = [ ":constraint_violation", ":cp_model_cc_proto", @@ -199,6 +207,7 @@ cc_library( ":cp_model_utils", ":integer", ":linear_model", + ":restart", ":sat_parameters_cc_proto", ":stat_tables", ":subsolver", @@ -206,11 +215,15 @@ cc_library( ":util", "//ortools/algorithms:binary_search", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/functional:function_ref", "@com_google_absl//absl/log", - "@com_google_absl//absl/random", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", ], ) @@ -219,7 +232,6 @@ cc_library( name = "linear_model", srcs = ["linear_model.cc"], hdrs = ["linear_model.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", @@ -235,11 +247,10 @@ cc_library( name = "parameters_validation", srcs = ["parameters_validation.cc"], hdrs = ["parameters_validation.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_search", ":sat_parameters_cc_proto", - "//ortools/base", + "@com_google_absl//absl/strings", ], ) @@ -254,22 +265,18 @@ cc_library( ":integer", ":integer_search", ":model", + ":sat_base", + ":sat_parameters_cc_proto", ":util", "//ortools/base", - "//ortools/base:cleanup", + "//ortools/base:types", + "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/strings:str_format", - ], -) - -cc_library( - name = "cp_model_objective", - srcs = ["cp_model_objective.cc"], - hdrs = ["cp_model_objective.h"], - deps = [ - ":cp_model_cc_proto", - ":cp_model_utils", - "//ortools/base", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", ], ) @@ -277,7 +284,6 @@ cc_library( name = "cp_model_solver", srcs = ["cp_model_solver.cc"], hdrs = ["cp_model_solver.h"], - visibility = ["//visibility:public"], deps = [ ":circuit", ":clause", @@ -285,6 +291,7 @@ cc_library( ":cp_model_checker", ":cp_model_lns", ":cp_model_loader", + ":cp_model_mapping", ":cp_model_postsolve", ":cp_model_presolve", ":cp_model_search", @@ -295,19 +302,23 @@ cc_library( ":drat_proof_handler", ":feasibility_jump", ":feasibility_pump", + ":implied_bounds", ":integer", ":integer_expr", ":integer_search", ":intervals", ":lb_tree_search", + ":linear_constraint", ":linear_model", ":linear_programming_constraint", ":linear_relaxation", + ":lp_utils", ":max_hs", ":model", ":optimization", ":parameters_validation", ":precedences", + ":presolve_context", ":probing", ":rins", ":sat_base", @@ -318,26 +329,39 @@ cc_library( ":stat_tables", ":subsolver", ":synchronization", + ":util", ":work_assignment", "//ortools/base", - "//ortools/base:file", - "//ortools/base:stl_util", + "//ortools/base:cleanup", + "//ortools/base:status_macros", "//ortools/base:strong_vector", "//ortools/base:threadpool", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:connected_components", + "//ortools/linear_solver:linear_solver_cc_proto", "//ortools/port:proto_utils", "//ortools/util:logging", + "//ortools/util:random_engine", "//ortools/util:sigint", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -345,7 +369,6 @@ cc_library( cc_library( name = "cp_model_mapping", hdrs = ["cp_model_mapping.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", @@ -356,12 +379,12 @@ cc_library( ":sat_base", "//ortools/base", "//ortools/base:strong_vector", - "//ortools/util:logging", - "//ortools/util:sorted_interval_list", + "//ortools/base:types", "//ortools/util:strong_integers", - "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -369,7 +392,6 @@ cc_library( name = "cp_model_loader", srcs = ["cp_model_loader.cc"], hdrs = ["cp_model_loader.h"], - visibility = ["//visibility:public"], deps = [ ":all_different", ":circuit", @@ -384,6 +406,7 @@ cc_library( ":integer", ":integer_expr", ":intervals", + ":linear_constraint", ":linear_relaxation", ":model", ":pb_constraint", @@ -394,10 +417,13 @@ cc_library( ":symmetry", ":table", ":timetable", + ":util", + "//ortools/algorithms:sparse_permutation", "//ortools/base", - "//ortools/base:file", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/util:logging", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", @@ -405,10 +431,24 @@ cc_library( "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_protobuf//:protobuf", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) +proto_library( + name = "boolean_problem_proto", + srcs = ["boolean_problem.proto"], +) + +cc_proto_library( + name = "boolean_problem_cc_proto", + deps = [":boolean_problem_proto"], +) + cc_library( name = "presolve_util", srcs = ["presolve_util.cc"], @@ -419,14 +459,23 @@ cc_library( ":util", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/random", "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -437,65 +486,93 @@ cc_library( deps = [ ":cp_model_cc_proto", ":cp_model_loader", + ":cp_model_mapping", ":cp_model_utils", + ":integer", ":lp_utils", ":model", ":presolve_util", ":sat_parameters_cc_proto", + ":sat_solver", ":util", "//ortools/base", "//ortools/base:mathutil", - "//ortools/base:strong_vector", "//ortools/port:proto_utils", "//ortools/util:affine_relation", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", - "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) cc_library( name = "cp_model_presolve", - srcs = ["cp_model_presolve.cc"], + srcs = [ + "cp_model_presolve.cc", + ], hdrs = ["cp_model_presolve.h"], deps = [ ":circuit", + ":clause", ":cp_model_cc_proto", ":cp_model_checker", ":cp_model_expand", - ":cp_model_loader", ":cp_model_mapping", - ":cp_model_objective", ":cp_model_symmetries", ":cp_model_utils", ":diffn_util", ":diophantine", ":inclusion", + ":integer", + ":model", ":presolve_context", ":presolve_util", ":probing", ":sat_base", + ":sat_inprocessing", ":sat_parameters_cc_proto", + ":sat_solver", ":simplification", + ":util", ":var_domination", "//ortools/base", - "//ortools/base:hash", "//ortools/base:mathutil", "//ortools/base:stl_util", - "//ortools/port:proto_utils", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/graph:strongly_connected_components", + "//ortools/graph:topologicalsorter", "//ortools/util:affine_relation", "//ortools/util:bitset", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "@com_google_protobuf//:protobuf", ], ) @@ -509,7 +586,9 @@ cc_library( ":cp_model_cc_proto", ":cp_model_utils", "//ortools/base", - "@com_google_absl//absl/base:core_headers", + "//ortools/base:types", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/log:check", ], ) @@ -519,15 +598,26 @@ cc_library( hdrs = ["cp_model_expand.h"], deps = [ ":cp_model_cc_proto", + ":cp_model_checker", ":cp_model_utils", ":presolve_context", + ":sat_parameters_cc_proto", ":util", "//ortools/base", - "//ortools/base:hash", + "//ortools/base:stl_util", + "//ortools/base:types", + "//ortools/port:proto_utils", + "//ortools/util:logging", "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", ], ) @@ -538,9 +628,12 @@ cc_library( ":model", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:strong_integers", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -549,6 +642,7 @@ cc_library( # Enable a warning to check for floating point to integer conversions. # In GCC-4.8, this was "-Wreal-conversion", but was removed in 4.9 # In Clang, this warning is "-Wfloat-conversion" +W_FLOAT_CONVERSION = "-Wfloat-conversion" cc_library( name = "sat_solver", @@ -569,16 +663,22 @@ cc_library( "//ortools/base", "//ortools/base:hash", "//ortools/base:stl_util", - "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/port:proto_utils", "//ortools/port:sysinfo", + "//ortools/util:bitset", "//ortools/util:logging", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -596,6 +696,7 @@ cc_library( "//ortools/port:proto_utils", "//ortools/util:bitset", "//ortools/util:running_stat", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], @@ -611,10 +712,21 @@ cc_library( ":integer", ":model", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -624,16 +736,29 @@ cc_library( hdrs = ["sat_inprocessing.h"], deps = [ ":clause", + ":drat_checker", ":model", ":probing", ":sat_base", ":sat_decision", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/util:bitset", + "//ortools/util:integer_pq", + "//ortools/util:logging", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:inlined_vector", - ], + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], ) cc_library( @@ -647,9 +772,12 @@ cc_library( ":sat_parameters_cc_proto", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:integer_pq", - "//ortools/util:random_engine", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -668,14 +796,20 @@ cc_library( "//ortools/base:hash", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:bitset", - "//ortools/util:random_engine", "//ortools/util:stats", "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/types:span", ], ) @@ -686,22 +820,25 @@ cc_library( hdrs = ["simplification.h"], deps = [ ":drat_proof_handler", + ":model", ":probing", ":sat_base", ":sat_inprocessing", ":sat_parameters_cc_proto", ":sat_solver", - ":util", "//ortools/algorithms:dynamic_partition", "//ortools/base", "//ortools/base:adjustable_priority_queue", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:logging", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -715,14 +852,16 @@ cc_library( ":sat_base", ":sat_parameters_cc_proto", "//ortools/base", - "//ortools/base:hash", - "//ortools/base:murmur", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -735,10 +874,10 @@ cc_library( deps = [ ":sat_base", "//ortools/algorithms:sparse_permutation", - "//ortools/base", "//ortools/base:strong_vector", "//ortools/util:stats", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -751,6 +890,7 @@ cc_library( "//ortools/algorithms:dynamic_partition", "//ortools/algorithms:sparse_permutation", "//ortools/base", + "@com_google_absl//absl/log:check", ], ) @@ -763,10 +903,21 @@ cc_library( ":cp_model_utils", ":integer", ":presolve_context", + ":presolve_util", + ":util", "//ortools/algorithms:dynamic_partition", "//ortools/base", + "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/util:affine_relation", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -783,9 +934,8 @@ cc_library( "//ortools/base", "//ortools/base:cleanup", "//ortools/base:hash", - "//ortools/base:iterator_adaptors", - "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/graph:iterators", "//ortools/util:bitset", "//ortools/util:rev", @@ -797,6 +947,8 @@ cc_library( "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], @@ -807,12 +959,16 @@ cc_library( srcs = ["integer_search.cc"], hdrs = ["integer_search.h"], deps = [ + ":cp_model_cc_proto", ":cp_model_mapping", ":implied_bounds", ":integer", + ":intervals", ":linear_programming_constraint", + ":model", ":probing", ":pseudo_costs", + ":restart", ":rins", ":sat_base", ":sat_decision", @@ -821,8 +977,15 @@ cc_library( ":sat_solver", ":synchronization", ":util", + "//ortools/base", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/types:span", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) @@ -833,12 +996,23 @@ cc_library( deps = [ ":cp_model_mapping", ":integer", + ":integer_expr", ":integer_search", ":linear_programming_constraint", + ":model", ":sat_base", + ":sat_decision", ":sat_parameters_cc_proto", ":sat_solver", ":synchronization", + ":util", + "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) @@ -848,8 +1022,14 @@ cc_library( hdrs = ["pseudo_costs.h"], deps = [ ":integer", - ":sat_decision", + ":model", + ":sat_base", ":sat_parameters_cc_proto", + ":util", + "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -870,8 +1050,15 @@ cc_library( ":sat_solver", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/util:rev", "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -891,37 +1078,19 @@ cc_library( "//ortools/base:cleanup", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/graph", "//ortools/graph:topologicalsorter", "//ortools/util:bitset", "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", - ], -) - -cc_library( - name = "implied_bounds", - srcs = ["implied_bounds.cc"], - hdrs = ["implied_bounds.h"], - deps = [ - ":integer", - ":linear_constraint", - ":model", - ":sat_base", - ":synchronization", - "//ortools/base", - "//ortools/base:strong_vector", - "//ortools/util:bitset", - "//ortools/util:strong_integers", - "@com_google_absl//absl/container:inlined_vector", - ], -) - -cc_library( - name = "inclusion", - hdrs = ["inclusion.h"], - deps = [ - "//ortools/base", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -937,13 +1106,19 @@ cc_library( ":model", ":precedences", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:mathutil", "//ortools/base:stl_util", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/types:span", ], ) @@ -953,11 +1128,23 @@ cc_library( hdrs = ["linear_propagation.h"], deps = [ ":integer", + ":model", ":sat_base", ":sat_solver", ":synchronization", + "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/util:bitset", + "//ortools/util:rev", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -972,13 +1159,14 @@ cc_library( ":sat_base", ":sat_solver", "//ortools/base", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:sort", - "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -989,7 +1177,7 @@ cc_library( deps = [ ":integer", "//ortools/base", - "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1010,8 +1198,11 @@ cc_library( ":theta_tree", ":timetable", "//ortools/base", - "//ortools/base:iterator_adaptors", + "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", ], ) @@ -1022,12 +1213,12 @@ cc_library( deps = [ ":integer", ":intervals", + ":model", ":sat_base", - ":sat_solver", - "//ortools/base", "//ortools/util:rev", - "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1036,13 +1227,13 @@ cc_library( srcs = ["timetable_edgefinding.cc"], hdrs = ["timetable_edgefinding.h"], deps = [ - ":implied_bounds", ":integer", ":intervals", + ":model", ":sat_base", - "//ortools/base", - "//ortools/util:sort", + "//ortools/base:iterator_adaptors", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1054,6 +1245,7 @@ cc_library( ":cumulative_energy", ":disjunctive", ":integer", + ":integer_expr", ":intervals", ":linear_constraint", ":model", @@ -1066,6 +1258,8 @@ cc_library( ":timetable_edgefinding", "//ortools/base", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1074,15 +1268,15 @@ cc_library( srcs = ["cumulative_energy.cc"], hdrs = ["cumulative_energy.h"], deps = [ - ":implied_bounds", ":integer", ":intervals", ":model", - ":sat_base", ":theta_tree", + ":util", "//ortools/base", - "//ortools/util:sort", + "//ortools/base:iterator_adaptors", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1102,13 +1296,19 @@ cc_library( "//ortools/algorithms:sparse_permutation", "//ortools/base", "//ortools/base:hash", + "//ortools/base:status_macros", "//ortools/base:strong_vector", + "//ortools/graph", "//ortools/graph:io", "//ortools/graph:util", "//ortools/port:proto_utils", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], ) @@ -1119,25 +1319,40 @@ cc_library( hdrs = ["linear_relaxation.h"], deps = [ ":circuit", + ":clause", ":cp_model_cc_proto", ":cp_model_mapping", + ":cp_model_utils", ":cuts", ":diffn_cuts", ":implied_bounds", ":integer", ":integer_expr", + ":intervals", ":linear_constraint", - ":linear_programming_constraint", ":model", + ":precedences", ":presolve_util", ":routing_cuts", ":sat_base", ":sat_parameters_cc_proto", + ":sat_solver", ":scheduling_cuts", + ":util", "//ortools/base", - "//ortools/base:iterator_adaptors", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/types:span", ], ) @@ -1148,8 +1363,15 @@ cc_library( deps = [ ":integer", ":model", - "//ortools/base", + ":sat_base", "//ortools/base:mathutil", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1158,6 +1380,7 @@ cc_library( srcs = ["linear_programming_constraint.cc"], hdrs = ["linear_programming_constraint.h"], deps = [ + ":cp_model_cc_proto", ":cuts", ":implied_bounds", ":integer", @@ -1165,25 +1388,35 @@ cc_library( ":linear_constraint", ":linear_constraint_manager", ":model", + ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":synchronization", ":util", ":zero_half_cuts", "//ortools/algorithms:binary_search", "//ortools/base", + "//ortools/base:mathutil", "//ortools/base:strong_vector", "//ortools/glop:parameters_cc_proto", - "//ortools/glop:preprocessor", "//ortools/glop:revised_simplex", "//ortools/glop:status", - "//ortools/graph:strongly_connected_components", + "//ortools/glop:variables_info", "//ortools/lp_data", "//ortools/lp_data:base", - "//ortools/lp_data:matrix_scaler", + "//ortools/lp_data:lp_data_utils", + "//ortools/lp_data:scattered_vector", + "//ortools/lp_data:sparse_column", + "//ortools/util:bitset", "//ortools/util:rev", "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1198,11 +1431,22 @@ cc_library( ":sat_parameters_cc_proto", ":synchronization", ":util", + "//ortools/base", + "//ortools/base:hash", + "//ortools/base:strong_vector", "//ortools/glop:revised_simplex", + "//ortools/glop:variables_info", + "//ortools/lp_data:base", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", ], ) @@ -1211,18 +1455,28 @@ cc_library( srcs = ["cuts.cc"], hdrs = ["cuts.h"], deps = [ + ":clause", ":implied_bounds", ":integer", ":linear_constraint", ":linear_constraint_manager", ":model", ":sat_base", - ":util", + ":synchronization", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", - "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1235,12 +1489,20 @@ cc_library( ":cuts", ":integer", ":linear_constraint", + ":linear_constraint_manager", ":model", + ":sat_base", + ":util", "//ortools/base", "//ortools/base:cleanup", "//ortools/base:mathutil", + "//ortools/base:strong_vector", "//ortools/graph", "//ortools/graph:max_flow", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1250,6 +1512,7 @@ cc_library( hdrs = ["scheduling_cuts.h"], deps = [ ":cuts", + ":implied_bounds", ":integer", ":intervals", ":linear_constraint", @@ -1259,8 +1522,17 @@ cc_library( ":util", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1271,6 +1543,7 @@ cc_library( deps = [ ":cuts", ":diffn_util", + ":implied_bounds", ":integer", ":intervals", ":linear_constraint", @@ -1280,8 +1553,17 @@ cc_library( ":util", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1295,6 +1577,7 @@ cc_library( "//ortools/base", "//ortools/lp_data:base", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1308,7 +1591,6 @@ cc_library( ":cp_model_cc_proto", ":cp_model_utils", ":integer", - ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", "//ortools/base", @@ -1319,7 +1601,12 @@ cc_library( "//ortools/lp_data", "//ortools/lp_data:base", "//ortools/util:fp_utils", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1330,6 +1617,7 @@ cc_library( deps = [ ":boolean_problem", ":boolean_problem_cc_proto", + ":clause", ":cp_model_mapping", ":encoding", ":integer", @@ -1340,15 +1628,24 @@ cc_library( ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", + ":synchronization", ":util", "//ortools/base", - "//ortools/linear_solver:linear_solver_cc_proto", + "//ortools/base:cleanup", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", "//ortools/port:proto_utils", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -1358,26 +1655,40 @@ cc_library( hdrs = ["max_hs.h"], deps = [ ":boolean_problem", - ":boolean_problem_cc_proto", + ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_utils", ":encoding", ":integer", ":integer_expr", ":integer_search", + ":linear_constraint", ":linear_relaxation", ":model", ":optimization", ":pb_constraint", + ":presolve_util", ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", + ":synchronization", ":util", "//ortools/base", + "//ortools/base:cleanup", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", "//ortools/linear_solver:linear_solver_cc_proto", "//ortools/port:proto_utils", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], @@ -1397,13 +1708,19 @@ cc_library( "//ortools/util:random_engine", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/log:log_streamer", "@com_google_absl//absl/numeric:int128", "@com_google_absl//absl/random", "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -1438,13 +1755,11 @@ cc_library( ":model", ":sat_base", ":sat_solver", - ":util", - "//ortools/base", - "//ortools/base:stl_util", - "//ortools/util:sorted_interval_list", + "//ortools/base:types", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1456,12 +1771,12 @@ cc_library( ":integer", ":model", ":sat_base", - ":sat_solver", "//ortools/base", + "//ortools/base:types", "//ortools/util:rev", - "//ortools/util:sort", "//ortools/util:strong_integers", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1472,8 +1787,16 @@ cc_library( deps = [ ":integer", ":intervals", + "//ortools/base", + "//ortools/base:stl_util", "//ortools/graph:connected_components", + "//ortools/util:integer_pq", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -1482,20 +1805,23 @@ cc_library( srcs = ["diffn.cc"], hdrs = ["diffn.h"], deps = [ - ":cumulative", + ":cumulative_energy", ":diffn_util", ":disjunctive", ":integer", + ":integer_expr", ":intervals", + ":linear_constraint", ":model", + ":precedences", ":sat_base", - ":sat_solver", - ":theta_tree", - "//ortools/base", - "//ortools/util:rev", - "//ortools/util:sort", + ":sat_parameters_cc_proto", + ":timetable", + "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1509,10 +1835,15 @@ cc_library( ":sat_base", ":sat_solver", "//ortools/base", + "//ortools/base:types", + "//ortools/graph:strongly_connected_components", "//ortools/util:rev", "//ortools/util:strong_integers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -1528,7 +1859,10 @@ cc_library( ":sat_solver", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:types", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1536,27 +1870,41 @@ cc_library( name = "cp_model_lns", srcs = ["cp_model_lns.cc"], hdrs = ["cp_model_lns.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_presolve", ":cp_model_utils", ":integer", + ":linear_constraint_manager", ":linear_programming_constraint", ":model", ":presolve_context", ":rins", + ":sat_parameters_cc_proto", ":subsolver", ":synchronization", + ":util", "//ortools/base", "//ortools/base:stl_util", - "//ortools/base:threadpool", + "//ortools/graph:connected_components", "//ortools/util:adaptative_parameter_value", - "//ortools/util:random_engine", + "//ortools/util:integer_pq", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", ], ) @@ -1564,22 +1912,32 @@ cc_library( name = "feasibility_pump", srcs = ["feasibility_pump.cc"], hdrs = ["feasibility_pump.h"], - visibility = ["//visibility:public"], deps = [ - ":cp_model_cc_proto", ":cp_model_mapping", ":integer", ":linear_constraint", + ":model", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":synchronization", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/glop:parameters_cc_proto", "//ortools/glop:revised_simplex", + "//ortools/glop:status", "//ortools/lp_data", "//ortools/lp_data:base", "//ortools/lp_data:lp_data_utils", + "//ortools/lp_data:sparse_column", "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -1587,16 +1945,15 @@ cc_library( name = "rins", srcs = ["rins.cc"], hdrs = ["rins.h"], - visibility = ["//visibility:public"], deps = [ - ":cp_model_mapping", ":integer", ":linear_programming_constraint", ":model", ":synchronization", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/types:optional", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", ], ) @@ -1604,12 +1961,15 @@ cc_library( name = "subsolver", srcs = ["subsolver.cc"], hdrs = ["subsolver.h"], - visibility = ["//visibility:public"], deps = [ "//ortools/base", "//ortools/base:threadpool", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/util:stats", - "//ortools/util:time_limit", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", ], @@ -1624,11 +1984,9 @@ cc_library( ":drat_writer", ":sat_base", "//ortools/base", - "//ortools/base:file", "//ortools/base:strong_vector", "//ortools/util:strong_integers", - "@com_google_absl//absl/hash", - "@com_google_absl//absl/status", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -1637,20 +1995,15 @@ cc_library( name = "drat_checker", srcs = ["drat_checker.cc"], hdrs = ["drat_checker.h"], - # data = [ - # "testdata/drup.cnf", - # "testdata/drup.drat", - # ], deps = [ ":sat_base", "//ortools/base", "//ortools/base:hash", - "//ortools/base:stl_util", "//ortools/base:strong_vector", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/status", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", @@ -1665,12 +2018,58 @@ cc_library( ":sat_base", "//ortools/base", "//ortools/base:file", + "//ortools/base:status_macros", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], ) +cc_binary( + name = "sat_runner", + srcs = [ + "opb_reader.h", + "sat_runner.cc", + ], + deps = [ + ":boolean_problem", + ":boolean_problem_cc_proto", + ":cp_model_cc_proto", + ":cp_model_solver", + ":model", + ":sat_cnf_reader", + ":sat_parameters_cc_proto", + "//ortools/base", + "//ortools/base:file", + "//ortools/base:path", + "//ortools/util:file_util", + "//ortools/util:filelineiter", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/log:flags", + "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "sat_cnf_reader", + hdrs = ["sat_cnf_reader.h"], + deps = [ + ":boolean_problem_cc_proto", + ":cp_model_cc_proto", + "//ortools/base", + "//ortools/util:filelineiter", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + cc_library( name = "cp_model_symmetries", srcs = ["cp_model_symmetries.cc"], @@ -1679,15 +2078,27 @@ cc_library( ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_utils", + ":model", ":presolve_context", + ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", ":symmetry_util", + ":util", "//ortools/algorithms:find_graph_symmetries", "//ortools/algorithms:sparse_permutation", + "//ortools/base", "//ortools/base:hash", + "//ortools/graph", + "//ortools/util:affine_relation", "//ortools/util:logging", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", "@com_google_protobuf//:protobuf", ], ) @@ -1707,8 +2118,47 @@ cc_library( ":cp_model_utils", ":model", ":sat_parameters_cc_proto", + "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:time_limit", + ], +) + +cc_library( + name = "implied_bounds", + srcs = ["implied_bounds.cc"], + hdrs = ["implied_bounds.h"], + deps = [ + "linear_constraint", + ":clause", + ":integer", + ":model", + ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":synchronization", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/util:bitset", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "inclusion", + hdrs = ["inclusion.h"], + deps = [ + "//ortools/base", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1718,6 +2168,7 @@ cc_library( hdrs = ["diophantine.h"], deps = [ ":util", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/numeric:int128", "@com_google_absl//absl/types:span", ], @@ -1738,47 +2189,26 @@ cc_library( ":sat_solver", ":synchronization", ":util", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", - "@com_google_absl//absl/random", "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/types:span", ], ) -cc_binary( - name = "sat_runner", - srcs = [ - "opb_reader.h", - "sat_cnf_reader.h", - "sat_runner.cc", - ], +cc_library( + name = "cp_model_objective", + srcs = ["cp_model_objective.cc"], + hdrs = ["cp_model_objective.h"], deps = [ - ":boolean_problem", - ":boolean_problem_cc_proto", ":cp_model_cc_proto", - ":cp_model_solver", - ":drat_proof_handler", - ":lp_utils", - ":optimization", - ":sat_solver", - ":simplification", - ":symmetry", - "//ortools/algorithms:sparse_permutation", - "//ortools/base", - "//ortools/base:file", - "//ortools/base:path", - "//ortools/base:threadpool", - "//ortools/lp_data:mps_reader", - "//ortools/lp_data:proto_utils", - "//ortools/util:filelineiter", - "//ortools/util:sigint", - "//ortools/util:time_limit", - "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", - "@com_google_protobuf//:protobuf", + ":cp_model_utils", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/log:check", ], ) diff --git a/ortools/sat/clause.cc b/ortools/sat/clause.cc index b17185b31d4..c6cc9ae7d5f 100644 --- a/ortools/sat/clause.cc +++ b/ortools/sat/clause.cc @@ -1199,7 +1199,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { DCHECK(InvariantsAreOk()); // TODO(user): We could just do it directly though. - int num_fixed_during_scc = 0; const int32_t size(implications_.size()); std::vector> scc; double dtime = 0.0; @@ -1213,7 +1212,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { for (const Literal l : graph.to_fix_) { if (assignment.LiteralIsFalse(l)) return false; if (assignment.LiteralIsTrue(l)) continue; - ++num_fixed_during_scc; if (!FixLiteral(l)) return false; } } @@ -1252,7 +1250,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { const Literal to_fix = all_true ? l : l.Negated(); if (assignment.LiteralIsFalse(to_fix)) return false; if (assignment.LiteralIsTrue(to_fix)) continue; - ++num_fixed_during_scc; if (!FixLiteral(l)) return false; } @@ -1355,9 +1352,9 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { } time_limit_->AdvanceDeterministicTime(dtime); - if (num_fixed_during_scc > 0) { - RemoveFixedVariables(); - } + const int num_fixed_during_scc = + trail_->Index() - num_processed_fixed_variables_; + RemoveFixedVariables(); DCHECK(InvariantsAreOk()); LOG_IF(INFO, log_info) << "SCC. " << num_equivalences << " redundant equivalent literals. " diff --git a/ortools/sat/cp_model_checker.cc b/ortools/sat/cp_model_checker.cc index 5e602e7b9df..54e7ade2064 100644 --- a/ortools/sat/cp_model_checker.cc +++ b/ortools/sat/cp_model_checker.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -915,7 +916,10 @@ bool PossibleIntegerOverflow(const CpModelProto& model, // In addition to computing the min/max possible sum, we also often compare // it with the constraint bounds, so we do not want max - min to overflow. // We might also create an intermediate variable to represent the sum. - if (sum_min < std::numeric_limits::min() / 2) return true; + // + // Note that it is important to be symmetric here, as we do not want expr to + // pass but not -expr! + if (sum_min < -std::numeric_limits::max() / 2) return true; if (sum_max > std::numeric_limits::max() / 2) return true; return false; } @@ -1376,6 +1380,7 @@ class ConstraintChecker { std::sort(events.begin(), events.end()); + // This works because we will process negative demands first. int64_t current_load = 0; for (const auto& [time, delta] : events) { current_load += delta; diff --git a/ortools/sat/cp_model_expand.cc b/ortools/sat/cp_model_expand.cc index 03e9b356252..7ba5a96064a 100644 --- a/ortools/sat/cp_model_expand.cc +++ b/ortools/sat/cp_model_expand.cc @@ -29,11 +29,13 @@ #include "absl/log/check.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" +#include "google/protobuf/message.h" #include "ortools/base/logging.h" #include "ortools/base/stl_util.h" #include "ortools/base/types.h" #include "ortools/port/proto_utils.h" #include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_checker.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/sat/presolve_context.h" #include "ortools/sat/sat_parameters.pb.h" @@ -573,14 +575,34 @@ void ExpandVariableElement(ConstraintProto* ct, PresolveContext* context) { if (var_domain.IsFixed()) { context->AddImplyInDomain(index_lit, target_ref, var_domain); } else { + // We make sure we only use positive ref. + // + // TODO(user): Get rid of this code once we accept affine in element + // constraint. ConstraintProto* const ct = context->working_model->add_constraints(); ct->add_enforcement_literal(index_lit); - ct->mutable_linear()->add_vars(var); - ct->mutable_linear()->add_coeffs(1); - ct->mutable_linear()->add_vars(target_ref); - ct->mutable_linear()->add_coeffs(-1); + if (RefIsPositive(var)) { + ct->mutable_linear()->add_vars(var); + ct->mutable_linear()->add_coeffs(1); + } else { + ct->mutable_linear()->add_vars(NegatedRef(var)); + ct->mutable_linear()->add_coeffs(-1); + } + if (RefIsPositive(target_ref)) { + ct->mutable_linear()->add_vars(target_ref); + ct->mutable_linear()->add_coeffs(-1); + } else { + ct->mutable_linear()->add_vars(NegatedRef(target_ref)); + ct->mutable_linear()->add_coeffs(1); + } ct->mutable_linear()->add_domain(0); ct->mutable_linear()->add_domain(0); + + // Note that this should have been checked at model validation. + DCHECK(!PossibleIntegerOverflow(*context->working_model, + ct->mutable_linear()->vars(), + ct->mutable_linear()->coeffs())) + << google::protobuf::ShortFormat(*ct); } } diff --git a/ortools/sat/cp_model_presolve.cc b/ortools/sat/cp_model_presolve.cc index e54104f56b7..a54f79a6068 100644 --- a/ortools/sat/cp_model_presolve.cc +++ b/ortools/sat/cp_model_presolve.cc @@ -1595,6 +1595,7 @@ bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) { bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) { if (context_->ModelIsUnsat()) return false; + // TODO(user): Presolve f(X) = g(X) % fixed_mod. const LinearExpressionProto target = ct->int_mod().target(); const LinearExpressionProto expr = ct->int_mod().exprs(0); const LinearExpressionProto mod = ct->int_mod().exprs(1); @@ -1668,7 +1669,8 @@ bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) { // expr.vars(0) is large, the implied domain is not too complex. if (target.vars().size() == 1 && expr.vars().size() == 1 && context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) && - context_->VariableIsUniqueAndRemovable(target.vars(0))) { + context_->VariableIsUniqueAndRemovable(target.vars(0)) && + target.vars(0) != expr.vars(0)) { const int64_t fixed_mod = context_->FixedValue(mod); std::vector values; const Domain dom = context_->DomainOf(target.vars(0)); @@ -5144,6 +5146,7 @@ bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) { if (!MarkConstraintAsFalse(interval_ct)) { return false; } + context_->UpdateConstraintVariableUsage(interval_index); context_->UpdateRuleStats( "no_overlap: unperform duplicate non zero-sized intervals"); // We can remove the interval from the no_overlap. @@ -6980,11 +6983,16 @@ bool CpModelPresolver::PresolvePureSatPart() { // removing variable from the objective if they can be set to their "low" // objective value, and also removing enforcement literal that can be set to // false and don't appear elsewhere. + int num_in_extra_constraints = 0; std::vector can_be_removed(num_variables, false); for (int i = 0; i < num_variables; ++i) { const int var = new_to_old_index[i]; if (context_->VarToConstraints(var).empty()) { can_be_removed[i] = true; + } else { + // That might correspond to the objective or a variable with an affine + // relation that is still in the model. + ++num_in_extra_constraints; } } @@ -7011,7 +7019,7 @@ bool CpModelPresolver::PresolvePureSatPart() { absl::StrongVector equiv_map; if (!context_->params().debug_postsolve_with_full_solver() && num_ignored_variables == 0 && num_ignored_constraints == 0 && - !context_->working_model->has_objective()) { + num_in_extra_constraints == 0) { // Some problems are formulated in such a way that our SAT heuristics // simply works without conflict. Get them out of the way first because it // is possible that the presolve lose this "lucky" ordering. This is in @@ -9065,7 +9073,7 @@ void CpModelPresolver::DetectDominatedLinearConstraints() { // TODO(user): Also substitute if this appear in the objective? // TODO(user): In some case we only need common_part <= new_var. -void CpModelPresolver::RemoveCommonPart( +bool CpModelPresolver::RemoveCommonPart( const absl::flat_hash_map& common_var_coeff_map, const std::vector>& block) { int new_var; @@ -9138,6 +9146,14 @@ void CpModelPresolver::RemoveCommonPart( new_linear->add_coeffs(-1); new_linear->add_domain(0); new_linear->add_domain(0); + if (PossibleIntegerOverflow(*context_->working_model, new_linear->vars(), + new_linear->coeffs())) { + context_->UpdateRuleStats( + "TODO linear matrix: possible overflow in common part!"); + context_->working_model->mutable_constraints()->RemoveLast(); + return false; + } + context_->UpdateNewConstraintsVariableUsage(); } @@ -9182,6 +9198,7 @@ void CpModelPresolver::RemoveCommonPart( } context_->UpdateConstraintVariableUsage(c); } + return true; } namespace { @@ -9330,10 +9347,10 @@ void CpModelPresolver::FindBigVerticalLinearOverlap() { } // Introduce new_var = common_part and perform the substitution. + if (!RemoveCommonPart(coeff_map, block)) continue; ++num_blocks; nz_reduction += saved_nz; context_->UpdateRuleStats("linear matrix: common vertical rectangle"); - RemoveCommonPart(coeff_map, block); } timer.AddCounter("blocks", num_blocks); @@ -9463,8 +9480,6 @@ void CpModelPresolver::FindBigHorizontalLinearOverlap() { // Introduce a new variable = common_part. // Use it in all linear constraint. if (block.size() > 1) { - context_->UpdateRuleStats("linear matrix: common horizontal rectangle"); - // Try to extend with exact matches that were skipped. const int match_size = var_to_coeff_non_zeros.size(); for (const auto [index, old_match_size] : old_matches) { @@ -9490,13 +9505,15 @@ void CpModelPresolver::FindBigHorizontalLinearOverlap() { // TODO(user): avoid creating the map? this is not visible in profile // though since we only do it when a reduction is performed. - ++num_blocks; absl::flat_hash_map coeff_map; for (const int var : var_to_coeff_non_zeros) { coeff_map[var] = var_to_coeff[var]; } + if (!RemoveCommonPart(coeff_map, block)) continue; + + ++num_blocks; nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size()); - RemoveCommonPart(coeff_map, block); + context_->UpdateRuleStats("linear matrix: common horizontal rectangle"); for (const int i : used_sorted_linear) sorted_linear[i] = -1; } } @@ -11742,7 +11759,8 @@ CpSolverStatus CpModelPresolver::Presolve() { if (context_->params().cp_model_use_sat_presolve()) { if (!time_limit_->LimitReached()) { if (!PresolvePureSatPart()) { - (void)context_->NotifyThatModelIsUnsat("UNSAT during SAT presolve"); + (void)context_->NotifyThatModelIsUnsat( + "Proven Infeasible during SAT presolve"); return InfeasibleStatus(); } } diff --git a/ortools/sat/cp_model_presolve.h b/ortools/sat/cp_model_presolve.h index 6b5538035c9..b0bc119efdb 100644 --- a/ortools/sat/cp_model_presolve.h +++ b/ortools/sat/cp_model_presolve.h @@ -263,7 +263,13 @@ class CpModelPresolver { // Assumes that all [constraint_index, multiple] in block are linear // constraint that contains multiple * common_part and perform the // substitution. - void RemoveCommonPart( + // + // Returns false if the substitution cannot be performed because the equation + // common_part = new_variable is a linear equation with potential overflow. + // + // TODO(user): I would be great to change the overflow precondition so that + // this cannot happen by maybe taking the rhs into account? + bool RemoveCommonPart( const absl::flat_hash_map& common_var_coeff_map, const std::vector>& block); diff --git a/ortools/sat/cp_model_solver.cc b/ortools/sat/cp_model_solver.cc index c44b65eb1f3..dc7c6b64d12 100644 --- a/ortools/sat/cp_model_solver.cc +++ b/ortools/sat/cp_model_solver.cc @@ -182,6 +182,8 @@ ABSL_FLAG( ABSL_FLAG(bool, cp_model_ignore_objective, false, "If true, ignore the objective."); +ABSL_FLAG(bool, cp_model_ignore_hints, false, + "If true, ignore any supplied hints."); ABSL_FLAG(bool, cp_model_fingerprint_model, true, "Fingerprint the model."); namespace operations_research { @@ -3865,6 +3867,9 @@ void TestSolutionHintForFeasibility(const CpModelProto& model_proto, // TODO(user): If the hint specifies all non-fixed variables we could also // do the check. if (model_proto.solution_hint().vars_size() != model_proto.variables_size()) { + SOLVER_LOG(logger, "The solution hint is incomplete: ", + model_proto.solution_hint().vars_size(), " out of ", + model_proto.variables_size(), " variables hinted."); return; } @@ -4134,6 +4139,12 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) { context->working_model->clear_floating_point_objective(); } + if (absl::GetFlag(FLAGS_cp_model_ignore_hints) && + context->working_model->has_solution_hint()) { + SOLVER_LOG(logger, "Ignoring solution hint"); + context->working_model->clear_solution_hint(); + } + // Checks for hints early in case they are forced to be hard constraints. if (params.fix_variables_to_their_hinted_value() && model_proto.has_solution_hint()) { diff --git a/ortools/sat/docs/README.md b/ortools/sat/docs/README.md index 4f15cbbd242..d0b442f43b1 100644 --- a/ortools/sat/docs/README.md +++ b/ortools/sat/docs/README.md @@ -40,21 +40,21 @@ def SimpleSatProgram(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") diff --git a/ortools/sat/docs/boolean_logic.md b/ortools/sat/docs/boolean_logic.md index e581c04be6e..52d43680168 100644 --- a/ortools/sat/docs/boolean_logic.md +++ b/ortools/sat/docs/boolean_logic.md @@ -29,8 +29,8 @@ from ortools.sat.python import cp_model def LiteralSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - not_x = x.Not() + x = model.new_bool_var("x") + not_x = x.negated() print(x) print(not_x) @@ -131,10 +131,10 @@ from ortools.sat.python import cp_model def BoolOrSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") + x = model.new_bool_var("x") + y = model.new_bool_var("y") - model.AddBoolOr([x, y.Not()]) + model.add_bool_or([x, y.negated()]) BoolOrSampleSat() @@ -241,20 +241,20 @@ def ReifiedSampleSat(): """Showcase creating a reified constraint.""" model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - b = model.NewBoolVar("b") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + b = model.new_bool_var("b") # First version using a half-reified bool and. - model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b) + model.add_bool_and(x, y.negated()).only_enforce_if(b) # Second version using implications. - model.AddImplication(b, x) - model.AddImplication(b, y.Not()) + model.add_implication(b, x) + model.add_implication(b, y.negated()) # Third version using bool or. - model.AddBoolOr(b.Not(), x) - model.AddBoolOr(b.Not(), y.Not()) + model.add_bool_or(b.negated(), x) + model.add_bool_or(b.negated(), y.negated()) ReifiedSampleSat() @@ -407,22 +407,22 @@ def BooleanProductSampleSat(): p == x * y, which is the same as p <=> x and y """ model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - p = model.NewBoolVar("p") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + p = model.new_bool_var("p") # x and y implies p, rewrite as not(x and y) or p. - model.AddBoolOr(x.Not(), y.Not(), p) + model.add_bool_or(x.negated(), y.negated(), p) # p implies x and y, expanded into two implications. - model.AddImplication(p, x) - model.AddImplication(p, y) + model.add_implication(p, x) + model.add_implication(p, y) # Create a solver and solve. solver = cp_model.CpSolver() solution_printer = cp_model.VarArraySolutionPrinter([x, y, p]) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) BooleanProductSampleSat() diff --git a/ortools/sat/docs/channeling.md b/ortools/sat/docs/channeling.md index 7dcf50f3f2d..3faa138b792 100644 --- a/ortools/sat/docs/channeling.md +++ b/ortools/sat/docs/channeling.md @@ -38,20 +38,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def ChannelingSampleSat(): """Demonstrates how to link integer constraints together.""" @@ -60,24 +55,24 @@ def ChannelingSampleSat(): model = cp_model.CpModel() # Declare our two primary variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") # Declare our intermediate boolean variable. - b = model.NewBoolVar("b") + b = model.new_bool_var("b") # Implement b == (x >= 5). - model.Add(x >= 5).OnlyEnforceIf(b) - model.Add(x < 5).OnlyEnforceIf(b.Not()) + model.add(x >= 5).only_enforce_if(b) + model.add(x < 5).only_enforce_if(b.negated()) # Create our two half-reified constraints. # First, b implies (y == 10 - x). - model.Add(y == 10 - x).OnlyEnforceIf(b) + model.add(y == 10 - x).only_enforce_if(b) # Second, not(b) implies y == 0. - model.Add(y == 0).OnlyEnforceIf(b.Not()) + model.add(y == 0).only_enforce_if(b.negated()) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -89,7 +84,7 @@ def ChannelingSampleSat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, y, b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) ChannelingSampleSat() @@ -364,43 +359,43 @@ def BinpackingProblemSat(): for i in all_items: num_copies = items[i][1] for b in all_bins: - x[(i, b)] = model.NewIntVar(0, num_copies, f"x[{i},{b}]") + x[(i, b)] = model.new_int_var(0, num_copies, f"x[{i},{b}]") # Load variables. - load = [model.NewIntVar(0, bin_capacity, f"load[{b}]") for b in all_bins] + load = [model.new_int_var(0, bin_capacity, f"load[{b}]") for b in all_bins] # Slack variables. - slacks = [model.NewBoolVar(f"slack[{b}]") for b in all_bins] + slacks = [model.new_bool_var(f"slack[{b}]") for b in all_bins] # Links load and x. for b in all_bins: - model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) + model.add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) # Place all items. for i in all_items: - model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1]) + model.add(sum(x[(i, b)] for b in all_bins) == items[i][1]) # Links load and slack through an equivalence relation. safe_capacity = bin_capacity - slack_capacity for b in all_bins: # slack[b] => load[b] <= safe_capacity. - model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b]) + model.add(load[b] <= safe_capacity).only_enforce_if(slacks[b]) # not(slack[b]) => load[b] > safe_capacity. - model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not()) + model.add(load[b] > safe_capacity).only_enforce_if(slacks[b].negated()) # Maximize sum of slacks. - model.Maximize(sum(slacks)) + model.maximize(sum(slacks)) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Solve status: {solver.StatusName(status)}") + status = solver.solve(model) + print(f"solve status: {solver.status_name(status)}") if status == cp_model.OPTIMAL: - print(f"Optimal objective value: {solver.ObjectiveValue()}") + print(f"Optimal objective value: {solver.objective_value}") print("Statistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()}s") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time}s") BinpackingProblemSat() diff --git a/ortools/sat/docs/integer_arithmetic.md b/ortools/sat/docs/integer_arithmetic.md index 11967ff53c4..665587f3d68 100644 --- a/ortools/sat/docs/integer_arithmetic.md +++ b/ortools/sat/docs/integer_arithmetic.md @@ -125,20 +125,20 @@ def RabbitsAndPheasantsSat(): """Solves the rabbits + pheasants problem.""" model = cp_model.CpModel() - r = model.NewIntVar(0, 100, "r") - p = model.NewIntVar(0, 100, "p") + r = model.new_int_var(0, 100, "r") + p = model.new_int_var(0, 100, "p") # 20 heads. - model.Add(r + p == 20) + model.add(r + p == 20) # 56 legs. - model.Add(4 * r + 2 * p == 56) + model.add(4 * r + 2 * p == 56) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants") + print(f"{solver.value(r)} rabbits and {solver.value(p)} pheasants") RabbitsAndPheasantsSat() @@ -307,20 +307,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def earliness_tardiness_cost_sample_sat(): """Encode the piecewise linear expression.""" @@ -334,7 +329,7 @@ def earliness_tardiness_cost_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the piecewise linear function. # @@ -343,24 +338,24 @@ def earliness_tardiness_cost_sample_sat(): # ed ld # large_constant = 1000 - expr = model.NewIntVar(0, large_constant, "expr") + expr = model.new_int_var(0, large_constant, "expr") # First segment. - s1 = model.NewIntVar(-large_constant, large_constant, "s1") - model.Add(s1 == earliness_cost * (earliness_date - x)) + s1 = model.new_int_var(-large_constant, large_constant, "s1") + model.add(s1 == earliness_cost * (earliness_date - x)) # Second segment. s2 = 0 # Third segment. - s3 = model.NewIntVar(-large_constant, large_constant, "s3") - model.Add(s3 == lateness_cost * (x - lateness_date)) + s3 = model.new_int_var(-large_constant, large_constant, "s3") + model.add(s3 == lateness_cost * (x - lateness_date)) # Link together expr and x through s1, s2, and s3. - model.AddMaxEquality(expr, [s1, s2, s3]) + model.add_max_equality(expr, [s1, s2, s3]) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -372,7 +367,7 @@ def earliness_tardiness_cost_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) earliness_tardiness_cost_sample_sat() @@ -649,20 +644,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def step_function_sample_sat(): """Encode the step function.""" @@ -671,7 +661,7 @@ def step_function_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the step function # Note it is not defined for x == 2. @@ -682,32 +672,32 @@ def step_function_sample_sat(): # -- --- 0 # 0 ================ 20 # - expr = model.NewIntVar(0, 3, "expr") + expr = model.new_int_var(0, 3, "expr") # expr == 0 on [5, 6] U [8, 10] - b0 = model.NewBoolVar("b0") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)]) - ).OnlyEnforceIf(b0) - model.Add(expr == 0).OnlyEnforceIf(b0) + b0 = model.new_bool_var("b0") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(5, 6), (8, 10)]) + ).only_enforce_if(b0) + model.add(expr == 0).only_enforce_if(b0) # expr == 2 on [0, 1] U [3, 4] U [11, 20] - b2 = model.NewBoolVar("b2") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)]) - ).OnlyEnforceIf(b2) - model.Add(expr == 2).OnlyEnforceIf(b2) + b2 = model.new_bool_var("b2") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)]) + ).only_enforce_if(b2) + model.add(expr == 2).only_enforce_if(b2) # expr == 3 when x == 7 - b3 = model.NewBoolVar("b3") - model.Add(x == 7).OnlyEnforceIf(b3) - model.Add(expr == 3).OnlyEnforceIf(b3) + b3 = model.new_bool_var("b3") + model.add(x == 7).only_enforce_if(b3) + model.add(expr == 3).only_enforce_if(b3) # At least one bi is true. (we could use an exactly one constraint). - model.AddBoolOr(b0, b2, b3) + model.add_bool_or(b0, b2, b3) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -719,7 +709,7 @@ def step_function_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) step_function_sample_sat() diff --git a/ortools/sat/docs/model.md b/ortools/sat/docs/model.md index 3e213210167..e1ad4eaa912 100644 --- a/ortools/sat/docs/model.md +++ b/ortools/sat/docs/model.md @@ -85,26 +85,26 @@ def SolutionHintingSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Solution hinting: x <- 1, y <- 2 - model.AddHint(x, 1) - model.AddHint(y, 2) + model.add_hint(x, 1) + model.add_hint(y, 2) # Creates a solver and solves. solver = cp_model.CpSolver() solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolutionHintingSampleSat() @@ -318,34 +318,34 @@ def CloneModelSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print("Optimal value of the original model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the original model: {}".format(solver.objective_value)) - # Clone the model. - copy = model.Clone() + # Clones the model. + copy = model.clone() - copy_x = copy.GetIntVarFromProtoIndex(x.Index()) - copy_y = copy.GetIntVarFromProtoIndex(y.Index()) + copy_x = copy.get_int_var_from_proto_index(x.index) + copy_y = copy.get_int_var_from_proto_index(y.index) - copy.Add(copy_x + copy_y <= 1) + copy.add(copy_x + copy_y <= 1) - status = solver.Solve(copy) + status = solver.solve(copy) if status == cp_model.OPTIMAL: - print("Optimal value of the modified model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the modified model: {}".format(solver.objective_value)) CloneModelSampleSat() diff --git a/ortools/sat/docs/scheduling.md b/ortools/sat/docs/scheduling.md index 776d4df361f..5a5d65c2482 100644 --- a/ortools/sat/docs/scheduling.md +++ b/ortools/sat/docs/scheduling.md @@ -38,22 +38,22 @@ def IntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, "interval") + end_var = model.new_int_var(0, horizon, "end") + interval_var = model.new_interval_var(start_var, duration, end_var + 2, "interval") print(f"interval = {repr(interval_var)}") # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewFixedSizeIntervalVar( + fixed_size_interval_var = model.new_fixed_size_interval_var( start_var, 10, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewFixedSizeIntervalVar(5, 10, "fixed_interval") + fixed_interval = model.new_fixed_size_interval_var(5, 10, "fixed_interval") print(f"fixed_interval = {repr(fixed_interval)}") @@ -207,11 +207,11 @@ def OptionalIntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - presence_var = model.NewBoolVar("presence") - interval_var = model.NewOptionalIntervalVar( + end_var = model.new_int_var(0, horizon, "end") + presence_var = model.new_bool_var("presence") + interval_var = model.new_optional_interval_var( start_var, duration, end_var + 2, presence_var, "interval" ) @@ -219,13 +219,13 @@ def OptionalIntervalSampleSat(): # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar( + fixed_size_interval_var = model.new_optional_fixed_size_interval_var( start_var, 10, presence_var, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewOptionalFixedSizeIntervalVar( + fixed_interval = model.new_optional_fixed_size_interval_var( 5, 10, presence_var, "fixed_interval" ) print(f"fixed_interval = {repr(fixed_interval)}") @@ -385,45 +385,45 @@ def NoOverlapSampleSat(): horizon = 21 # 3 weeks. # Task 0, duration 2. - start_0 = model.NewIntVar(0, horizon, "start_0") + start_0 = model.new_int_var(0, horizon, "start_0") duration_0 = 2 # Python cp/sat code accepts integer variables or constants. - end_0 = model.NewIntVar(0, horizon, "end_0") - task_0 = model.NewIntervalVar(start_0, duration_0, end_0, "task_0") + end_0 = model.new_int_var(0, horizon, "end_0") + task_0 = model.new_interval_var(start_0, duration_0, end_0, "task_0") # Task 1, duration 4. - start_1 = model.NewIntVar(0, horizon, "start_1") + start_1 = model.new_int_var(0, horizon, "start_1") duration_1 = 4 # Python cp/sat code accepts integer variables or constants. - end_1 = model.NewIntVar(0, horizon, "end_1") - task_1 = model.NewIntervalVar(start_1, duration_1, end_1, "task_1") + end_1 = model.new_int_var(0, horizon, "end_1") + task_1 = model.new_interval_var(start_1, duration_1, end_1, "task_1") # Task 2, duration 3. - start_2 = model.NewIntVar(0, horizon, "start_2") + start_2 = model.new_int_var(0, horizon, "start_2") duration_2 = 3 # Python cp/sat code accepts integer variables or constants. - end_2 = model.NewIntVar(0, horizon, "end_2") - task_2 = model.NewIntervalVar(start_2, duration_2, end_2, "task_2") + end_2 = model.new_int_var(0, horizon, "end_2") + task_2 = model.new_interval_var(start_2, duration_2, end_2, "task_2") # Weekends. - weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0") - weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1") - weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2") + weekend_0 = model.new_interval_var(5, 2, 7, "weekend_0") + weekend_1 = model.new_interval_var(12, 2, 14, "weekend_1") + weekend_2 = model.new_interval_var(19, 2, 21, "weekend_2") # No Overlap constraint. - model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + model.add_no_overlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) # Makespan objective. - obj = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(obj, [end_0, end_1, end_2]) - model.Minimize(obj) + obj = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(obj, [end_0, end_1, end_2]) + model.minimize(obj) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Print out makespan and the start times for all tasks. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") - print(f"Task 0 starts at {solver.Value(start_0)}") - print(f"Task 1 starts at {solver.Value(start_1)}") - print(f"Task 2 starts at {solver.Value(start_2)}") + print(f"Optimal Schedule Length: {solver.objective_value}") + print(f"Task 0 starts at {solver.value(start_0)}") + print(f"Task 1 starts at {solver.value(start_1)}") + print(f"Task 2 starts at {solver.value(start_2)}") else: print(f"Solver exited with nonoptimal status: {status}") @@ -659,7 +659,8 @@ the capacity between the actual profile and it max capacity. ```python #!/usr/bin/env python3 -"""Solve a simple scheduling problem with a variable work load.""" +"""Solves a simple scheduling problem with a variable work load.""" + import io import pandas as pd @@ -741,12 +742,12 @@ def main(): horizon: int = 24 * 60 # Variables - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index ) - performed = model.NewBoolVarSeries(name="performed", index=tasks_df.index) + performed = model.new_bool_var_series(name="performed", index=tasks_df.index) - intervals = model.NewOptionalFixedSizeIntervalVarSeries( + intervals = model.new_optional_fixed_size_interval_var_series( name="intervals", index=tasks_df.index, starts=starts, @@ -756,7 +757,7 @@ def main(): # Set up the profile. We use fixed (intervals, demands) to fill in the space # between the actual load profile and the max capacity. - time_period_intervals = model.NewFixedSizeIntervalVarSeries( + time_period_intervals = model.new_fixed_size_interval_var_series( name="time_period_intervals", index=capacity_df.index, starts=capacity_df.start_hour * minutes_per_period, @@ -765,7 +766,7 @@ def main(): time_period_heights = max_capacity - capacity_df.capacity # Cumulative constraint. - model.AddCumulative( + model.add_cumulative( intervals.to_list() + time_period_intervals.to_list(), tasks_df.load.to_list() + time_period_heights.to_list(), max_capacity, @@ -774,18 +775,18 @@ def main(): # Objective: maximize the value of performed intervals. # 1 is the max priority. max_priority = max(tasks_df.priority) - model.Maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) + model.maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) # Create the solver and solve the model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 30.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - start_values = solver.Values(starts) - performed_values = solver.BooleanValues(performed) + start_values = solver.values(starts) + performed_values = solver.boolean_values(performed) for task in tasks_df.index: if performed_values[task]: print(f"task {task} starts at {start_values[task]}") @@ -828,7 +829,12 @@ number of other intervals that precede it. from ortools.sat.python import cp_model -def RankTasks(model, starts, presences, ranks): +def RankTasks( + model: cp_model.CpModel, + starts: list[cp_model.IntVar], + presences: list[cp_model.IntVar], + ranks: list[cp_model.IntVar], +): """This method adds constraints and variables to links tasks and ranks. This method assumes that all starts are disjoint, meaning that all tasks have @@ -852,36 +858,44 @@ def RankTasks(model, starts, presences, ranks): if i == j: precedences[(i, j)] = presences[i] else: - prec = model.NewBoolVar(f"{i} before {j}") + prec = model.new_bool_var(f"{i} before {j}") precedences[(i, j)] = prec - model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec) + model.add(starts[i] < starts[j]).only_enforce_if(prec) # Treats optional intervals. for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if not cp_model.ObjectIsATrueLiteral(presences[i]): - tmp_array.append(presences[i].Not()) + if not cp_model.object_is_a_true_literal(presences[i]): + tmp_array.append(presences[i].negated()) # Makes sure that if i is not performed, all precedences are false. - model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if not cp_model.ObjectIsATrueLiteral(presences[j]): - tmp_array.append(presences[j].Not()) + model.add_implication( + presences[i].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[i].negated(), precedences[(j, i)].negated() + ) + if not cp_model.object_is_a_true_literal(presences[j]): + tmp_array.append(presences[j].negated()) # Makes sure that if j is not performed, all precedences are false. - model.AddImplication(presences[j].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[j].Not(), precedences[(j, i)].Not()) + model.add_implication( + presences[j].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[j].negated(), precedences[(j, i)].negated() + ) # The following bool_or will enforce that for any two intervals: # i precedes j or j precedes i or at least one interval is not # performed. - model.AddBoolOr(tmp_array) + model.add_bool_or(tmp_array) # Redundant constraint: it propagates early that at most one precedence # is true. - model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not()) - model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not()) + model.add_implication(precedences[(i, j)], precedences[(j, i)].negated()) + model.add_implication(precedences[(j, i)], precedences[(i, j)].negated()) # Links precedences and ranks. for i in all_tasks: - model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) + model.add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) def RankingSampleSat(): @@ -900,15 +914,15 @@ def RankingSampleSat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - end = model.NewIntVar(0, horizon, f"end[{t}]") + end = model.new_int_var(0, horizon, f"end[{t}]") if t < num_tasks // 2: - interval = model.NewIntervalVar(start, duration, end, f"interval[{t}]") + interval = model.new_interval_var(start, duration, end, f"interval[{t}]") presence = True else: - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_interval_var( start, duration, end, presence, f"o_interval[{t}]" ) starts.append(start) @@ -917,45 +931,44 @@ def RankingSampleSat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. RankTasks(model, starts, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(ends[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") @@ -1510,26 +1523,26 @@ def rank_tasks_with_circuit( arcs: List[cp_model.ArcT] = [] for i in all_tasks: # if node i is first. - start_lit = model.NewBoolVar(f"start_{i}") + start_lit = model.new_bool_var(f"start_{i}") arcs.append((0, i + 1, start_lit)) - model.Add(ranks[i] == 0).OnlyEnforceIf(start_lit) + model.add(ranks[i] == 0).only_enforce_if(start_lit) # As there are no other constraints on the problem, we can add this # redundant constraint. - model.Add(starts[i] == 0).OnlyEnforceIf(start_lit) + model.add(starts[i] == 0).only_enforce_if(start_lit) # if node i is last. - end_lit = model.NewBoolVar(f"end_{i}") + end_lit = model.new_bool_var(f"end_{i}") arcs.append((i + 1, 0, end_lit)) for j in all_tasks: if i == j: - arcs.append((i + 1, i + 1, presences[i].Not())) - model.Add(ranks[i] == -1).OnlyEnforceIf(presences[i].Not()) + arcs.append((i + 1, i + 1, presences[i].negated())) + model.add(ranks[i] == -1).only_enforce_if(presences[i].negated()) else: - literal = model.NewBoolVar(f"arc_{i}_to_{j}") + literal = model.new_bool_var(f"arc_{i}_to_{j}") arcs.append((i + 1, j + 1, literal)) - model.Add(ranks[j] == ranks[i] + 1).OnlyEnforceIf(literal) + model.add(ranks[j] == ranks[i] + 1).only_enforce_if(literal) # To perform the transitive reduction from precedences to successors, # we need to tie the starts of the tasks with 'literal'. @@ -1538,17 +1551,19 @@ def rank_tasks_with_circuit( # # Note that we could use this literal to penalize the transition, add an # extra delay to the precedence. - model.Add(starts[j] >= starts[i] + durations[i]).OnlyEnforceIf(literal) + model.add(starts[j] >= starts[i] + durations[i]).only_enforce_if( + literal + ) # Manage the empty circuit - empty = model.NewBoolVar("empty") + empty = model.new_bool_var("empty") arcs.append((0, 0, empty)) for i in all_tasks: - model.AddImplication(empty, presences[i].Not()) + model.add_implication(empty, presences[i].negated()) # Add the circuit constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) def ranking_sample_sat(): @@ -1567,14 +1582,14 @@ def ranking_sample_sat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalFixedSizeIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_fixed_size_interval_var( start, duration, presence, f"opt_interval[{t}]" ) if t < num_tasks // 2: - model.Add(presence == 1) + model.add(presence == 1) starts.append(start) durations.append(duration) @@ -1582,45 +1597,44 @@ def ranking_sample_sat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. rank_tasks_with_circuit(model, starts, durations, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(starts[t] + durations[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(starts[t] + durations[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") @@ -1660,20 +1674,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def SchedulingWithCalendarSampleSat(): """Interval spanning across a lunch break.""" @@ -1687,25 +1696,27 @@ def SchedulingWithCalendarSampleSat(): # Because the duration is at least 3 hours, work cannot start after 15h. # Because of the break, work cannot start at 13h. - start = model.NewIntVarFromDomain( - cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), "start" + start = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([(8, 12), (14, 15)]), "start" ) - duration = model.NewIntVar(3, 4, "duration") - end = model.NewIntVar(8, 18, "end") - unused_interval = model.NewIntervalVar(start, duration, end, "interval") + duration = model.new_int_var(3, 4, "duration") + end = model.new_int_var(8, 18, "end") + unused_interval = model.new_interval_var(start, duration, end, "interval") # We have 2 states (spanning across lunch or not) - across = model.NewBoolVar("across") - non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15]) - model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf( - across.Not() + across = model.new_bool_var("across") + non_spanning_hours = cp_model.Domain.from_values([8, 9, 10, 14, 15]) + model.add_linear_expression_in_domain(start, non_spanning_hours).only_enforce_if( + across.negated() ) - model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across) - model.Add(duration == 3).OnlyEnforceIf(across.Not()) - model.Add(duration == 4).OnlyEnforceIf(across) + model.add_linear_constraint(start, 11, 12).only_enforce_if(across) + model.add(duration == 3).only_enforce_if(across.negated()) + model.add(duration == 4).only_enforce_if(across) # Search for x values in increasing order. - model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy( + [start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + ) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -1717,7 +1728,7 @@ def SchedulingWithCalendarSampleSat(): # Search and print all solutions. solution_printer = VarArraySolutionPrinter([start, duration, across]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) SchedulingWithCalendarSampleSat() @@ -1768,20 +1779,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def OverlappingIntervals(): """Create the overlapping Boolean variables and enumerate all states.""" @@ -1790,45 +1796,47 @@ def OverlappingIntervals(): horizon = 7 # First interval. - start_var_a = model.NewIntVar(0, horizon, "start_a") + start_var_a = model.new_int_var(0, horizon, "start_a") duration_a = 3 - end_var_a = model.NewIntVar(0, horizon, "end_a") - unused_interval_var_a = model.NewIntervalVar( + end_var_a = model.new_int_var(0, horizon, "end_a") + unused_interval_var_a = model.new_interval_var( start_var_a, duration_a, end_var_a, "interval_a" ) # Second interval. - start_var_b = model.NewIntVar(0, horizon, "start_b") + start_var_b = model.new_int_var(0, horizon, "start_b") duration_b = 2 - end_var_b = model.NewIntVar(0, horizon, "end_b") - unused_interval_var_b = model.NewIntervalVar( + end_var_b = model.new_int_var(0, horizon, "end_b") + unused_interval_var_b = model.new_interval_var( start_var_b, duration_b, end_var_b, "interval_b" ) # a_after_b Boolean variable. - a_after_b = model.NewBoolVar("a_after_b") - model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b) - model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not()) + a_after_b = model.new_bool_var("a_after_b") + model.add(start_var_a >= end_var_b).only_enforce_if(a_after_b) + model.add(start_var_a < end_var_b).only_enforce_if(a_after_b.negated()) # b_after_a Boolean variable. - b_after_a = model.NewBoolVar("b_after_a") - model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a) - model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not()) + b_after_a = model.new_bool_var("b_after_a") + model.add(start_var_b >= end_var_a).only_enforce_if(b_after_a) + model.add(start_var_b < end_var_a).only_enforce_if(b_after_a.negated()) # Result Boolean variable. - a_overlaps_b = model.NewBoolVar("a_overlaps_b") + a_overlaps_b = model.new_bool_var("a_overlaps_b") # Option a: using only clauses - model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b) - model.AddImplication(a_after_b, a_overlaps_b.Not()) - model.AddImplication(b_after_a, a_overlaps_b.Not()) + model.add_bool_or(a_after_b, b_after_a, a_overlaps_b) + model.add_implication(a_after_b, a_overlaps_b.negated()) + model.add_implication(b_after_a, a_overlaps_b.negated()) # Option b: using an exactly one constraint. - # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b) + # model.add_exactly_one(a_after_b, b_after_a, a_overlaps_b) # Search for start values in increasing order for the two intervals. - model.AddDecisionStrategy( - [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + model.add_decision_strategy( + [start_var_a, start_var_b], + cp_model.CHOOSE_FIRST, + cp_model.SELECT_MIN_VALUE, ) # Create a solver and solve with a fixed search. @@ -1841,7 +1849,7 @@ def OverlappingIntervals(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) OverlappingIntervals() diff --git a/ortools/sat/docs/solver.md b/ortools/sat/docs/solver.md index 019d3621417..abf193e5ef5 100644 --- a/ortools/sat/docs/solver.md +++ b/ortools/sat/docs/solver.md @@ -24,11 +24,11 @@ def SolveWithTimeLimitSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Adds an all-different constraint. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() @@ -36,12 +36,12 @@ def SolveWithTimeLimitSampleSat(): # Sets a time limit of 10 seconds. solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") SolveWithTimeLimitSampleSat() @@ -207,20 +207,21 @@ from ortools.sat.python import cp_model class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: print(f"Solution {self.__solution_count}") - print(f" objective value = {self.ObjectiveValue()}") + print(f" objective value = {self.objective_value}") for v in self.__variables: - print(f" {v}={self.Value(v)}", end=" ") + print(f" {v}={self.value(v)}", end=" ") print() self.__solution_count += 1 - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -231,22 +232,22 @@ def SolveAndPrintIntermediateSolutionsSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Creates a solver and solves. solver = cp_model.CpSolver() solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolveAndPrintIntermediateSolutionsSampleSat() @@ -469,18 +470,19 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -491,12 +493,12 @@ def SearchForAllSolutionsSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create the constraints. - model.Add(x != y) + model.add(x != y) # Create a solver and solve. solver = cp_model.CpSolver() @@ -504,10 +506,10 @@ def SearchForAllSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SearchForAllSolutionsSampleSat() @@ -725,22 +727,23 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables, limit): + def __init__(self, variables: list[cp_model.IntVar], limit: int): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 self.__solution_limit = limit - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() if self.__solution_count >= self.__solution_limit: print(f"Stop search after {self.__solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -750,9 +753,9 @@ def StopAfterNSolutionsSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create a solver and solve. solver = cp_model.CpSolver() @@ -760,10 +763,10 @@ def StopAfterNSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") - assert solution_printer.solution_count() == 5 + status = solver.solve(model, solution_printer) + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") + assert solution_printer.solution_count == 5 StopAfterNSolutionsSampleSat() diff --git a/ortools/sat/docs/troubleshooting.md b/ortools/sat/docs/troubleshooting.md index ed921ef8e6e..6ce7339086b 100644 --- a/ortools/sat/docs/troubleshooting.md +++ b/ortools/sat/docs/troubleshooting.md @@ -107,31 +107,31 @@ def main(): model = cp_model.CpModel() # Creates the variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") - a = model.NewBoolVar("a") - b = model.NewBoolVar("b") - c = model.NewBoolVar("c") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") + a = model.new_bool_var("a") + b = model.new_bool_var("b") + c = model.new_bool_var("c") # Creates the constraints. - model.Add(x > y).OnlyEnforceIf(a) - model.Add(y > z).OnlyEnforceIf(b) - model.Add(z > x).OnlyEnforceIf(c) + model.add(x > y).only_enforce_if(a) + model.add(y > z).only_enforce_if(b) + model.add(z > x).only_enforce_if(c) # Add assumptions - model.AddAssumptions([a, b, c]) + model.add_assumptions([a, b, c]) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # Print solution. - print(f"Status = {solver.StatusName(status)}") + print(f"Status = {solver.status_name(status)}") if status == cp_model.INFEASIBLE: print( - "SufficientAssumptionsForInfeasibility = " - f"{solver.SufficientAssumptionsForInfeasibility()}" + "sufficient_assumptions_for_infeasibility = " + f"{solver.sufficient_assumptions_for_infeasibility()}" ) diff --git a/ortools/sat/feasibility_jump.cc b/ortools/sat/feasibility_jump.cc index 73b60ea1f52..f4ede40b812 100644 --- a/ortools/sat/feasibility_jump.cc +++ b/ortools/sat/feasibility_jump.cc @@ -273,6 +273,14 @@ void FeasibilityJumpSolver::ResetCurrentSolution() { } } } + + // Overwrite with the (partial) hint on the first batch. + if (num_batches_ == 0 && linear_model_->model_proto().has_solution_hint()) { + const auto& hint = linear_model_->model_proto().solution_hint(); + for (int i = 0; i < hint.vars().size(); ++i) { + solution[hint.vars(i)] = hint.values(i); + } + } } void FeasibilityJumpSolver::PerturbateCurrentSolution() { @@ -348,7 +356,7 @@ std::function FeasibilityJumpSolver::GenerateTask(int64_t /*task_id*/) { // In incomplete mode, query the starting solution for the shared response // manager. - if (type() == SubSolver::INCOMPLETE) { + if (type() == SubSolver::INCOMPLETE) { // violation_ls. // Choose a base solution for this neighborhood. const SharedSolutionRepository& repo = shared_response_->SolutionsRepository(); @@ -375,7 +383,7 @@ std::function FeasibilityJumpSolver::GenerateTask(int64_t /*task_id*/) { should_recompute_violations = true; reset_weights = true; } - } else { + } else { // feasibility_jump. // Restart? Note that we always "restart" the first time. const double dtime = evaluator_->DeterministicTime(); if (dtime >= dtime_restart_threshold_ && diff --git a/ortools/sat/lp_utils.cc b/ortools/sat/lp_utils.cc index 220ee3eb6aa..2d1a2f3ed08 100644 --- a/ortools/sat/lp_utils.cc +++ b/ortools/sat/lp_utils.cc @@ -816,14 +816,21 @@ double FindBestScalingAndComputeErrors( // error of wanted_absolute_activity_precision and still make sure we will // have no integer overflow. // + // Important: the loop is written in such a way that ComputeScalingErrors() + // is called on the last factor. + // // TODO(user): Make this faster. double x = std::min(scaling_factor, 1.0); for (; x <= scaling_factor; x *= 2) { ComputeScalingErrors(coefficients, lower_bounds, upper_bounds, x, relative_coeff_error, scaled_sum_error); if (*scaled_sum_error < wanted_absolute_activity_precision * x) break; + + // This could happen if we always have enough precision. + if (x == scaling_factor) break; } scaling_factor = x; + DCHECK(std::isfinite(scaling_factor)); // Because we deal with an approximate input, scaling with a power of 2 might // not be the best choice. It is also possible user used rational coeff and @@ -834,6 +841,7 @@ double FindBestScalingAndComputeErrors( // Note that if our current precisions is already above the requested one, // we choose integer scaling if we get a better precision. const double integer_factor = FindFractionalScaling(coefficients, 1e-8); + DCHECK(std::isfinite(integer_factor)); if (integer_factor != 0 && integer_factor < scaling_factor) { double local_relative_coeff_error; double local_scaled_sum_error; @@ -850,6 +858,7 @@ double FindBestScalingAndComputeErrors( } } + DCHECK(std::isfinite(scaling_factor)); return scaling_factor; } diff --git a/ortools/sat/presolve_context.cc b/ortools/sat/presolve_context.cc index 31ba1ca2b73..9d4f677501b 100644 --- a/ortools/sat/presolve_context.cc +++ b/ortools/sat/presolve_context.cc @@ -446,8 +446,9 @@ ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith( } modified_domains.Set(var); if (domains[var].IsEmpty()) { - is_unsat_ = true; - return false; + return NotifyThatModelIsUnsat( + absl::StrCat("var #", ref, " as empty domain after intersecting with ", + domain.ToString())); } // Propagate the domain of the representative right away. @@ -467,8 +468,9 @@ ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith( if (domain.Contains(expr.offset())) { return true; } else { - is_unsat_ = true; - return false; + return NotifyThatModelIsUnsat(absl::StrCat( + expr.ShortDebugString(), " as empty domain after intersecting with ", + domain.ToString())); } } if (expr.vars().size() == 1) { // Affine diff --git a/ortools/sat/presolve_context.h b/ortools/sat/presolve_context.h index 4cebda89e0d..8b80a8ea32b 100644 --- a/ortools/sat/presolve_context.h +++ b/ortools/sat/presolve_context.h @@ -251,7 +251,6 @@ class PresolveContext { absl::string_view message = "") { // TODO(user): Report any explanation for the client in a nicer way? SOLVER_LOG(logger_, "INFEASIBLE: '", message, "'"); - DCHECK(!is_unsat_); is_unsat_ = true; return false; } diff --git a/ortools/sat/python/cp_model.py b/ortools/sat/python/cp_model.py index aa0529aba28..44d97490784 100644 --- a/ortools/sat/python/cp_model.py +++ b/ortools/sat/python/cp_model.py @@ -121,6 +121,7 @@ IntegralT = Union[numbers.Integral, np.integer, int] NumberT = Union[numbers.Integral, np.integer, int, numbers.Number, np.double, float] LiteralT = Union["IntVar", "_NotBooleanVariable", IntegralT, bool] +BoolVarT = Union["IntVar", "_NotBooleanVariable"] VariableT = Union["IntVar", IntegralT] LinearExprT = Union["LinearExpr", "IntVar", IntegralT] ObjLinearExprT = Union["LinearExpr", "IntVar", NumberT] @@ -128,7 +129,7 @@ _IndexOrSeries = Union[pd.Index, pd.Series] -def DisplayBounds(bounds: Sequence[int]) -> str: +def display_bounds(bounds: Sequence[int]) -> str: """Displays a flattened list of intervals.""" out = "" for i in range(0, len(bounds), 2): @@ -141,27 +142,27 @@ def DisplayBounds(bounds: Sequence[int]) -> str: return out -def ShortName(model: cp_model_pb2.CpModelProto, i: int) -> str: +def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str: """Returns a short name of an integer variable, or its negation.""" if i < 0: - return "Not(%s)" % ShortName(model, -i - 1) + return "not(%s)" % short_name(model, -i - 1) v = model.variables[i] if v.name: return v.name elif len(v.domain) == 2 and v.domain[0] == v.domain[1]: return str(v.domain[0]) else: - return "[%s]" % DisplayBounds(v.domain) + return "[%s]" % display_bounds(v.domain) -def ShortExprName( +def short_expr_name( model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto ) -> str: """Pretty-print LinearExpressionProto instances.""" if not e.vars: return str(e.offset) if len(e.vars) == 1: - var_name = ShortName(model, e.vars[0]) + var_name = short_name(model, e.vars[0]) coeff = e.coeffs[0] result = "" if coeff == 1: @@ -191,14 +192,14 @@ class LinearExpr: * You can define linear constraints as in: ``` - model.Add(x + 2 * y <= 5) - model.Add(sum(array_of_vars) == 5) + model.add(x + 2 * y <= 5) + model.add(sum(array_of_vars) == 5) ``` * In CP-SAT, the objective is a linear expression: ``` - model.Minimize(x + 2 * y + z) + model.minimize(x + 2 * y + z) ``` * For large arrays, using the LinearExpr class is faster that using the python @@ -206,13 +207,13 @@ class LinearExpr: linear expressions or coefficients as follows: ``` - model.Minimize(cp_model.LinearExpr.Sum(expressions)) - model.Add(cp_model.LinearExpr.WeightedSum(expressions, coefficients) >= 0) + model.minimize(cp_model.LinearExpr.sum(expressions)) + model.add(cp_model.LinearExpr.weighted_sum(expressions, coefficients) >= 0) ``` """ @classmethod - def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: + def sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: """Creates the expression sum(expressions).""" if len(expressions) == 1: return expressions[0] @@ -220,7 +221,7 @@ def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[LinearExprT], coefficients: Sequence[IntegralT], @@ -229,7 +230,7 @@ def WeightedSum( @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[ObjLinearExprT], coefficients: Sequence[NumberT], @@ -237,9 +238,9 @@ def WeightedSum( ... @classmethod - def WeightedSum(cls, expressions, coefficients): + def weighted_sum(cls, expressions, coefficients): """Creates the expression sum(expressions[i] * coefficients[i]).""" - if LinearExpr.IsEmptyOrAllNull(coefficients): + if LinearExpr.is_empty_or_all_null(coefficients): return 0 elif len(expressions) == 1: return expressions[0] * coefficients[0] @@ -248,7 +249,7 @@ def WeightedSum(cls, expressions, coefficients): @overload @classmethod - def Term( + def term( cls, expressions: LinearExprT, coefficients: IntegralT, @@ -257,7 +258,7 @@ def Term( @overload @classmethod - def Term( + def term( cls, expressions: ObjLinearExprT, coefficients: NumberT, @@ -265,7 +266,7 @@ def Term( ... @classmethod - def Term(cls, expression, coefficient): + def term(cls, expression, coefficient): """Creates `expression * coefficient`.""" if cmh.is_zero(coefficient): return 0 @@ -273,14 +274,14 @@ def Term(cls, expression, coefficient): return expression * coefficient @classmethod - def IsEmptyOrAllNull(cls, coefficients: Sequence[NumberT]) -> bool: + def is_empty_or_all_null(cls, coefficients: Sequence[NumberT]) -> bool: for c in coefficients: if not cmh.is_zero(c): return False return True @classmethod - def RebuildFromLinearExpressionProto( + def rebuild_from_linear_expression_proto( cls, model: cp_model_pb2.CpModelProto, proto: cp_model_pb2.LinearExpressionProto, @@ -306,7 +307,7 @@ def RebuildFromLinearExpressionProto( else: return _WeightedSum(variables, coeffs, offset) - def GetIntegerVarValueMap(self) -> Tuple[Dict[VariableT, IntegralT], int]: + def get_integer_var_value_map(self) -> Tuple[Dict[VariableT, IntegralT], int]: """Scans the expression, and returns (var_coef_map, constant).""" coeffs = collections.defaultdict(int) constant = 0 @@ -316,29 +317,31 @@ def GetIntegerVarValueMap(self) -> Tuple[Dict[VariableT, IntegralT], int]: if cmh.is_integral(expr): constant += coeff * int(expr) elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, IntVar): coeffs[expr] += coeff elif isinstance(expr, _NotBooleanVariable): constant += coeff - coeffs[expr.Not()] -= coeff + coeffs[expr.negated()] -= coeff else: raise TypeError("Unrecognized linear expression: " + str(expr)) return coeffs, constant - def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: + def get_float_var_value_map( + self, + ) -> Tuple[Dict[VariableT, float], float, bool]: """Scans the expression. Returns (var_coef_map, constant, is_integer).""" coeffs = {} constant = 0 @@ -350,18 +353,18 @@ def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: elif cmh.is_a_number(expr): constant += coeff * float(expr) elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, IntVar): if expr in coeffs: coeffs[expr] += coeff @@ -369,10 +372,10 @@ def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: coeffs[expr] = coeff elif isinstance(expr, _NotBooleanVariable): constant += coeff - if expr.Not() in coeffs: - coeffs[expr.Not()] -= coeff + if expr.negated() in coeffs: + coeffs[expr.negated()] -= coeff else: - coeffs[expr.Not()] = -coeff + coeffs[expr.negated()] = -coeff else: raise TypeError("Unrecognized linear expression: " + str(expr)) is_integer = cmh.is_integral(constant) @@ -389,7 +392,7 @@ def __hash__(self): def __abs__(self): raise NotImplementedError( "calling abs() on a linear expression is not supported, " - "please use CpModel.AddAbsEquality" + "please use CpModel.add_abs_equality" ) def __add__(self, arg): @@ -429,25 +432,25 @@ def __rmul__(self, arg): def __div__(self, _): raise NotImplementedError( "calling / on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality" + "please use CpModel.add_division_equality" ) def __truediv__(self, _): raise NotImplementedError( "calling // on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality" + "please use CpModel.add_division_equality" ) def __mod__(self, _): raise NotImplementedError( "calling %% on a linear expression is not supported, " - "please use CpModel.AddModuloEquality" + "please use CpModel.add_modulo_equality" ) def __pow__(self, _): raise NotImplementedError( "calling ** on a linear expression is not supported, " - "please use CpModel.AddMultiplicationEquality" + "please use CpModel.add_multiplication_equality" ) def __lshift__(self, _): @@ -463,19 +466,19 @@ def __rshift__(self, _): def __and__(self, _): raise NotImplementedError( "calling and on a linear expression is not supported, " - "please use CpModel.AddBoolAnd" + "please use CpModel.add_bool_and" ) def __or__(self, _): raise NotImplementedError( "calling or on a linear expression is not supported, " - "please use CpModel.AddBoolOr" + "please use CpModel.add_bool_or" ) def __xor__(self, _): raise NotImplementedError( "calling xor on a linear expression is not supported, " - "please use CpModel.AddBoolXor" + "please use CpModel.add_bool_xor" ) def __neg__(self): @@ -543,6 +546,61 @@ def __ne__(self, arg): else: return BoundedLinearExpression(self - arg, [INT_MIN, -1, 1, INT_MAX]) + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + @classmethod + def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: + """Creates the expression sum(expressions).""" + return cls.sum(expressions) + + @overload + @classmethod + def WeightedSum( + cls, + expressions: Sequence[LinearExprT], + coefficients: Sequence[IntegralT], + ) -> LinearExprT: + ... + + @overload + @classmethod + def WeightedSum( + cls, + expressions: Sequence[ObjLinearExprT], + coefficients: Sequence[NumberT], + ) -> ObjLinearExprT: + ... + + @classmethod + def WeightedSum(cls, expressions, coefficients): + """Creates the expression sum(expressions[i] * coefficients[i]).""" + return cls.weighted_sum(expressions, coefficients) + + @overload + @classmethod + def Term( + cls, + expressions: LinearExprT, + coefficients: IntegralT, + ) -> LinearExprT: + ... + + @overload + @classmethod + def Term( + cls, + expressions: ObjLinearExprT, + coefficients: NumberT, + ) -> ObjLinearExprT: + ... + + @classmethod + def Term(cls, expression, coefficient): + """Creates `expression * coefficient`.""" + return cls.term(expression, coefficient) + + # pylint: enable=invalid-name + class _Sum(LinearExpr): """Represents the sum of two LinearExprs.""" @@ -550,21 +608,21 @@ class _Sum(LinearExpr): def __init__(self, left, right): for x in [left, right]: if not cmh.is_a_number(x) and not isinstance(x, LinearExpr): - raise TypeError("Not an linear expression: " + str(x)) + raise TypeError("not an linear expression: " + str(x)) self.__left = left self.__right = right - def Left(self): + def left(self): return self.__left - def Right(self): + def right(self): return self.__right def __str__(self): return f"({self.__left} + {self.__right})" def __repr__(self): - return f"Sum({repr(self.__left)}, {repr(self.__right)})" + return f"sum({self.__left!r}, {self.__right!r})" class _ProductCst(LinearExpr): @@ -573,8 +631,8 @@ class _ProductCst(LinearExpr): def __init__(self, expr, coeff): coeff = cmh.assert_is_a_number(coeff) if isinstance(expr, _ProductCst): - self.__expr = expr.Expression() - self.__coef = expr.Coefficient() * coeff + self.__expr = expr.expression() + self.__coef = expr.coefficient() * coeff else: self.__expr = expr self.__coef = coeff @@ -586,12 +644,12 @@ def __str__(self): return "(" + str(self.__coef) + " * " + str(self.__expr) + ")" def __repr__(self): - return "ProductCst(" + repr(self.__expr) + ", " + repr(self.__coef) + ")" + return f"ProductCst({self.__expr!r}, {self.__coef!r})" - def Coefficient(self): + def coefficient(self): return self.__coef - def Expression(self): + def expression(self): return self.__expr @@ -610,7 +668,7 @@ def __init__(self, expressions, constant=0): elif isinstance(x, LinearExpr): self.__expressions.append(x) else: - raise TypeError("Not an linear expression: " + str(x)) + raise TypeError("not an linear expression: " + str(x)) def __str__(self): constant_terms = (self.__constant,) if self.__constant != 0 else () @@ -625,10 +683,10 @@ def __repr__(self): exprs_str = ", ".join(map(repr, self.__expressions)) return f"SumArray({exprs_str}, {self.__constant})" - def Expressions(self): + def expressions(self): return self.__expressions - def Constant(self): + def constant(self): return self.__constant @@ -641,7 +699,7 @@ def __init__(self, expressions, coefficients, constant=0): self.__constant = constant if len(expressions) != len(coefficients): raise TypeError( - "In the LinearExpr.WeightedSum method, the expression array and the " + "In the LinearExpr.weighted_sum method, the expression array and the " " coefficient array must have the same length." ) for e, c in zip(expressions, coefficients): @@ -655,7 +713,7 @@ def __init__(self, expressions, coefficients, constant=0): self.__expressions.append(e) self.__coefficients.append(c) else: - raise TypeError("Not an linear expression: " + str(e)) + raise TypeError("not an linear expression: " + str(e)) def __str__(self): output = None @@ -683,17 +741,18 @@ def __str__(self): return output def __repr__(self): - exprs_str = ", ".join(map(repr, self.__expressions)) - coeffs_str = ", ".join(map(repr, self.__coefficients)) - return f"WeightedSum([{exprs_str}], [{coeffs_str}], {self.__constant})" + return ( + f"weighted_sum({self.__expressions!r}, {self.__coefficients!r}," + f" {self.__constant})" + ) - def Expressions(self): + def expressions(self): return self.__expressions - def Coefficients(self): + def coefficients(self): return self.__coefficients - def Constant(self): + def constant(self): return self.__constant @@ -717,7 +776,7 @@ def __init__( domain: Union[int, Domain], name: Optional[str], ): - """See CpModel.NewIntVar below.""" + """See CpModel.new_int_var below.""" self.__negation: Optional[_NotBooleanVariable] = None # Python do not support multiple __init__ methods. # This method is only called from the CpModel class. @@ -732,22 +791,24 @@ def __init__( else: self.__index: int = len(model.variables) self.__var: cp_model_pb2.IntegerVariableProto = model.variables.add() - self.__var.domain.extend(cast(Domain, domain).FlattenedIntervals()) + self.__var.domain.extend(cast(Domain, domain).flattened_intervals()) self.__var.name = name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the variable in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.IntegerVariableProto: + @property + def proto(self) -> cp_model_pb2.IntegerVariableProto: """Returns the variable protobuf.""" return self.__var - def IsEqualTo(self, other: ...) -> bool: + def is_equal_to(self, other: ...) -> bool: """Returns true if self == other in the python sense.""" if not isinstance(other, IntVar): return False - return self.Index() == other.Index() + return self.index == other.index def __str__(self) -> str: if not self.__var.name: @@ -762,29 +823,47 @@ def __str__(self) -> str: return self.__var.name def __repr__(self) -> str: - return "%s(%s)" % (self.__var.name, DisplayBounds(self.__var.domain)) + return "%s(%s)" % (self.__var.name, display_bounds(self.__var.domain)) - def Name(self) -> str: + @property + def name(self) -> str: if not self.__var or not self.__var.name: return "" return self.__var.name - def Not(self) -> "_NotBooleanVariable": + def negated(self) -> "_NotBooleanVariable": """Returns the negation of a Boolean variable. This method implements the logical negation of a Boolean variable. It is only valid if the variable has a Boolean domain (0 or 1). - Note that this method is nilpotent: `x.Not().Not() == x`. + Note that this method is nilpotent: `x.negated().negated() == x`. """ for bound in self.__var.domain: if bound < 0 or bound > 1: - raise TypeError("Cannot call Not on a non boolean variable: %s" % self) + raise TypeError( + f"cannot call negated on a non boolean variable: {self}" + ) if self.__negation is None: self.__negation = _NotBooleanVariable(self) return self.__negation + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + Not = negated + + def Name(self) -> str: + return self.name + + def Proto(self) -> cp_model_pb2.IntegerVariableProto: + return self.proto + + def Index(self) -> int: + return self.index + + # pylint: enable=invalid-name + class _NotBooleanVariable(LinearExpr): """Negation of a boolean variable.""" @@ -792,13 +871,18 @@ class _NotBooleanVariable(LinearExpr): def __init__(self, boolvar: IntVar): self.__boolvar: IntVar = boolvar - def Index(self) -> int: - return -self.__boolvar.Index() - 1 + @property + def index(self) -> int: + return -self.__boolvar.index - 1 - def Not(self) -> IntVar: + def negated(self) -> IntVar: return self.__boolvar def __str__(self) -> str: + return self.name + + @property + def name(self) -> str: return "not(%s)" % str(self.__boolvar) def __bool__(self) -> bool: @@ -806,14 +890,24 @@ def __bool__(self) -> bool: "Evaluating a literal as a Boolean value is not implemented." ) + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + def Not(self) -> "IntVar": + return self.negated() + + def Index(self) -> int: + return self.index + + # pylint: enable=invalid-name + class BoundedLinearExpression: """Represents a linear constraint: `lb <= linear expression <= ub`. The only use of this class is to be added to the CpModel through - `CpModel.Add(expression)`, as in: + `CpModel.add(expression)`, as in: - model.Add(x + 2 * y -1 >= z) + model.add(x + 2 * y -1 >= z) """ def __init__(self, expr: LinearExprT, bounds: Sequence[int]): @@ -842,17 +936,17 @@ def __str__(self): ): return str(self.__expr) + " != " + str(self.__bounds[1] + 1) else: - return str(self.__expr) + " in [" + DisplayBounds(self.__bounds) + "]" + return str(self.__expr) + " in [" + display_bounds(self.__bounds) + "]" - def Expression(self) -> LinearExprT: + def expression(self) -> LinearExprT: return self.__expr - def Bounds(self) -> Sequence[int]: + def bounds(self) -> Sequence[int]: return self.__bounds def __bool__(self) -> bool: if isinstance(self.__expr, LinearExpr): - coeffs_map, constant = self.__expr.GetIntegerVarValueMap() + coeffs_map, constant = self.__expr.get_integer_var_value_map() all_coeffs = set(coeffs_map.values()) same_var = set([0]) eq_bounds = [0, 0] @@ -882,37 +976,37 @@ def __bool__(self) -> bool: class Constraint: """Base class for constraints. - Constraints are built by the CpModel through the Add methods. + Constraints are built by the CpModel through the add methods. Once created by the CpModel class, they are automatically added to the model. The purpose of this class is to allow specification of enforcement literals for this constraint. - b = model.NewBoolVar('b') - x = model.NewIntVar(0, 10, 'x') - y = model.NewIntVar(0, 10, 'y') + b = model.new_bool_var('b') + x = model.new_int_var(0, 10, 'x') + y = model.new_int_var(0, 10, 'y') - model.Add(x + 2 * y == 5).OnlyEnforceIf(b.Not()) + model.add(x + 2 * y == 5).only_enforce_if(b.negated()) """ def __init__( self, cp_model: "CpModel", ): - self.__index: int = len(cp_model.Proto().constraints) + self.__index: int = len(cp_model.proto.constraints) self.__cp_model: "CpModel" = cp_model self.__constraint: cp_model_pb2.ConstraintProto = ( - cp_model.Proto().constraints.add() + cp_model.proto.constraints.add() ) @overload - def OnlyEnforceIf(self, boolvar: Iterable[LiteralT]) -> "Constraint": + def only_enforce_if(self, boolvar: Iterable[LiteralT]) -> "Constraint": ... @overload - def OnlyEnforceIf(self, *boolvar: LiteralT) -> "Constraint": + def only_enforce_if(self, *boolvar: LiteralT) -> "Constraint": ... - def OnlyEnforceIf(self, *boolvar) -> "Constraint": + def only_enforce_if(self, *boolvar) -> "Constraint": """Adds an enforcement literal to the constraint. This method adds one or more literals (that is, a boolean variable or its @@ -929,7 +1023,7 @@ def OnlyEnforceIf(self, *boolvar) -> "Constraint": Returns: self. """ - for lit in ExpandGeneratorOrTuple(boolvar): + for lit in expand_generator_or_tuple(boolvar): if (isinstance(lit, bool) and lit) or (cmh.is_integral(lit) and lit == 1): # Always true. Do nothing. pass @@ -937,15 +1031,15 @@ def OnlyEnforceIf(self, *boolvar) -> "Constraint": cmh.is_integral(lit) and lit == 0 ): self.__constraint.enforcement_literal.append( - self.__cp_model.NewConstant(0).Index() + self.__cp_model.new_constant(0).index ) else: self.__constraint.enforcement_literal.append( - cast(Union[IntVar, _NotBooleanVariable], lit).Index() + cast(Union[IntVar, _NotBooleanVariable], lit).index ) return self - def WithName(self, name: str) -> "Constraint": + def with_name(self, name: str) -> "Constraint": """Sets the name of the constraint.""" if name: self.__constraint.name = name @@ -953,20 +1047,39 @@ def WithName(self, name: str) -> "Constraint": self.__constraint.ClearField("name") return self - def Name(self) -> str: + @property + def name(self) -> str: """Returns the name of the constraint.""" if not self.__constraint or not self.__constraint.name: return "" return self.__constraint.name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the constraint in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.ConstraintProto: + @property + def proto(self) -> cp_model_pb2.ConstraintProto: """Returns the constraint protobuf.""" return self.__constraint + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + OnlyEnforceIf = only_enforce_if + WithName = with_name + + def Name(self) -> str: + return self.name + + def Index(self) -> int: + return self.index + + def Proto(self) -> cp_model_pb2.ConstraintProto: + return self.proto + + # pylint: enable=invalid-name + class IntervalVar: """Represents an Interval variable. @@ -1017,11 +1130,13 @@ def __init__( if name: self.__ct.name = name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the interval constraint in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.IntervalConstraintProto: + @property + def proto(self) -> cp_model_pb2.IntervalConstraintProto: """Returns the interval protobuf.""" return self.__ct.interval @@ -1033,60 +1148,78 @@ def __repr__(self): if self.__ct.enforcement_literal: return "%s(start = %s, size = %s, end = %s, is_present = %s)" % ( self.__ct.name, - ShortExprName(self.__model, interval.start), - ShortExprName(self.__model, interval.size), - ShortExprName(self.__model, interval.end), - ShortName(self.__model, self.__ct.enforcement_literal[0]), + short_expr_name(self.__model, interval.start), + short_expr_name(self.__model, interval.size), + short_expr_name(self.__model, interval.end), + short_name(self.__model, self.__ct.enforcement_literal[0]), ) else: return "%s(start = %s, size = %s, end = %s)" % ( self.__ct.name, - ShortExprName(self.__model, interval.start), - ShortExprName(self.__model, interval.size), - ShortExprName(self.__model, interval.end), + short_expr_name(self.__model, interval.start), + short_expr_name(self.__model, interval.size), + short_expr_name(self.__model, interval.end), ) - def Name(self) -> str: + @property + def name(self) -> str: if not self.__ct or not self.__ct.name: return "" return self.__ct.name - def StartExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def start_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.start ) - def SizeExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def size_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.size ) - def EndExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def end_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.end ) + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + def Name(self) -> str: + return self.name -def ObjectIsATrueLiteral(literal: LiteralT) -> bool: + def Index(self) -> int: + return self.index + + def Proto(self) -> cp_model_pb2.IntervalConstraintProto: + return self.proto + + StartExpr = start_expr + SizeExpr = size_expr + EndExpr = end_expr + + # pylint: enable=invalid-name + + +def object_is_a_true_literal(literal: LiteralT) -> bool: """Checks if literal is either True, or a Boolean literals fixed to True.""" if isinstance(literal, IntVar): - proto = literal.Proto() + proto = literal.proto return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 if isinstance(literal, _NotBooleanVariable): - proto = literal.Not().Proto() + proto = literal.negated().proto return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 if cmh.is_integral(literal): return int(literal) == 1 return False -def ObjectIsAFalseLiteral(literal: LiteralT) -> bool: +def object_is_a_false_literal(literal: LiteralT) -> bool: """Checks if literal is either False, or a Boolean literals fixed to False.""" if isinstance(literal, IntVar): - proto = literal.Proto() + proto = literal.proto return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 if isinstance(literal, _NotBooleanVariable): - proto = literal.Not().Proto() + proto = literal.negated().proto return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 if cmh.is_integral(literal): return int(literal) == 0 @@ -1099,7 +1232,7 @@ class CpModel: Methods beginning with: * ```New``` create integer, boolean, or interval variables. - * ```Add``` create new constraints and add them to the model. + * ```add``` create new constraints and add them to the model. """ def __init__(self): @@ -1107,19 +1240,21 @@ def __init__(self): self.__constant_map = {} # Naming. - def Name(self) -> str: + @property + def name(self) -> str: """Returns the name of the model.""" if not self.__model or not self.__model.name: return "" return self.__model.name - def SetName(self, name: str): + @name.setter + def name(self, name: str): """Sets the name of the model.""" self.__model.name = name # Integer variable. - def NewIntVar(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: + def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: """Create an integer variable with domain [lb, ub]. The CP-SAT solver is limited to integer variables. If you have fractional @@ -1137,12 +1272,12 @@ def NewIntVar(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: return IntVar(self.__model, Domain(lb, ub), name) - def NewIntVarFromDomain(self, domain: Domain, name: str) -> IntVar: + def new_int_var_from_domain(self, domain: Domain, name: str) -> IntVar: """Create an integer variable from a domain. A domain is a set of integers specified by a collection of intervals. - For example, `model.NewIntVarFromDomain(cp_model. - Domain.FromIntervals([[1, 2], [4, 6]]), 'x')` + For example, `model.new_int_var_from_domain(cp_model. + Domain.from_intervals([[1, 2], [4, 6]]), 'x')` Args: domain: An instance of the Domain class. @@ -1153,15 +1288,15 @@ def NewIntVarFromDomain(self, domain: Domain, name: str) -> IntVar: """ return IntVar(self.__model, domain, name) - def NewBoolVar(self, name: str) -> IntVar: + def new_bool_var(self, name: str) -> IntVar: """Creates a 0-1 variable with the given name.""" return IntVar(self.__model, Domain(0, 1), name) - def NewConstant(self, value: IntegralT) -> IntVar: + def new_constant(self, value: IntegralT) -> IntVar: """Declares a constant integer.""" - return IntVar(self.__model, self.GetOrMakeIndexFromConstant(value), None) + return IntVar(self.__model, self.get_or_make_index_from_constant(value), None) - def NewIntVarSeries( + def new_int_var_series( self, name: str, index: pd.Index, @@ -1204,8 +1339,12 @@ def NewIntVarSeries( f" upper_bound={upper_bounds} for variable set={name}" ) - lower_bounds = _ConvertToIntegralSeriesAndValidateIndex(lower_bounds, index) - upper_bounds = _ConvertToIntegralSeriesAndValidateIndex(upper_bounds, index) + lower_bounds = _convert_to_integral_series_and_validate_index( + lower_bounds, index + ) + upper_bounds = _convert_to_integral_series_and_validate_index( + upper_bounds, index + ) return pd.Series( index=index, data=[ @@ -1219,7 +1358,7 @@ def NewIntVarSeries( ], ) - def NewBoolVarSeries( + def new_bool_var_series( self, name: str, index: pd.Index, @@ -1237,53 +1376,53 @@ def NewBoolVarSeries( TypeError: if the `index` is invalid (e.g. a `DataFrame`). ValueError: if the `name` is not a valid identifier or already exists. """ - return self.NewIntVarSeries( + return self.new_int_var_series( name=name, index=index, lower_bounds=0, upper_bounds=1 ) # Linear constraints. - def AddLinearConstraint( + def add_linear_constraint( self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT ) -> Constraint: """Adds the constraint: `lb <= linear_expr <= ub`.""" - return self.AddLinearExpressionInDomain(linear_expr, Domain(lb, ub)) + return self.add_linear_expression_in_domain(linear_expr, Domain(lb, ub)) - def AddLinearExpressionInDomain( + def add_linear_expression_in_domain( self, linear_expr: LinearExprT, domain: Domain ) -> Constraint: """Adds the constraint: `linear_expr` in `domain`.""" if isinstance(linear_expr, LinearExpr): ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - coeffs_map, constant = linear_expr.GetIntegerVarValueMap() + model_ct = self.__model.constraints[ct.index] + coeffs_map, constant = linear_expr.get_integer_var_value_map() for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError("Wrong argument" + str(t)) c = cmh.assert_is_int64(t[1]) - model_ct.linear.vars.append(t[0].Index()) + model_ct.linear.vars.append(t[0].index) model_ct.linear.coeffs.append(c) model_ct.linear.domain.extend( [ cmh.capped_subtraction(x, constant) - for x in domain.FlattenedIntervals() + for x in domain.flattened_intervals() ] ) return ct elif cmh.is_integral(linear_expr): - if not domain.Contains(int(linear_expr)): - return self.AddBoolOr([]) # Evaluate to false. + if not domain.contains(int(linear_expr)): + return self.add_bool_or([]) # Evaluate to false. else: - return self.AddBoolAnd([]) # Evaluate to true. + return self.add_bool_and([]) # Evaluate to true. raise TypeError( - "Not supported: CpModel.AddLinearExpressionInDomain(" + "not supported: CpModel.add_linear_expression_in_domain(" + str(linear_expr) + " " + str(domain) + ")" ) - def Add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: + def add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: """Adds a `BoundedLinearExpression` to the model. Args: @@ -1293,26 +1432,26 @@ def Add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: An instance of the `Constraint` class. """ if isinstance(ct, BoundedLinearExpression): - return self.AddLinearExpressionInDomain( - ct.Expression(), Domain.FromFlatIntervals(ct.Bounds()) + return self.add_linear_expression_in_domain( + ct.expression(), Domain.from_flat_intervals(ct.bounds()) ) elif ct and isinstance(ct, bool): - return self.AddBoolOr([True]) + return self.add_bool_or([True]) elif not ct and isinstance(ct, bool): - return self.AddBoolOr([]) # Evaluate to false. - raise TypeError("Not supported: CpModel.Add(" + str(ct) + ")") + return self.add_bool_or([]) # Evaluate to false. + raise TypeError("not supported: CpModel.add(" + str(ct) + ")") # General Integer Constraints. @overload - def AddAllDifferent(self, expressions: Iterable[LinearExprT]) -> Constraint: + def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ... @overload - def AddAllDifferent(self, *expressions: LinearExprT) -> Constraint: + def add_all_different(self, *expressions: LinearExprT) -> Constraint: ... - def AddAllDifferent(self, *expressions): + def add_all_different(self, *expressions): """Adds AllDifferent(expressions). This constraint forces all expressions to have different values. @@ -1324,14 +1463,14 @@ def AddAllDifferent(self, *expressions): An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - expanded = ExpandGeneratorOrTuple(expressions) + model_ct = self.__model.constraints[ct.index] + expanded = expand_generator_or_tuple(expressions) model_ct.all_diff.exprs.extend( - [self.ParseLinearExpression(x) for x in expanded] + self.parse_linear_expression(x) for x in expanded ) return ct - def AddElement( + def add_element( self, index: VariableT, variables: Sequence[VariableT], target: VariableT ) -> Constraint: """Adds the element constraint: `variables[index] == target`. @@ -1346,19 +1485,19 @@ def AddElement( """ if not variables: - raise ValueError("AddElement expects a non-empty variables array") + raise ValueError("add_element expects a non-empty variables array") if cmh.is_integral(index): - return self.Add(list(variables)[int(index)] == target) + return self.add(list(variables)[int(index)] == target) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.element.index = self.GetOrMakeIndex(index) - model_ct.element.vars.extend([self.GetOrMakeIndex(x) for x in variables]) - model_ct.element.target = self.GetOrMakeIndex(target) + model_ct = self.__model.constraints[ct.index] + model_ct.element.index = self.get_or_make_index(index) + model_ct.element.vars.extend([self.get_or_make_index(x) for x in variables]) + model_ct.element.target = self.get_or_make_index(target) return ct - def AddCircuit(self, arcs: Sequence[ArcT]) -> Constraint: + def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint: """Adds Circuit(arcs). Adds a circuit constraint from a sparse list of arcs that encode the graph. @@ -1381,19 +1520,19 @@ def AddCircuit(self, arcs: Sequence[ArcT]) -> Constraint: ValueError: If the list of arcs is empty. """ if not arcs: - raise ValueError("AddCircuit expects a non-empty array of arcs") + raise ValueError("add_circuit expects a non-empty array of arcs") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] for arc in arcs: tail = cmh.assert_is_int32(arc[0]) head = cmh.assert_is_int32(arc[1]) - lit = self.GetOrMakeBooleanIndex(arc[2]) + lit = self.get_or_make_boolean_index(arc[2]) model_ct.circuit.tails.append(tail) model_ct.circuit.heads.append(head) model_ct.circuit.literals.append(lit) return ct - def AddMultipleCircuit(self, arcs: Sequence[ArcT]) -> Constraint: + def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint: """Adds a multiple circuit constraint, aka the 'VRP' constraint. The direct graph where arc #i (from tails[i] to head[i]) is present iff @@ -1418,19 +1557,19 @@ def AddMultipleCircuit(self, arcs: Sequence[ArcT]) -> Constraint: ValueError: If the list of arcs is empty. """ if not arcs: - raise ValueError("AddMultipleCircuit expects a non-empty array of arcs") + raise ValueError("add_multiple_circuit expects a non-empty array of arcs") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] for arc in arcs: tail = cmh.assert_is_int32(arc[0]) head = cmh.assert_is_int32(arc[1]) - lit = self.GetOrMakeBooleanIndex(arc[2]) + lit = self.get_or_make_boolean_index(arc[2]) model_ct.routes.tails.append(tail) model_ct.routes.heads.append(head) model_ct.routes.literals.append(lit) return ct - def AddAllowedAssignments( + def add_allowed_assignments( self, variables: Sequence[VariableT], tuples_list: Iterable[Sequence[IntegralT]], @@ -1458,12 +1597,12 @@ def AddAllowedAssignments( if not variables: raise ValueError( - "AddAllowedAssignments expects a non-empty variables array" + "add_allowed_assignments expects a non-empty variables array" ) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.table.vars.extend([self.GetOrMakeIndex(x) for x in variables]) + model_ct = self.__model.constraints[ct.index] + model_ct.table.vars.extend([self.get_or_make_index(x) for x in variables]) arity = len(variables) for t in tuples_list: if len(t) != arity: @@ -1474,12 +1613,12 @@ def AddAllowedAssignments( model_ct.table.values.extend(ar) return ct - def AddForbiddenAssignments( + def add_forbidden_assignments( self, variables: Sequence[VariableT], tuples_list: Iterable[Sequence[IntegralT]], ) -> Constraint: - """Adds AddForbiddenAssignments(variables, [tuples_list]). + """Adds add_forbidden_assignments(variables, [tuples_list]). A ForbiddenAssignments constraint is a constraint on an array of variables where the list of impossible combinations is provided in the tuples list. @@ -1501,15 +1640,15 @@ def AddForbiddenAssignments( if not variables: raise ValueError( - "AddForbiddenAssignments expects a non-empty variables array" + "add_forbidden_assignments expects a non-empty variables array" ) index = len(self.__model.constraints) - ct = self.AddAllowedAssignments(variables, tuples_list) + ct = self.add_allowed_assignments(variables, tuples_list) self.__model.constraints[index].table.negated = True return ct - def AddAutomaton( + def add_automaton( self, transition_variables: Sequence[VariableT], starting_state: IntegralT, @@ -1558,18 +1697,18 @@ def AddAutomaton( if not transition_variables: raise ValueError( - "AddAutomaton expects a non-empty transition_variables array" + "add_automaton expects a non-empty transition_variables array" ) if not final_states: - raise ValueError("AddAutomaton expects some final states") + raise ValueError("add_automaton expects some final states") if not transition_triples: - raise ValueError("AddAutomaton expects some transition triples") + raise ValueError("add_automaton expects some transition triples") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.automaton.vars.extend( - [self.GetOrMakeIndex(x) for x in transition_variables] + [self.get_or_make_index(x) for x in transition_variables] ) starting_state = cmh.assert_is_int64(starting_state) model_ct.automaton.starting_state = starting_state @@ -1587,7 +1726,7 @@ def AddAutomaton( model_ct.automaton.transition_head.append(head) return ct - def AddInverse( + def add_inverse( self, variables: Sequence[VariableT], inverse_variables: Sequence[VariableT], @@ -1617,14 +1756,14 @@ def AddInverse( " inverse_variables must have the same length." ) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.inverse.f_direct.extend([self.GetOrMakeIndex(x) for x in variables]) + model_ct = self.__model.constraints[ct.index] + model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables]) model_ct.inverse.f_inverse.extend( - [self.GetOrMakeIndex(x) for x in inverse_variables] + [self.get_or_make_index(x) for x in inverse_variables] ) return ct - def AddReservoirConstraint( + def add_reservoir_constraint( self, times: Iterable[LinearExprT], level_changes: Iterable[LinearExprT], @@ -1676,18 +1815,18 @@ def AddReservoirConstraint( raise ValueError("Reservoir constraint must have a min_level <= 0") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.reservoir.time_exprs.extend( - [self.ParseLinearExpression(x) for x in times] + [self.parse_linear_expression(x) for x in times] ) model_ct.reservoir.level_changes.extend( - [self.ParseLinearExpression(x) for x in level_changes] + [self.parse_linear_expression(x) for x in level_changes] ) model_ct.reservoir.min_level = min_level model_ct.reservoir.max_level = max_level return ct - def AddReservoirConstraintWithActive( + def add_reservoir_constraint_with_active( self, times: Iterable[LinearExprT], level_changes: Iterable[LinearExprT], @@ -1749,28 +1888,28 @@ def AddReservoirConstraintWithActive( raise ValueError("Reservoir constraint must have a min_level <= 0") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.reservoir.time_exprs.extend( - [self.ParseLinearExpression(x) for x in times] + [self.parse_linear_expression(x) for x in times] ) model_ct.reservoir.level_changes.extend( - [self.ParseLinearExpression(x) for x in level_changes] + [self.parse_linear_expression(x) for x in level_changes] ) model_ct.reservoir.active_literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in actives] + [self.get_or_make_boolean_index(x) for x in actives] ) model_ct.reservoir.min_level = min_level model_ct.reservoir.max_level = max_level return ct - def AddMapDomain( + def add_map_domain( self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0 ): """Adds `var == i + offset <=> bool_var_array[i] == true for all i`.""" for i, bool_var in enumerate(bool_var_array): - b_index = bool_var.Index() - var_index = var.Index() + b_index = bool_var.index + var_index = var.index model_ct = self.__model.constraints.add() model_ct.linear.vars.append(var_index) model_ct.linear.coeffs.append(1) @@ -1786,107 +1925,119 @@ def AddMapDomain( if offset + i + 1 <= INT_MAX: model_ct.linear.domain.extend([offset + i + 1, INT_MAX]) - def AddImplication(self, a: LiteralT, b: LiteralT) -> Constraint: + def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint: """Adds `a => b` (`a` implies `b`).""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.bool_or.literals.append(self.GetOrMakeBooleanIndex(b)) - model_ct.enforcement_literal.append(self.GetOrMakeBooleanIndex(a)) + model_ct = self.__model.constraints[ct.index] + model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b)) + model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a)) return ct @overload - def AddBoolOr(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolOr(self, *literals: LiteralT) -> Constraint: + def add_bool_or(self, *literals: LiteralT) -> Constraint: ... - def AddBoolOr(self, *literals): - """Adds `Or(literals) == true`: Sum(literals) >= 1.""" + def add_bool_or(self, *literals): + """Adds `Or(literals) == true`: sum(literals) >= 1.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_or.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddAtLeastOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddAtLeastOne(self, *literals: LiteralT) -> Constraint: + def add_at_least_one(self, *literals: LiteralT) -> Constraint: ... - def AddAtLeastOne(self, *literals): - """Same as `AddBoolOr`: `Sum(literals) >= 1`.""" - return self.AddBoolOr(*literals) + def add_at_least_one(self, *literals): + """Same as `add_bool_or`: `sum(literals) >= 1`.""" + return self.add_bool_or(*literals) @overload - def AddAtMostOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddAtMostOne(self, *literals: LiteralT) -> Constraint: + def add_at_most_one(self, *literals: LiteralT) -> Constraint: ... - def AddAtMostOne(self, *literals): - """Adds `AtMostOne(literals)`: `Sum(literals) <= 1`.""" + def add_at_most_one(self, *literals): + """Adds `AtMostOne(literals)`: `sum(literals) <= 1`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.at_most_one.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddExactlyOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddExactlyOne(self, *literals: LiteralT) -> Constraint: + def add_exactly_one(self, *literals: LiteralT) -> Constraint: ... - def AddExactlyOne(self, *literals): - """Adds `ExactlyOne(literals)`: `Sum(literals) == 1`.""" + def add_exactly_one(self, *literals): + """Adds `ExactlyOne(literals)`: `sum(literals) == 1`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.exactly_one.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddBoolAnd(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolAnd(self, *literals: LiteralT) -> Constraint: + def add_bool_and(self, *literals: LiteralT) -> Constraint: ... - def AddBoolAnd(self, *literals): + def add_bool_and(self, *literals): """Adds `And(literals) == true`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_and.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddBoolXOr(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolXOr(self, *literals: LiteralT) -> Constraint: + def add_bool_xor(self, *literals: LiteralT) -> Constraint: ... - def AddBoolXOr(self, *literals): + def add_bool_xor(self, *literals): """Adds `XOr(literals) == true`. - In contrast to AddBoolOr and AddBoolAnd, it does not support - .OnlyEnforceIf(). + In contrast to add_bool_or and add_bool_and, it does not support + .only_enforce_if(). Args: *literals: the list of literals in the constraint. @@ -1895,85 +2046,88 @@ def AddBoolXOr(self, *literals): An `Constraint` object. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_xor.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct - def AddMinEquality( + def add_min_equality( self, target: LinearExprT, exprs: Iterable[LinearExprT] ) -> Constraint: """Adds `target == Min(exprs)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.lin_max.exprs.extend( - [self.ParseLinearExpression(x, True) for x in exprs] + [self.parse_linear_expression(x, True) for x in exprs] ) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target, True)) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True)) return ct - def AddMaxEquality( + def add_max_equality( self, target: LinearExprT, exprs: Iterable[LinearExprT] ) -> Constraint: """Adds `target == Max(exprs)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.lin_max.exprs.extend([self.ParseLinearExpression(x) for x in exprs]) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs]) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddDivisionEquality( + def add_division_equality( self, target: LinearExprT, num: LinearExprT, denom: LinearExprT ) -> Constraint: """Adds `target == num // denom` (integer division rounded towards 0).""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.int_div.exprs.append(self.ParseLinearExpression(num)) - model_ct.int_div.exprs.append(self.ParseLinearExpression(denom)) - model_ct.int_div.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.int_div.exprs.append(self.parse_linear_expression(num)) + model_ct.int_div.exprs.append(self.parse_linear_expression(denom)) + model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddAbsEquality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: + def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: """Adds `target == Abs(var)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.lin_max.exprs.append(self.ParseLinearExpression(expr)) - model_ct.lin_max.exprs.append(self.ParseLinearExpression(expr, True)) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.lin_max.exprs.append(self.parse_linear_expression(expr)) + model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True)) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddModuloEquality( + def add_modulo_equality( self, target: LinearExprT, var: LinearExprT, mod: LinearExprT ) -> Constraint: """Adds `target = var % mod`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.int_mod.exprs.append(self.ParseLinearExpression(var)) - model_ct.int_mod.exprs.append(self.ParseLinearExpression(mod)) - model_ct.int_mod.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.int_mod.exprs.append(self.parse_linear_expression(var)) + model_ct.int_mod.exprs.append(self.parse_linear_expression(mod)) + model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddMultiplicationEquality( + def add_multiplication_equality( self, target: LinearExprT, *expressions: Union[Iterable[LinearExprT], LinearExprT], ) -> Constraint: """Adds `target == expressions[0] * .. * expressions[n]`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.int_prod.exprs.extend( [ - self.ParseLinearExpression(expr) - for expr in ExpandGeneratorOrTuple(expressions) + self.parse_linear_expression(expr) + for expr in expand_generator_or_tuple(expressions) ] ) - model_ct.int_prod.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target)) return ct # Scheduling support - def NewIntervalVar( + def new_interval_var( self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str ) -> IntervalVar: """Creates an interval variable from start, size, and end. @@ -1995,22 +2149,26 @@ def NewIntervalVar( An `IntervalVar` object. """ - self.Add(start + size == end) + self.add(start + size == end) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(end) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(end) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) if len(size_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: size must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: size must be affine or constant." + ) if len(end_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: end must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: end must be affine or constant." + ) return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name) - def NewIntervalVarSeries( + def new_interval_var_series( self, name: str, index: pd.Index, @@ -2047,13 +2205,13 @@ def NewIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToLinearExprSeriesAndValidateIndex(sizes, index) - ends = _ConvertToLinearExprSeriesAndValidateIndex(ends, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index) + ends = _convert_to_linear_expr_series_and_validate_index(ends, index) interval_array = [] for i in index: interval_array.append( - self.NewIntervalVar( + self.new_interval_var( start=starts[i], size=sizes[i], end=ends[i], @@ -2062,7 +2220,7 @@ def NewIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewFixedSizeIntervalVar( + def new_fixed_size_interval_var( self, start: LinearExprT, size: IntegralT, name: str ) -> IntervalVar: """Creates an interval variable from start, and a fixed size. @@ -2080,16 +2238,16 @@ def NewFixedSizeIntervalVar( An `IntervalVar` object. """ size = cmh.assert_is_int64(size) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(start + size) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(start + size) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name) - def NewFixedSizeIntervalVarSeries( + def new_fixed_size_interval_var_series( self, name: str, index: pd.Index, @@ -2122,12 +2280,12 @@ def NewFixedSizeIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToIntegralSeriesAndValidateIndex(sizes, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_integral_series_and_validate_index(sizes, index) interval_array = [] for i in index: interval_array.append( - self.NewFixedSizeIntervalVar( + self.new_fixed_size_interval_var( start=starts[i], size=sizes[i], name=f"{name}[{i}]", @@ -2135,7 +2293,7 @@ def NewFixedSizeIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewOptionalIntervalVar( + def new_optional_interval_var( self, start: LinearExprT, size: LinearExprT, @@ -2167,27 +2325,31 @@ def NewOptionalIntervalVar( An `IntervalVar` object. """ - # Add the linear constraint. - self.Add(start + size == end).OnlyEnforceIf(is_present) + # add the linear constraint. + self.add(start + size == end).only_enforce_if(is_present) # Creates the IntervalConstraintProto object. - is_present_index = self.GetOrMakeBooleanIndex(is_present) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(end) + is_present_index = self.get_or_make_boolean_index(is_present) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(end) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) if len(size_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: size must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: size must be affine or constant." + ) if len(end_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: end must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: end must be affine or constant." + ) return IntervalVar( self.__model, start_expr, size_expr, end_expr, is_present_index, name ) - def NewOptionalIntervalVarSeries( + def new_optional_interval_var_series( self, name: str, index: pd.Index, @@ -2228,15 +2390,15 @@ def NewOptionalIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToLinearExprSeriesAndValidateIndex(sizes, index) - ends = _ConvertToLinearExprSeriesAndValidateIndex(ends, index) - are_present = _ConvertToLiteralSeriesAndValidateIndex(are_present, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index) + ends = _convert_to_linear_expr_series_and_validate_index(ends, index) + are_present = _convert_to_literal_series_and_validate_index(are_present, index) interval_array = [] for i in index: interval_array.append( - self.NewOptionalIntervalVar( + self.new_optional_interval_var( start=starts[i], size=sizes[i], end=ends[i], @@ -2246,7 +2408,7 @@ def NewOptionalIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewOptionalFixedSizeIntervalVar( + def new_optional_fixed_size_interval_var( self, start: LinearExprT, size: IntegralT, @@ -2270,14 +2432,14 @@ def NewOptionalFixedSizeIntervalVar( An `IntervalVar` object. """ size = cmh.assert_is_int64(size) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(start + size) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(start + size) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) - is_present_index = self.GetOrMakeBooleanIndex(is_present) + is_present_index = self.get_or_make_boolean_index(is_present) return IntervalVar( self.__model, start_expr, @@ -2287,7 +2449,7 @@ def NewOptionalFixedSizeIntervalVar( name, ) - def NewOptionalFixedSizeIntervalVarSeries( + def new_optional_fixed_size_interval_var_series( self, name: str, index: pd.Index, @@ -2324,13 +2486,13 @@ def NewOptionalFixedSizeIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToIntegralSeriesAndValidateIndex(sizes, index) - are_present = _ConvertToLiteralSeriesAndValidateIndex(are_present, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_integral_series_and_validate_index(sizes, index) + are_present = _convert_to_literal_series_and_validate_index(are_present, index) interval_array = [] for i in index: interval_array.append( - self.NewOptionalFixedSizeIntervalVar( + self.new_optional_fixed_size_interval_var( start=starts[i], size=sizes[i], is_present=are_present[i], @@ -2339,7 +2501,7 @@ def NewOptionalFixedSizeIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def AddNoOverlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: + def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: """Adds NoOverlap(interval_vars). A NoOverlap constraint ensures that all present intervals do not overlap @@ -2352,13 +2514,13 @@ def AddNoOverlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.no_overlap.intervals.extend( - [self.GetIntervalIndex(x) for x in interval_vars] + [self.get_interval_index(x) for x in interval_vars] ) return ct - def AddNoOverlap2D( + def add_no_overlap_2d( self, x_intervals: Iterable[IntervalVar], y_intervals: Iterable[IntervalVar], @@ -2380,16 +2542,16 @@ def AddNoOverlap2D( An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.no_overlap_2d.x_intervals.extend( - [self.GetIntervalIndex(x) for x in x_intervals] + [self.get_interval_index(x) for x in x_intervals] ) model_ct.no_overlap_2d.y_intervals.extend( - [self.GetIntervalIndex(x) for x in y_intervals] + [self.get_interval_index(x) for x in y_intervals] ) return ct - def AddCumulative( + def add_cumulative( self, intervals: Iterable[IntervalVar], demands: Iterable[LinearExprT], @@ -2415,59 +2577,63 @@ def AddCumulative( An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.cumulative.intervals.extend( - [self.GetIntervalIndex(x) for x in intervals] + [self.get_interval_index(x) for x in intervals] ) for d in demands: - model_ct.cumulative.demands.append(self.ParseLinearExpression(d)) - model_ct.cumulative.capacity.CopyFrom(self.ParseLinearExpression(capacity)) + model_ct.cumulative.demands.append(self.parse_linear_expression(d)) + model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity)) return ct # Support for model cloning. - def Clone(self) -> "CpModel": + def clone(self) -> "CpModel": """Reset the model, and creates a new one from a CpModelProto instance.""" clone = CpModel() - clone.Proto().CopyFrom(self.Proto()) - clone.RebuildConstantMap() + clone.proto.CopyFrom(self.proto) + clone.rebuild_constant_map() return clone - def RebuildConstantMap(self): + def rebuild_constant_map(self): """Internal method used during model cloning.""" for i, var in enumerate(self.__model.variables): if len(var.domain) == 2 and var.domain[0] == var.domain[1]: self.__constant_map[var.domain[0]] = i - def GetBoolVarFromProtoIndex(self, index: int) -> IntVar: + def get_bool_var_from_proto_index(self, index: int) -> IntVar: """Returns an already created Boolean variable from its index.""" if index < 0 or index >= len(self.__model.variables): - raise ValueError(f"GetBoolVarFromProtoIndex: out of bound index {index}") + raise ValueError( + f"get_bool_var_from_proto_index: out of bound index {index}" + ) var = self.__model.variables[index] if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: raise ValueError( - f"GetBoolVarFromProtoIndex: index {index} does not reference" + f"get_bool_var_from_proto_index: index {index} does not reference" + " a Boolean variable" ) return IntVar(self.__model, index, None) - def GetIntVarFromProtoIndex(self, index: int) -> IntVar: + def get_int_var_from_proto_index(self, index: int) -> IntVar: """Returns an already created integer variable from its index.""" if index < 0 or index >= len(self.__model.variables): - raise ValueError(f"GetIntVarFromProtoIndex: out of bound index {index}") + raise ValueError( + f"get_int_var_from_proto_index: out of bound index {index}" + ) return IntVar(self.__model, index, None) - def GetIntervalVarFromProtoIndex(self, index: int) -> IntervalVar: + def get_interval_var_from_proto_index(self, index: int) -> IntervalVar: """Returns an already created interval variable from its index.""" if index < 0 or index >= len(self.__model.constraints): raise ValueError( - f"GetIntervalVarFromProtoIndex: out of bound index {index}" + f"get_interval_var_from_proto_index: out of bound index {index}" ) ct = self.__model.constraints[index] if not ct.HasField("interval"): raise ValueError( - f"GetIntervalVarFromProtoIndex: index {index} does not reference an" - + " interval variable" + f"get_interval_var_from_proto_index: index {index} does not" + " reference an" + " interval variable" ) return IntervalVar(self.__model, index, None, None, None, None) @@ -2477,51 +2643,50 @@ def GetIntervalVarFromProtoIndex(self, index: int) -> IntervalVar: def __str__(self): return str(self.__model) - def Proto(self) -> cp_model_pb2.CpModelProto: + @property + def proto(self) -> cp_model_pb2.CpModelProto: """Returns the underlying CpModelProto.""" return self.__model - def Negated(self, index: int) -> int: + def negated(self, index: int) -> int: return -index - 1 - def GetOrMakeIndex(self, arg: VariableT) -> int: + def get_or_make_index(self, arg: VariableT) -> int: """Returns the index of a variable, its negation, or a number.""" if isinstance(arg, IntVar): - return arg.Index() + return arg.index elif ( isinstance(arg, _ProductCst) - and isinstance(arg.Expression(), IntVar) - and arg.Coefficient() == -1 + and isinstance(arg.expression(), IntVar) + and arg.coefficient() == -1 ): - return -arg.Expression().Index() - 1 + return -arg.expression().index - 1 elif cmh.is_integral(arg): arg = cmh.assert_is_int64(arg) - return self.GetOrMakeIndexFromConstant(arg) + return self.get_or_make_index_from_constant(arg) else: - raise TypeError("NotSupported: model.GetOrMakeIndex(" + str(arg) + ")") + raise TypeError("NotSupported: model.get_or_make_index(" + str(arg) + ")") - def GetOrMakeBooleanIndex(self, arg: LiteralT) -> int: + def get_or_make_boolean_index(self, arg: LiteralT) -> int: """Returns an index from a boolean expression.""" if isinstance(arg, IntVar): - self.AssertIsBooleanVariable(arg) - return arg.Index() + self.assert_is_boolean_variable(arg) + return arg.index elif isinstance(arg, _NotBooleanVariable): - self.AssertIsBooleanVariable(arg.Not()) - return arg.Index() + self.assert_is_boolean_variable(arg.negated()) + return arg.index elif cmh.is_integral(arg): cmh.assert_is_boolean(arg) - return self.GetOrMakeIndexFromConstant(int(arg)) + return self.get_or_make_index_from_constant(int(arg)) else: - raise TypeError( - "NotSupported: model.GetOrMakeBooleanIndex(" + str(arg) + ")" - ) + raise TypeError(f"not supported: model.get_or_make_boolean_index({arg})") - def GetIntervalIndex(self, arg: IntervalVar) -> int: + def get_interval_index(self, arg: IntervalVar) -> int: if not isinstance(arg, IntervalVar): - raise TypeError("NotSupported: model.GetIntervalIndex(%s)" % arg) - return arg.Index() + raise TypeError("NotSupported: model.get_interval_index(%s)" % arg) + return arg.index - def GetOrMakeIndexFromConstant(self, value: IntegralT) -> int: + def get_or_make_index_from_constant(self, value: IntegralT) -> int: if value in self.__constant_map: return self.__constant_map[value] index = len(self.__model.variables) @@ -2529,13 +2694,15 @@ def GetOrMakeIndexFromConstant(self, value: IntegralT) -> int: self.__constant_map[value] = index return index - def VarIndexToVarProto(self, var_index: int) -> cp_model_pb2.IntegerVariableProto: + def var_index_to_var_proto( + self, var_index: int + ) -> cp_model_pb2.IntegerVariableProto: if var_index >= 0: return self.__model.variables[var_index] else: return self.__model.variables[-var_index - 1] - def ParseLinearExpression( + def parse_linear_expression( self, linear_expr: LinearExprT, negate: bool = False ) -> cp_model_pb2.LinearExpressionProto: """Returns a LinearExpressionProto built from a LinearExpr instance.""" @@ -2548,34 +2715,34 @@ def ParseLinearExpression( return result if isinstance(linear_expr, IntVar): - result.vars.append(self.GetOrMakeIndex(linear_expr)) + result.vars.append(self.get_or_make_index(linear_expr)) result.coeffs.append(mult) return result - coeffs_map, constant = cast(LinearExpr, linear_expr).GetIntegerVarValueMap() + coeffs_map, constant = cast(LinearExpr, linear_expr).get_integer_var_value_map() result.offset = constant * mult for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError("Wrong argument" + str(t)) c = cmh.assert_is_int64(t[1]) - result.vars.append(t[0].Index()) + result.vars.append(t[0].index) result.coeffs.append(c * mult) return result - def _SetObjective(self, obj: ObjLinearExprT, minimize: bool): + def _set_objective(self, obj: ObjLinearExprT, minimize: bool): """Sets the objective of the model.""" - self.ClearObjective() + self.clear_objective() if isinstance(obj, IntVar): self.__model.objective.coeffs.append(1) self.__model.objective.offset = 0 if minimize: - self.__model.objective.vars.append(obj.Index()) + self.__model.objective.vars.append(obj.index) self.__model.objective.scaling_factor = 1 else: - self.__model.objective.vars.append(self.Negated(obj.Index())) + self.__model.objective.vars.append(self.negated(obj.index)) self.__model.objective.scaling_factor = -1 elif isinstance(obj, LinearExpr): - coeffs_map, constant, is_integer = obj.GetFloatVarValueMap() + coeffs_map, constant, is_integer = obj.get_float_var_value_map() if is_integer: if minimize: self.__model.objective.scaling_factor = 1 @@ -2586,39 +2753,39 @@ def _SetObjective(self, obj: ObjLinearExprT, minimize: bool): for v, c in coeffs_map.items(): self.__model.objective.coeffs.append(c) if minimize: - self.__model.objective.vars.append(v.Index()) + self.__model.objective.vars.append(v.index) else: - self.__model.objective.vars.append(self.Negated(v.Index())) + self.__model.objective.vars.append(self.negated(v.index)) else: self.__model.floating_point_objective.maximize = not minimize self.__model.floating_point_objective.offset = constant for v, c in coeffs_map.items(): self.__model.floating_point_objective.coeffs.append(c) - self.__model.floating_point_objective.vars.append(v.Index()) + self.__model.floating_point_objective.vars.append(v.index) elif cmh.is_integral(obj): self.__model.objective.offset = int(obj) self.__model.objective.scaling_factor = 1 else: raise TypeError("TypeError: " + str(obj) + " is not a valid objective") - def Minimize(self, obj: ObjLinearExprT): + def minimize(self, obj: ObjLinearExprT): """Sets the objective of the model to minimize(obj).""" - self._SetObjective(obj, minimize=True) + self._set_objective(obj, minimize=True) - def Maximize(self, obj: ObjLinearExprT): + def maximize(self, obj: ObjLinearExprT): """Sets the objective of the model to maximize(obj).""" - self._SetObjective(obj, minimize=False) + self._set_objective(obj, minimize=False) - def HasObjective(self) -> bool: + def has_objective(self) -> bool: return self.__model.HasField("objective") or self.__model.HasField( "floating_point_objective" ) - def ClearObjective(self): + def clear_objective(self): self.__model.ClearField("objective") self.__model.ClearField("floating_point_objective") - def AddDecisionStrategy( + def add_decision_strategy( self, variables: Sequence[IntVar], var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy, @@ -2632,24 +2799,24 @@ def AddDecisionStrategy( domain_strategy: heuristic to reduce the domain of the selected variable. Currently, this is advanced code: the union of all strategies added to the model must be complete, i.e. instantiates all variables. Otherwise, - Solve() will fail. + solve() will fail. """ strategy = self.__model.search_strategy.add() for v in variables: - strategy.variables.append(v.Index()) + strategy.variables.append(v.index) strategy.variable_selection_strategy = var_strategy strategy.domain_reduction_strategy = domain_strategy - def ModelStats(self) -> str: + def model_stats(self) -> str: """Returns a string containing some model statistics.""" - return swig_helper.CpSatHelper.ModelStats(self.__model) + return swig_helper.CpSatHelper.model_stats(self.__model) - def Validate(self) -> str: + def validate(self) -> str: """Returns a string indicating that the model is invalid.""" - return swig_helper.CpSatHelper.ValidateModel(self.__model) + return swig_helper.CpSatHelper.validate_model(self.__model) - def ExportToFile(self, file: str) -> bool: + def export_to_file(self, file: str) -> bool: """Write the model as a protocol buffer to 'file'. Args: @@ -2662,52 +2829,126 @@ def ExportToFile(self, file: str) -> bool: """ return swig_helper.CpSatHelper.WriteModelToFile(self.__model, file) - def AssertIsBooleanVariable(self, x: LiteralT) -> None: - if isinstance(x, IntVar): - var = self.__model.variables[x.Index()] - if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: - raise TypeError("TypeError: " + str(x) + " is not a boolean variable") - elif not isinstance(x, _NotBooleanVariable): - raise TypeError("TypeError: " + str(x) + " is not a boolean variable") - - def AddHint(self, var: IntVar, value: int) -> None: + def add_hint(self, var: IntVar, value: int) -> None: """Adds 'var == value' as a hint to the solver.""" - self.__model.solution_hint.vars.append(self.GetOrMakeIndex(var)) + self.__model.solution_hint.vars.append(self.get_or_make_index(var)) self.__model.solution_hint.values.append(value) - def ClearHints(self): - """Remove any solution hint from the model.""" + def clear_hints(self): + """Removes any solution hint from the model.""" self.__model.ClearField("solution_hint") - def AddAssumption(self, lit: LiteralT) -> None: - """Add the literal 'lit' to the model as assumptions.""" - self.__model.assumptions.append(self.GetOrMakeBooleanIndex(lit)) + def add_assumption(self, lit: LiteralT) -> None: + """Adds the literal to the model as assumptions.""" + self.__model.assumptions.append(self.get_or_make_boolean_index(lit)) - def AddAssumptions(self, literals: Iterable[LiteralT]) -> None: - """Add the literals to the model as assumptions.""" + def add_assumptions(self, literals: Iterable[LiteralT]) -> None: + """Adds the literals to the model as assumptions.""" for lit in literals: - self.AddAssumption(lit) + self.add_assumption(lit) - def ClearAssumptions(self) -> None: - """Remove all assumptions from the model.""" + def clear_assumptions(self) -> None: + """Removes all assumptions from the model.""" self.__model.ClearField("assumptions") + # Helpers. + def assert_is_boolean_variable(self, x: LiteralT) -> None: + if isinstance(x, IntVar): + var = self.__model.variables[x.index] + if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: + raise TypeError("TypeError: " + str(x) + " is not a boolean variable") + elif not isinstance(x, _NotBooleanVariable): + raise TypeError("TypeError: " + str(x) + " is not a boolean variable") + + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + + def Name(self) -> str: + return self.name + + def SetName(self, name: str) -> None: + self.name = name + + def Proto(self) -> cp_model_pb2.CpModelProto: + return self.proto + + NewIntVar = new_int_var + NewIntVarFromDomain = new_int_var_from_domain + NewBoolVar = new_bool_var + NewConstant = new_constant + NewIntVarSeries = new_int_var_series + NewBoolVarSeries = new_bool_var_series + AddLinearConstraint = add_linear_constraint + AddLinearExpressionInDomain = add_linear_expression_in_domain + Add = add + AddAllDifferent = add_all_different + AddElement = add_element + AddCircuit = add_circuit + AddMultipleCircuit = add_multiple_circuit + AddAllowedAssignments = add_allowed_assignments + AddForbiddenAssignments = add_forbidden_assignments + AddAutomaton = add_automaton + AddInverse = add_inverse + AddReservoirConstraint = add_reservoir_constraint + AddImplication = add_implication + AddBoolOr = add_bool_or + AddAtLeastOne = add_at_least_one + AddAtMostOne = add_at_most_one + AddExactlyOne = add_exactly_one + AddBoolAnd = add_bool_and + AddBoolXOr = add_bool_xor + AddMinEquality = add_min_equality + AddMaxEquality = add_max_equality + AddDivisionEquality = add_division_equality + AddAbsEquality = add_abs_equality + AddModuloEquality = add_modulo_equality + AddMultiplicationEquality = add_multiplication_equality + NewIntervalVar = new_interval_var + NewIntervalVarSeries = new_interval_var_series + NewFixedSizedIntervalVar = new_fixed_size_interval_var + NewOptionalIntervalVar = new_optional_interval_var + NewOptionalIntervalVarSeries = new_optional_interval_var_series + NewOptionalFixedSizedIntervalVar = new_optional_fixed_size_interval_var + NewOptionalFixedSizedIntervalVarSeries = new_optional_fixed_size_interval_var_series + AddNoOverlap = add_no_overlap + AddNoOverlap2D = add_no_overlap_2d + AddCumulative = add_cumulative + Clone = clone + GetBoolVarFromProtoIndex = get_bool_var_from_proto_index + GetIntVarFromProtoIndex = get_int_var_from_proto_index + GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index + Minimize = minimize + Maximize = maximize + HasObjective = has_objective + ClearObjective = clear_objective + AddDecisionStrategy = add_decision_strategy + ModelStats = model_stats + Validate = validate + ExportToFile = export_to_file + AddHint = add_hint + ClearHints = clear_hints + AddAssumption = add_assumption + AddAssumptions = add_assumptions + ClearAssumptions = clear_assumptions + + # pylint: enable=invalid-name + @overload -def ExpandGeneratorOrTuple( +def expand_generator_or_tuple( args: Union[Tuple[LiteralT, ...], Iterable[LiteralT]] ) -> Union[Iterable[LiteralT], LiteralT]: ... @overload -def ExpandGeneratorOrTuple( +def expand_generator_or_tuple( args: Union[Tuple[LinearExprT, ...], Iterable[LinearExprT]] ) -> Union[Iterable[LinearExprT], LinearExprT]: ... -def ExpandGeneratorOrTuple(args): +def expand_generator_or_tuple(args): if hasattr(args, "__len__"): # Tuple if len(args) != 1: return args @@ -2717,7 +2958,7 @@ def ExpandGeneratorOrTuple(args): return args[0] -def EvaluateLinearExpr( +def evaluate_linear_expr( expression: LinearExprT, solution: cp_model_pb2.CpSolverResponse ) -> int: """Evaluate a linear expression against a solution.""" @@ -2733,36 +2974,36 @@ def EvaluateLinearExpr( if cmh.is_integral(expr): value += int(expr) * coeff elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, IntVar): - value += coeff * solution.solution[expr.Index()] + value += coeff * solution.solution[expr.index] elif isinstance(expr, _NotBooleanVariable): - value += coeff * (1 - solution.solution[expr.Not().Index()]) + value += coeff * (1 - solution.solution[expr.negated().index]) else: raise TypeError(f"Cannot interpret {expr} as a linear expression.") return value -def EvaluateBooleanExpression( +def evaluate_boolean_expression( literal: LiteralT, solution: cp_model_pb2.CpSolverResponse ) -> bool: """Evaluate a boolean expression against a solution.""" if cmh.is_integral(literal): return bool(literal) elif isinstance(literal, IntVar) or isinstance(literal, _NotBooleanVariable): - index: int = cast(Union[IntVar, _NotBooleanVariable], literal).Index() + index: int = cast(Union[IntVar, _NotBooleanVariable], literal).index if index >= 0: return bool(solution.solution[index]) else: @@ -2775,10 +3016,10 @@ class CpSolver: """Main solver class. The purpose of this class is to search for a solution to the model provided - to the Solve() method. + to the solve() method. - Once Solve() is called, this class allows inspecting the solution found - with the Value() and BooleanValue() methods, as well as general statistics + Once solve() is called, this class allows inspecting the solution found + with the value() and boolean_value() methods, as well as general statistics about the solve procedure. """ @@ -2791,7 +3032,7 @@ def __init__(self): self.__solve_wrapper: Optional[swig_helper.SolveWrapper] = None self.__lock: threading.Lock = threading.Lock() - def Solve( + def solve( self, model: CpModel, solution_callback: Optional["CpSolverSolutionCallback"] = None, @@ -2800,93 +3041,34 @@ def Solve( with self.__lock: self.__solve_wrapper = swig_helper.SolveWrapper() - self.__solve_wrapper.SetParameters(self.parameters) + self.__solve_wrapper.set_parameters(self.parameters) if solution_callback is not None: - self.__solve_wrapper.AddSolutionCallback(solution_callback) + self.__solve_wrapper.add_solution_callback(solution_callback) if self.log_callback is not None: - self.__solve_wrapper.AddLogCallback(self.log_callback) + self.__solve_wrapper.add_log_callback(self.log_callback) - self.__solution = self.__solve_wrapper.Solve(model.Proto()) + self.__solution = self.__solve_wrapper.solve(model.proto) if solution_callback is not None: - self.__solve_wrapper.ClearSolutionCallback(solution_callback) + self.__solve_wrapper.clear_solution_callback(solution_callback) with self.__lock: self.__solve_wrapper = None return self.__solution.status - def SolveWithSolutionCallback( - self, model: CpModel, callback: "CpSolverSolutionCallback" - ) -> cp_model_pb2.CpSolverStatus: - """DEPRECATED Use Solve() with the callback argument.""" - warnings.warn( - "SolveWithSolutionCallback is deprecated; use Solve() with" - + "the callback argument.", - DeprecationWarning, - ) - return self.Solve(model, callback) - - def SearchForAllSolutions( - self, model: CpModel, callback: "CpSolverSolutionCallback" - ) -> cp_model_pb2.CpSolverStatus: - """DEPRECATED Use Solve() with the right parameter. - - Search for all solutions of a satisfiability problem. - - This method searches for all feasible solutions of a given model. - Then it feeds the solution to the callback. - - Note that the model cannot contain an objective. - - Args: - model: The model to solve. - callback: The callback that will be called at each solution. - - Returns: - The status of the solve: - - * *FEASIBLE* if some solutions have been found - * *INFEASIBLE* if the solver has proved there are no solution - * *OPTIMAL* if all solutions have been found - """ - warnings.warn( - "SearchForAllSolutions is deprecated; use Solve() with" - + "enumerate_all_solutions = True.", - DeprecationWarning, - ) - if model.HasObjective(): - raise TypeError( - "Search for all solutions is only defined on satisfiability problems" - ) - # Store old parameter. - enumerate_all = self.parameters.enumerate_all_solutions - self.parameters.enumerate_all_solutions = True - - self.Solve(model, callback) - - # Restore parameter. - self.parameters.enumerate_all_solutions = enumerate_all - return self.__solution.status - - def StopSearch(self) -> None: + def stop_search(self) -> None: """Stops the current search asynchronously.""" with self.__lock: if self.__solve_wrapper: - self.__solve_wrapper.StopSearch() + self.__solve_wrapper.stop_search() - def _Solution(self) -> cp_model_pb2.CpSolverResponse: - """Checks Solve() has been called, and returns the solution.""" - if self.__solution is None: - raise RuntimeError("Solve() has not been called.") - return self.__solution - - def Value(self, expression: LinearExprT) -> int: + def value(self, expression: LinearExprT) -> int: """Returns the value of a linear expression after solve.""" - return EvaluateLinearExpr(expression, self._Solution()) + return evaluate_linear_expr(expression, self._solution) - def Values(self, variables: _IndexOrSeries) -> pd.Series: + def values(self, variables: _IndexOrSeries) -> pd.Series: """Returns the values of the input variables. If `variables` is a `pd.Index`, then the output will be indexed by the @@ -2901,17 +3083,17 @@ def Values(self, variables: _IndexOrSeries) -> pd.Series: Returns: pd.Series: The values of all variables in the set. """ - solution = self._Solution() - return _AttributeSeries( - func=lambda v: solution.solution[v.Index()], + solution = self._solution + return _attribute_series( + func=lambda v: solution.solution[v.index], values=variables, ) - def BooleanValue(self, literal: LiteralT) -> bool: + def boolean_value(self, literal: LiteralT) -> bool: """Returns the boolean value of a literal after solve.""" - return EvaluateBooleanExpression(literal, self._Solution()) + return evaluate_boolean_expression(literal, self._solution) - def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: + def boolean_values(self, variables: _IndexOrSeries) -> pd.Series: """Returns the values of the input variables. If `variables` is a `pd.Index`, then the output will be indexed by the @@ -2926,68 +3108,200 @@ def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: Returns: pd.Series: The values of all variables in the set. """ - solution = self._Solution() - return _AttributeSeries( - func=lambda literal: EvaluateBooleanExpression(literal, solution), + solution = self._solution + return _attribute_series( + func=lambda literal: evaluate_boolean_expression(literal, solution), values=variables, ) - def ObjectiveValue(self) -> float: + @property + def objective_value(self) -> float: """Returns the value of the objective after solve.""" - return self._Solution().objective_value + return self._solution.objective_value - def BestObjectiveBound(self) -> float: + @property + def best_objective_bound(self) -> float: """Returns the best lower (upper) bound found when min(max)imizing.""" - return self._Solution().best_objective_bound - - def StatusName(self, status: ... = None) -> str: - """Returns the name of the status returned by Solve().""" - if status is None: - status = self._Solution().status - return cp_model_pb2.CpSolverStatus.Name(status) + return self._solution.best_objective_bound - def NumBooleans(self) -> int: + @property + def num_booleans(self) -> int: """Returns the number of boolean variables managed by the SAT solver.""" - return self._Solution().num_booleans + return self._solution.num_booleans - def NumConflicts(self) -> int: + @property + def num_conflicts(self) -> int: """Returns the number of conflicts since the creation of the solver.""" - return self._Solution().num_conflicts + return self._solution.num_conflicts - def NumBranches(self) -> int: + @property + def num_branches(self) -> int: """Returns the number of search branches explored by the solver.""" - return self._Solution().num_branches + return self._solution.num_branches - def WallTime(self) -> float: + @property + def wall_time(self) -> float: """Returns the wall time in seconds since the creation of the solver.""" - return self._Solution().wall_time + return self._solution.wall_time - def UserTime(self) -> float: + @property + def user_time(self) -> float: """Returns the user time in seconds since the creation of the solver.""" - return self._Solution().user_time - - def ResponseStats(self) -> str: - """Returns some statistics on the solution found as a string.""" - return swig_helper.CpSatHelper.SolverResponseStats(self._Solution()) + return self._solution.user_time - def ResponseProto(self) -> cp_model_pb2.CpSolverResponse: + @property + def response_proto(self) -> cp_model_pb2.CpSolverResponse: """Returns the response object.""" - return self._Solution() + return self._solution - def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: + def response_stats(self) -> str: + """Returns some statistics on the solution found as a string.""" + return swig_helper.CpSatHelper.solver_response_stats(self._solution) + + def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]: """Returns the indices of the infeasible assumptions.""" - return self._Solution().sufficient_assumptions_for_infeasibility + return self._solution.sufficient_assumptions_for_infeasibility - def SolutionInfo(self) -> str: + def status_name(self, status: ... = None) -> str: + """Returns the name of the status returned by solve().""" + if status is None: + status = self._solution.status + return cp_model_pb2.CpSolverStatus.Name(status) + + def solution_info(self) -> str: """Returns some information on the solve process. Returns some information on how the solution was found, or the reason why the model or the parameters are invalid. Raises: - RuntimeError: if Solve() has not been called. + RuntimeError: if solve() has not been called. + """ + return self._solution.solution_info + + @property + def _solution(self) -> cp_model_pb2.CpSolverResponse: + """Checks solve() has been called, and returns the solution.""" + if self.__solution is None: + raise RuntimeError("solve() has not been called.") + return self.__solution + + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + + def BestObjectiveBound(self) -> float: + return self.best_objective_bound + + def BooleanValue(self, literal: LiteralT) -> bool: + return self.boolean_value(literal) + + def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: + return self.boolean_values(variables) + + def NumBooleans(self) -> int: + return self.num_booleans + + def NumConflicts(self) -> int: + return self.num_conflicts + + def NumBranches(self) -> int: + return self.num_branches + + def ObjectiveValue(self) -> float: + return self.objective_value + + def ResponseProto(self) -> cp_model_pb2.CpSolverResponse: + return self.response_proto + + def ResponseStats(self) -> str: + return self.response_stats() + + def Solve( + self, + model: CpModel, + solution_callback: Optional["CpSolverSolutionCallback"] = None, + ) -> cp_model_pb2.CpSolverStatus: + return self.solve(model, solution_callback) + + def SolutionInfo(self) -> str: + return self.solution_info() + + def StatusName(self, status: ... = None) -> str: + return self.status_name(status) + + def StopSearch(self) -> None: + self.stop_search() + + def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: + return self.sufficient_assumptions_for_infeasibility() + + def UserTime(self) -> float: + return self.user_time + + def Value(self, expression: LinearExprT) -> int: + return self.value(expression) + + def Values(self, variables: _IndexOrSeries) -> pd.Series: + return self.values(variables) + + def WallTime(self) -> float: + return self.wall_time + + def SolveWithSolutionCallback( + self, model: CpModel, callback: "CpSolverSolutionCallback" + ) -> cp_model_pb2.CpSolverStatus: + """DEPRECATED Use solve() with the callback argument.""" + warnings.warn( + "solve_with_solution_callback is deprecated; use solve() with" + + "the callback argument.", + DeprecationWarning, + ) + return self.solve(model, callback) + + def SearchForAllSolutions( + self, model: CpModel, callback: "CpSolverSolutionCallback" + ) -> cp_model_pb2.CpSolverStatus: + """DEPRECATED Use solve() with the right parameter. + + Search for all solutions of a satisfiability problem. + + This method searches for all feasible solutions of a given model. + Then it feeds the solution to the callback. + + Note that the model cannot contain an objective. + + Args: + model: The model to solve. + callback: The callback that will be called at each solution. + + Returns: + The status of the solve: + + * *FEASIBLE* if some solutions have been found + * *INFEASIBLE* if the solver has proved there are no solution + * *OPTIMAL* if all solutions have been found """ - return self._Solution().solution_info + warnings.warn( + "search_for_all_solutions is deprecated; use solve() with" + + "enumerate_all_solutions = True.", + DeprecationWarning, + ) + if model.has_objective(): + raise TypeError( + "Search for all solutions is only defined on satisfiability problems" + ) + # Store old parameter. + enumerate_all = self.parameters.enumerate_all_solutions + self.parameters.enumerate_all_solutions = True + + self.solve(model, callback) + + # Restore parameter. + self.parameters.enumerate_all_solutions = enumerate_all + return self.__solution.status + + +# pylint: enable=invalid-name class CpSolverSolutionCallback(swig_helper.SolutionCallback): @@ -2996,19 +3310,9 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): This class implements a callback that will be called at each new solution found during search. - The method OnSolutionCallback() will be called by the solver, and must be - implemented. The current solution can be queried using the BooleanValue() - and Value() methods. - - It inherits the following methods from its base class: - - * `ObjectiveValue(self)` - * `BestObjectiveBound(self)` - * `NumBooleans(self)` - * `NumConflicts(self)` - * `NumBranches(self)` - * `WallTime(self)` - * `UserTime(self)` + The method on_solution_callback() will be called by the solver, and must be + implemented. The current solution can be queried using the boolean_value() + and value() methods. These methods returns the same information as their counterpart in the `CpSolver` class. @@ -3021,7 +3325,7 @@ def OnSolutionCallback(self) -> None: """Proxy for the same method in snake case.""" self.on_solution_callback() - def BooleanValue(self, lit: LiteralT) -> bool: + def boolean_value(self, lit: LiteralT) -> bool: """Returns the boolean value of a boolean literal. Args: @@ -3033,17 +3337,17 @@ def BooleanValue(self, lit: LiteralT) -> bool: Raises: RuntimeError: if `lit` is not a boolean variable or its negation. """ - if not self.HasResponse(): - raise RuntimeError("Solve() has not been called.") + if not self.has_response(): + raise RuntimeError("solve() has not been called.") if cmh.is_integral(lit): return bool(lit) elif isinstance(lit, IntVar) or isinstance(lit, _NotBooleanVariable): return self.SolutionBooleanValue( - cast(Union[IntVar, _NotBooleanVariable], lit).Index() + cast(Union[IntVar, _NotBooleanVariable], lit).index ) raise TypeError(f"Cannot interpret {lit} as a boolean expression.") - def Value(self, expression: LinearExprT) -> int: + def value(self, expression: LinearExprT) -> int: """Evaluates an linear expression in the current solution. Args: @@ -3056,8 +3360,8 @@ def Value(self, expression: LinearExprT) -> int: Raises: RuntimeError: if 'expression' is not a LinearExpr. """ - if not self.HasResponse(): - raise RuntimeError("Solve() has not been called.") + if not self.has_response(): + raise RuntimeError("solve() has not been called.") value = 0 to_process = [(expression, 1)] @@ -3066,32 +3370,118 @@ def Value(self, expression: LinearExprT) -> int: if cmh.is_integral(expr): value += int(expr) * coeff elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, IntVar): - value += coeff * self.SolutionIntegerValue(expr.Index()) + value += coeff * self.SolutionIntegerValue(expr.index) elif isinstance(expr, _NotBooleanVariable): - value += coeff * (1 - self.SolutionIntegerValue(expr.Not().Index())) + value += coeff * (1 - self.SolutionIntegerValue(expr.negated().index)) else: raise TypeError( - f"Cannot interpret {expression} as a linear expression." + f"cannot interpret {expression} as a linear expression." ) return value - def Response(self) -> cp_model_pb2.CpSolverResponse: - """Returns the current solution response.""" - return swig_helper.SolutionCallback.Response(self) + def has_response(self) -> bool: + return self.HasResponse() + + def stop_search(self) -> None: + """Stops the current search asynchronously.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + self.StopSearch() + + @property + def objective_value(self) -> float: + """Returns the value of the objective after solve.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.ObjectiveValue() + + @property + def best_objective_bound(self) -> float: + """Returns the best lower (upper) bound found when min(max)imizing.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.BestObjectiveBound() + + @property + def num_booleans(self) -> int: + """Returns the number of boolean variables managed by the SAT solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBooleans() + + @property + def num_conflicts(self) -> int: + """Returns the number of conflicts since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumConflicts() + + @property + def num_branches(self) -> int: + """Returns the number of search branches explored by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBranches() + + @property + def num_integer_propagations(self) -> int: + """Returns the number of integer propagations done by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumIntegerPropagations() + + @property + def num_boolean_propagations(self) -> int: + """Returns the number of Boolean propagations done by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBooleanPropagations() + + @property + def deterministic_time(self) -> float: + """Returns the determistic time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.DeterministicTime() + + @property + def wall_time(self) -> float: + """Returns the wall time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.WallTime() + + @property + def user_time(self) -> float: + """Returns the user time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.UserTime() + + @property + def response_proto(self) -> cp_model_pb2.CpSolverResponse: + """Returns the response object.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.Response() + + # PeP8 compatibility + Value = value + BooleanValue = boolean_value class ObjectiveSolutionPrinter(CpSolverSolutionCallback): @@ -3105,7 +3495,7 @@ def __init__(self): def on_solution_callback(self) -> None: """Called on each new solution.""" current_time = time.time() - obj = self.ObjectiveValue() + obj = self.objective_value print( "Solution %i, time = %0.2f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, obj) @@ -3129,16 +3519,17 @@ def __init__(self, variables): def on_solution_callback(self) -> None: """Called on each new solution.""" current_time = time.time() - obj = self.ObjectiveValue() + obj = self.objective_value print( "Solution %i, time = %0.2f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, obj) ) for v in self.__variables: - print(" %s = %i" % (v, self.Value(v)), end=" ") + print(" %s = %i" % (v, self.value(v)), end=" ") print() self.__solution_count += 1 + @property def solution_count(self) -> int: """Returns the number of solutions found.""" return self.__solution_count @@ -3161,23 +3552,24 @@ def on_solution_callback(self) -> None: % (self.__solution_count, current_time - self.__start_time) ) for v in self.__variables: - print(" %s = %i" % (v, self.Value(v)), end=" ") + print(" %s = %i" % (v, self.value(v)), end=" ") print() self.__solution_count += 1 + @property def solution_count(self) -> int: """Returns the number of solutions found.""" return self.__solution_count -def _GetIndex(obj: _IndexOrSeries) -> pd.Index: +def _get_index(obj: _IndexOrSeries) -> pd.Index: """Returns the indices of `obj` as a `pd.Index`.""" if isinstance(obj, pd.Series): return obj.index return obj -def _AttributeSeries( +def _attribute_series( *, func: Callable[[IntVar], IntegralT], values: _IndexOrSeries, @@ -3193,11 +3585,11 @@ def _AttributeSeries( """ return pd.Series( data=[func(v) for v in values], - index=_GetIndex(values), + index=_get_index(values), ) -def _ConvertToIntegralSeriesAndValidateIndex( +def _convert_to_integral_series_and_validate_index( value_or_series: Union[IntegralT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. @@ -3225,7 +3617,7 @@ def _ConvertToIntegralSeriesAndValidateIndex( return result -def _ConvertToLinearExprSeriesAndValidateIndex( +def _convert_to_linear_expr_series_and_validate_index( value_or_series: Union[LinearExprT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. @@ -3253,7 +3645,7 @@ def _ConvertToLinearExprSeriesAndValidateIndex( return result -def _ConvertToLiteralSeriesAndValidateIndex( +def _convert_to_literal_series_and_validate_index( value_or_series: Union[LiteralT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. diff --git a/ortools/sat/python/cp_model_test.py b/ortools/sat/python/cp_model_test.py index b317b3b1fe8..3a6370dcf3a 100644 --- a/ortools/sat/python/cp_model_test.py +++ b/ortools/sat/python/cp_model_test.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for ortools.sat.python.cp_model.""" + from absl.testing import absltest import pandas as pd @@ -26,10 +27,11 @@ def __init__(self): cp_model.CpSolverSolutionCallback.__init__(self) self.__solution_count = 0 - def OnSolutionCallback(self): + def on_solution_callback(self): self.__solution_count += 1 - def SolutionCount(self): + @property + def solution_count(self): return self.__solution_count @@ -41,10 +43,11 @@ def __init__(self, variables): self.__sum = 0 self.__vars = variables - def OnSolutionCallback(self): - self.__sum = sum(self.Value(x) for x in self.__vars) + def on_solution_callback(self): + self.__sum = sum(self.value(x) for x in self.__vars) - def Sum(self): + @property + def sum(self): return self.__sum @@ -55,10 +58,11 @@ def __init__(self): cp_model.CpSolverSolutionCallback.__init__(self) self.__obj = 0 - def OnSolutionCallback(self): - self.__obj = self.ObjectiveValue() + def on_solution_callback(self): + self.__obj = self.objective_value - def Obj(self): + @property + def obj(self): return self.__obj @@ -68,11 +72,12 @@ class LogToString: def __init__(self): self.__log = "" - def NewMessage(self, message: str): + def new_message(self, message: str): self.__log += message self.__log += "\n" - def Log(self): + @property + def log(self): return self.__log @@ -80,60 +85,64 @@ class CpModelTest(absltest.TestCase): def testCreateIntegerVariable(self): print("testCreateIntegerVariable") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") self.assertEqual("x", str(x)) self.assertEqual("x(-10..10)", repr(x)) - y = model.NewIntVarFromDomain(cp_model.Domain.FromIntervals([[2, 4], [7]]), "y") + y = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([[2, 4], [7]]), "y" + ) self.assertEqual("y", str(y)) self.assertEqual("y(2..4, 7)", repr(y)) - z = model.NewIntVarFromDomain(cp_model.Domain.FromValues([2, 3, 4, 7]), "z") + z = model.new_int_var_from_domain( + cp_model.Domain.from_values([2, 3, 4, 7]), "z" + ) self.assertEqual("z", str(z)) self.assertEqual("z(2..4, 7)", repr(z)) - t = model.NewIntVarFromDomain( - cp_model.Domain.FromFlatIntervals([2, 4, 7, 7]), "t" + t = model.new_int_var_from_domain( + cp_model.Domain.from_flat_intervals([2, 4, 7, 7]), "t" ) self.assertEqual("t", str(t)) self.assertEqual("t(2..4, 7)", repr(t)) - cst = model.NewConstant(5) + cst = model.new_constant(5) self.assertEqual("5", str(cst)) def testNegation(self): print("testNegation") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - b = model.NewBoolVar("b") - nb = b.Not() - self.assertEqual(b.Not(), nb) - self.assertEqual(b.Not().Not(), b) - self.assertEqual(nb.Index(), -b.Index() - 1) - self.assertRaises(TypeError, x.Not) + x = model.new_int_var(-10, 10, "x") + b = model.new_bool_var("b") + nb = b.negated() + self.assertEqual(b.negated(), nb) + self.assertEqual(b.negated().negated(), b) + self.assertEqual(nb.index, -b.index - 1) + self.assertRaises(TypeError, x.negated) def testEqualityOverload(self): print("testEqualityOverload") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(0, 5, "y") + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(0, 5, "y") self.assertEqual(x, x) self.assertNotEqual(x, y) def testLinear(self): print("testLinear") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(10, solver.Value(x)) - self.assertEqual(-5, solver.Value(y)) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(10, solver.value(x)) + self.assertEqual(-5, solver.value(y)) def testLinearNonEqual(self): print("testLinearNonEqual") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(-x + y != 3).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(-x + y != 3).proto self.assertLen(ct.linear.domain, 4) self.assertEqual(cp_model.INT_MIN, ct.linear.domain[0]) self.assertEqual(2, ct.linear.domain[1]) @@ -143,8 +152,8 @@ def testLinearNonEqual(self): def testEq(self): print("testEq") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x == 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x == 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -154,8 +163,8 @@ def testEq(self): def testGe(self): print("testGe") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x >= 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x >= 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -165,8 +174,8 @@ def testGe(self): def testGt(self): print("testGt") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x > 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x > 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -176,8 +185,8 @@ def testGt(self): def testLe(self): print("testLe") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x <= 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x <= 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -187,8 +196,8 @@ def testLe(self): def testLt(self): print("testLt") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x < 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x < 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -198,9 +207,9 @@ def testLt(self): def testEqVar(self): print("testEqVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x == y + 2).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x == y + 2).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -212,9 +221,9 @@ def testEqVar(self): def testGeVar(self): print("testGeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x >= 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x >= 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -227,9 +236,9 @@ def testGeVar(self): def testGtVar(self): print("testGeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x > 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x > 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -242,9 +251,9 @@ def testGtVar(self): def testLeVar(self): print("testLeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x <= 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x <= 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -257,9 +266,9 @@ def testLeVar(self): def testLtVar(self): print("testLtVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x < 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x < 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -272,41 +281,41 @@ def testLtVar(self): def testSimplification1(self): print("testSimplification1") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = (x * 2) * 2 - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification2(self): print("testSimplification2") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = 2 * (x * 2) - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification3(self): print("testSimplification3") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = (2 * x) * 2 - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification4(self): print("testSimplification4") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = 2 * (2 * x) - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testLinearNonEqualWithConstant(self): print("testLinearNonEqualWithConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x + y + 5 != 3).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x + y + 5 != 3).proto self.assertLen(ct.linear.domain, 4) # Checks that saturated arithmetics worked. self.assertEqual(cp_model.INT_MIN, ct.linear.domain[0]) @@ -317,254 +326,254 @@ def testLinearNonEqualWithConstant(self): def testLinearWithEnforcement(self): print("testLinearWithEnforcement") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - b = model.NewBoolVar("b") - model.AddLinearConstraint(x + 2 * y, 0, 10).OnlyEnforceIf(b.Not()) - model.Minimize(y) - self.assertLen(model.Proto().constraints, 1) - self.assertEqual(-3, model.Proto().constraints[0].enforcement_literal[0]) - c = model.NewBoolVar("c") - model.AddLinearConstraint(x + 4 * y, 0, 10).OnlyEnforceIf([b, c]) - self.assertLen(model.Proto().constraints, 2) - self.assertEqual(2, model.Proto().constraints[1].enforcement_literal[0]) - self.assertEqual(3, model.Proto().constraints[1].enforcement_literal[1]) - model.AddLinearConstraint(x + 5 * y, 0, 10).OnlyEnforceIf(c.Not(), b) - self.assertLen(model.Proto().constraints, 3) - self.assertEqual(-4, model.Proto().constraints[2].enforcement_literal[0]) - self.assertEqual(2, model.Proto().constraints[2].enforcement_literal[1]) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + b = model.new_bool_var("b") + model.add_linear_constraint(x + 2 * y, 0, 10).only_enforce_if(b.negated()) + model.minimize(y) + self.assertLen(model.proto.constraints, 1) + self.assertEqual(-3, model.proto.constraints[0].enforcement_literal[0]) + c = model.new_bool_var("c") + model.add_linear_constraint(x + 4 * y, 0, 10).only_enforce_if([b, c]) + self.assertLen(model.proto.constraints, 2) + self.assertEqual(2, model.proto.constraints[1].enforcement_literal[0]) + self.assertEqual(3, model.proto.constraints[1].enforcement_literal[1]) + model.add_linear_constraint(x + 5 * y, 0, 10).only_enforce_if(c.negated(), b) + self.assertLen(model.proto.constraints, 3) + self.assertEqual(-4, model.proto.constraints[2].enforcement_literal[0]) + self.assertEqual(2, model.proto.constraints[2].enforcement_literal[1]) def testConstraintWithName(self): print("testConstraintWithName") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.AddLinearConstraint(x + 2 * y, 0, 10).WithName("test_constraint") - self.assertEqual("test_constraint", ct.Name()) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add_linear_constraint(x + 2 * y, 0, 10).with_name("test_constraint") + self.assertEqual("test_constraint", ct.name) def testNaturalApiMinimize(self): print("testNaturalApiMinimize") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.Add(x * 2 - 1 * y == 1) - model.Minimize(x * 1 - 2 * y + 3) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add(x * 2 - 1 * y == 1) + model.minimize(x * 1 - 2 * y + 3) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.Value(x)) - self.assertEqual(15, solver.Value(x * 3)) - self.assertEqual(6, solver.Value(1 + x)) - self.assertEqual(-10.0, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.value(x)) + self.assertEqual(15, solver.value(x * 3)) + self.assertEqual(6, solver.value(1 + x)) + self.assertEqual(-10.0, solver.objective_value) def testNaturalApiMaximizeFloat(self): print("testNaturalApiMaximizeFloat") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewIntVar(0, 10, "y") - model.Maximize(x.Not() * 3.5 + x.Not() - y + 2 * y + 1.6) + x = model.new_bool_var("x") + y = model.new_int_var(0, 10, "y") + model.maximize(x.negated() * 3.5 + x.negated() - y + 2 * y + 1.6) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertFalse(solver.BooleanValue(x)) - self.assertTrue(solver.BooleanValue(x.Not())) - self.assertEqual(-10, solver.Value(-y)) - self.assertEqual(16.1, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertFalse(solver.boolean_value(x)) + self.assertTrue(solver.boolean_value(x.negated())) + self.assertEqual(-10, solver.value(-y)) + self.assertEqual(16.1, solver.objective_value) def testNaturalApiMaximizeComplex(self): print("testNaturalApiMaximizeFloat") model = cp_model.CpModel() - x1 = model.NewBoolVar("x1") - x2 = model.NewBoolVar("x1") - x3 = model.NewBoolVar("x1") - x4 = model.NewBoolVar("x1") - model.Maximize( - cp_model.LinearExpr.Sum([x1, x2]) - + cp_model.LinearExpr.WeightedSum([x3, x4.Not()], [2, 4]) + x1 = model.new_bool_var("x1") + x2 = model.new_bool_var("x1") + x3 = model.new_bool_var("x1") + x4 = model.new_bool_var("x1") + model.maximize( + cp_model.LinearExpr.sum([x1, x2]) + + cp_model.LinearExpr.weighted_sum([x3, x4.negated()], [2, 4]) ) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.Value(3 + 2 * x1)) - self.assertEqual(3, solver.Value(x1 + x2 + x3)) - self.assertEqual(1, solver.Value(cp_model.LinearExpr.Sum([x1, x2, x3, 0, -2]))) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.value(3 + 2 * x1)) + self.assertEqual(3, solver.value(x1 + x2 + x3)) + self.assertEqual(1, solver.value(cp_model.LinearExpr.sum([x1, x2, x3, 0, -2]))) self.assertEqual( 7, - solver.Value( - cp_model.LinearExpr.WeightedSum([x1, x2, x4, 3], [2, 2, 2, 1]) + solver.value( + cp_model.LinearExpr.weighted_sum([x1, x2, x4, 3], [2, 2, 2, 1]) ), ) - self.assertEqual(5, solver.Value(5 * x4.Not())) - self.assertEqual(8, solver.ObjectiveValue()) + self.assertEqual(5, solver.value(5 * x4.negated())) + self.assertEqual(8, solver.objective_value) def testNaturalApiMaximize(self): print("testNaturalApiMaximize") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.Add(2 * x - y == 1) - model.Maximize(x - 2 * y + 3) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add(2 * x - y == 1) + model.maximize(x - 2 * y + 3) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(-4, solver.Value(x)) - self.assertEqual(-9, solver.Value(y)) - self.assertEqual(17, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(-4, solver.value(x)) + self.assertEqual(-9, solver.value(y)) + self.assertEqual(17, solver.objective_value) def testMinimizeConstant(self): print("testMinimizeConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(x >= -1) - model.Minimize(10) + x = model.new_int_var(-10, 10, "x") + model.add(x >= -1) + model.minimize(10) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(10, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(10, solver.objective_value) def testMaximizeConstant(self): print("testMinimizeConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(x >= -1) - model.Maximize(5) + x = model.new_int_var(-10, 10, "x") + model.add(x >= -1) + model.maximize(5) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.objective_value) def testAddTrue(self): print("testAddTrue") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(3 >= -1) - model.Minimize(x) + x = model.new_int_var(-10, 10, "x") + model.add(3 >= -1) + model.minimize(x) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(-10, solver.Value(x)) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(-10, solver.value(x)) def testAddFalse(self): print("testAddFalse") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(3 <= -1) - model.Minimize(x) + x = model.new_int_var(-10, 10, "x") + model.add(3 <= -1) + model.minimize(x) solver = cp_model.CpSolver() - self.assertEqual("INFEASIBLE", solver.StatusName(solver.Solve(model))) + self.assertEqual("INFEASIBLE", solver.status_name(solver.solve(model))) def testSum(self): print("testSum") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] - model.Add(sum(x) <= 1) - model.Maximize(x[99]) + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] + model.add(sum(x) <= 1) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testSumWithApi(self): print("testSumWithApi") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] - model.Add(cp_model.LinearExpr.Sum(x) <= 1) - model.Maximize(x[99]) + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] + model.add(cp_model.LinearExpr.sum(x) <= 1) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testWeightedSum(self): print("testWeightedSum") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] c = [2] * 100 - model.Add(cp_model.LinearExpr.WeightedSum(x, c) <= 3) - model.Maximize(x[99]) + model.add(cp_model.LinearExpr.weighted_sum(x, c) <= 3) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testAllDifferent(self): print("testAllDifferent") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllDifferent(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_all_different(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testAllDifferentGen(self): print("testAllDifferentGen") model = cp_model.CpModel() - model.AddAllDifferent(model.NewIntVar(0, 4, "x%i" % i) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + model.add_all_different(model.new_int_var(0, 4, "x%i" % i) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testAllDifferentList(self): print("testAllDifferentList") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllDifferent(x[0], x[1], x[2], x[3], x[4]) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_all_different(x[0], x[1], x[2], x[3], x[4]) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testElement(self): print("testElement") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddElement(x[0], [x[1], 2, 4, x[2]], x[4]) - self.assertLen(model.Proto().variables, 7) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].element.vars, 4) - self.assertEqual(0, model.Proto().constraints[0].element.index) - self.assertEqual(4, model.Proto().constraints[0].element.target) - self.assertRaises(ValueError, model.AddElement, x[0], [], x[4]) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_element(x[0], [x[1], 2, 4, x[2]], x[4]) + self.assertLen(model.proto.variables, 7) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].element.vars, 4) + self.assertEqual(0, model.proto.constraints[0].element.index) + self.assertEqual(4, model.proto.constraints[0].element.target) + self.assertRaises(ValueError, model.add_element, x[0], [], x[4]) def testCircuit(self): print("testCircuit") model = cp_model.CpModel() - x = [model.NewBoolVar(f"x{i}") for i in range(5)] - model.AddCircuit((i, i + 1, x[i]) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].circuit.heads, 5) - self.assertLen(model.Proto().constraints[0].circuit.tails, 5) - self.assertLen(model.Proto().constraints[0].circuit.literals, 5) - self.assertRaises(ValueError, model.AddCircuit, []) + x = [model.new_bool_var(f"x{i}") for i in range(5)] + model.add_circuit((i, i + 1, x[i]) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].circuit.heads, 5) + self.assertLen(model.proto.constraints[0].circuit.tails, 5) + self.assertLen(model.proto.constraints[0].circuit.literals, 5) + self.assertRaises(ValueError, model.add_circuit, []) def testMultipleCircuit(self): print("testMultipleCircuit") model = cp_model.CpModel() - x = [model.NewBoolVar(f"x{i}") for i in range(5)] - model.AddMultipleCircuit((i, i + 1, x[i]) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].routes.heads, 5) - self.assertLen(model.Proto().constraints[0].routes.tails, 5) - self.assertLen(model.Proto().constraints[0].routes.literals, 5) - self.assertRaises(ValueError, model.AddMultipleCircuit, []) + x = [model.new_bool_var(f"x{i}") for i in range(5)] + model.add_multiple_circuit((i, i + 1, x[i]) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].routes.heads, 5) + self.assertLen(model.proto.constraints[0].routes.tails, 5) + self.assertLen(model.proto.constraints[0].routes.literals, 5) + self.assertRaises(ValueError, model.add_multiple_circuit, []) def testAllowedAssignments(self): print("testAllowedAssignments") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllowedAssignments( + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_allowed_assignments( x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0, 0)] ) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].table.vars, 5) - self.assertLen(model.Proto().constraints[0].table.values, 15) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].table.vars, 5) + self.assertLen(model.proto.constraints[0].table.values, 15) self.assertRaises( TypeError, - model.AddAllowedAssignments, + model.add_allowed_assignments, x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) self.assertRaises( ValueError, - model.AddAllowedAssignments, + model.add_allowed_assignments, [], [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) @@ -572,24 +581,24 @@ def testAllowedAssignments(self): def testForbiddenAssignments(self): print("testForbiddenAssignments") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddForbiddenAssignments( + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_forbidden_assignments( x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0, 0)] ) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].table.vars, 5) - self.assertLen(model.Proto().constraints[0].table.values, 15) - self.assertTrue(model.Proto().constraints[0].table.negated) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].table.vars, 5) + self.assertLen(model.proto.constraints[0].table.values, 15) + self.assertTrue(model.proto.constraints[0].table.negated) self.assertRaises( TypeError, - model.AddForbiddenAssignments, + model.add_forbidden_assignments, x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) self.assertRaises( ValueError, - model.AddForbiddenAssignments, + model.add_forbidden_assignments, [], [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) @@ -597,19 +606,19 @@ def testForbiddenAssignments(self): def testAutomaton(self): print("testAutomaton") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAutomaton(x, 0, [2, 3], [(0, 0, 0), (0, 1, 1), (1, 2, 2), (2, 3, 3)]) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].automaton.vars, 5) - self.assertLen(model.Proto().constraints[0].automaton.transition_tail, 4) - self.assertLen(model.Proto().constraints[0].automaton.transition_head, 4) - self.assertLen(model.Proto().constraints[0].automaton.transition_label, 4) - self.assertLen(model.Proto().constraints[0].automaton.final_states, 2) - self.assertEqual(0, model.Proto().constraints[0].automaton.starting_state) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_automaton(x, 0, [2, 3], [(0, 0, 0), (0, 1, 1), (1, 2, 2), (2, 3, 3)]) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].automaton.vars, 5) + self.assertLen(model.proto.constraints[0].automaton.transition_tail, 4) + self.assertLen(model.proto.constraints[0].automaton.transition_head, 4) + self.assertLen(model.proto.constraints[0].automaton.transition_label, 4) + self.assertLen(model.proto.constraints[0].automaton.final_states, 2) + self.assertEqual(0, model.proto.constraints[0].automaton.starting_state) self.assertRaises( TypeError, - model.AddAutomaton, + model.add_automaton, x, 0, [2, 3], @@ -617,93 +626,98 @@ def testAutomaton(self): ) self.assertRaises( ValueError, - model.AddAutomaton, + model.add_automaton, [], 0, [2, 3], [(0, 0, 0), (0, 1, 1), (2, 3, 3)], ) self.assertRaises( - ValueError, model.AddAutomaton, x, 0, [], [(0, 0, 0), (0, 1, 1), (2, 3, 3)] + ValueError, + model.add_automaton, + x, + 0, + [], + [(0, 0, 0), (0, 1, 1), (2, 3, 3)], ) - self.assertRaises(ValueError, model.AddAutomaton, x, 0, [2, 3], []) + self.assertRaises(ValueError, model.add_automaton, x, 0, [2, 3], []) def testInverse(self): print("testInverse") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddInverse(x, y) - self.assertLen(model.Proto().variables, 10) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].inverse.f_direct, 5) - self.assertLen(model.Proto().constraints[0].inverse.f_inverse, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_inverse(x, y) + self.assertLen(model.proto.variables, 10) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].inverse.f_direct, 5) + self.assertLen(model.proto.constraints[0].inverse.f_inverse, 5) def testMaxEquality(self): print("testMaxEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMaxEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_max_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEquality(self): print("testMinEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityList(self): print("testMinEqualityList") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, [y[0], y[2], y[1], y[3]]) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 4) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, [y[0], y[2], y[1], y[3]]) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 4) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityTuple(self): print("testMinEqualityTuple") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, (y[0], y[2], y[1], y[3])) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 4) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, (y[0], y[2], y[1], y[3])) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 4) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityGenerator(self): print("testMinEqualityGenerator") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, (z for z in y)) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, (z for z in y)) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityWithConstant(self): print("testMinEqualityWithConstant") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 4, "y") - model.AddMinEquality(x, [y, 3]) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - lin_max = model.Proto().constraints[0].lin_max + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 4, "y") + model.add_min_equality(x, [y, 3]) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + lin_max = model.proto.constraints[0].lin_max self.assertLen(lin_max.exprs, 2) self.assertLen(lin_max.exprs[0].vars, 1) self.assertEqual(1, lin_max.exprs[0].vars[0]) @@ -715,16 +729,16 @@ def testMinEqualityWithConstant(self): def testAbs(self): print("testAbs") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(-5, 5, "y") - model.AddAbsEquality(x, y) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 2) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[0].vars[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[0].coeffs[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[1].vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.exprs[1].coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(-5, 5, "y") + model.add_abs_equality(x, y) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 2) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[0].vars[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[0].coeffs[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[1].vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.exprs[1].coeffs[0]) passed = False error_msg = None try: @@ -734,7 +748,7 @@ def testAbs(self): passed = True self.assertEqual( "calling abs() on a linear expression is not supported, " - "please use CpModel.AddAbsEquality", + "please use CpModel.add_abs_equality", error_msg, ) self.assertTrue(passed) @@ -742,16 +756,16 @@ def testAbs(self): def testDivision(self): print("testDivision") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 50, "y") - model.AddDivisionEquality(x, y, 6) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_div.exprs, 2) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[0].vars[0], 1) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[0].coeffs[0], 1) - self.assertEmpty(model.Proto().constraints[0].int_div.exprs[1].vars) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[1].offset, 6) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 50, "y") + model.add_division_equality(x, y, 6) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_div.exprs, 2) + self.assertEqual(model.proto.constraints[0].int_div.exprs[0].vars[0], 1) + self.assertEqual(model.proto.constraints[0].int_div.exprs[0].coeffs[0], 1) + self.assertEmpty(model.proto.constraints[0].int_div.exprs[1].vars) + self.assertEqual(model.proto.constraints[0].int_div.exprs[1].offset, 6) passed = False error_msg = None try: @@ -761,7 +775,7 @@ def testDivision(self): passed = True self.assertEqual( "calling // on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality", + "please use CpModel.add_division_equality", error_msg, ) self.assertTrue(passed) @@ -769,16 +783,16 @@ def testDivision(self): def testModulo(self): print("testModulo") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 50, "y") - model.AddModuloEquality(x, y, 6) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_mod.exprs, 2) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[0].vars[0], 1) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[0].coeffs[0], 1) - self.assertEmpty(model.Proto().constraints[0].int_mod.exprs[1].vars) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[1].offset, 6) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 50, "y") + model.add_modulo_equality(x, y, 6) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_mod.exprs, 2) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[0].vars[0], 1) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[0].coeffs[0], 1) + self.assertEmpty(model.proto.constraints[0].int_mod.exprs[1].vars) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[1].offset, 6) passed = False error_msg = None try: @@ -788,7 +802,7 @@ def testModulo(self): passed = True self.assertEqual( "calling %% on a linear expression is not supported, " - "please use CpModel.AddModuloEquality", + "please use CpModel.add_modulo_equality", error_msg, ) self.assertTrue(passed) @@ -796,223 +810,225 @@ def testModulo(self): def testMultiplicationEquality(self): print("testMultiplicationEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMultiplicationEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_prod.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].int_prod.target.vars[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_multiplication_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_prod.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].int_prod.target.vars[0]) def testImplication(self): print("testImplication") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - model.AddImplication(x, y) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 1) - self.assertLen(model.Proto().constraints[0].enforcement_literal, 1) - self.assertEqual(x.Index(), model.Proto().constraints[0].enforcement_literal[0]) - self.assertEqual(y.Index(), model.Proto().constraints[0].bool_or.literals[0]) + x = model.new_bool_var("x") + y = model.new_bool_var("y") + model.add_implication(x, y) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 1) + self.assertLen(model.proto.constraints[0].enforcement_literal, 1) + self.assertEqual(x.index, model.proto.constraints[0].enforcement_literal[0]) + self.assertEqual(y.index, model.proto.constraints[0].bool_or.literals[0]) def testBoolOr(self): print("testBoolOr") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolOr(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - model.AddBoolOr([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddBoolOr, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddBoolOr, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_or(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + model.add_bool_or([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_bool_or, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_bool_or, [y, False]) def testBoolOrListOrGet(self): print("testBoolOrListOrGet") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolOr(x) - model.AddBoolOr(True, x[0], x[2]) - model.AddBoolOr(False, x[0]) - model.AddBoolOr(x[i] for i in [0, 2, 3, 4]) - self.assertLen(model.Proto().variables, 7) - self.assertLen(model.Proto().constraints, 4) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - self.assertLen(model.Proto().constraints[1].bool_or.literals, 3) - self.assertLen(model.Proto().constraints[2].bool_or.literals, 2) - self.assertLen(model.Proto().constraints[3].bool_or.literals, 4) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_or(x) + model.add_bool_or(True, x[0], x[2]) + model.add_bool_or(False, x[0]) + model.add_bool_or(x[i] for i in [0, 2, 3, 4]) + self.assertLen(model.proto.variables, 7) + self.assertLen(model.proto.constraints, 4) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + self.assertLen(model.proto.constraints[1].bool_or.literals, 3) + self.assertLen(model.proto.constraints[2].bool_or.literals, 2) + self.assertLen(model.proto.constraints[3].bool_or.literals, 4) def testAtLeastOne(self): print("testAtLeastOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddAtLeastOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - model.AddAtLeastOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddAtLeastOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddAtLeastOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_at_least_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + model.add_at_least_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_at_least_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_at_least_one, [y, False]) def testAtMostOne(self): print("testAtMostOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddAtMostOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].at_most_one.literals, 5) - model.AddAtMostOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddAtMostOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddAtMostOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_at_most_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].at_most_one.literals, 5) + model.add_at_most_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_at_most_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_at_most_one, [y, False]) def testExactlyOne(self): print("testExactlyOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddExactlyOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].exactly_one.literals, 5) - model.AddExactlyOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddExactlyOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddExactlyOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_exactly_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].exactly_one.literals, 5) + model.add_exactly_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_exactly_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_exactly_one, [y, False]) def testBoolAnd(self): print("testBoolAnd") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolAnd(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_and.literals, 5) - model.AddBoolAnd([x[1], x[2].Not(), True]) - self.assertEqual(1, model.Proto().constraints[1].bool_and.literals[0]) - self.assertEqual(-3, model.Proto().constraints[1].bool_and.literals[1]) - self.assertEqual(5, model.Proto().constraints[1].bool_and.literals[2]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_and(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_and.literals, 5) + model.add_bool_and([x[1], x[2].negated(), True]) + self.assertEqual(1, model.proto.constraints[1].bool_and.literals[0]) + self.assertEqual(-3, model.proto.constraints[1].bool_and.literals[1]) + self.assertEqual(5, model.proto.constraints[1].bool_and.literals[2]) def testBoolXOr(self): print("testBoolXOr") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolXOr(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_xor.literals, 5) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_xor(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_xor.literals, 5) def testMapDomain(self): print("testMapDomain") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - y = model.NewIntVar(0, 10, "y") - model.AddMapDomain(y, x, 2) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 10) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + y = model.new_int_var(0, 10, "y") + model.add_map_domain(y, x, 2) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 10) def testInterval(self): print("testInterval") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - self.assertEqual(1, i.Index()) - - j = model.NewFixedSizeIntervalVar(x, 2, "j") - self.assertEqual(2, j.Index()) - start_expr = j.StartExpr() - size_expr = j.SizeExpr() - end_expr = j.EndExpr() - self.assertEqual(x.Index(), start_expr.Index()) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + self.assertEqual(1, i.index) + + j = model.new_fixed_size_interval_var(x, 2, "j") + self.assertEqual(2, j.index) + start_expr = j.start_expr() + size_expr = j.size_expr() + end_expr = j.end_expr() + self.assertEqual(x.index, start_expr.index) self.assertEqual(size_expr, 2) self.assertEqual(str(end_expr), "(x + 2)") def testOptionalInterval(self): print("testOptionalInterval") model = cp_model.CpModel() - b = model.NewBoolVar("b") - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewOptionalIntervalVar(x, 3, y, b, "i") - j = model.NewOptionalIntervalVar(x, y, 10, b, "j") - k = model.NewOptionalIntervalVar(x, -y, 10, b, "k") - l = model.NewOptionalIntervalVar(x, 10, -y, b, "l") - self.assertEqual(1, i.Index()) - self.assertEqual(3, j.Index()) - self.assertEqual(5, k.Index()) - self.assertEqual(7, l.Index()) - self.assertRaises(TypeError, model.NewOptionalIntervalVar, 1, 2, 3, x, "x") - self.assertRaises(TypeError, model.NewOptionalIntervalVar, b + x, 2, 3, b, "x") + b = model.new_bool_var("b") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_optional_interval_var(x, 3, y, b, "i") + j = model.new_optional_interval_var(x, y, 10, b, "j") + k = model.new_optional_interval_var(x, -y, 10, b, "k") + l = model.new_optional_interval_var(x, 10, -y, b, "l") + self.assertEqual(1, i.index) + self.assertEqual(3, j.index) + self.assertEqual(5, k.index) + self.assertEqual(7, l.index) + self.assertRaises(TypeError, model.new_optional_interval_var, 1, 2, 3, x, "x") + self.assertRaises( + TypeError, model.new_optional_interval_var, b + x, 2, 3, b, "x" + ) self.assertRaises( - AttributeError, model.NewOptionalIntervalVar, 1, 2, 3, b + 1, "x" + AttributeError, model.new_optional_interval_var, 1, 2, 3, b + 1, "x" ) def testNoOverlap(self): print("testNoOverlap") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - j = model.NewIntervalVar(x, 5, z, "j") - ct = model.AddNoOverlap([i, j]) - self.assertEqual(4, ct.Index()) - self.assertLen(ct.Proto().no_overlap.intervals, 2) - self.assertEqual(1, ct.Proto().no_overlap.intervals[0]) - self.assertEqual(3, ct.Proto().no_overlap.intervals[1]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + j = model.new_interval_var(x, 5, z, "j") + ct = model.add_no_overlap([i, j]) + self.assertEqual(4, ct.index) + self.assertLen(ct.proto.no_overlap.intervals, 2) + self.assertEqual(1, ct.proto.no_overlap.intervals[0]) + self.assertEqual(3, ct.proto.no_overlap.intervals[1]) def testNoOverlap2D(self): print("testNoOverlap2D") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - j = model.NewIntervalVar(x, 5, z, "j") - ct = model.AddNoOverlap2D([i, j], [j, i]) - self.assertEqual(4, ct.Index()) - self.assertLen(ct.Proto().no_overlap_2d.x_intervals, 2) - self.assertEqual(1, ct.Proto().no_overlap_2d.x_intervals[0]) - self.assertEqual(3, ct.Proto().no_overlap_2d.x_intervals[1]) - self.assertLen(ct.Proto().no_overlap_2d.y_intervals, 2) - self.assertEqual(3, ct.Proto().no_overlap_2d.y_intervals[0]) - self.assertEqual(1, ct.Proto().no_overlap_2d.y_intervals[1]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + j = model.new_interval_var(x, 5, z, "j") + ct = model.add_no_overlap_2d([i, j], [j, i]) + self.assertEqual(4, ct.index) + self.assertLen(ct.proto.no_overlap_2d.x_intervals, 2) + self.assertEqual(1, ct.proto.no_overlap_2d.x_intervals[0]) + self.assertEqual(3, ct.proto.no_overlap_2d.x_intervals[1]) + self.assertLen(ct.proto.no_overlap_2d.y_intervals, 2) + self.assertEqual(3, ct.proto.no_overlap_2d.y_intervals[0]) + self.assertEqual(1, ct.proto.no_overlap_2d.y_intervals[1]) def testCumulative(self): print("testCumulative") model = cp_model.CpModel() intervals = [ - model.NewIntervalVar( - model.NewIntVar(0, 10, f"s_{i}"), + model.new_interval_var( + model.new_int_var(0, 10, f"s_{i}"), 5, - model.NewIntVar(5, 15, f"e_{i}"), + model.new_int_var(5, 15, f"e_{i}"), f"interval[{i}]", ) for i in range(10) ] demands = [1, 3, 5, 2, 4, 5, 3, 4, 2, 3] capacity = 4 - ct = model.AddCumulative(intervals, demands, capacity) - self.assertEqual(20, ct.Index()) - self.assertLen(ct.Proto().cumulative.intervals, 10) - self.assertRaises(TypeError, model.AddCumulative, [intervals[0], 3], [2, 3], 3) + ct = model.add_cumulative(intervals, demands, capacity) + self.assertEqual(20, ct.index) + self.assertLen(ct.proto.cumulative.intervals, 10) + self.assertRaises(TypeError, model.add_cumulative, [intervals[0], 3], [2, 3], 3) def testGetOrMakeIndexFromConstant(self): print("testGetOrMakeIndexFromConstant") model = cp_model.CpModel() - self.assertEqual(0, model.GetOrMakeIndexFromConstant(3)) - self.assertEqual(0, model.GetOrMakeIndexFromConstant(3)) - self.assertEqual(1, model.GetOrMakeIndexFromConstant(5)) - model_var = model.Proto().variables[0] + self.assertEqual(0, model.get_or_make_index_from_constant(3)) + self.assertEqual(0, model.get_or_make_index_from_constant(3)) + self.assertEqual(1, model.get_or_make_index_from_constant(5)) + model_var = model.proto.variables[0] self.assertLen(model_var.domain, 2) self.assertEqual(3, model_var.domain[0]) self.assertEqual(3, model_var.domain[1]) @@ -1020,7 +1036,7 @@ def testGetOrMakeIndexFromConstant(self): def testStr(self): print("testStr") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") + x = model.new_int_var(0, 4, "x") self.assertEqual(str(x == 2), "x == 2") self.assertEqual(str(x >= 2), "x >= 2") self.assertEqual(str(x <= 2), "x <= 2") @@ -1033,71 +1049,71 @@ def testStr(self): self.assertEqual(str(x <= cp_model.INT_MAX), "True (unbounded expr x)") self.assertEqual(str(x != 9223372036854775807), "x <= 9223372036854775806") self.assertEqual(str(x != -9223372036854775808), "x >= -9223372036854775807") - y = model.NewIntVar(0, 4, "y") + y = model.new_int_var(0, 4, "y") self.assertEqual( - str(cp_model.LinearExpr.WeightedSum([x, y + 1, 2], [1, -2, 3])), + str(cp_model.LinearExpr.weighted_sum([x, y + 1, 2], [1, -2, 3])), "x - 2 * (y + 1) + 6", ) - self.assertEqual(str(cp_model.LinearExpr.Term(x, 3)), "(3 * x)") + self.assertEqual(str(cp_model.LinearExpr.term(x, 3)), "(3 * x)") self.assertEqual(str(x != y), "(x + -y) != 0") self.assertEqual( "0 <= x <= 10", str(cp_model.BoundedLinearExpression(x, [0, 10])) ) print(str(model)) - b = model.NewBoolVar("b") - self.assertEqual(str(cp_model.LinearExpr.Term(b.Not(), 3)), "(3 * not(b))") + b = model.new_bool_var("b") + self.assertEqual(str(cp_model.LinearExpr.term(b.negated(), 3)), "(3 * not(b))") - i = model.NewIntervalVar(x, 2, y, "i") + i = model.new_interval_var(x, 2, y, "i") self.assertEqual(str(i), "i") def testRepr(self): print("testRepr") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "z") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "z") self.assertEqual(repr(x), "x(0..4)") self.assertEqual(repr(x * 2), "ProductCst(x(0..4), 2)") - self.assertEqual(repr(x + y), "Sum(x(0..4), y(0..3))") + self.assertEqual(repr(x + y), "sum(x(0..4), y(0..3))") self.assertEqual( - repr(cp_model.LinearExpr.Sum([x, y, z])), + repr(cp_model.LinearExpr.sum([x, y, z])), "SumArray(x(0..4), y(0..3), z(0..3), 0)", ) self.assertEqual( - repr(cp_model.LinearExpr.WeightedSum([x, y, 2], [1, 2, 3])), - "WeightedSum([x(0..4), y(0..3)], [1, 2], 6)", + repr(cp_model.LinearExpr.weighted_sum([x, y, 2], [1, 2, 3])), + "weighted_sum([x(0..4), y(0..3)], [1, 2], 6)", ) - i = model.NewIntervalVar(x, 2, y, "i") + i = model.new_interval_var(x, 2, y, "i") self.assertEqual(repr(i), "i(start = x, size = 2, end = y)") - b = model.NewBoolVar("b") - x1 = model.NewIntVar(0, 4, "x1") - y1 = model.NewIntVar(0, 3, "y1") - j = model.NewOptionalIntervalVar(x1, 2, y1, b, "j") + b = model.new_bool_var("b") + x1 = model.new_int_var(0, 4, "x1") + y1 = model.new_int_var(0, 3, "y1") + j = model.new_optional_interval_var(x1, 2, y1, b, "j") self.assertEqual(repr(j), "j(start = x1, size = 2, end = y1, is_present = b)") - x2 = model.NewIntVar(0, 4, "x2") - y2 = model.NewIntVar(0, 3, "y2") - k = model.NewOptionalIntervalVar(x2, 2, y2, b.Not(), "k") + x2 = model.new_int_var(0, 4, "x2") + y2 = model.new_int_var(0, 3, "y2") + k = model.new_optional_interval_var(x2, 2, y2, b.negated(), "k") self.assertEqual( - repr(k), "k(start = x2, size = 2, end = y2, is_present = Not(b))" + repr(k), "k(start = x2, size = 2, end = y2, is_present = not(b))" ) def testDisplayBounds(self): print("testDisplayBounds") - self.assertEqual("10..20", cp_model.DisplayBounds([10, 20])) - self.assertEqual("10", cp_model.DisplayBounds([10, 10])) - self.assertEqual("10..15, 20..30", cp_model.DisplayBounds([10, 15, 20, 30])) + self.assertEqual("10..20", cp_model.display_bounds([10, 20])) + self.assertEqual("10", cp_model.display_bounds([10, 10])) + self.assertEqual("10..15, 20..30", cp_model.display_bounds([10, 15, 20, 30])) def testShortName(self): print("testShortName") model = cp_model.CpModel() - model.Proto().variables.add(domain=[5, 10]) - self.assertEqual("[5..10]", cp_model.ShortName(model.Proto(), 0)) + model.proto.variables.add(domain=[5, 10]) + self.assertEqual("[5..10]", cp_model.short_name(model.proto, 0)) def testIntegerExpressionErrors(self): print("testIntegerExpressionErrors") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(0, 3, "y") + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(0, 3, "y") self.assertRaises(TypeError, x.__mul__, y) self.assertRaises(NotImplementedError, x.__div__, y) self.assertRaises(NotImplementedError, x.__truediv__, y) @@ -1116,118 +1132,116 @@ def testIntegerExpressionErrors(self): def testModelErrors(self): print("testModelErrors") model = cp_model.CpModel() - self.assertRaises(TypeError, model.Add, "dummy") - self.assertRaises(TypeError, model.GetOrMakeIndex, "dummy") - self.assertRaises(TypeError, model.Minimize, "dummy") + self.assertRaises(TypeError, model.add, "dummy") + self.assertRaises(TypeError, model.get_or_make_index, "dummy") + self.assertRaises(TypeError, model.minimize, "dummy") def testSolverErrors(self): print("testSolverErrors") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() - self.assertRaises(RuntimeError, solver.Value, x) - solver.Solve(model) - self.assertRaises(TypeError, solver.Value, "not_a_variable") - self.assertRaises(TypeError, model.AddBoolOr, [x, y]) + self.assertRaises(RuntimeError, solver.value, x) + solver.solve(model) + self.assertRaises(TypeError, solver.value, "not_a_variable") + self.assertRaises(TypeError, model.add_bool_or, [x, y]) def testHasObjectiveMinimize(self): print("testHasObjectiveMinimizs") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - self.assertFalse(model.HasObjective()) - model.Minimize(y) - self.assertTrue(model.HasObjective()) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + self.assertFalse(model.has_objective()) + model.minimize(y) + self.assertTrue(model.has_objective()) def testHasObjectiveMaximize(self): print("testHasObjectiveMaximizs") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - self.assertFalse(model.HasObjective()) - model.Maximize(y) - self.assertTrue(model.HasObjective()) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + self.assertFalse(model.has_objective()) + model.maximize(y) + self.assertTrue(model.has_objective()) def testSearchForAllSolutions(self): print("testSearchForAllSolutions") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) solver = cp_model.CpSolver() + solver.parameters.enumerate_all_solutions = True solution_counter = SolutionCounter() - status = solver.SearchForAllSolutions(model, solution_counter) + status = solver.solve(model, solution_counter) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(5, solution_counter.SolutionCount()) - model.Minimize(x) - self.assertRaises( - TypeError, solver.SearchForAllSolutions, model, solution_counter - ) + self.assertEqual(5, solution_counter.solution_count) + model.minimize(x) def testSolveWithSolutionCallback(self): print("testSolveWithSolutionCallback") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) solver = cp_model.CpSolver() solution_sum = SolutionSum([x, y]) - self.assertRaises(RuntimeError, solution_sum.Value, x) - status = solver.SolveWithSolutionCallback(model, solution_sum) + self.assertRaises(RuntimeError, solution_sum.value, x) + status = solver.solve(model, solution_sum) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(6, solution_sum.Sum()) + self.assertEqual(6, solution_sum.sum) def testValue(self): print("testValue") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - model.Add(x + 2 * y == 29) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + model.add(x + 2 * y == 29) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.Value(x), 9) - self.assertEqual(solver.Value(y), 10) - self.assertEqual(solver.Value(2), 2) + self.assertEqual(solver.value(x), 9) + self.assertEqual(solver.value(y), 10) + self.assertEqual(solver.value(2), 2) def testBooleanValue(self): print("testBooleanValue") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - z = model.NewBoolVar("z") - model.AddBoolOr([x, z.Not()]) - model.AddBoolOr([x, z]) - model.AddBoolOr([x.Not(), y.Not()]) + x = model.new_bool_var("x") + y = model.new_bool_var("y") + z = model.new_bool_var("z") + model.add_bool_or([x, z.negated()]) + model.add_bool_or([x, z]) + model.add_bool_or([x.negated(), y.negated()]) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.BooleanValue(x), True) - self.assertEqual(solver.Value(x), 1 - solver.Value(x.Not())) - self.assertEqual(solver.Value(y), 1 - solver.Value(y.Not())) - self.assertEqual(solver.Value(z), 1 - solver.Value(z.Not())) - self.assertEqual(solver.BooleanValue(y), False) - self.assertEqual(solver.BooleanValue(True), True) - self.assertEqual(solver.BooleanValue(False), False) - self.assertEqual(solver.BooleanValue(2), True) - self.assertEqual(solver.BooleanValue(0), False) + self.assertEqual(solver.boolean_value(x), True) + self.assertEqual(solver.value(x), 1 - solver.value(x.negated())) + self.assertEqual(solver.value(y), 1 - solver.value(y.negated())) + self.assertEqual(solver.value(z), 1 - solver.value(z.negated())) + self.assertEqual(solver.boolean_value(y), False) + self.assertEqual(solver.boolean_value(True), True) + self.assertEqual(solver.boolean_value(False), False) + self.assertEqual(solver.boolean_value(2), True) + self.assertEqual(solver.boolean_value(0), False) def testUnsupportedOperators(self): print("testUnsupportedOperators") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") with self.assertRaises(NotImplementedError): - model.Add(x == min(y, z)) + model.add(x == min(y, z)) with self.assertRaises(NotImplementedError): if x > y: print("passed1") @@ -1238,76 +1252,76 @@ def testUnsupportedOperators(self): def testIsLiteralTrueFalse(self): print("testIsLiteralTrueFalse") model = cp_model.CpModel() - x = model.NewConstant(0) - self.assertFalse(cp_model.ObjectIsATrueLiteral(x)) - self.assertTrue(cp_model.ObjectIsAFalseLiteral(x)) - self.assertTrue(cp_model.ObjectIsATrueLiteral(x.Not())) - self.assertFalse(cp_model.ObjectIsAFalseLiteral(x.Not())) - self.assertTrue(cp_model.ObjectIsATrueLiteral(True)) - self.assertTrue(cp_model.ObjectIsAFalseLiteral(False)) - self.assertFalse(cp_model.ObjectIsATrueLiteral(False)) - self.assertFalse(cp_model.ObjectIsAFalseLiteral(True)) + x = model.new_constant(0) + self.assertFalse(cp_model.object_is_a_true_literal(x)) + self.assertTrue(cp_model.object_is_a_false_literal(x)) + self.assertTrue(cp_model.object_is_a_true_literal(x.negated())) + self.assertFalse(cp_model.object_is_a_false_literal(x.negated())) + self.assertTrue(cp_model.object_is_a_true_literal(True)) + self.assertTrue(cp_model.object_is_a_false_literal(False)) + self.assertFalse(cp_model.object_is_a_true_literal(False)) + self.assertFalse(cp_model.object_is_a_false_literal(True)) def testSolveMinimizeWithSolutionCallback(self): print("testSolveMinimizeWithSolutionCallback") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) solver = cp_model.CpSolver() solution_obj = SolutionObjective() - status = solver.SolveWithSolutionCallback(model, solution_obj) + status = solver.solve(model, solution_obj) self.assertEqual(cp_model.OPTIMAL, status) - print("obj = ", solution_obj.Obj()) - self.assertEqual(11, solution_obj.Obj()) + print("obj = ", solution_obj.obj) + self.assertEqual(11, solution_obj.obj) def testSolutionHinting(self): print("testSolutionHinting") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.AddHint(x, 2) - model.AddHint(y, 4) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.add_hint(x, 2) + model.add_hint(y, 4) solver = cp_model.CpSolver() solver.parameters.cp_model_presolve = False - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(2, solver.Value(x)) - self.assertEqual(4, solver.Value(y)) + self.assertEqual(2, solver.value(x)) + self.assertEqual(4, solver.value(y)) def testStats(self): print("testStats") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 4, 6) - model.AddLinearConstraint(2 * x + y, 0, 10) - model.Maximize(x + 2 * y) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 4, 6) + model.add_linear_constraint(2 * x + y, 0, 10) + model.maximize(x + 2 * y) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.NumBooleans(), 0) - self.assertEqual(solver.NumConflicts(), 0) - self.assertEqual(solver.NumBranches(), 0) - self.assertGreater(solver.WallTime(), 0.0) + self.assertEqual(solver.num_booleans, 0) + self.assertEqual(solver.num_conflicts, 0) + self.assertEqual(solver.num_branches, 0) + self.assertGreater(solver.wall_time, 0.0) def testSearchStrategy(self): print("testSearchStrategy") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddDecisionStrategy( + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_decision_strategy( [y, x], cp_model.CHOOSE_MIN_DOMAIN_SIZE, cp_model.SELECT_MAX_VALUE ) - self.assertLen(model.Proto().search_strategy, 1) - strategy = model.Proto().search_strategy[0] + self.assertLen(model.proto.search_strategy, 1) + strategy = model.proto.search_strategy[0] self.assertLen(strategy.variables, 2) - self.assertEqual(y.Index(), strategy.variables[0]) - self.assertEqual(x.Index(), strategy.variables[1]) + self.assertEqual(y.index, strategy.variables[0]) + self.assertEqual(x.index, strategy.variables[1]) self.assertEqual( cp_model.CHOOSE_MIN_DOMAIN_SIZE, strategy.variable_selection_strategy ) @@ -1316,181 +1330,181 @@ def testSearchStrategy(self): def testModelAndResponseStats(self): print("testStats") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) - self.assertTrue(model.ModelStats()) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) + self.assertTrue(model.model_stats()) solver = cp_model.CpSolver() - solver.Solve(model) - self.assertTrue(solver.ResponseStats()) + solver.solve(model) + self.assertTrue(solver.response_stats()) def testValidateModel(self): print("testValidateModel") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) - self.assertFalse(model.Validate()) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) + self.assertFalse(model.validate()) def testValidateModelWithOverflow(self): print("testValidateModel") model = cp_model.CpModel() - x = model.NewIntVar(0, cp_model.INT_MAX, "x") - y = model.NewIntVar(0, 10, "y") - model.AddLinearConstraint(x + y, 6, cp_model.INT_MAX) - model.Maximize(x + 2 * y) - self.assertTrue(model.Validate()) + x = model.new_int_var(0, cp_model.INT_MAX, "x") + y = model.new_int_var(0, 10, "y") + model.add_linear_constraint(x + y, 6, cp_model.INT_MAX) + model.maximize(x + 2 * y) + self.assertTrue(model.validate()) def testCopyModel(self): print("testCopyModel") model = cp_model.CpModel() - b = model.NewBoolVar("b") - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewOptionalIntervalVar(x, 12, y, b, "i") - lin = model.Add(x + y <= 10) + b = model.new_bool_var("b") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_optional_interval_var(x, 12, y, b, "i") + lin = model.add(x + y <= 10) - new_model = model.Clone() - copy_b = new_model.GetBoolVarFromProtoIndex(b.Index()) - copy_x = new_model.GetIntVarFromProtoIndex(x.Index()) - copy_y = new_model.GetIntVarFromProtoIndex(y.Index()) - copy_i = new_model.GetIntervalVarFromProtoIndex(i.Index()) + new_model = model.clone() + copy_b = new_model.get_bool_var_from_proto_index(b.index) + copy_x = new_model.get_int_var_from_proto_index(x.index) + copy_y = new_model.get_int_var_from_proto_index(y.index) + copy_i = new_model.get_interval_var_from_proto_index(i.index) - self.assertEqual(b.Index(), copy_b.Index()) - self.assertEqual(x.Index(), copy_x.Index()) - self.assertEqual(y.Index(), copy_y.Index()) - self.assertEqual(i.Index(), copy_i.Index()) + self.assertEqual(b.index, copy_b.index) + self.assertEqual(x.index, copy_x.index) + self.assertEqual(y.index, copy_y.index) + self.assertEqual(i.index, copy_i.index) with self.assertRaises(ValueError): - new_model.GetBoolVarFromProtoIndex(-1) + new_model.get_bool_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetIntVarFromProtoIndex(-1) + new_model.get_int_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetIntervalVarFromProtoIndex(-1) + new_model.get_interval_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetBoolVarFromProtoIndex(x.Index()) + new_model.get_bool_var_from_proto_index(x.index) with self.assertRaises(ValueError): - new_model.GetIntervalVarFromProtoIndex(lin.Index()) + new_model.get_interval_var_from_proto_index(lin.index) - interval_ct = new_model.Proto().constraints[copy_i.Index()].interval + interval_ct = new_model.proto.constraints[copy_i.index].interval self.assertEqual(12, interval_ct.size.offset) def testCustomLog(self): print("testCustomLog") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.log_to_stdout = False log_callback = LogToString() - solver.log_callback = log_callback.NewMessage + solver.log_callback = log_callback.new_message - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(10, solver.Value(x)) - self.assertEqual(-5, solver.Value(y)) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(10, solver.value(x)) + self.assertEqual(-5, solver.value(y)) - self.assertRegex(log_callback.Log(), ".*log_to_stdout.*") + self.assertRegex(log_callback.log, ".*log_to_stdout.*") def testIssue2762(self): print("testIssue2762") model = cp_model.CpModel() - x = [model.NewBoolVar("a"), model.NewBoolVar("b")] + x = [model.new_bool_var("a"), model.new_bool_var("b")] with self.assertRaises(NotImplementedError): - model.Add((x[0] != 0) or (x[1] != 0)) + model.add((x[0] != 0) or (x[1] != 0)) def testModelError(self): print("TestModelError") model = cp_model.CpModel() - x = [model.NewIntVar(0, -2, "x%i" % i) for i in range(100)] - model.Add(sum(x) <= 1) + x = [model.new_int_var(0, -2, "x%i" % i) for i in range(100)] + model.add(sum(x) <= 1) solver = cp_model.CpSolver() solver.parameters.log_search_progress = True - self.assertEqual(cp_model.MODEL_INVALID, solver.Solve(model)) - self.assertEqual(solver.SolutionInfo(), 'var #0 has no domain(): name: "x0"') + self.assertEqual(cp_model.MODEL_INVALID, solver.solve(model)) + self.assertEqual(solver.solution_info(), 'var #0 has no domain(): name: "x0"') def testIntVarSeries(self): print("testIntVarSeries") df = pd.DataFrame([1, -1, 1], columns=["coeffs"]) model = cp_model.CpModel() - x = model.NewIntVarSeries( + x = model.new_int_var_series( name="x", index=df.index, lower_bounds=0, upper_bounds=5 ) - model.Minimize(df.coeffs.dot(x)) + model.minimize(df.coeffs.dot(x)) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - solution = solver.Values(x) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + solution = solver.values(x) self.assertTrue((solution.values == [0, 5, 0]).all()) def testBoolVarSeries(self): print("testBoolVarSeries") df = pd.DataFrame([1, -1, 1], columns=["coeffs"]) model = cp_model.CpModel() - x = model.NewBoolVarSeries(name="x", index=df.index) - model.Minimize(df.coeffs.dot(x)) + x = model.new_bool_var_series(name="x", index=df.index) + model.minimize(df.coeffs.dot(x)) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - solution = solver.BooleanValues(x) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + solution = solver.boolean_values(x) self.assertTrue((solution.values == [False, True, False]).all()) def testFixedSizeIntervalVarSeries(self): print("testFixedSizeIntervalVarSeries") df = pd.DataFrame([2, 4, 6], columns=["size"]) model = cp_model.CpModel() - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", index=df.index, lower_bounds=0, upper_bounds=5 ) - presences = model.NewBoolVarSeries(name="rresences", index=df.index) - fixed_size_intervals = model.NewFixedSizeIntervalVarSeries( + presences = model.new_bool_var_series(name="rresences", index=df.index) + fixed_size_intervals = model.new_fixed_size_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=df.size, ) - opt_fixed_size_intervals = model.NewOptionalFixedSizeIntervalVarSeries( + opt_fixed_size_intervals = model.new_optional_fixed_size_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=df.size, are_present=presences, ) - model.AddNoOverlap( + model.add_no_overlap( fixed_size_intervals.to_list() + opt_fixed_size_intervals.to_list() ) - self.assertLen(model.Proto().constraints, 7) + self.assertLen(model.proto.constraints, 7) def testIntervalVarSeries(self): print("testIntervalVarSeries") df = pd.DataFrame([2, 4, 6], columns=["size"]) model = cp_model.CpModel() - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", index=df.index, lower_bounds=0, upper_bounds=5 ) - sizes = model.NewIntVarSeries( + sizes = model.new_int_var_series( name="sizes", index=df.index, lower_bounds=2, upper_bounds=4 ) - ends = model.NewIntVarSeries( + ends = model.new_int_var_series( name="ends", index=df.index, lower_bounds=0, upper_bounds=10 ) - presences = model.NewBoolVarSeries(name="rresences", index=df.index) - intervals = model.NewIntervalVarSeries( + presences = model.new_bool_var_series(name="rresences", index=df.index) + intervals = model.new_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=sizes, ends=ends, ) - opt_intervals = model.NewOptionalIntervalVarSeries( + opt_intervals = model.new_optional_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, @@ -1498,8 +1512,8 @@ def testIntervalVarSeries(self): ends=ends, are_present=presences, ) - model.AddNoOverlap(intervals.to_list() + opt_intervals.to_list()) - self.assertLen(model.Proto().constraints, 13) + model.add_no_overlap(intervals.to_list() + opt_intervals.to_list()) + self.assertLen(model.proto.constraints, 13) if __name__ == "__main__": diff --git a/ortools/sat/python/swig_helper.cc b/ortools/sat/python/swig_helper.cc index 1612d6dedd5..073c5800fe8 100644 --- a/ortools/sat/python/swig_helper.cc +++ b/ortools/sat/python/swig_helper.cc @@ -28,7 +28,9 @@ #include "absl/strings/string_view.h" #include "ortools/sat/cp_model.pb.h" #include "ortools/util/sorted_interval_list.h" +#include "pybind11/cast.h" #include "pybind11/functional.h" +#include "pybind11/gil.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" #include "pybind11_protobuf/native_proto_caster.h" @@ -86,27 +88,28 @@ PYBIND11_MODULE(swig_helper, m) { pybind11::class_(m, "SolveWrapper") .def(pybind11::init<>()) - .def("AddLogCallback", &SolveWrapper::AddLogCallback, arg("log_callback")) - .def("AddSolutionCallback", &SolveWrapper::AddSolutionCallback, + .def("add_log_callback", &SolveWrapper::AddLogCallback, + arg("log_callback")) + .def("add_solution_callback", &SolveWrapper::AddSolutionCallback, arg("callback")) - .def("ClearSolutionCallback", &SolveWrapper::ClearSolutionCallback) - .def("SetParameters", &SolveWrapper::SetParameters, arg("parameters")) - .def("Solve", + .def("clear_solution_callback", &SolveWrapper::ClearSolutionCallback) + .def("set_parameters", &SolveWrapper::SetParameters, arg("parameters")) + .def("solve", [](SolveWrapper* solve_wrapper, const CpModelProto& model_proto) -> CpSolverResponse { ::pybind11::gil_scoped_release release; return solve_wrapper->Solve(model_proto); }) - .def("StopSearch", &SolveWrapper::StopSearch); + .def("stop_search", &SolveWrapper::StopSearch); pybind11::class_(m, "CpSatHelper") - .def_static("ModelStats", &CpSatHelper::ModelStats, arg("model_proto")) - .def_static("SolverResponseStats", &CpSatHelper::SolverResponseStats, + .def_static("model_stats", &CpSatHelper::ModelStats, arg("model_proto")) + .def_static("solver_response_stats", &CpSatHelper::SolverResponseStats, arg("response")) - .def_static("ValidateModel", &CpSatHelper::ValidateModel, + .def_static("validate_model", &CpSatHelper::ValidateModel, arg("model_proto")) - .def_static("VariableDomain", &CpSatHelper::VariableDomain, + .def_static("variable_domain", &CpSatHelper::VariableDomain, arg("variable_proto")) - .def_static("WriteModelToFile", &CpSatHelper::WriteModelToFile, + .def_static("write_model_to_file", &CpSatHelper::WriteModelToFile, arg("model_proto"), arg("filename")); } diff --git a/ortools/sat/python/swig_helper_test.py b/ortools/sat/python/swig_helper_test.py index a44900ed9e0..8479885ff0a 100644 --- a/ortools/sat/python/swig_helper_test.py +++ b/ortools/sat/python/swig_helper_test.py @@ -30,7 +30,7 @@ def OnSolutionCallback(self): print("New Solution") self.__solution_count += 1 - def SolutionCount(self): + def solution_count(self): return self.__solution_count @@ -43,11 +43,11 @@ def testVariableDomain(self): model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - d0 = swig_helper.CpSatHelper.VariableDomain(model.variables[0]) - d1 = swig_helper.CpSatHelper.VariableDomain(model.variables[1]) + d0 = swig_helper.CpSatHelper.variable_domain(model.variables[0]) + d1 = swig_helper.CpSatHelper.variable_domain(model.variables[1]) - self.assertEqual(d0.FlattenedIntervals(), [-10, 10]) - self.assertEqual(d1.FlattenedIntervals(), [-5, -5, 3, 6]) + self.assertEqual(d0.flattened_intervals(), [-10, 10]) + self.assertEqual(d1.flattened_intervals(), [-5, -5, 3, 6]) def testSimpleSolve(self): model_string = """ @@ -85,7 +85,7 @@ def testSimpleSolve(self): self.assertTrue(text_format.Parse(model_string, model)) solve_wrapper = swig_helper.SolveWrapper() - solution = solve_wrapper.Solve(model) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -125,12 +125,11 @@ def testSimpleSolveWithCore(self): model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - parameters = sat_parameters_pb2.SatParameters() - parameters.optimize_with_core = True + parameters = sat_parameters_pb2.SatParameters(optimize_with_core=True) solve_wrapper = swig_helper.SolveWrapper() - solve_wrapper.SetParameters(parameters) - solution = solve_wrapper.Solve(model) + solve_wrapper.set_parameters(parameters) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -152,7 +151,7 @@ def testSimpleSolveWithProtoApi(self): model.objective.scaling_factor = -1 solve_wrapper = swig_helper.SolveWrapper() - solution = solve_wrapper.Solve(model) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -170,13 +169,13 @@ def testSolutionCallback(self): solve_wrapper = swig_helper.SolveWrapper() callback = Callback() - solve_wrapper.AddSolutionCallback(callback) + solve_wrapper.add_solution_callback(callback) params = sat_parameters_pb2.SatParameters() params.enumerate_all_solutions = True - solve_wrapper.SetParameters(params) - solution = solve_wrapper.Solve(model) + solve_wrapper.set_parameters(params) + solution = solve_wrapper.solve(model) - self.assertEqual(5, callback.SolutionCount()) + self.assertEqual(5, callback.solution_count()) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) def testModelStats(self): @@ -215,7 +214,7 @@ def testModelStats(self): """ model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - stats = swig_helper.CpSatHelper.ModelStats(model) + stats = swig_helper.CpSatHelper.model_stats(model) self.assertTrue(stats) diff --git a/ortools/sat/samples/assignment_groups_sat.py b/ortools/sat/samples/assignment_groups_sat.py index f240ceb2e3c..8358d0bf799 100644 --- a/ortools/sat/samples/assignment_groups_sat.py +++ b/ortools/sat/samples/assignment_groups_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve assignment problem for given group of workers.""" +"""Solves an assignment problem for given group of workers.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -77,34 +77,34 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.AddAtMostOne(x[worker, task] for task in range(num_tasks)) + model.add_at_most_one(x[worker, task] for task in range(num_tasks)) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # [END constraints] # [START assignments] # Create variables for each worker, indicating whether they work on some task. work = {} for worker in range(num_workers): - work[worker] = model.NewBoolVar(f"work[{worker}]") + work[worker] = model.new_bool_var(f"work[{worker}]") for worker in range(num_workers): for task in range(num_tasks): - model.Add(work[worker] == sum(x[worker, task] for task in range(num_tasks))) + model.add(work[worker] == sum(x[worker, task] for task in range(num_tasks))) # Define the allowed groups of worders - model.AddAllowedAssignments([work[0], work[1], work[2], work[3]], group1) - model.AddAllowedAssignments([work[4], work[5], work[6], work[7]], group2) - model.AddAllowedAssignments([work[8], work[9], work[10], work[11]], group3) + model.add_allowed_assignments([work[0], work[1], work[2], work[3]], group1) + model.add_allowed_assignments([work[4], work[5], work[6], work[7]], group2) + model.add_allowed_assignments([work[8], work[9], work[10], work[11]], group3) # [END assignments] # Objective @@ -113,22 +113,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assignment_sat.py b/ortools/sat/samples/assignment_sat.py index f8cbfe7f3b7..bc7dbaaf4a3 100644 --- a/ortools/sat/samples/assignment_sat.py +++ b/ortools/sat/samples/assignment_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple assignment problem with CP-SAT.""" +"""Solves a simple assignment problem with CP-SAT.""" + # [START program] # [START import] import io @@ -60,36 +61,36 @@ def main(): # Variables # [START variables] - x = model.NewBoolVarSeries(name="x", index=data.index) + x = model.new_bool_var_series(name="x", index=data.index) # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for unused_name, tasks in data.groupby("worker"): - model.AddAtMostOne(x[tasks.index]) + model.add_at_most_one(x[tasks.index]) # Each task is assigned to exactly one worker. for unused_name, workers in data.groupby("task"): - model.AddExactlyOne(x[workers.index]) + model.add_exactly_one(x[workers.index]) # [END constraints] # Objective # [START objective] - model.Minimize(data.cost.dot(x)) + model.minimize(data.cost.dot(x)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") - selected = data.loc[solver.BooleanValues(x).loc[lambda x: x].index] + print(f"Total cost = {solver.objective_value}\n") + selected = data.loc[solver.boolean_values(x).loc[lambda x: x].index] for unused_index, row in selected.iterrows(): print(f"{row.task} assigned to {row.worker} with a cost of {row.cost}") elif status == cp_model.INFEASIBLE: diff --git a/ortools/sat/samples/assignment_task_sizes_sat.py b/ortools/sat/samples/assignment_task_sizes_sat.py index a8a97d30f10..7bc0ea9e578 100644 --- a/ortools/sat/samples/assignment_task_sizes_sat.py +++ b/ortools/sat/samples/assignment_task_sizes_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve a simple assignment problem.""" +"""Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -52,21 +52,21 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.Add( + model.add( sum(task_sizes[task] * x[worker, task] for task in range(num_tasks)) <= total_size_max ) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # [END constraints] # Objective @@ -75,22 +75,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assignment_teams_sat.py b/ortools/sat/samples/assignment_teams_sat.py index 583325aca33..a75d72bf1a1 100644 --- a/ortools/sat/samples/assignment_teams_sat.py +++ b/ortools/sat/samples/assignment_teams_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve a simple assignment problem.""" +"""Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -49,31 +49,31 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.AddAtMostOne(x[worker, task] for task in range(num_tasks)) + model.add_at_most_one(x[worker, task] for task in range(num_tasks)) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # Each team takes at most two tasks. team1_tasks = [] for worker in team1: for task in range(num_tasks): team1_tasks.append(x[worker, task]) - model.Add(sum(team1_tasks) <= team_max) + model.add(sum(team1_tasks) <= team_max) team2_tasks = [] for worker in team2: for task in range(num_tasks): team2_tasks.append(x[worker, task]) - model.Add(sum(team2_tasks) <= team_max) + model.add(sum(team2_tasks) <= team_max) # [END constraints] # Objective @@ -82,22 +82,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assumptions_sample_sat.py b/ortools/sat/samples/assumptions_sample_sat.py old mode 100755 new mode 100644 index 8007ba32a3b..ee50dfa0126 --- a/ortools/sat/samples/assumptions_sample_sat.py +++ b/ortools/sat/samples/assumptions_sample_sat.py @@ -28,37 +28,37 @@ def main(): # Creates the variables. # [START variables] - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") - a = model.NewBoolVar("a") - b = model.NewBoolVar("b") - c = model.NewBoolVar("c") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") + a = model.new_bool_var("a") + b = model.new_bool_var("b") + c = model.new_bool_var("c") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x > y).OnlyEnforceIf(a) - model.Add(y > z).OnlyEnforceIf(b) - model.Add(z > x).OnlyEnforceIf(c) + model.add(x > y).only_enforce_if(a) + model.add(y > z).only_enforce_if(b) + model.add(z > x).only_enforce_if(c) # [END constraints] # Add assumptions - model.AddAssumptions([a, b, c]) + model.add_assumptions([a, b, c]) # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] - print(f"Status = {solver.StatusName(status)}") + print(f"Status = {solver.status_name(status)}") if status == cp_model.INFEASIBLE: print( - "SufficientAssumptionsForInfeasibility = " - f"{solver.SufficientAssumptionsForInfeasibility()}" + "sufficient_assumptions_for_infeasibility = " + f"{solver.sufficient_assumptions_for_infeasibility()}" ) # [END print_solution] diff --git a/ortools/sat/samples/bin_packing_sat.py b/ortools/sat/samples/bin_packing_sat.py index 0efeb0315a7..97f1d37cb80 100644 --- a/ortools/sat/samples/bin_packing_sat.py +++ b/ortools/sat/samples/bin_packing_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple bin packing problem using CP-SAT.""" +"""Solves a simple bin packing problem using CP-SAT.""" + # [START program] # [START import] import io @@ -78,22 +79,22 @@ def main(): items_x_bins = pd.MultiIndex.from_product( [items.index, bins.index], names=["item", "bin"] ) - x = model.NewBoolVarSeries(name="x", index=items_x_bins) + x = model.new_bool_var_series(name="x", index=items_x_bins) # y[j] = 1 if bin j is used. - y = model.NewBoolVarSeries(name="y", index=bins.index) + y = model.new_bool_var_series(name="y", index=bins.index) # [END variables] # [START constraints] # Constraints # Each item must be in exactly one bin. for unused_name, all_copies in x.groupby("item"): - model.AddExactlyOne(x[all_copies.index]) + model.add_exactly_one(x[all_copies.index]) # The amount packed in each bin cannot exceed its capacity. for selected_bin in bins.index: items_in_bin = x.xs(selected_bin, level="bin") - model.Add( + model.add( items_in_bin.dot(items.weight) <= bins.loc[selected_bin].capacity * y[selected_bin] ) @@ -101,21 +102,21 @@ def main(): # [START objective] # Objective: minimize the number of bins used. - model.Minimize(y.sum()) + model.minimize(y.sum()) # [END objective] # [START solve] # Create the solver and solve the model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Number of bins used = {solver.ObjectiveValue()}") + print(f"Number of bins used = {solver.objective_value}") - x_values = solver.BooleanValues(x) - y_values = solver.BooleanValues(y) + x_values = solver.boolean_values(x) + y_values = solver.boolean_values(y) active_bins = y_values.loc[lambda x: x].index for b in active_bins: @@ -128,7 +129,7 @@ def main(): print(f"Total packed weight: {items.weight.sum()}") print() - print(f"Time = {solver.WallTime()} seconds") + print(f"Time = {solver.wall_time} seconds") elif status == cp_model.INFEASIBLE: print("No solution found") else: diff --git a/ortools/sat/samples/binpacking_problem_sat.py b/ortools/sat/samples/binpacking_problem_sat.py index 228ab369fba..12ebc9aa0b8 100644 --- a/ortools/sat/samples/binpacking_problem_sat.py +++ b/ortools/sat/samples/binpacking_problem_sat.py @@ -38,43 +38,43 @@ def BinpackingProblemSat(): for i in all_items: num_copies = items[i][1] for b in all_bins: - x[(i, b)] = model.NewIntVar(0, num_copies, f"x[{i},{b}]") + x[(i, b)] = model.new_int_var(0, num_copies, f"x[{i},{b}]") # Load variables. - load = [model.NewIntVar(0, bin_capacity, f"load[{b}]") for b in all_bins] + load = [model.new_int_var(0, bin_capacity, f"load[{b}]") for b in all_bins] # Slack variables. - slacks = [model.NewBoolVar(f"slack[{b}]") for b in all_bins] + slacks = [model.new_bool_var(f"slack[{b}]") for b in all_bins] # Links load and x. for b in all_bins: - model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) + model.add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) # Place all items. for i in all_items: - model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1]) + model.add(sum(x[(i, b)] for b in all_bins) == items[i][1]) # Links load and slack through an equivalence relation. safe_capacity = bin_capacity - slack_capacity for b in all_bins: # slack[b] => load[b] <= safe_capacity. - model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b]) + model.add(load[b] <= safe_capacity).only_enforce_if(slacks[b]) # not(slack[b]) => load[b] > safe_capacity. - model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not()) + model.add(load[b] > safe_capacity).only_enforce_if(slacks[b].negated()) # Maximize sum of slacks. - model.Maximize(sum(slacks)) + model.maximize(sum(slacks)) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Solve status: {solver.StatusName(status)}") + status = solver.solve(model) + print(f"solve status: {solver.status_name(status)}") if status == cp_model.OPTIMAL: - print(f"Optimal objective value: {solver.ObjectiveValue()}") + print(f"Optimal objective value: {solver.objective_value}") print("Statistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()}s") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time}s") BinpackingProblemSat() diff --git a/ortools/sat/samples/bool_or_sample_sat.py b/ortools/sat/samples/bool_or_sample_sat.py index e692be0c6b4..ad21c3a13d4 100644 --- a/ortools/sat/samples/bool_or_sample_sat.py +++ b/ortools/sat/samples/bool_or_sample_sat.py @@ -21,10 +21,10 @@ def BoolOrSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") + x = model.new_bool_var("x") + y = model.new_bool_var("y") - model.AddBoolOr([x, y.Not()]) + model.add_bool_or([x, y.negated()]) BoolOrSampleSat() diff --git a/ortools/sat/samples/boolean_product_sample_sat.py b/ortools/sat/samples/boolean_product_sample_sat.py index 3994c5eb370..e91c5bcbf86 100644 --- a/ortools/sat/samples/boolean_product_sample_sat.py +++ b/ortools/sat/samples/boolean_product_sample_sat.py @@ -24,22 +24,22 @@ def BooleanProductSampleSat(): p == x * y, which is the same as p <=> x and y """ model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - p = model.NewBoolVar("p") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + p = model.new_bool_var("p") # x and y implies p, rewrite as not(x and y) or p. - model.AddBoolOr(x.Not(), y.Not(), p) + model.add_bool_or(x.negated(), y.negated(), p) # p implies x and y, expanded into two implications. - model.AddImplication(p, x) - model.AddImplication(p, y) + model.add_implication(p, x) + model.add_implication(p, y) # Create a solver and solve. solver = cp_model.CpSolver() solution_printer = cp_model.VarArraySolutionPrinter([x, y, p]) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) BooleanProductSampleSat() diff --git a/ortools/sat/samples/channeling_sample_sat.py b/ortools/sat/samples/channeling_sample_sat.py index d0602b66a31..99f359c9144 100644 --- a/ortools/sat/samples/channeling_sample_sat.py +++ b/ortools/sat/samples/channeling_sample_sat.py @@ -21,20 +21,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def ChannelingSampleSat(): """Demonstrates how to link integer constraints together.""" @@ -43,24 +38,24 @@ def ChannelingSampleSat(): model = cp_model.CpModel() # Declare our two primary variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") # Declare our intermediate boolean variable. - b = model.NewBoolVar("b") + b = model.new_bool_var("b") # Implement b == (x >= 5). - model.Add(x >= 5).OnlyEnforceIf(b) - model.Add(x < 5).OnlyEnforceIf(b.Not()) + model.add(x >= 5).only_enforce_if(b) + model.add(x < 5).only_enforce_if(b.negated()) # Create our two half-reified constraints. # First, b implies (y == 10 - x). - model.Add(y == 10 - x).OnlyEnforceIf(b) + model.add(y == 10 - x).only_enforce_if(b) # Second, not(b) implies y == 0. - model.Add(y == 0).OnlyEnforceIf(b.Not()) + model.add(y == 0).only_enforce_if(b.negated()) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -72,7 +67,7 @@ def ChannelingSampleSat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, y, b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) ChannelingSampleSat() diff --git a/ortools/sat/samples/clone_model_sample_sat.py b/ortools/sat/samples/clone_model_sample_sat.py index 2bca1dac6c1..ad0ea1e6781 100644 --- a/ortools/sat/samples/clone_model_sample_sat.py +++ b/ortools/sat/samples/clone_model_sample_sat.py @@ -28,43 +28,43 @@ def CloneModelSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] if status == cp_model.OPTIMAL: - print("Optimal value of the original model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the original model: {}".format(solver.objective_value)) - # Clone the model. + # Clones the model. # [START clone] - copy = model.Clone() + copy = model.clone() - copy_x = copy.GetIntVarFromProtoIndex(x.Index()) - copy_y = copy.GetIntVarFromProtoIndex(y.Index()) + copy_x = copy.get_int_var_from_proto_index(x.index) + copy_y = copy.get_int_var_from_proto_index(y.index) - copy.Add(copy_x + copy_y <= 1) + copy.add(copy_x + copy_y <= 1) # [END clone] - status = solver.Solve(copy) + status = solver.solve(copy) if status == cp_model.OPTIMAL: - print("Optimal value of the modified model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the modified model: {}".format(solver.objective_value)) CloneModelSampleSat() diff --git a/ortools/sat/samples/cp_is_fun_sat.py b/ortools/sat/samples/cp_is_fun_sat.py index 586f3db272e..9a6489b5edf 100644 --- a/ortools/sat/samples/cp_is_fun_sat.py +++ b/ortools/sat/samples/cp_is_fun_sat.py @@ -29,24 +29,25 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END solution_printer] def main(): - """Solve the CP+IS+FUN==TRUE cryptarithm.""" + """solve the CP+IS+FUN==TRUE cryptarithm.""" # Constraint programming engine # [START model] model = cp_model.CpModel() @@ -55,16 +56,16 @@ def main(): # [START variables] base = 10 - c = model.NewIntVar(1, base - 1, "C") - p = model.NewIntVar(0, base - 1, "P") - i = model.NewIntVar(1, base - 1, "I") - s = model.NewIntVar(0, base - 1, "S") - f = model.NewIntVar(1, base - 1, "F") - u = model.NewIntVar(0, base - 1, "U") - n = model.NewIntVar(0, base - 1, "N") - t = model.NewIntVar(1, base - 1, "T") - r = model.NewIntVar(0, base - 1, "R") - e = model.NewIntVar(0, base - 1, "E") + c = model.new_int_var(1, base - 1, "C") + p = model.new_int_var(0, base - 1, "P") + i = model.new_int_var(1, base - 1, "I") + s = model.new_int_var(0, base - 1, "S") + f = model.new_int_var(1, base - 1, "F") + u = model.new_int_var(0, base - 1, "U") + n = model.new_int_var(0, base - 1, "N") + t = model.new_int_var(1, base - 1, "T") + r = model.new_int_var(0, base - 1, "R") + e = model.new_int_var(0, base - 1, "E") # We need to group variables in a list to use the constraint AllDifferent. letters = [c, p, i, s, f, u, n, t, r, e] @@ -75,10 +76,10 @@ def main(): # Define constraints. # [START constraints] - model.AddAllDifferent(letters) + model.add_all_different(letters) # CP + IS + FUN = TRUE - model.Add( + model.add( c * base + p + i * base + s + f * base * base + u * base + n == t * base * base * base + r * base * base + u * base + e ) @@ -91,17 +92,17 @@ def main(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" status : {solver.StatusName(status)}") - print(f" conflicts: {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time: {solver.WallTime()} s") - print(f" sol found: {solution_printer.solution_count()}") + print(f" status : {solver.status_name(status)}") + print(f" conflicts: {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time: {solver.wall_time} s") + print(f" sol found: {solution_printer.solution_count}") # [END statistics] diff --git a/ortools/sat/samples/cp_sat_example.py b/ortools/sat/samples/cp_sat_example.py index 0e26f113ea7..406491ef6f1 100755 --- a/ortools/sat/samples/cp_sat_example.py +++ b/ortools/sat/samples/cp_sat_example.py @@ -29,34 +29,34 @@ def main(): # Creates the variables. # [START variables] var_upper_bound = max(50, 45, 37) - x = model.NewIntVar(0, var_upper_bound, "x") - y = model.NewIntVar(0, var_upper_bound, "y") - z = model.NewIntVar(0, var_upper_bound, "z") + x = model.new_int_var(0, var_upper_bound, "x") + y = model.new_int_var(0, var_upper_bound, "y") + z = model.new_int_var(0, var_upper_bound, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(2 * x + 7 * y + 3 * z <= 50) - model.Add(3 * x - 5 * y + 7 * z <= 45) - model.Add(5 * x + 2 * y - 6 * z <= 37) + model.add(2 * x + 7 * y + 3 * z <= 50) + model.add(3 * x - 5 * y + 7 * z <= 45) + model.add(5 * x + 2 * y - 6 * z <= 37) # [END constraints] # [START objective] - model.Maximize(2 * x + 2 * y + 3 * z) + model.maximize(2 * x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves the model. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Maximum of objective function: {solver.ObjectiveValue()}\n") - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"Maximum of objective function: {solver.objective_value}\n") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") # [END print_solution] @@ -64,10 +64,10 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" status : {solver.StatusName(status)}") - print(f" conflicts: {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time: {solver.WallTime()} s") + print(f" status : {solver.status_name(status)}") + print(f" conflicts: {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time: {solver.wall_time} s") # [END statistics] diff --git a/ortools/sat/samples/cumulative_variable_profile_sample_sat.py b/ortools/sat/samples/cumulative_variable_profile_sample_sat.py index 49484443a09..77f6c771159 100644 --- a/ortools/sat/samples/cumulative_variable_profile_sample_sat.py +++ b/ortools/sat/samples/cumulative_variable_profile_sample_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple scheduling problem with a variable work load.""" +"""Solves a simple scheduling problem with a variable work load.""" + # [START program] # [START import] import io @@ -107,12 +108,12 @@ def main(): # [START program_part2] # [START variables] # Variables - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index ) - performed = model.NewBoolVarSeries(name="performed", index=tasks_df.index) + performed = model.new_bool_var_series(name="performed", index=tasks_df.index) - intervals = model.NewOptionalFixedSizeIntervalVarSeries( + intervals = model.new_optional_fixed_size_interval_var_series( name="intervals", index=tasks_df.index, starts=starts, @@ -124,7 +125,7 @@ def main(): # [START constraints] # Set up the profile. We use fixed (intervals, demands) to fill in the space # between the actual load profile and the max capacity. - time_period_intervals = model.NewFixedSizeIntervalVarSeries( + time_period_intervals = model.new_fixed_size_interval_var_series( name="time_period_intervals", index=capacity_df.index, starts=capacity_df.start_hour * minutes_per_period, @@ -133,7 +134,7 @@ def main(): time_period_heights = max_capacity - capacity_df.capacity # Cumulative constraint. - model.AddCumulative( + model.add_cumulative( intervals.to_list() + time_period_intervals.to_list(), tasks_df.load.to_list() + time_period_heights.to_list(), max_capacity, @@ -144,7 +145,7 @@ def main(): # Objective: maximize the value of performed intervals. # 1 is the max priority. max_priority = max(tasks_df.priority) - model.Maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) + model.maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) # [END objective] # [START solve] @@ -153,13 +154,13 @@ def main(): solver.parameters.log_search_progress = True solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 30.0 - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - start_values = solver.Values(starts) - performed_values = solver.BooleanValues(performed) + start_values = solver.values(starts) + performed_values = solver.boolean_values(performed) for task in tasks_df.index: if performed_values[task]: print(f"task {task} starts at {start_values[task]}") diff --git a/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py b/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py index 3fdb214a94f..6da9b6ff1f7 100644 --- a/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py +++ b/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py @@ -21,20 +21,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def earliness_tardiness_cost_sample_sat(): """Encode the piecewise linear expression.""" @@ -48,7 +43,7 @@ def earliness_tardiness_cost_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the piecewise linear function. # @@ -57,24 +52,24 @@ def earliness_tardiness_cost_sample_sat(): # ed ld # large_constant = 1000 - expr = model.NewIntVar(0, large_constant, "expr") + expr = model.new_int_var(0, large_constant, "expr") # First segment. - s1 = model.NewIntVar(-large_constant, large_constant, "s1") - model.Add(s1 == earliness_cost * (earliness_date - x)) + s1 = model.new_int_var(-large_constant, large_constant, "s1") + model.add(s1 == earliness_cost * (earliness_date - x)) # Second segment. s2 = 0 # Third segment. - s3 = model.NewIntVar(-large_constant, large_constant, "s3") - model.Add(s3 == lateness_cost * (x - lateness_date)) + s3 = model.new_int_var(-large_constant, large_constant, "s3") + model.add(s3 == lateness_cost * (x - lateness_date)) # Link together expr and x through s1, s2, and s3. - model.AddMaxEquality(expr, [s1, s2, s3]) + model.add_max_equality(expr, [s1, s2, s3]) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -86,7 +81,7 @@ def earliness_tardiness_cost_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) earliness_tardiness_cost_sample_sat() diff --git a/ortools/sat/samples/interval_sample_sat.py b/ortools/sat/samples/interval_sample_sat.py index 13fb88af87a..43dfc98bbcb 100644 --- a/ortools/sat/samples/interval_sample_sat.py +++ b/ortools/sat/samples/interval_sample_sat.py @@ -24,22 +24,22 @@ def IntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, "interval") + end_var = model.new_int_var(0, horizon, "end") + interval_var = model.new_interval_var(start_var, duration, end_var + 2, "interval") print(f"interval = {repr(interval_var)}") # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewFixedSizeIntervalVar( + fixed_size_interval_var = model.new_fixed_size_interval_var( start_var, 10, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewFixedSizeIntervalVar(5, 10, "fixed_interval") + fixed_interval = model.new_fixed_size_interval_var(5, 10, "fixed_interval") print(f"fixed_interval = {repr(fixed_interval)}") diff --git a/ortools/sat/samples/literal_sample_sat.py b/ortools/sat/samples/literal_sample_sat.py index 04ca45513f2..3c83cf44145 100644 --- a/ortools/sat/samples/literal_sample_sat.py +++ b/ortools/sat/samples/literal_sample_sat.py @@ -20,8 +20,8 @@ def LiteralSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - not_x = x.Not() + x = model.new_bool_var("x") + not_x = x.negated() print(x) print(not_x) diff --git a/ortools/sat/samples/minimal_jobshop_sat.py b/ortools/sat/samples/minimal_jobshop_sat.py index 782f1b9a07a..4ef60a3cfa5 100644 --- a/ortools/sat/samples/minimal_jobshop_sat.py +++ b/ortools/sat/samples/minimal_jobshop_sat.py @@ -57,9 +57,9 @@ def main(): for task_id, task in enumerate(job): machine, duration = task suffix = f"_{job_id}_{task_id}" - start_var = model.NewIntVar(0, horizon, "start" + suffix) - end_var = model.NewIntVar(0, horizon, "end" + suffix) - interval_var = model.NewIntervalVar( + start_var = model.new_int_var(0, horizon, "start" + suffix) + end_var = model.new_int_var(0, horizon, "end" + suffix) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval" + suffix ) all_tasks[job_id, task_id] = task_type( @@ -71,30 +71,30 @@ def main(): # [START constraints] # Create and add disjunctive constraints. for machine in all_machines: - model.AddNoOverlap(machine_to_intervals[machine]) + model.add_no_overlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): - model.Add( + model.add( all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end ) # [END constraints] # [START objective] # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)], ) - model.Minimize(obj_var) + model.minimize(obj_var) # [END objective] # Creates the solver and solve. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] @@ -107,7 +107,7 @@ def main(): machine = task[0] assigned_jobs[machine].append( assigned_task_type( - start=solver.Value(all_tasks[job_id, task_id].start), + start=solver.value(all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1], @@ -124,13 +124,13 @@ def main(): for assigned_task in assigned_jobs[machine]: name = f"job_{assigned_task.job}_task_{assigned_task.index}" - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line_tasks += f"{name:15}" start = assigned_task.start duration = assigned_task.duration sol_tmp = f"[{start},{start + duration}]" - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line += f"{sol_tmp:15}" sol_line += "\n" @@ -139,7 +139,7 @@ def main(): output += sol_line # Finally print the solution found. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") + print(f"Optimal Schedule Length: {solver.objective_value}") print(output) else: print("No solution found.") @@ -148,9 +148,9 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts: {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time: {solver.WallTime()}s") + print(f" - conflicts: {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time: {solver.wall_time}s") # [END statistics] diff --git a/ortools/sat/samples/multiple_knapsack_sat.py b/ortools/sat/samples/multiple_knapsack_sat.py index 293a7e1bc20..c1513c0def4 100644 --- a/ortools/sat/samples/multiple_knapsack_sat.py +++ b/ortools/sat/samples/multiple_knapsack_sat.py @@ -43,18 +43,18 @@ def main(): x = {} for i in data["all_items"]: for b in data["all_bins"]: - x[i, b] = model.NewBoolVar(f"x_{i}_{b}") + x[i, b] = model.new_bool_var(f"x_{i}_{b}") # [END variables] # Constraints. # [START constraints] # Each item is assigned to at most one bin. for i in data["all_items"]: - model.AddAtMostOne(x[i, b] for b in data["all_bins"]) + model.add_at_most_one(x[i, b] for b in data["all_bins"]) # The amount packed in each bin cannot exceed its capacity. for b in data["all_bins"]: - model.Add( + model.add( sum(x[i, b] * data["weights"][i] for i in data["all_items"]) <= data["bin_capacities"][b] ) @@ -62,31 +62,31 @@ def main(): # Objective. # [START objective] - # Maximize total value of packed items. + # maximize total value of packed items. objective = [] for i in data["all_items"]: for b in data["all_bins"]: - objective.append(cp_model.LinearExpr.Term(x[i, b], data["values"][i])) - model.Maximize(cp_model.LinearExpr.Sum(objective)) + objective.append(cp_model.LinearExpr.term(x[i, b], data["values"][i])) + model.maximize(cp_model.LinearExpr.sum(objective)) # [END objective] # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL: - print(f"Total packed value: {solver.ObjectiveValue()}") + print(f"Total packed value: {solver.objective_value}") total_weight = 0 for b in data["all_bins"]: print(f"Bin {b}") bin_weight = 0 bin_value = 0 for i in data["all_items"]: - if solver.Value(x[i, b]) > 0: + if solver.value(x[i, b]) > 0: print( - f"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}" + f"Item:{i} weight:{data['weights'][i]} value:{data['values'][i]}" ) bin_weight += data["weights"][i] bin_value += data["values"][i] diff --git a/ortools/sat/samples/no_overlap_sample_sat.py b/ortools/sat/samples/no_overlap_sample_sat.py index 7b2c77b6db6..24d5f63fb32 100644 --- a/ortools/sat/samples/no_overlap_sample_sat.py +++ b/ortools/sat/samples/no_overlap_sample_sat.py @@ -23,45 +23,45 @@ def NoOverlapSampleSat(): horizon = 21 # 3 weeks. # Task 0, duration 2. - start_0 = model.NewIntVar(0, horizon, "start_0") + start_0 = model.new_int_var(0, horizon, "start_0") duration_0 = 2 # Python cp/sat code accepts integer variables or constants. - end_0 = model.NewIntVar(0, horizon, "end_0") - task_0 = model.NewIntervalVar(start_0, duration_0, end_0, "task_0") + end_0 = model.new_int_var(0, horizon, "end_0") + task_0 = model.new_interval_var(start_0, duration_0, end_0, "task_0") # Task 1, duration 4. - start_1 = model.NewIntVar(0, horizon, "start_1") + start_1 = model.new_int_var(0, horizon, "start_1") duration_1 = 4 # Python cp/sat code accepts integer variables or constants. - end_1 = model.NewIntVar(0, horizon, "end_1") - task_1 = model.NewIntervalVar(start_1, duration_1, end_1, "task_1") + end_1 = model.new_int_var(0, horizon, "end_1") + task_1 = model.new_interval_var(start_1, duration_1, end_1, "task_1") # Task 2, duration 3. - start_2 = model.NewIntVar(0, horizon, "start_2") + start_2 = model.new_int_var(0, horizon, "start_2") duration_2 = 3 # Python cp/sat code accepts integer variables or constants. - end_2 = model.NewIntVar(0, horizon, "end_2") - task_2 = model.NewIntervalVar(start_2, duration_2, end_2, "task_2") + end_2 = model.new_int_var(0, horizon, "end_2") + task_2 = model.new_interval_var(start_2, duration_2, end_2, "task_2") # Weekends. - weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0") - weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1") - weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2") + weekend_0 = model.new_interval_var(5, 2, 7, "weekend_0") + weekend_1 = model.new_interval_var(12, 2, 14, "weekend_1") + weekend_2 = model.new_interval_var(19, 2, 21, "weekend_2") # No Overlap constraint. - model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + model.add_no_overlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) # Makespan objective. - obj = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(obj, [end_0, end_1, end_2]) - model.Minimize(obj) + obj = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(obj, [end_0, end_1, end_2]) + model.minimize(obj) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Print out makespan and the start times for all tasks. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") - print(f"Task 0 starts at {solver.Value(start_0)}") - print(f"Task 1 starts at {solver.Value(start_1)}") - print(f"Task 2 starts at {solver.Value(start_2)}") + print(f"Optimal Schedule Length: {solver.objective_value}") + print(f"Task 0 starts at {solver.value(start_0)}") + print(f"Task 1 starts at {solver.value(start_1)}") + print(f"Task 2 starts at {solver.value(start_2)}") else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/non_linear_sat.py b/ortools/sat/samples/non_linear_sat.py index 5fa2e76ffab..3899063a9a0 100644 --- a/ortools/sat/samples/non_linear_sat.py +++ b/ortools/sat/samples/non_linear_sat.py @@ -15,7 +15,7 @@ """Non linear example. Finds a rectangle with maximum available area for given perimeter using -AddMultiplicationEquality(). +add_multiplication_equality(). """ from ortools.sat.python import cp_model @@ -27,23 +27,23 @@ def non_linear_sat(): model = cp_model.CpModel() - x = model.NewIntVar(0, perimeter, "x") - y = model.NewIntVar(0, perimeter, "y") - model.Add(2 * (x + y) == perimeter) + x = model.new_int_var(0, perimeter, "x") + y = model.new_int_var(0, perimeter, "y") + model.add(2 * (x + y) == perimeter) - area = model.NewIntVar(0, perimeter * perimeter, "s") - model.AddMultiplicationEquality(area, x, y) + area = model.new_int_var(0, perimeter * perimeter, "s") + model.add_multiplication_equality(area, x, y) - model.Maximize(area) + model.maximize(area) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"s = {solver.Value(area)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"s = {solver.value(area)}") else: print("No solution found.") diff --git a/ortools/sat/samples/nqueens_sat.py b/ortools/sat/samples/nqueens_sat.py index da13a1043b5..675e40f335b 100644 --- a/ortools/sat/samples/nqueens_sat.py +++ b/ortools/sat/samples/nqueens_sat.py @@ -25,13 +25,14 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, queens): + def __init__(self, queens: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__queens = queens self.__solution_count = 0 self.__start_time = time.time() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count def on_solution_callback(self): @@ -45,7 +46,7 @@ def on_solution_callback(self): all_queens = range(len(self.__queens)) for i in all_queens: for j in all_queens: - if self.Value(self.__queens[j]) == i: + if self.value(self.__queens[j]) == i: # There is a queen in column j, row i. print("Q", end=" ") else: @@ -66,17 +67,17 @@ def main(board_size): # [START variables] # There are `board_size` number of variables, one for a queen in each column # of the board. The value of each variable is the row that the queen is in. - queens = [model.NewIntVar(0, board_size - 1, f"x_{i}") for i in range(board_size)] + queens = [model.new_int_var(0, board_size - 1, f"x_{i}") for i in range(board_size)] # [END variables] # Creates the constraints. # [START constraints] # All rows must be different. - model.AddAllDifferent(queens) + model.add_all_different(queens) # No two queens can be on the same diagonal. - model.AddAllDifferent(queens[i] + i for i in range(board_size)) - model.AddAllDifferent(queens[i] - i for i in range(board_size)) + model.add_all_different(queens[i] + i for i in range(board_size)) + model.add_all_different(queens[i] - i for i in range(board_size)) # [END constraints] # Solve the model. @@ -84,16 +85,16 @@ def main(board_size): solver = cp_model.CpSolver() solution_printer = NQueenSolutionPrinter(queens) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" conflicts : {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time : {solver.WallTime()} s") - print(f" solutions found: {solution_printer.solution_count()}") + print(f" conflicts : {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time : {solver.wall_time} s") + print(f" solutions found: {solution_printer.solution_count}") # [END statistics] diff --git a/ortools/sat/samples/nurses_sat.py b/ortools/sat/samples/nurses_sat.py index 3b27b718345..494d84e1efe 100644 --- a/ortools/sat/samples/nurses_sat.py +++ b/ortools/sat/samples/nurses_sat.py @@ -42,21 +42,21 @@ def main(): for n in all_nurses: for d in all_days: for s in all_shifts: - shifts[(n, d, s)] = model.NewBoolVar(f"shift_n{n}_d{d}_s{s}") + shifts[(n, d, s)] = model.new_bool_var(f"shift_n{n}_d{d}_s{s}") # [END variables] # Each shift is assigned to exactly one nurse in the schedule period. # [START exactly_one_nurse] for d in all_days: for s in all_shifts: - model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses) + model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses) # [END exactly_one_nurse] # Each nurse works at most one shift per day. # [START at_most_one_shift] for n in all_nurses: for d in all_days: - model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts) + model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts) # [END at_most_one_shift] # [START assign_nurses_evenly] @@ -74,8 +74,8 @@ def main(): for d in all_days: for s in all_shifts: shifts_worked.append(shifts[(n, d, s)]) - model.Add(min_shifts_per_nurse <= sum(shifts_worked)) - model.Add(sum(shifts_worked) <= max_shifts_per_nurse) + model.add(min_shifts_per_nurse <= sum(shifts_worked)) + model.add(sum(shifts_worked) <= max_shifts_per_nurse) # [END assign_nurses_evenly] # Creates the solver and solve. @@ -107,16 +107,16 @@ def on_solution_callback(self): for n in range(self._num_nurses): is_working = False for s in range(self._num_shifts): - if self.Value(self._shifts[(n, d, s)]): + if self.value(self._shifts[(n, d, s)]): is_working = True print(f" Nurse {n} works shift {s}") if not is_working: print(f" Nurse {n} does not work") if self._solution_count >= self._solution_limit: print(f"Stop search after {self._solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + def solutionCount(self): return self._solution_count # Display the first five solutions. @@ -127,16 +127,16 @@ def solution_count(self): # [END solution_printer] # [START solve] - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()} s") - print(f" - solutions found: {solution_printer.solution_count()}") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time} s") + print(f" - solutions found: {solution_printer.solutionCount()}") # [END statistics] diff --git a/ortools/sat/samples/optional_interval_sample_sat.py b/ortools/sat/samples/optional_interval_sample_sat.py index ad78866a1f5..05b0e823daf 100644 --- a/ortools/sat/samples/optional_interval_sample_sat.py +++ b/ortools/sat/samples/optional_interval_sample_sat.py @@ -23,11 +23,11 @@ def OptionalIntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - presence_var = model.NewBoolVar("presence") - interval_var = model.NewOptionalIntervalVar( + end_var = model.new_int_var(0, horizon, "end") + presence_var = model.new_bool_var("presence") + interval_var = model.new_optional_interval_var( start_var, duration, end_var + 2, presence_var, "interval" ) @@ -35,13 +35,13 @@ def OptionalIntervalSampleSat(): # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar( + fixed_size_interval_var = model.new_optional_fixed_size_interval_var( start_var, 10, presence_var, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewOptionalFixedSizeIntervalVar( + fixed_interval = model.new_optional_fixed_size_interval_var( 5, 10, presence_var, "fixed_interval" ) print(f"fixed_interval = {repr(fixed_interval)}") diff --git a/ortools/sat/samples/overlapping_intervals_sample_sat.py b/ortools/sat/samples/overlapping_intervals_sample_sat.py index 23dd02c3a05..41197344649 100644 --- a/ortools/sat/samples/overlapping_intervals_sample_sat.py +++ b/ortools/sat/samples/overlapping_intervals_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def OverlappingIntervals(): """Create the overlapping Boolean variables and enumerate all states.""" @@ -42,45 +37,47 @@ def OverlappingIntervals(): horizon = 7 # First interval. - start_var_a = model.NewIntVar(0, horizon, "start_a") + start_var_a = model.new_int_var(0, horizon, "start_a") duration_a = 3 - end_var_a = model.NewIntVar(0, horizon, "end_a") - unused_interval_var_a = model.NewIntervalVar( + end_var_a = model.new_int_var(0, horizon, "end_a") + unused_interval_var_a = model.new_interval_var( start_var_a, duration_a, end_var_a, "interval_a" ) # Second interval. - start_var_b = model.NewIntVar(0, horizon, "start_b") + start_var_b = model.new_int_var(0, horizon, "start_b") duration_b = 2 - end_var_b = model.NewIntVar(0, horizon, "end_b") - unused_interval_var_b = model.NewIntervalVar( + end_var_b = model.new_int_var(0, horizon, "end_b") + unused_interval_var_b = model.new_interval_var( start_var_b, duration_b, end_var_b, "interval_b" ) # a_after_b Boolean variable. - a_after_b = model.NewBoolVar("a_after_b") - model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b) - model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not()) + a_after_b = model.new_bool_var("a_after_b") + model.add(start_var_a >= end_var_b).only_enforce_if(a_after_b) + model.add(start_var_a < end_var_b).only_enforce_if(a_after_b.negated()) # b_after_a Boolean variable. - b_after_a = model.NewBoolVar("b_after_a") - model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a) - model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not()) + b_after_a = model.new_bool_var("b_after_a") + model.add(start_var_b >= end_var_a).only_enforce_if(b_after_a) + model.add(start_var_b < end_var_a).only_enforce_if(b_after_a.negated()) # Result Boolean variable. - a_overlaps_b = model.NewBoolVar("a_overlaps_b") + a_overlaps_b = model.new_bool_var("a_overlaps_b") # Option a: using only clauses - model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b) - model.AddImplication(a_after_b, a_overlaps_b.Not()) - model.AddImplication(b_after_a, a_overlaps_b.Not()) + model.add_bool_or(a_after_b, b_after_a, a_overlaps_b) + model.add_implication(a_after_b, a_overlaps_b.negated()) + model.add_implication(b_after_a, a_overlaps_b.negated()) # Option b: using an exactly one constraint. - # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b) + # model.add_exactly_one(a_after_b, b_after_a, a_overlaps_b) # Search for start values in increasing order for the two intervals. - model.AddDecisionStrategy( - [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + model.add_decision_strategy( + [start_var_a, start_var_b], + cp_model.CHOOSE_FIRST, + cp_model.SELECT_MIN_VALUE, ) # Create a solver and solve with a fixed search. @@ -93,7 +90,7 @@ def OverlappingIntervals(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) OverlappingIntervals() diff --git a/ortools/sat/samples/rabbits_and_pheasants_sat.py b/ortools/sat/samples/rabbits_and_pheasants_sat.py old mode 100755 new mode 100644 index bcecbfe130b..71dd74ffb5c --- a/ortools/sat/samples/rabbits_and_pheasants_sat.py +++ b/ortools/sat/samples/rabbits_and_pheasants_sat.py @@ -21,20 +21,20 @@ def RabbitsAndPheasantsSat(): """Solves the rabbits + pheasants problem.""" model = cp_model.CpModel() - r = model.NewIntVar(0, 100, "r") - p = model.NewIntVar(0, 100, "p") + r = model.new_int_var(0, 100, "r") + p = model.new_int_var(0, 100, "p") # 20 heads. - model.Add(r + p == 20) + model.add(r + p == 20) # 56 legs. - model.Add(4 * r + 2 * p == 56) + model.add(4 * r + 2 * p == 56) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants") + print(f"{solver.value(r)} rabbits and {solver.value(p)} pheasants") RabbitsAndPheasantsSat() diff --git a/ortools/sat/samples/ranking_circuit_sample_sat.py b/ortools/sat/samples/ranking_circuit_sample_sat.py index 6ed5ad9c9e8..7d5bd951e02 100644 --- a/ortools/sat/samples/ranking_circuit_sample_sat.py +++ b/ortools/sat/samples/ranking_circuit_sample_sat.py @@ -60,26 +60,26 @@ def rank_tasks_with_circuit( arcs: List[cp_model.ArcT] = [] for i in all_tasks: # if node i is first. - start_lit = model.NewBoolVar(f"start_{i}") + start_lit = model.new_bool_var(f"start_{i}") arcs.append((0, i + 1, start_lit)) - model.Add(ranks[i] == 0).OnlyEnforceIf(start_lit) + model.add(ranks[i] == 0).only_enforce_if(start_lit) # As there are no other constraints on the problem, we can add this # redundant constraint. - model.Add(starts[i] == 0).OnlyEnforceIf(start_lit) + model.add(starts[i] == 0).only_enforce_if(start_lit) # if node i is last. - end_lit = model.NewBoolVar(f"end_{i}") + end_lit = model.new_bool_var(f"end_{i}") arcs.append((i + 1, 0, end_lit)) for j in all_tasks: if i == j: - arcs.append((i + 1, i + 1, presences[i].Not())) - model.Add(ranks[i] == -1).OnlyEnforceIf(presences[i].Not()) + arcs.append((i + 1, i + 1, presences[i].negated())) + model.add(ranks[i] == -1).only_enforce_if(presences[i].negated()) else: - literal = model.NewBoolVar(f"arc_{i}_to_{j}") + literal = model.new_bool_var(f"arc_{i}_to_{j}") arcs.append((i + 1, j + 1, literal)) - model.Add(ranks[j] == ranks[i] + 1).OnlyEnforceIf(literal) + model.add(ranks[j] == ranks[i] + 1).only_enforce_if(literal) # To perform the transitive reduction from precedences to successors, # we need to tie the starts of the tasks with 'literal'. @@ -88,17 +88,19 @@ def rank_tasks_with_circuit( # # Note that we could use this literal to penalize the transition, add an # extra delay to the precedence. - model.Add(starts[j] >= starts[i] + durations[i]).OnlyEnforceIf(literal) + model.add(starts[j] >= starts[i] + durations[i]).only_enforce_if( + literal + ) # Manage the empty circuit - empty = model.NewBoolVar("empty") + empty = model.new_bool_var("empty") arcs.append((0, 0, empty)) for i in all_tasks: - model.AddImplication(empty, presences[i].Not()) + model.add_implication(empty, presences[i].negated()) # Add the circuit constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) def ranking_sample_sat(): @@ -117,14 +119,14 @@ def ranking_sample_sat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalFixedSizeIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_fixed_size_interval_var( start, duration, presence, f"opt_interval[{t}]" ) if t < num_tasks // 2: - model.Add(presence == 1) + model.add(presence == 1) starts.append(start) durations.append(duration) @@ -132,45 +134,44 @@ def ranking_sample_sat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. rank_tasks_with_circuit(model, starts, durations, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(starts[t] + durations[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(starts[t] + durations[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/ranking_sample_sat.py b/ortools/sat/samples/ranking_sample_sat.py index 549e67d897b..4687d9c2fcf 100644 --- a/ortools/sat/samples/ranking_sample_sat.py +++ b/ortools/sat/samples/ranking_sample_sat.py @@ -17,7 +17,12 @@ from ortools.sat.python import cp_model -def RankTasks(model, starts, presences, ranks): +def RankTasks( + model: cp_model.CpModel, + starts: list[cp_model.IntVar], + presences: list[cp_model.IntVar], + ranks: list[cp_model.IntVar], +): """This method adds constraints and variables to links tasks and ranks. This method assumes that all starts are disjoint, meaning that all tasks have @@ -41,36 +46,44 @@ def RankTasks(model, starts, presences, ranks): if i == j: precedences[(i, j)] = presences[i] else: - prec = model.NewBoolVar(f"{i} before {j}") + prec = model.new_bool_var(f"{i} before {j}") precedences[(i, j)] = prec - model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec) + model.add(starts[i] < starts[j]).only_enforce_if(prec) # Treats optional intervals. for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if not cp_model.ObjectIsATrueLiteral(presences[i]): - tmp_array.append(presences[i].Not()) + if not cp_model.object_is_a_true_literal(presences[i]): + tmp_array.append(presences[i].negated()) # Makes sure that if i is not performed, all precedences are false. - model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if not cp_model.ObjectIsATrueLiteral(presences[j]): - tmp_array.append(presences[j].Not()) + model.add_implication( + presences[i].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[i].negated(), precedences[(j, i)].negated() + ) + if not cp_model.object_is_a_true_literal(presences[j]): + tmp_array.append(presences[j].negated()) # Makes sure that if j is not performed, all precedences are false. - model.AddImplication(presences[j].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[j].Not(), precedences[(j, i)].Not()) + model.add_implication( + presences[j].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[j].negated(), precedences[(j, i)].negated() + ) # The following bool_or will enforce that for any two intervals: # i precedes j or j precedes i or at least one interval is not # performed. - model.AddBoolOr(tmp_array) + model.add_bool_or(tmp_array) # Redundant constraint: it propagates early that at most one precedence # is true. - model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not()) - model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not()) + model.add_implication(precedences[(i, j)], precedences[(j, i)].negated()) + model.add_implication(precedences[(j, i)], precedences[(i, j)].negated()) # Links precedences and ranks. for i in all_tasks: - model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) + model.add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) def RankingSampleSat(): @@ -89,15 +102,15 @@ def RankingSampleSat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - end = model.NewIntVar(0, horizon, f"end[{t}]") + end = model.new_int_var(0, horizon, f"end[{t}]") if t < num_tasks // 2: - interval = model.NewIntervalVar(start, duration, end, f"interval[{t}]") + interval = model.new_interval_var(start, duration, end, f"interval[{t}]") presence = True else: - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_interval_var( start, duration, end, presence, f"o_interval[{t}]" ) starts.append(start) @@ -106,45 +119,44 @@ def RankingSampleSat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. RankTasks(model, starts, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(ends[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/reified_sample_sat.py b/ortools/sat/samples/reified_sample_sat.py index c01949c9a30..19894bb700a 100644 --- a/ortools/sat/samples/reified_sample_sat.py +++ b/ortools/sat/samples/reified_sample_sat.py @@ -21,20 +21,20 @@ def ReifiedSampleSat(): """Showcase creating a reified constraint.""" model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - b = model.NewBoolVar("b") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + b = model.new_bool_var("b") # First version using a half-reified bool and. - model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b) + model.add_bool_and(x, y.negated()).only_enforce_if(b) # Second version using implications. - model.AddImplication(b, x) - model.AddImplication(b, y.Not()) + model.add_implication(b, x) + model.add_implication(b, y.negated()) # Third version using bool or. - model.AddBoolOr(b.Not(), x) - model.AddBoolOr(b.Not(), y.Not()) + model.add_bool_or(b.negated(), x) + model.add_bool_or(b.negated(), y.negated()) ReifiedSampleSat() diff --git a/ortools/sat/samples/schedule_requests_sat.py b/ortools/sat/samples/schedule_requests_sat.py index 04ff4f53ab4..4c4038e246f 100644 --- a/ortools/sat/samples/schedule_requests_sat.py +++ b/ortools/sat/samples/schedule_requests_sat.py @@ -52,21 +52,21 @@ def main(): for n in all_nurses: for d in all_days: for s in all_shifts: - shifts[(n, d, s)] = model.NewBoolVar(f"shift_n{n}_d{d}_s{s}") + shifts[(n, d, s)] = model.new_bool_var(f"shift_n{n}_d{d}_s{s}") # [END variables] # Each shift is assigned to exactly one nurse in . # [START exactly_one_nurse] for d in all_days: for s in all_shifts: - model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses) + model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses) # [END exactly_one_nurse] # Each nurse works at most one shift per day. # [START at_most_one_shift] for n in all_nurses: for d in all_days: - model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts) + model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts) # [END at_most_one_shift] # [START assign_nurses_evenly] @@ -84,13 +84,12 @@ def main(): for d in all_days: for s in all_shifts: num_shifts_worked += shifts[(n, d, s)] - model.Add(min_shifts_per_nurse <= num_shifts_worked) - model.Add(num_shifts_worked <= max_shifts_per_nurse) + model.add(min_shifts_per_nurse <= num_shifts_worked) + model.add(num_shifts_worked <= max_shifts_per_nurse) # [END assign_nurses_evenly] # [START objective] - # pylint: disable=g-complex-comprehension - model.Maximize( + model.maximize( sum( shift_requests[n][d][s] * shifts[(n, d, s)] for n in all_nurses @@ -103,7 +102,7 @@ def main(): # Creates the solver and solve. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] @@ -113,14 +112,14 @@ def main(): print("Day", d) for n in all_nurses: for s in all_shifts: - if solver.Value(shifts[(n, d, s)]) == 1: + if solver.value(shifts[(n, d, s)]) == 1: if shift_requests[n][d][s] == 1: print("Nurse", n, "works shift", s, "(requested).") else: print("Nurse", n, "works shift", s, "(not requested).") print() print( - f"Number of shift requests met = {solver.ObjectiveValue()}", + f"Number of shift requests met = {solver.objective_value}", f"(out of {num_nurses * min_shifts_per_nurse})", ) else: @@ -130,9 +129,9 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts: {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time: {solver.WallTime()}s") + print(f" - conflicts: {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time: {solver.wall_time}s") # [END statistics] diff --git a/ortools/sat/samples/scheduling_with_calendar_sample_sat.py b/ortools/sat/samples/scheduling_with_calendar_sample_sat.py index 86f1c3a334f..c7302f7bbff 100644 --- a/ortools/sat/samples/scheduling_with_calendar_sample_sat.py +++ b/ortools/sat/samples/scheduling_with_calendar_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def SchedulingWithCalendarSampleSat(): """Interval spanning across a lunch break.""" @@ -47,25 +42,27 @@ def SchedulingWithCalendarSampleSat(): # Because the duration is at least 3 hours, work cannot start after 15h. # Because of the break, work cannot start at 13h. - start = model.NewIntVarFromDomain( - cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), "start" + start = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([(8, 12), (14, 15)]), "start" ) - duration = model.NewIntVar(3, 4, "duration") - end = model.NewIntVar(8, 18, "end") - unused_interval = model.NewIntervalVar(start, duration, end, "interval") + duration = model.new_int_var(3, 4, "duration") + end = model.new_int_var(8, 18, "end") + unused_interval = model.new_interval_var(start, duration, end, "interval") # We have 2 states (spanning across lunch or not) - across = model.NewBoolVar("across") - non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15]) - model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf( - across.Not() + across = model.new_bool_var("across") + non_spanning_hours = cp_model.Domain.from_values([8, 9, 10, 14, 15]) + model.add_linear_expression_in_domain(start, non_spanning_hours).only_enforce_if( + across.negated() ) - model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across) - model.Add(duration == 3).OnlyEnforceIf(across.Not()) - model.Add(duration == 4).OnlyEnforceIf(across) + model.add_linear_constraint(start, 11, 12).only_enforce_if(across) + model.add(duration == 3).only_enforce_if(across.negated()) + model.add(duration == 4).only_enforce_if(across) # Search for x values in increasing order. - model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy( + [start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + ) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -77,7 +74,7 @@ def SchedulingWithCalendarSampleSat(): # Search and print all solutions. solution_printer = VarArraySolutionPrinter([start, duration, across]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) SchedulingWithCalendarSampleSat() diff --git a/ortools/sat/samples/search_for_all_solutions_sample_sat.py b/ortools/sat/samples/search_for_all_solutions_sample_sat.py index 75ce0f31bc7..145738acfdb 100644 --- a/ortools/sat/samples/search_for_all_solutions_sample_sat.py +++ b/ortools/sat/samples/search_for_all_solutions_sample_sat.py @@ -22,18 +22,19 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END print_solution] @@ -48,14 +49,14 @@ def SearchForAllSolutionsSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Create the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # Create a solver and solve. @@ -65,11 +66,11 @@ def SearchForAllSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SearchForAllSolutionsSampleSat() diff --git a/ortools/sat/samples/simple_sat_program.py b/ortools/sat/samples/simple_sat_program.py index 22bd3c45465..f16b677a4fe 100644 --- a/ortools/sat/samples/simple_sat_program.py +++ b/ortools/sat/samples/simple_sat_program.py @@ -29,27 +29,27 @@ def SimpleSatProgram(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # Creates a solver and solves the model. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") # [END print_solution] diff --git a/ortools/sat/samples/solution_hinting_sample_sat.py b/ortools/sat/samples/solution_hinting_sample_sat.py index 25717665678..9b45bec90d0 100644 --- a/ortools/sat/samples/solution_hinting_sample_sat.py +++ b/ortools/sat/samples/solution_hinting_sample_sat.py @@ -28,33 +28,33 @@ def SolutionHintingSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Solution hinting: x <- 1, y <- 2 - model.AddHint(x, 1) - model.AddHint(y, 2) + model.add_hint(x, 1) + model.add_hint(y, 2) # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolutionHintingSampleSat() diff --git a/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py b/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py index 8431fdf82aa..fb47d1caa72 100644 --- a/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py +++ b/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py @@ -23,20 +23,21 @@ class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: print(f"Solution {self.__solution_count}") - print(f" objective value = {self.ObjectiveValue()}") + print(f" objective value = {self.objective_value}") for v in self.__variables: - print(f" {v}={self.Value(v)}", end=" ") + print(f" {v}={self.value(v)}", end=" ") print() self.__solution_count += 1 - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END print_solution] @@ -51,29 +52,29 @@ def SolveAndPrintIntermediateSolutionsSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolveAndPrintIntermediateSolutionsSampleSat() diff --git a/ortools/sat/samples/solve_with_time_limit_sample_sat.py b/ortools/sat/samples/solve_with_time_limit_sample_sat.py index 6688e49989b..ba46aa7e8c5 100644 --- a/ortools/sat/samples/solve_with_time_limit_sample_sat.py +++ b/ortools/sat/samples/solve_with_time_limit_sample_sat.py @@ -24,11 +24,11 @@ def SolveWithTimeLimitSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Adds an all-different constraint. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() @@ -36,12 +36,12 @@ def SolveWithTimeLimitSampleSat(): # Sets a time limit of 10 seconds. solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") SolveWithTimeLimitSampleSat() diff --git a/ortools/sat/samples/step_function_sample_sat.py b/ortools/sat/samples/step_function_sample_sat.py index a6e32e71997..0eba41d9a05 100644 --- a/ortools/sat/samples/step_function_sample_sat.py +++ b/ortools/sat/samples/step_function_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def step_function_sample_sat(): """Encode the step function.""" @@ -42,7 +37,7 @@ def step_function_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the step function # Note it is not defined for x == 2. @@ -53,32 +48,32 @@ def step_function_sample_sat(): # -- --- 0 # 0 ================ 20 # - expr = model.NewIntVar(0, 3, "expr") + expr = model.new_int_var(0, 3, "expr") # expr == 0 on [5, 6] U [8, 10] - b0 = model.NewBoolVar("b0") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)]) - ).OnlyEnforceIf(b0) - model.Add(expr == 0).OnlyEnforceIf(b0) + b0 = model.new_bool_var("b0") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(5, 6), (8, 10)]) + ).only_enforce_if(b0) + model.add(expr == 0).only_enforce_if(b0) # expr == 2 on [0, 1] U [3, 4] U [11, 20] - b2 = model.NewBoolVar("b2") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)]) - ).OnlyEnforceIf(b2) - model.Add(expr == 2).OnlyEnforceIf(b2) + b2 = model.new_bool_var("b2") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)]) + ).only_enforce_if(b2) + model.add(expr == 2).only_enforce_if(b2) # expr == 3 when x == 7 - b3 = model.NewBoolVar("b3") - model.Add(x == 7).OnlyEnforceIf(b3) - model.Add(expr == 3).OnlyEnforceIf(b3) + b3 = model.new_bool_var("b3") + model.add(x == 7).only_enforce_if(b3) + model.add(expr == 3).only_enforce_if(b3) # At least one bi is true. (we could use an exactly one constraint). - model.AddBoolOr(b0, b2, b3) + model.add_bool_or(b0, b2, b3) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -90,7 +85,7 @@ def step_function_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) step_function_sample_sat() diff --git a/ortools/sat/samples/stop_after_n_solutions_sample_sat.py b/ortools/sat/samples/stop_after_n_solutions_sample_sat.py index 5b38f856121..36beb1414f4 100644 --- a/ortools/sat/samples/stop_after_n_solutions_sample_sat.py +++ b/ortools/sat/samples/stop_after_n_solutions_sample_sat.py @@ -21,22 +21,23 @@ class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables, limit): + def __init__(self, variables: list[cp_model.IntVar], limit: int): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 self.__solution_limit = limit - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() if self.__solution_count >= self.__solution_limit: print(f"Stop search after {self.__solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -46,9 +47,9 @@ def StopAfterNSolutionsSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create a solver and solve. solver = cp_model.CpSolver() @@ -56,10 +57,10 @@ def StopAfterNSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") - assert solution_printer.solution_count() == 5 + status = solver.solve(model, solution_printer) + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") + assert solution_printer.solution_count == 5 StopAfterNSolutionsSampleSat() diff --git a/ortools/util/fp_utils.cc b/ortools/util/fp_utils.cc index 336ce097954..c9eb39b7e75 100644 --- a/ortools/util/fp_utils.cc +++ b/ortools/util/fp_utils.cc @@ -26,6 +26,7 @@ #include "absl/base/casts.h" #include "absl/base/internal/endian.h" +#include "absl/log/check.h" #include "ortools/util/bitset.h" namespace operations_research { @@ -113,6 +114,9 @@ void GetBestScalingOfDoublesToInt64(const std::vector& input, // round(fabs(c).2^candidate) <= max_absolute_sum. const double c = std::max(-min_term, max_term); int candidate = msb - ilogb(c); + if (candidate >= std::numeric_limits::max_exponent) { + candidate = std::numeric_limits::max_exponent - 1; + } if (std::round(ldexp(std::abs(c), candidate)) > max_absolute_sum) { --candidate; } @@ -185,6 +189,7 @@ double GetBestScalingOfDoublesToInt64(const std::vector& input, double scaling_factor; GetBestScalingOfDoublesToInt64(input, lb, ub, max_absolute_sum, &scaling_factor); + DCHECK(std::isfinite(scaling_factor)); return scaling_factor; } @@ -197,10 +202,12 @@ void GetBestScalingOfDoublesToInt64(const std::vector& input, scaling_factor); ComputeScalingErrors(input, {}, {}, *scaling_factor, max_relative_coeff_error, &max_scaled_sum_error); + DCHECK(std::isfinite(*scaling_factor)); } int64_t ComputeGcdOfRoundedDoubles(const std::vector& x, double scaling_factor) { + DCHECK(std::isfinite(scaling_factor)); int64_t gcd = 0; const int size = static_cast(x.size()); for (int i = 0; i < size && gcd != 1; ++i) { diff --git a/ortools/util/python/sorted_interval_list.cc b/ortools/util/python/sorted_interval_list.cc index 282fea72cee..ed3d54b2926 100644 --- a/ortools/util/python/sorted_interval_list.cc +++ b/ortools/util/python/sorted_interval_list.cc @@ -16,6 +16,7 @@ #include #include "ortools/util/python/sorted_interval_list_doc.h" +#include "pybind11/cast.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" @@ -24,36 +25,49 @@ using ::pybind11::arg; PYBIND11_MODULE(sorted_interval_list, m) { pybind11::class_(m, "Domain", DOC(operations_research, Domain)) - .def_static("AllValues", &Domain::AllValues, + .def_static("all_values", &Domain::AllValues, DOC(operations_research, Domain, AllValues)) - .def_static("FromValues", &Domain::FromValues, + .def_static("from_values", &Domain::FromValues, DOC(operations_research, Domain, FromValues), arg("values")) - .def_static("FromIntervals", &Domain::FromVectorIntervals, + .def_static("from_intervals", &Domain::FromVectorIntervals, DOC(operations_research, Domain, FromVectorIntervals), arg("intervals")) - .def_static("FromFlatIntervals", &Domain::FromFlatIntervals, + .def_static("from_flat_intervals", &Domain::FromFlatIntervals, DOC(operations_research, Domain, FromFlatIntervals), arg("flat_intervals")) .def(pybind11::init(), DOC(operations_research, Domain, Domain)) - .def("AdditionWith", &Domain::AdditionWith, + .def("addition_with", &Domain::AdditionWith, DOC(operations_research, Domain, AdditionWith), arg("domain")) - .def("Complement", &Domain::Complement, + .def("complement", &Domain::Complement, DOC(operations_research, Domain, Complement)) - .def("Contains", &Domain::Contains, + .def("contains", &Domain::Contains, DOC(operations_research, Domain, Contains), arg("value")) - .def("FlattenedIntervals", &Domain::FlattenedIntervals, + .def("flattened_intervals", &Domain::FlattenedIntervals, DOC(operations_research, Domain, FlattenedIntervals)) - .def("IntersectionWith", &Domain::IntersectionWith, + .def("intersection_with", &Domain::IntersectionWith, DOC(operations_research, Domain, IntersectionWith), arg("domain")) - .def("IsEmpty", &Domain::IsEmpty, + .def("is_empty", &Domain::IsEmpty, DOC(operations_research, Domain, IsEmpty)) - .def("Size", &Domain::Size, DOC(operations_research, Domain, Size)) - .def("Max", &Domain::Max, DOC(operations_research, Domain, Max)) - .def("Min", &Domain::Min, DOC(operations_research, Domain, Min)) - .def("Negation", &Domain::Negation, + .def("size", &Domain::Size, DOC(operations_research, Domain, Size)) + .def("max", &Domain::Max, DOC(operations_research, Domain, Max)) + .def("min", &Domain::Min, DOC(operations_research, Domain, Min)) + .def("negation", &Domain::Negation, DOC(operations_research, Domain, Negation)) - .def("UnionWith", &Domain::UnionWith, + .def("union_with", &Domain::UnionWith, DOC(operations_research, Domain, UnionWith), arg("domain")) - .def("__str__", &Domain::ToString); + .def("__str__", &Domain::ToString) + // Compatibility with pre PEP8 APIs. + .def_static("AllValues", &Domain::AllValues, + DOC(operations_research, Domain, AllValues)) + .def_static("FromValues", &Domain::FromValues, + DOC(operations_research, Domain, FromValues), arg("values")) + .def_static("FromIntervals", &Domain::FromVectorIntervals, + DOC(operations_research, Domain, FromVectorIntervals), + arg("intervals")) + .def_static("FromFlatIntervals", &Domain::FromFlatIntervals, + DOC(operations_research, Domain, FromFlatIntervals), + arg("flat_intervals")) + .def("FlattenedIntervals", &Domain::FlattenedIntervals, + DOC(operations_research, Domain, FlattenedIntervals)); } diff --git a/ortools/util/python/sorted_interval_list_test.py b/ortools/util/python/sorted_interval_list_test.py index 5cd4f7affa9..90dbeb4d932 100755 --- a/ortools/util/python/sorted_interval_list_test.py +++ b/ortools/util/python/sorted_interval_list_test.py @@ -21,68 +21,68 @@ class SortedIntervalListTest(absltest.TestCase): def testCtorAndGetter(self): bool_domain = sorted_interval_list.Domain(0, 1) - self.assertEqual(2, bool_domain.Size()) - self.assertEqual(0, bool_domain.Min()) - self.assertEqual(1, bool_domain.Max()) - self.assertFalse(bool_domain.IsEmpty()) + self.assertEqual(2, bool_domain.size()) + self.assertEqual(0, bool_domain.min()) + self.assertEqual(1, bool_domain.max()) + self.assertFalse(bool_domain.is_empty()) self.assertEqual(str(bool_domain), "[0,1]") def testFromValues(self): domain = sorted_interval_list.Domain.FromValues([1, 3, -5, 5]) - self.assertEqual(4, domain.Size()) - self.assertEqual(-5, domain.Min()) - self.assertEqual(5, domain.Max()) - self.assertEqual([-5, -5, 1, 1, 3, 3, 5, 5], domain.FlattenedIntervals()) - self.assertTrue(domain.Contains(1)) - self.assertFalse(domain.Contains(0)) + self.assertEqual(4, domain.size()) + self.assertEqual(-5, domain.min()) + self.assertEqual(5, domain.max()) + self.assertEqual([-5, -5, 1, 1, 3, 3, 5, 5], domain.flattened_intervals()) + self.assertTrue(domain.contains(1)) + self.assertFalse(domain.contains(0)) def testFromIntervals(self): - domain = sorted_interval_list.Domain.FromIntervals([[2, 4], [-2, 0]]) - self.assertEqual(6, domain.Size()) - self.assertEqual(-2, domain.Min()) - self.assertEqual(4, domain.Max()) - self.assertEqual([-2, 0, 2, 4], domain.FlattenedIntervals()) + domain = sorted_interval_list.Domain.from_intervals([[2, 4], [-2, 0]]) + self.assertEqual(6, domain.size()) + self.assertEqual(-2, domain.min()) + self.assertEqual(4, domain.max()) + self.assertEqual([-2, 0, 2, 4], domain.flattened_intervals()) def testFromFlatIntervals(self): - domain = sorted_interval_list.Domain.FromFlatIntervals([2, 4, -2, 0]) - self.assertEqual(6, domain.Size()) - self.assertEqual(-2, domain.Min()) - self.assertEqual(4, domain.Max()) - self.assertEqual([-2, 0, 2, 4], domain.FlattenedIntervals()) + domain = sorted_interval_list.Domain.from_flat_intervals([2, 4, -2, 0]) + self.assertEqual(6, domain.size()) + self.assertEqual(-2, domain.min()) + self.assertEqual(4, domain.max()) + self.assertEqual([-2, 0, 2, 4], domain.flattened_intervals()) def testNegation(self): domain = sorted_interval_list.Domain(5, 20) - self.assertEqual([-20, -5], domain.Negation().FlattenedIntervals()) + self.assertEqual([-20, -5], domain.negation().flattened_intervals()) def testUnion(self): d1 = sorted_interval_list.Domain(0, 5) d2 = sorted_interval_list.Domain(10, 15) - d3 = d1.UnionWith(d2) - self.assertEqual([0, 5], d1.FlattenedIntervals()) - self.assertEqual([10, 15], d2.FlattenedIntervals()) - self.assertEqual([0, 5, 10, 15], d3.FlattenedIntervals()) + d3 = d1.union_with(d2) + self.assertEqual([0, 5], d1.flattened_intervals()) + self.assertEqual([10, 15], d2.flattened_intervals()) + self.assertEqual([0, 5, 10, 15], d3.flattened_intervals()) def testIntersection(self): d1 = sorted_interval_list.Domain(0, 10) d2 = sorted_interval_list.Domain(5, 15) - d3 = d1.IntersectionWith(d2) - self.assertEqual([0, 10], d1.FlattenedIntervals()) - self.assertEqual([5, 15], d2.FlattenedIntervals()) - self.assertEqual([5, 10], d3.FlattenedIntervals()) + d3 = d1.intersection_with(d2) + self.assertEqual([0, 10], d1.flattened_intervals()) + self.assertEqual([5, 15], d2.flattened_intervals()) + self.assertEqual([5, 10], d3.flattened_intervals()) def testAddition(self): d1 = sorted_interval_list.Domain(0, 5) d2 = sorted_interval_list.Domain(10, 15) - d3 = d1.AdditionWith(d2) - self.assertEqual([0, 5], d1.FlattenedIntervals()) - self.assertEqual([10, 15], d2.FlattenedIntervals()) - self.assertEqual([10, 20], d3.FlattenedIntervals()) + d3 = d1.addition_with(d2) + self.assertEqual([0, 5], d1.flattened_intervals()) + self.assertEqual([10, 15], d2.flattened_intervals()) + self.assertEqual([10, 20], d3.flattened_intervals()) def testComplement(self): d1 = sorted_interval_list.Domain(-9223372036854775808, 5) - d2 = d1.Complement() - self.assertEqual([-9223372036854775808, 5], d1.FlattenedIntervals()) - self.assertEqual([6, 9223372036854775807], d2.FlattenedIntervals()) + d2 = d1.complement() + self.assertEqual([-9223372036854775808, 5], d1.flattened_intervals()) + self.assertEqual([6, 9223372036854775807], d2.flattened_intervals()) if __name__ == "__main__": diff --git a/ortools/util/zvector.h b/ortools/util/zvector.h index 25fb6701d9d..78a66cafde2 100644 --- a/ortools/util/zvector.h +++ b/ortools/util/zvector.h @@ -16,18 +16,15 @@ #if (defined(__APPLE__) || defined(__FreeBSD__)) && defined(__GNUC__) #include -#elif !defined(_MSC_VER) +#elif !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__MINGW64__) #include #endif -#include -#include -#include +#include #include -#include +#include // IWYU pragma: keep +#include "absl/log/check.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" -#include "ortools/base/types.h" // An array class for storing arrays of integers. //