From 5b6c803db3f1461c7ebb7defd39f5aad4d3e73fe Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 16 Nov 2023 19:46:56 +0100 Subject: [PATCH] [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 | 110 +- ortools/sat/BUILD.bazel | 938 +++++++--- 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 | 1614 ++++++++++------- ortools/sat/python/cp_model_test.py | 1368 +++++++------- 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 | 56 +- .../util/python/sorted_interval_list_test.py | 72 +- ortools/util/zvector.h | 11 +- 103 files changed, 4648 insertions(+), 3430 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 e88571070d..017e8c9009 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -44,17 +44,19 @@ class AllSolutionCollector(cp_model.CpSolverSolutionCallback): 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 b0dc5b5db6..3a805787a1 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 @@ from ortools.sat.python import cp_model 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 b11fb8fe11..87093264c7 100644 --- a/examples/python/balance_group_sat.py +++ b/examples/python/balance_group_sat.py @@ -41,14 +41,14 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): 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 18d5713f97..a1972edc1f 100644 --- a/examples/python/bus_driver_scheduling_sat.py +++ b/examples/python/bus_driver_scheduling_sat.py @@ -1708,7 +1708,7 @@ SAMPLE_SHIFTS_LARGE = [ ] # 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 6585e7d12c..d3604c6bfd 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 e4acc1809f..9cfb60cf0d 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 dc4dcd640e..2beb09ce0a 100644 --- a/examples/python/cover_rectangle_sat.py +++ b/examples/python/cover_rectangle_sat.py @@ -19,7 +19,7 @@ from absl import app 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 d96b5c6ad3..c519d990e0 100644 --- a/examples/python/cryptarithm_sat.py +++ b/examples/python/cryptarithm_sat.py @@ -20,58 +20,58 @@ from ortools.sat.python import cp_model 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 9eea9d24f4..f216ae9b24 100644 --- a/examples/python/flexible_job_shop_sat.py +++ b/examples/python/flexible_job_shop_sat.py @@ -41,13 +41,13 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): """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 00aebdeebd..67790f42dc 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 c23a11a2b8..561cf44fdd 100644 --- a/examples/python/golomb_sat.py +++ b/examples/python/golomb_sat.py @@ -38,7 +38,7 @@ _PARAMS = flags.DEFINE_string( ) -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 02fb4a9d9c..eb84aa6cc6 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 a0edaa02bd..dbea74de66 100755 --- a/examples/python/jobshop_ft06_distance_sat.py +++ b/examples/python/jobshop_ft06_distance_sat.py @@ -31,7 +31,7 @@ import collections 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 7de58ff934..24bc31ca26 100755 --- a/examples/python/jobshop_ft06_sat.py +++ b/examples/python/jobshop_ft06_sat.py @@ -29,7 +29,7 @@ from ortools.sat.colab import visualization 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 953ad9380d..d9a7e76a89 100644 --- a/examples/python/jobshop_with_maintenance_sat.py +++ b/examples/python/jobshop_with_maintenance_sat.py @@ -31,7 +31,7 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): """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 3972a77267..3400f34965 100644 --- a/examples/python/knapsack_2d_sat.py +++ b/examples/python/knapsack_2d_sat.py @@ -29,6 +29,7 @@ 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." ) @@ -42,7 +43,7 @@ _MODEL = flags.DEFINE_string( ) -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 b2999ebfee..47bd950670 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 cbd123bd70..e01948b9c6 --- 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 0000000000..51b970f725 --- /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 4cd2ab0971..1c970b1aad --- 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 fb93eebb9d..a2c32d87c2 100644 --- a/examples/python/nqueens_sat.py +++ b/examples/python/nqueens_sat.py @@ -26,16 +26,17 @@ _SIZE = flags.DEFINE_integer("size", 8, "Number of queens.") 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 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): 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 a82e465741..bdb363f3b2 --- a/examples/python/prize_collecting_tsp_sat.py +++ b/examples/python/prize_collecting_tsp_sat.py @@ -71,14 +71,19 @@ VISIT_VALUES[0] = 0 # 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 7387323250..d0aee475ff --- a/examples/python/prize_collecting_vrp_sat.py +++ b/examples/python/prize_collecting_vrp_sat.py @@ -71,7 +71,13 @@ VISIT_VALUES[0] = 0 # 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 4fb965cbbd..523d2f2c20 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 3d7364f837..9af9e02c2a 100644 --- a/examples/python/qubo_sat.py +++ b/examples/python/qubo_sat.py @@ -653,14 +653,14 @@ RAW_DATA = [ 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 1cd34e6932..e698ebd609 --- a/examples/python/rcpsp_sat.py +++ b/examples/python/rcpsp_sat.py @@ -19,10 +19,10 @@ Introduction to the problem: 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 @@ _HORIZON = flags.DEFINE_integer("horizon", -1, "Force horizon.") _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 @@ _PREEMPTIVE_LB_TIME_LIMIT = flags.DEFINE_float( ) -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 96b36c80e6..c681f46cee 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 @@ _PARAMS = flags.DEFINE_string( ) -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 2381de7831..fa496da34e 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 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): """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 fb5011b316..d85fc1afa8 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 f119ff3c26..667f021754 --- a/examples/python/steel_mill_slab_sat.py +++ b/examples/python/steel_mill_slab_sat.py @@ -131,22 +131,22 @@ class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback): 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 bf4b391ca9..732ee74a97 --- 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 0027f533be..04009582aa 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 0960f59c5c..83f2d62b32 100644 --- a/examples/python/tasks_and_workers_assignment_sat.py +++ b/examples/python/tasks_and_workers_assignment_sat.py @@ -29,13 +29,13 @@ class ObjectivePrinter(cp_model.CpSolverSolutionCallback): 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 f0ef4f916e..02b149377f 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 ac65975e47..60957851ef 100644 --- a/examples/python/vendor_scheduling_sat.py +++ b/examples/python/vendor_scheduling_sat.py @@ -48,13 +48,13 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): 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 a9c0cf8521..e23803a064 100644 --- a/examples/python/wedding_optimal_chart_sat.py +++ b/examples/python/wedding_optimal_chart_sat.py @@ -56,7 +56,7 @@ class WeddingChartPrinter(cp_model.CpSolverSolutionCallback): 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 @@ class WeddingChartPrinter(cp_model.CpSolverSolutionCallback): 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 3db149a385..8acb20d911 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 b0e4ba01e8..24a5afceb0 --- 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") + 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.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") + 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.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") + 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.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") + 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.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") + 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.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_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) + 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_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.NewIntVar(-4, 4, "diff_horse_kools") - model.Add(diff_horse_kools == horse - kools) - model.AddAbsEquality(1, diff_horse_kools) + 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) + 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) + 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 daef72cf5e..633b0bcab4 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: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", - ], -) - -cc_library( - name = "cp_model_presolve", - srcs = ["cp_model_presolve.cc"], - hdrs = ["cp_model_presolve.h"], - deps = [ - ":circuit", - ":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", - ":presolve_context", - ":presolve_util", - ":probing", - ":sat_base", - ":sat_parameters_cc_proto", - ":simplification", - ":var_domination", - "//ortools/base", - "//ortools/base:hash", - "//ortools/base:mathutil", - "//ortools/base:stl_util", - "//ortools/port:proto_utils", - "//ortools/util:affine_relation", - "//ortools/util:bitset", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "//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", + ], + hdrs = ["cp_model_presolve.h"], + deps = [ + ":circuit", + ":clause", + ":cp_model_cc_proto", + ":cp_model_checker", + ":cp_model_expand", + ":cp_model_mapping", + ":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:mathutil", + "//ortools/base:stl_util", + "//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,15 +736,28 @@ 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", ], ) @@ -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 b17185b31d..c6cc9ae7d5 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 5e602e7b9d..54e7ade206 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 03e9b35625..7ba5a96064 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 e54104f56b..a54f79a606 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 6b5538035c..b0bc119efd 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 c44b65eb1f..dc7c6b64d1 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 4f15cbbd24..d0b442f43b 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 e581c04be6..52d4368016 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 7dcf50f3f2..3faa138b79 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 11967ff53c..665587f3d6 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 3e21321016..e1ad4eaa91 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 776d4df361..5a5d65c248 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 019d362141..abf193e5ef 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 ed921ef8e6..6ce7339086 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 73b60ea1f5..f4ede40b81 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 220ee3eb6a..2d1a2f3ed0 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 31ba1ca2b7..9d4f677501 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 4cebda89e0..8b80a8ea32 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 aa0529aba2..44d9749078 100644 --- a/ortools/sat/python/cp_model.py +++ b/ortools/sat/python/cp_model.py @@ -121,6 +121,7 @@ RANDOMIZED_SEARCH = sat_parameters_pb2.SatParameters.RANDOMIZED_SEARCH 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 @@ ArcT = Tuple[IntegralT, IntegralT, LiteralT] _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 @@ class LinearExpr: @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[LinearExprT], coefficients: Sequence[IntegralT], @@ -229,7 +230,7 @@ class LinearExpr: @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[ObjLinearExprT], coefficients: Sequence[NumberT], @@ -237,9 +238,9 @@ class LinearExpr: ... @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 @@ class LinearExpr: @overload @classmethod - def Term( + def term( cls, expressions: LinearExprT, coefficients: IntegralT, @@ -257,7 +258,7 @@ class LinearExpr: @overload @classmethod - def Term( + def term( cls, expressions: ObjLinearExprT, coefficients: NumberT, @@ -265,7 +266,7 @@ class LinearExpr: ... @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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class LinearExpr: 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 @@ class _ProductCst(LinearExpr): 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 @@ class _SumArray(LinearExpr): 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 @@ class _SumArray(LinearExpr): 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 @@ class _WeightedSum(LinearExpr): 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 @@ class _WeightedSum(LinearExpr): 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 @@ class _WeightedSum(LinearExpr): 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 @@ class IntVar(LinearExpr): 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 @@ class IntVar(LinearExpr): 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 @@ class IntVar(LinearExpr): 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 @@ class _NotBooleanVariable(LinearExpr): "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 @@ class BoundedLinearExpression: ): 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 @@ class BoundedLinearExpression: 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 @@ class 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 @@ class 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 @@ class 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 @@ class IntervalVar: 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 @@ class IntervalVar: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: """ 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 @@ class CpModel: 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 @@ class CpModel: ], ) - def NewBoolVarSeries( + def new_bool_var_series( self, name: str, index: pd.Index, @@ -1237,53 +1376,53 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: """ 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: " 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: ) 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: ) return pd.Series(index=index, data=interval_array) - def NewOptionalIntervalVar( + def new_optional_interval_var( self, start: LinearExprT, size: LinearExprT, @@ -2167,27 +2325,31 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: ) 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 @@ class CpModel: 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 @@ class CpModel: name, ) - def NewOptionalFixedSizeIntervalVarSeries( + def new_optional_fixed_size_interval_var_series( self, name: str, index: pd.Index, @@ -2324,13 +2486,13 @@ class CpModel: 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 @@ class CpModel: ) 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: 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 @@ class CpModel: """ return swig_helper.CpSatHelper.WriteModelToFile(self.__model, file) - def AssertIsBooleanVariable(self, x: LiteralT) -> 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.get_or_make_index(var)) + self.__model.solution_hint.values.append(value) + + def clear_hints(self): + """Removes any solution hint from the model.""" + self.__model.ClearField("solution_hint") + + 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 add_assumptions(self, literals: Iterable[LiteralT]) -> None: + """Adds the literals to the model as assumptions.""" + for lit in literals: + self.add_assumption(lit) + + 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()] + 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: - """Adds 'var == value' as a hint to the solver.""" - self.__model.solution_hint.vars.append(self.GetOrMakeIndex(var)) - self.__model.solution_hint.values.append(value) + # Compatibility with pre PEP8 + # pylint: disable=invalid-name - def ClearHints(self): - """Remove any solution hint from the model.""" - self.__model.ClearField("solution_hint") + def Name(self) -> str: + return self.name - def AddAssumption(self, lit: LiteralT) -> None: - """Add the literal 'lit' to the model as assumptions.""" - self.__model.assumptions.append(self.GetOrMakeBooleanIndex(lit)) + def SetName(self, name: str) -> None: + self.name = name - def AddAssumptions(self, literals: Iterable[LiteralT]) -> None: - """Add the literals to the model as assumptions.""" - for lit in literals: - self.AddAssumption(lit) + def Proto(self) -> cp_model_pb2.CpModelProto: + return self.proto - def ClearAssumptions(self) -> None: - """Remove all assumptions from the model.""" - self.__model.ClearField("assumptions") + 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 @@ class CpSolver: 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,38 +3041,227 @@ class CpSolver: 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 stop_search(self) -> None: + """Stops the current search asynchronously.""" + with self.__lock: + if self.__solve_wrapper: + self.__solve_wrapper.stop_search() + + def value(self, expression: LinearExprT) -> int: + """Returns the value of a linear expression after solve.""" + return evaluate_linear_expr(expression, self._solution) + + 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 + variables. If `variables` is a `pd.Series` indexed by the underlying + dimensions, then the output will be indexed by the same underlying + dimensions. + + Args: + variables (Union[pd.Index, pd.Series]): The set of variables from which to + get the values. + + Returns: + pd.Series: The values of all variables in the set. + """ + solution = self._solution + return _attribute_series( + func=lambda v: solution.solution[v.index], + values=variables, + ) + + def boolean_value(self, literal: LiteralT) -> bool: + """Returns the boolean value of a literal after solve.""" + return evaluate_boolean_expression(literal, self._solution) + + 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 + variables. If `variables` is a `pd.Series` indexed by the underlying + dimensions, then the output will be indexed by the same underlying + dimensions. + + Args: + variables (Union[pd.Index, pd.Series]): The set of variables from which to + get the values. + + Returns: + pd.Series: The values of all variables in the set. + """ + solution = self._solution + return _attribute_series( + func=lambda literal: evaluate_boolean_expression(literal, solution), + values=variables, + ) + + @property + def objective_value(self) -> float: + """Returns the value of the objective after solve.""" + return self._solution.objective_value + + @property + def best_objective_bound(self) -> float: + """Returns the best lower (upper) bound found when min(max)imizing.""" + return self._solution.best_objective_bound + + @property + def num_booleans(self) -> int: + """Returns the number of boolean variables managed by the SAT solver.""" + return self._solution.num_booleans + + @property + def num_conflicts(self) -> int: + """Returns the number of conflicts since the creation of the solver.""" + return self._solution.num_conflicts + + @property + def num_branches(self) -> int: + """Returns the number of search branches explored by the solver.""" + return self._solution.num_branches + + @property + def wall_time(self) -> float: + """Returns the wall time in seconds since the creation of the solver.""" + return self._solution.wall_time + + @property + def user_time(self) -> float: + """Returns the user time in seconds since the creation of the solver.""" + return self._solution.user_time + + @property + def response_proto(self) -> cp_model_pb2.CpSolverResponse: + """Returns the response object.""" + return self._solution + + 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 + + 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. + """ + 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.""" + """DEPRECATED Use solve() with the callback argument.""" warnings.warn( - "SolveWithSolutionCallback is deprecated; use Solve() with" + "solve_with_solution_callback is deprecated; use solve() with" + "the callback argument.", DeprecationWarning, ) - return self.Solve(model, callback) + return self.solve(model, callback) def SearchForAllSolutions( self, model: CpModel, callback: "CpSolverSolutionCallback" ) -> cp_model_pb2.CpSolverStatus: - """DEPRECATED Use Solve() with the right parameter. + """DEPRECATED Use solve() with the right parameter. Search for all solutions of a satisfiability problem. @@ -2852,11 +3282,11 @@ class CpSolver: * *OPTIMAL* if all solutions have been found """ warnings.warn( - "SearchForAllSolutions is deprecated; use Solve() with" + "search_for_all_solutions is deprecated; use solve() with" + "enumerate_all_solutions = True.", DeprecationWarning, ) - if model.HasObjective(): + if model.has_objective(): raise TypeError( "Search for all solutions is only defined on satisfiability problems" ) @@ -2864,130 +3294,14 @@ class CpSolver: enumerate_all = self.parameters.enumerate_all_solutions self.parameters.enumerate_all_solutions = True - self.Solve(model, callback) + self.solve(model, callback) # Restore parameter. self.parameters.enumerate_all_solutions = enumerate_all return self.__solution.status - def StopSearch(self) -> None: - """Stops the current search asynchronously.""" - with self.__lock: - if self.__solve_wrapper: - self.__solve_wrapper.StopSearch() - 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: - """Returns the value of a linear expression after solve.""" - return EvaluateLinearExpr(expression, self._Solution()) - - 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 - variables. If `variables` is a `pd.Series` indexed by the underlying - dimensions, then the output will be indexed by the same underlying - dimensions. - - Args: - variables (Union[pd.Index, pd.Series]): The set of variables from which to - get the values. - - Returns: - pd.Series: The values of all variables in the set. - """ - solution = self._Solution() - return _AttributeSeries( - func=lambda v: solution.solution[v.Index()], - values=variables, - ) - - def BooleanValue(self, literal: LiteralT) -> bool: - """Returns the boolean value of a literal after solve.""" - return EvaluateBooleanExpression(literal, self._Solution()) - - def BooleanValues(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 - variables. If `variables` is a `pd.Series` indexed by the underlying - dimensions, then the output will be indexed by the same underlying - dimensions. - - Args: - variables (Union[pd.Index, pd.Series]): The set of variables from which to - get the values. - - Returns: - pd.Series: The values of all variables in the set. - """ - solution = self._Solution() - return _AttributeSeries( - func=lambda literal: EvaluateBooleanExpression(literal, solution), - values=variables, - ) - - def ObjectiveValue(self) -> float: - """Returns the value of the objective after solve.""" - return self._Solution().objective_value - - def BestObjectiveBound(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) - - def NumBooleans(self) -> int: - """Returns the number of boolean variables managed by the SAT solver.""" - return self._Solution().num_booleans - - def NumConflicts(self) -> int: - """Returns the number of conflicts since the creation of the solver.""" - return self._Solution().num_conflicts - - def NumBranches(self) -> int: - """Returns the number of search branches explored by the solver.""" - return self._Solution().num_branches - - def WallTime(self) -> float: - """Returns the wall time in seconds since the creation of the solver.""" - return self._Solution().wall_time - - def UserTime(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()) - - def ResponseProto(self) -> cp_model_pb2.CpSolverResponse: - """Returns the response object.""" - return self._Solution() - - def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: - """Returns the indices of the infeasible assumptions.""" - return self._Solution().sufficient_assumptions_for_infeasibility - - def SolutionInfo(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. - """ - return self._Solution().solution_info +# 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 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): """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 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): 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 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): 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 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): 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 @@ class ObjectiveSolutionPrinter(CpSolverSolutionCallback): 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 @@ class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback): 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 @@ class VarArraySolutionPrinter(CpSolverSolutionCallback): % (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 b317b3b1fe..3a6370dcf3 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 @@ class SolutionCounter(cp_model.CpSolverSolutionCallback): 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 @@ class SolutionSum(cp_model.CpSolverSolutionCallback): 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 @@ class SolutionObjective(cp_model.CpSolverSolutionCallback): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): ) 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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()) + 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.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()) + 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( - AttributeError, model.NewOptionalIntervalVar, 1, 2, 3, b + 1, "x" + TypeError, model.new_optional_interval_var, b + x, 2, 3, b, "x" + ) + self.assertRaises( + 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 @@ class CpModelTest(absltest.TestCase): 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 1612d6dedd..073c5800fe 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 a44900ed9e..8479885ff0 100644 --- a/ortools/sat/python/swig_helper_test.py +++ b/ortools/sat/python/swig_helper_test.py @@ -30,7 +30,7 @@ class Callback(swig_helper.SolutionCallback): print("New Solution") self.__solution_count += 1 - def SolutionCount(self): + def solution_count(self): return self.__solution_count @@ -43,11 +43,11 @@ class SwigHelperTest(absltest.TestCase): 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 @@ class SwigHelperTest(absltest.TestCase): 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 @@ class SwigHelperTest(absltest.TestCase): 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 @@ class SwigHelperTest(absltest.TestCase): 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 @@ class SwigHelperTest(absltest.TestCase): 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 @@ class SwigHelperTest(absltest.TestCase): """ 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 f240ceb2e3..8358d0bf79 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 f8cbfe7f3b..bc7dbaaf4a 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 a8a97d30f1..7bc0ea9e57 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 583325aca3..a75d72bf1a 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 8007ba32a3..ee50dfa012 --- 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 0efeb0315a..97f1d37cb8 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 228ab369fb..12ebc9aa0b 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 e692be0c6b..ad21c3a13d 100644 --- a/ortools/sat/samples/bool_or_sample_sat.py +++ b/ortools/sat/samples/bool_or_sample_sat.py @@ -21,10 +21,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() diff --git a/ortools/sat/samples/boolean_product_sample_sat.py b/ortools/sat/samples/boolean_product_sample_sat.py index 3994c5eb37..e91c5bcbf8 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 d0602b66a3..99f359c914 100644 --- a/ortools/sat/samples/channeling_sample_sat.py +++ b/ortools/sat/samples/channeling_sample_sat.py @@ -21,20 +21,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.""" @@ -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 2bca1dac6c..ad0ea1e678 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 586f3db272..9a6489b5ed 100644 --- a/ortools/sat/samples/cp_is_fun_sat.py +++ b/ortools/sat/samples/cp_is_fun_sat.py @@ -29,24 +29,25 @@ 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 # [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 0e26f113ea..406491ef6f 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 49484443a0..77f6c77115 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 3fdb214a94..6da9b6ff1f 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 @@ 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.""" @@ -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 13fb88af87..43dfc98bbc 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 04ca45513f..3c83cf4414 100644 --- a/ortools/sat/samples/literal_sample_sat.py +++ b/ortools/sat/samples/literal_sample_sat.py @@ -20,8 +20,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) diff --git a/ortools/sat/samples/minimal_jobshop_sat.py b/ortools/sat/samples/minimal_jobshop_sat.py index 782f1b9a07..4ef60a3cfa 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 293a7e1bc2..c1513c0def 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 7b2c77b6db..24d5f63fb3 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 5fa2e76ffa..3899063a9a 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 da13a1043b..675e40f335 100644 --- a/ortools/sat/samples/nqueens_sat.py +++ b/ortools/sat/samples/nqueens_sat.py @@ -25,13 +25,14 @@ from ortools.sat.python import cp_model 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 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): 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 3b27b71834..494d84e1ef 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 main(): 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 main(): # [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 ad78866a1f..05b0e823da 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 23dd02c3a0..4119734464 100644 --- a/ortools/sat/samples/overlapping_intervals_sample_sat.py +++ b/ortools/sat/samples/overlapping_intervals_sample_sat.py @@ -20,20 +20,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.""" @@ -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 bcecbfe130..71dd74ffb5 --- 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 6ed5ad9c9e..7d5bd951e0 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 549e67d897..4687d9c2fc 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 c01949c9a3..19894bb700 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 04ff4f53ab..4c4038e246 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 86f1c3a334..c7302f7bbf 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 @@ 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.""" @@ -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 75ce0f31bc..145738acfd 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 @@ 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 # [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 22bd3c4546..f16b677a4f 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 2571766567..9b45bec90d 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 8431fdf82a..fb47d1caa7 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 @@ 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 # [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 6688e49989..ba46aa7e8c 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 a6e32e7199..0eba41d9a0 100644 --- a/ortools/sat/samples/step_function_sample_sat.py +++ b/ortools/sat/samples/step_function_sample_sat.py @@ -20,20 +20,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.""" @@ -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 5b38f85612..36beb1414f 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 @@ 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 @@ -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 336ce09795..c9eb39b7e7 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 282fea72ce..ed3d54b292 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,6 +25,39 @@ using ::pybind11::arg; PYBIND11_MODULE(sorted_interval_list, m) { pybind11::class_(m, "Domain", DOC(operations_research, Domain)) + .def_static("all_values", &Domain::AllValues, + DOC(operations_research, Domain, AllValues)) + .def_static("from_values", &Domain::FromValues, + DOC(operations_research, Domain, FromValues), arg("values")) + .def_static("from_intervals", &Domain::FromVectorIntervals, + DOC(operations_research, Domain, FromVectorIntervals), + arg("intervals")) + .def_static("from_flat_intervals", &Domain::FromFlatIntervals, + DOC(operations_research, Domain, FromFlatIntervals), + arg("flat_intervals")) + .def(pybind11::init(), + DOC(operations_research, Domain, Domain)) + .def("addition_with", &Domain::AdditionWith, + DOC(operations_research, Domain, AdditionWith), arg("domain")) + .def("complement", &Domain::Complement, + DOC(operations_research, Domain, Complement)) + .def("contains", &Domain::Contains, + DOC(operations_research, Domain, Contains), arg("value")) + .def("flattened_intervals", &Domain::FlattenedIntervals, + DOC(operations_research, Domain, FlattenedIntervals)) + .def("intersection_with", &Domain::IntersectionWith, + DOC(operations_research, Domain, IntersectionWith), arg("domain")) + .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, + DOC(operations_research, Domain, Negation)) + .def("union_with", &Domain::UnionWith, + DOC(operations_research, Domain, UnionWith), arg("domain")) + .def("__str__", &Domain::ToString) + // Compatibility with pre PEP8 APIs. .def_static("AllValues", &Domain::AllValues, DOC(operations_research, Domain, AllValues)) .def_static("FromValues", &Domain::FromValues, @@ -34,26 +68,6 @@ PYBIND11_MODULE(sorted_interval_list, m) { .def_static("FromFlatIntervals", &Domain::FromFlatIntervals, DOC(operations_research, Domain, FromFlatIntervals), arg("flat_intervals")) - .def(pybind11::init(), - DOC(operations_research, Domain, Domain)) - .def("AdditionWith", &Domain::AdditionWith, - DOC(operations_research, Domain, AdditionWith), arg("domain")) - .def("Complement", &Domain::Complement, - DOC(operations_research, Domain, Complement)) - .def("Contains", &Domain::Contains, - DOC(operations_research, Domain, Contains), arg("value")) .def("FlattenedIntervals", &Domain::FlattenedIntervals, - DOC(operations_research, Domain, FlattenedIntervals)) - .def("IntersectionWith", &Domain::IntersectionWith, - DOC(operations_research, Domain, IntersectionWith), arg("domain")) - .def("IsEmpty", &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, - DOC(operations_research, Domain, Negation)) - .def("UnionWith", &Domain::UnionWith, - DOC(operations_research, Domain, UnionWith), arg("domain")) - .def("__str__", &Domain::ToString); + 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 5cd4f7affa..90dbeb4d93 100755 --- a/ortools/util/python/sorted_interval_list_test.py +++ b/ortools/util/python/sorted_interval_list_test.py @@ -21,68 +21,68 @@ from ortools.util.python import sorted_interval_list 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 25fb6701d9..78a66cafde 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. //