From 84ec414e61d1bd2e104c85aadfca3e1798032a3e Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Sat, 1 Jul 2023 06:06:53 +0200 Subject: [PATCH] use black on examples/python --- examples/python/appointments.py | 195 +- .../python/assignment_with_constraints_sat.py | 71 +- examples/python/balance_group_sat.py | 73 +- examples/python/bus_driver_scheduling_sat.py | 3449 +++++---- examples/python/chemical_balance_sat.py | 28 +- examples/python/clustering_sat.py | 1748 ++++- examples/python/cover_rectangle_sat.py | 41 +- examples/python/cryptarithm_sat.py | 48 +- examples/python/flexible_job_shop_sat.py | 58 +- examples/python/gate_scheduling_sat.py | 62 +- examples/python/golomb8.py | 26 +- examples/python/golomb_sat.py | 34 +- examples/python/hidato_sat.py | 126 +- examples/python/integer_programming.py | 60 +- examples/python/jobshop_ft06_distance_sat.py | 56 +- examples/python/jobshop_ft06_sat.py | 54 +- .../python/jobshop_with_maintenance_sat.py | 92 +- examples/python/knapsack_2d_sat.py | 202 +- examples/python/line_balancing_sat.py | 112 +- examples/python/linear_assignment_api.py | 23 +- examples/python/linear_programming.py | 87 +- examples/python/magic_sequence_distribute.py | 11 +- examples/python/maze_escape_sat.py | 72 +- .../python/no_wait_baking_scheduling_sat.py | 150 +- examples/python/nqueens_sat.py | 33 +- examples/python/prize_collecting_tsp_sat.py | 1756 ++++- examples/python/prize_collecting_vrp_sat.py | 1773 ++++- examples/python/pyflow_example.py | 49 +- examples/python/qubo_sat.py | 6452 +++++++++++++++-- examples/python/shift_scheduling_sat.py | 244 +- ...duling_with_setup_release_due_dates_sat.py | 425 +- examples/python/spread_robots_sat.py | 49 +- examples/python/steel_mill_slab_sat.py | 291 +- examples/python/sudoku_sat.py | 22 +- examples/python/task_allocation_sat.py | 2898 +++++++- .../tasks_and_workers_assignment_sat.py | 40 +- examples/python/tsp_sat.py | 1738 ++++- examples/python/vendor_scheduling_sat.py | 76 +- examples/python/wedding_optimal_chart_sat.py | 113 +- .../python/weighted_latency_problem_sat.py | 30 +- examples/python/zebra_sat.py | 74 +- 41 files changed, 18801 insertions(+), 4140 deletions(-) mode change 100755 => 100644 examples/python/bus_driver_scheduling_sat.py mode change 100755 => 100644 examples/python/chemical_balance_sat.py mode change 100755 => 100644 examples/python/flexible_job_shop_sat.py mode change 100755 => 100644 examples/python/gate_scheduling_sat.py mode change 100755 => 100644 examples/python/golomb_sat.py mode change 100644 => 100755 examples/python/hidato_sat.py mode change 100644 => 100755 examples/python/integer_programming.py mode change 100644 => 100755 examples/python/jobshop_ft06_distance_sat.py mode change 100644 => 100755 examples/python/jobshop_ft06_sat.py mode change 100755 => 100644 examples/python/knapsack_2d_sat.py mode change 100755 => 100644 examples/python/line_balancing_sat.py mode change 100755 => 100644 examples/python/shift_scheduling_sat.py diff --git a/examples/python/appointments.py b/examples/python/appointments.py index 1354abcff1..e88571070d 100755 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -27,14 +27,13 @@ from absl import app from absl import flags from ortools.linear_solver import pywraplp from ortools.sat.python import cp_model + # [END import] -_LOAD_MIN = flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.') -_LOAD_MAX = flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.') -_COMMUTE_TIME = flags.DEFINE_integer('commute_time', 30, - 'Commute time in minutes.') -_NUM_WORKERS = flags.DEFINE_integer('num_workers', 98, - 'Maximum number of workers.') +_LOAD_MIN = flags.DEFINE_integer("load_min", 480, "Minimum load in minutes.") +_LOAD_MAX = flags.DEFINE_integer("load_max", 540, "Maximum load in minutes.") +_COMMUTE_TIME = flags.DEFINE_integer("commute_time", 30, "Commute time in minutes.") +_NUM_WORKERS = flags.DEFINE_integer("num_workers", 98, "Maximum number of workers.") class AllSolutionCollector(cp_model.CpSolverSolutionCallback): @@ -55,24 +54,21 @@ class AllSolutionCollector(cp_model.CpSolverSolutionCallback): return self.__collect -def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, - total_size_max): +def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max): """Enumerate all possible knapsacks with total size in the given range. - Args: - item_sizes: a list of integers. item_sizes[i] is the size of item #i. - total_size_min: an integer, the minimum total size. - total_size_max: an integer, the maximum total size. + Args: + item_sizes: a list of integers. item_sizes[i] is the size of item #i. + total_size_min: an integer, the minimum total size. + total_size_max: an integer, the maximum total size. - Returns: - The list of all the knapsacks whose total size is in the given inclusive - range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an - nonnegative integer: the number of times we put item #K in the knapsack. - """ + Returns: + The list of all the knapsacks whose total size is in the given inclusive + range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an + 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.NewIntVar(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) @@ -85,52 +81,52 @@ def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, return solution_collector.combinations() -def AggregateItemCollectionsOptimally(item_collections, max_num_collections, - ideal_item_ratios): +def AggregateItemCollectionsOptimally( + item_collections, max_num_collections, ideal_item_ratios +): """Selects a set (with repetition) of combination of items optimally. - Given a set of collections of N possible items (in each collection, an item - may appear multiple times), a given "ideal breakdown of items", and a - maximum number of collections, this method finds the optimal way to - aggregate the collections in order to: - - maximize the overall number of items - - while keeping the ratio of each item, among the overall selection, as close - as possible to a given input ratio (which depends on the item). - Each collection may be selected more than one time. + Given a set of collections of N possible items (in each collection, an item + may appear multiple times), a given "ideal breakdown of items", and a + maximum number of collections, this method finds the optimal way to + aggregate the collections in order to: + - maximize the overall number of items + - while keeping the ratio of each item, among the overall selection, as close + as possible to a given input ratio (which depends on the item). + Each collection may be selected more than one time. - Args: - item_collections: a list of item collections. Each item collection is a list - of integers [#item0, ..., #itemN-1], where #itemK is the number of times - item #K appears in the collection, and N is the number of distinct items. - max_num_collections: an integer, the maximum number of item collections that - may be selected (counting repetitions of the same collection). - ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is - the ideal ratio of item #K in the whole aggregated selection. + Args: + item_collections: a list of item collections. Each item collection is a list + of integers [#item0, ..., #itemN-1], where #itemK is the number of times + item #K appears in the collection, and N is the number of distinct items. + max_num_collections: an integer, the maximum number of item collections that + may be selected (counting repetitions of the same collection). + ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is + the ideal ratio of item #K in the whole aggregated selection. - Returns: - A pair (objective value, list of pairs (item collection, num_selections)), - where: - - "objective value" is the value of the internal objective function used - by the MIP Solver - - Each "item collection" is an element of the input item_collections - - and its associated "num_selections" is the number of times it was - selected. - """ - solver = pywraplp.Solver.CreateSolver('SCIP') + Returns: + A pair (objective value, list of pairs (item collection, num_selections)), + where: + - "objective value" is the value of the internal objective function used + by the MIP Solver + - Each "item collection" is an element of the input item_collections + - and its associated "num_selections" is the number of times it was + selected. + """ + solver = pywraplp.Solver.CreateSolver("SCIP") if not solver: return [] n = len(ideal_item_ratios) num_distinct_collections = len(item_collections) max_num_items_per_collection = 0 for template in item_collections: - max_num_items_per_collection = max(max_num_items_per_collection, - sum(template)) + max_num_items_per_collection = max(max_num_items_per_collection, sum(template)) upper_bound = max_num_items_per_collection * max_num_collections # num_selections_of_collection[i] is an IntVar that represents the number # of times that we will use collection #i in our global selection. num_selections_of_collection = [ - solver.IntVar(0, max_num_collections, 's[%d]' % i) + solver.IntVar(0, max_num_collections, "s[%d]" % i) for i in range(num_distinct_collections) ] @@ -138,19 +134,17 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections, # aggregated over all selected collections. This is enforced with dedicated # constraints that bind them with the num_selections_of_collection vars. num_overall_item = [ - solver.IntVar(0, upper_bound, 'num_overall_item[%d]' % i) - for i in range(n) + solver.IntVar(0, upper_bound, "num_overall_item[%d]" % i) for i in range(n) ] for i in range(n): ct = solver.Constraint(0.0, 0.0) ct.SetCoefficient(num_overall_item[i], -1) for j in range(num_distinct_collections): - ct.SetCoefficient(num_selections_of_collection[j], - item_collections[j][i]) + ct.SetCoefficient(num_selections_of_collection[j], item_collections[j][i]) # Maintain the num_all_item variable as the sum of all num_overall_item # variables. - num_all_items = solver.IntVar(0, upper_bound, 'num_all_items') + num_all_items = solver.IntVar(0, upper_bound, "num_all_items") solver.Add(solver.Sum(num_overall_item) == num_all_items) # Sets the total number of workers. @@ -158,15 +152,16 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections, # Objective variables. deviation_vars = [ - solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i) - for i in range(n) + solver.NumVar(0, upper_bound, "deviation_vars[%d]" % i) for i in range(n) ] for i in range(n): deviation = deviation_vars[i] - solver.Add(deviation >= num_overall_item[i] - - ideal_item_ratios[i] * num_all_items) - solver.Add(deviation >= ideal_item_ratios[i] * num_all_items - - num_overall_item[i]) + solver.Add( + deviation >= num_overall_item[i] - ideal_item_ratios[i] * num_all_items + ) + solver.Add( + deviation >= ideal_item_ratios[i] * num_all_items - num_overall_item[i] + ) solver.Maximize(num_all_items - solver.Sum(deviation_vars)) @@ -181,43 +176,57 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections, def GetOptimalSchedule(demand): """Computes the optimal schedule for the installation input. - Args: - demand: a list of "appointment types". Each "appointment type" is a triple - (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the - ideal percentage (in [0..100.0]) of that type of appointment among all - appointments scheduled. + Args: + demand: a list of "appointment types". Each "appointment type" is a triple + (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the + ideal percentage (in [0..100.0]) of that type of appointment among all + appointments scheduled. - Returns: - The same output type as EnumerateAllKnapsacksWithRepetition. - """ + Returns: + The same output type as EnumerateAllKnapsacksWithRepetition. + """ combinations = EnumerateAllKnapsacksWithRepetition( - [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, - _LOAD_MAX.value) - print(('Found %d possible day schedules ' % len(combinations) + - '(i.e. combination of appointments filling up one worker\'s day)')) + [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value + ) + print( + ( + "Found %d possible day schedules " % len(combinations) + + "(i.e. combination of appointments filling up one worker's day)" + ) + ) selection = AggregateItemCollectionsOptimally( - combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand]) + combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand] + ) output = [] for i in range(len(selection)): if selection[i] != 0: - output.append((selection[i], [(combinations[i][t], demand[t][1]) - for t in range(len(demand)) - if combinations[i][t] != 0])) + output.append( + ( + selection[i], + [ + (combinations[i][t], demand[t][1]) + for t in range(len(demand)) + if combinations[i][t] != 0 + ], + ) + ) return output def main(_): - demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)] - print('*** input problem ***') - print('Appointments: ') + demand = [(45.0, "Type1", 90), (30.0, "Type2", 120), (25.0, "Type3", 180)] + print("*** input problem ***") + print("Appointments: ") for a in demand: - print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2])) - print('Commute time = %d' % _COMMUTE_TIME.value) - print('Acceptable duration of a work day = [%d..%d]' % - (_LOAD_MIN.value, _LOAD_MAX.value)) - print('%d workers' % _NUM_WORKERS.value) + print(" %.2f%% of %s : %d min" % (a[0], a[1], a[2])) + print("Commute time = %d" % _COMMUTE_TIME.value) + print( + "Acceptable duration of a work day = [%d..%d]" + % (_LOAD_MIN.value, _LOAD_MAX.value) + ) + print("%d workers" % _NUM_WORKERS.value) selection = GetOptimalSchedule(demand) print() installed = 0 @@ -226,30 +235,30 @@ def main(_): installed_per_type[a[1]] = 0 # [START print_solution] - print('*** output solution ***') + print("*** output solution ***") for template in selection: num_instances = template[0] - print('%d schedules with ' % num_instances) + print("%d schedules with " % num_instances) for t in template[1]: mult = t[0] - print(' %d installation of type %s' % (mult, t[1])) + print(" %d installation of type %s" % (mult, t[1])) installed += num_instances * mult installed_per_type[t[1]] += num_instances * mult print() - print('%d installations planned' % installed) + print("%d installations planned" % installed) for a in demand: name = a[1] 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 type {name} planned" ) else: - print(f' {per_type} installations of type {name} planned') + print(f" {per_type} installations of type {name} planned") # [END print_solution] -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) # [END program] diff --git a/examples/python/assignment_with_constraints_sat.py b/examples/python/assignment_with_constraints_sat.py index b16e89e145..b0dc5b5db6 100644 --- a/examples/python/assignment_with_constraints_sat.py +++ b/examples/python/assignment_with_constraints_sat.py @@ -11,6 +11,7 @@ # 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. + """Solve an assignment problem with combination constraints on workers.""" from typing import Sequence @@ -21,19 +22,27 @@ from ortools.sat.python import cp_model def solve_assignment(): """Solve the assignment problem.""" # Data. - cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48, 101], - [125, 95, 90, 105, 59, 120], [45, 110, 95, 115, 104, 83], - [60, 105, 80, 75, 59, 62], [45, 65, 110, 95, 47, 31], - [38, 51, 107, 41, 69, 99], [47, 85, 57, 71, 92, 77], - [39, 63, 97, 49, 118, 56], [47, 101, 71, 60, 88, 109], - [17, 39, 103, 64, 61, 92], [101, 45, 83, 59, 92, 27]] + cost = [ + [90, 76, 75, 70, 50, 74], + [35, 85, 55, 65, 48, 101], + [125, 95, 90, 105, 59, 120], + [45, 110, 95, 115, 104, 83], + [60, 105, 80, 75, 59, 62], + [45, 65, 110, 95, 47, 31], + [38, 51, 107, 41, 69, 99], + [47, 85, 57, 71, 92, 77], + [39, 63, 97, 49, 118, 56], + [47, 101, 71, 60, 88, 109], + [17, 39, 103, 64, 61, 92], + [101, 45, 83, 59, 92, 27], + ] group1 = [ [0, 0, 1, 1], # Workers 2, 3 [0, 1, 0, 1], # Workers 1, 3 [0, 1, 1, 0], # Workers 1, 2 [1, 1, 0, 0], # Workers 0, 1 - [1, 0, 1, 0] + [1, 0, 1, 0], ] # Workers 0, 2 group2 = [ @@ -41,7 +50,7 @@ def solve_assignment(): [0, 1, 0, 1], # Workers 5, 7 [0, 1, 1, 0], # Workers 5, 6 [1, 1, 0, 0], # Workers 4, 5 - [1, 0, 0, 1] + [1, 0, 0, 1], ] # Workers 4, 7 group3 = [ @@ -49,7 +58,7 @@ def solve_assignment(): [0, 1, 0, 1], # Workers 9, 11 [0, 1, 1, 0], # Workers 9, 10 [1, 0, 1, 0], # Workers 8, 10 - [1, 0, 0, 1] + [1, 0, 0, 1], ] # Workers 8, 11 sizes = [10, 7, 3, 12, 15, 4, 11, 5] @@ -63,10 +72,10 @@ 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] - works = [model.NewBoolVar('works[%i]' % i) for i in all_workers] + selected = [ + [model.NewBoolVar("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] # Constraints @@ -80,49 +89,45 @@ def solve_assignment(): # 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.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) # Objective model.Minimize( - sum(selected[i][j] * cost[i][j] - for j in all_tasks - for i in all_workers)) + 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) if status == cp_model.OPTIMAL: - print('Total cost = %i' % solver.ObjectiveValue()) + print("Total cost = %i" % solver.ObjectiveValue()) print() for i in all_workers: for j in all_tasks: if solver.BooleanValue(selected[i][j]): - print('Worker ', i, ' assigned to task ', j, ' Cost = ', - cost[i][j]) + print( + "Worker ", i, " assigned to task ", j, " Cost = ", cost[i][j] + ) print() - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") solve_assignment() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/balance_group_sat.py b/examples/python/balance_group_sat.py index e6098f88a7..b11fb8fe11 100644 --- a/examples/python/balance_group_sat.py +++ b/examples/python/balance_group_sat.py @@ -11,6 +11,7 @@ # 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. + """We are trying to group items in equal sized groups. Each item has a color and a value. We want the sum of values of each group to @@ -37,10 +38,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): self.__item_in_group = item_in_group def on_solution_callback(self): - print('Solution %i' % self.__solution_count) + print("Solution %i" % self.__solution_count) self.__solution_count += 1 - print(' objective value = %i' % self.ObjectiveValue()) + print(" objective value = %i" % self.ObjectiveValue()) groups = {} sums = {} for g in self.__all_groups: @@ -53,19 +54,19 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): for g in self.__all_groups: group = groups[g] - print('group %i: sum = %0.2f [' % (g, sums[g]), end='') + print("group %i: sum = %0.2f [" % (g, sums[g]), end="") for item in group: value = self.__values[item] color = self.__colors[item] - print(' (%i, %i, %i)' % (item, value, color), end='') - print(']') + print(" (%i, %i, %i)" % (item, value, color), end="") + print("]") def main(argv: Sequence[str]) -> None: """Solves a group balancing problem.""" if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") # Data. num_groups = 10 num_items = 100 @@ -94,9 +95,11 @@ def main(argv: Sequence[str]) -> None: if colors[i] == c: items_per_color[c].append(i) - print('Model has %i items, %i groups, and %i colors' % - (num_items, num_groups, num_colors)) - print(' average sum per group = %i' % average_sum_per_group) + print( + "Model has %i items, %i groups, and %i colors" + % (num_items, num_groups, num_colors) + ) + print(" average sum per group = %i" % average_sum_per_group) # Model. @@ -105,43 +108,42 @@ 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.NewBoolVar("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) # The deviation of the sum of each items in a group against the average. - e = model.NewIntVar(0, 550, 'epsilon') + e = model.NewIntVar(0, 550, "epsilon") # Constrain the sum of values in one group around the average sum per group. for g in all_groups: model.Add( - sum(item_in_group[(i, g)] * values[i] - for i in all_items) <= average_sum_per_group + e) + sum(item_in_group[(i, g)] * values[i] for i in all_items) + <= average_sum_per_group + e + ) model.Add( - sum(item_in_group[(i, g)] * values[i] - for i in all_items) >= average_sum_per_group - e) + sum(item_in_group[(i, g)] * values[i] for i in all_items) + >= average_sum_per_group - e + ) # color_in_group variables. color_in_group = {} for g in all_groups: for c in all_colors: color_in_group[(c, g)] = model.NewBoolVar( - 'color %d is in group %d' % (c, g)) + "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.AddImplication(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. @@ -149,8 +151,9 @@ def main(argv: Sequence[str]) -> None: for g in all_groups: literal = color_in_group[(c, g)] model.Add( - sum(item_in_group[(i, g)] for i in items_per_color[c]) >= - min_items_of_same_color_per_group).OnlyEnforceIf(literal) + sum(item_in_group[(i, g)] for i in items_per_color[c]) + >= min_items_of_same_color_per_group + ).OnlyEnforceIf(literal) # Compute the maximum number of colors in a group. max_color = num_items_per_group // min_items_of_same_color_per_group @@ -158,8 +161,7 @@ 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) @@ -167,19 +169,20 @@ def main(argv: Sequence[str]) -> None: solver = cp_model.CpSolver() # solver.parameters.log_search_progress = True solver.parameters.num_workers = 16 - solution_printer = SolutionPrinter(values, colors, all_groups, all_items, - item_in_group) + solution_printer = SolutionPrinter( + values, colors, all_groups, all_items, item_in_group + ) status = solver.Solve(model, solution_printer) if status == cp_model.OPTIMAL: - print('Optimal epsilon: %i' % solver.ObjectiveValue()) - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) + print("Optimal epsilon: %i" % solver.ObjectiveValue()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) else: - print('No solution found') + print("No solution found") -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/bus_driver_scheduling_sat.py b/examples/python/bus_driver_scheduling_sat.py old mode 100755 new mode 100644 index 765f7562f2..18d5713f97 --- a/examples/python/bus_driver_scheduling_sat.py +++ b/examples/python/bus_driver_scheduling_sat.py @@ -11,6 +11,7 @@ # 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. + """This model implements a bus driver scheduling problem. Constraints: @@ -32,13 +33,16 @@ 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.') + "output_proto", "", "Output file to write the cp_model proto to." +) _PARAMS = flags.DEFINE_string( - 'params', - 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:30', - 'Sat solver parameters.') -_INSTANCE = flags.DEFINE_integer('instance', 0, - 'Instance to select (0, 1, 2, 3).', 0, 3) + "params", + "num_search_workers:16,log_search_progress:true,max_time_in_seconds:30", + "Sat solver parameters.", +) +_INSTANCE = flags.DEFINE_integer( + "instance", 0, "Instance to select (0, 1, 2, 3).", 0, 3 +) SAMPLE_SHIFTS_TINY = [ # @@ -50,33 +54,33 @@ SAMPLE_SHIFTS_TINY = [ # - shift end minute # - shift duration in minutes # - [1, '08:00', '09:05', 480, 545, 65], - [2, '08:00', '08:35', 480, 515, 35], - [3, '08:11', '09:41', 491, 581, 90], - [4, '08:28', '08:50', 508, 530, 22], - [5, '08:35', '08:45', 515, 525, 10], - [6, '08:40', '08:50', 520, 530, 10], - [7, '09:03', '10:28', 543, 628, 85], - [8, '09:23', '09:49', 563, 589, 26], - [9, '09:30', '09:40', 570, 580, 10], - [10, '09:57', '10:20', 597, 620, 23], - [11, '10:09', '11:03', 609, 663, 54], - [12, '10:20', '10:30', 620, 630, 10], - [13, '11:00', '11:10', 660, 670, 10], - [14, '11:45', '12:24', 705, 744, 39], - [15, '12:18', '13:00', 738, 780, 42], - [16, '13:18', '14:44', 798, 884, 86], - [17, '13:53', '14:49', 833, 889, 56], - [18, '14:03', '14:50', 843, 890, 47], - [19, '14:28', '15:15', 868, 915, 47], - [20, '14:30', '15:41', 870, 941, 71], - [21, '14:48', '15:35', 888, 935, 47], - [22, '15:03', '15:50', 903, 950, 47], - [23, '15:28', '16:54', 928, 1014, 86], - [24, '15:38', '16:25', 938, 985, 47], - [25, '15:40', '15:56', 940, 956, 16], - [26, '15:58', '16:45', 958, 1005, 47], - [27, '16:04', '17:30', 964, 1050, 86], + [1, "08:00", "09:05", 480, 545, 65], + [2, "08:00", "08:35", 480, 515, 35], + [3, "08:11", "09:41", 491, 581, 90], + [4, "08:28", "08:50", 508, 530, 22], + [5, "08:35", "08:45", 515, 525, 10], + [6, "08:40", "08:50", 520, 530, 10], + [7, "09:03", "10:28", 543, 628, 85], + [8, "09:23", "09:49", 563, 589, 26], + [9, "09:30", "09:40", 570, 580, 10], + [10, "09:57", "10:20", 597, 620, 23], + [11, "10:09", "11:03", 609, 663, 54], + [12, "10:20", "10:30", 620, 630, 10], + [13, "11:00", "11:10", 660, 670, 10], + [14, "11:45", "12:24", 705, 744, 39], + [15, "12:18", "13:00", 738, 780, 42], + [16, "13:18", "14:44", 798, 884, 86], + [17, "13:53", "14:49", 833, 889, 56], + [18, "14:03", "14:50", 843, 890, 47], + [19, "14:28", "15:15", 868, 915, 47], + [20, "14:30", "15:41", 870, 941, 71], + [21, "14:48", "15:35", 888, 935, 47], + [22, "15:03", "15:50", 903, 950, 47], + [23, "15:28", "16:54", 928, 1014, 86], + [24, "15:38", "16:25", 938, 985, 47], + [25, "15:40", "15:56", 940, 956, 16], + [26, "15:58", "16:45", 958, 1005, 47], + [27, "16:04", "17:30", 964, 1050, 86], ] # yapf:disable SAMPLE_SHIFTS_SMALL = [ @@ -89,1643 +93,1642 @@ SAMPLE_SHIFTS_SMALL = [ # - shift end minute # - shift duration in minutes # - [0, '05:18', '06:00', 318, 360, 42], - [1, '05:26', '06:08', 326, 368, 42], - [2, '05:40', '05:56', 340, 356, 16], - [3, '06:06', '06:51', 366, 411, 45], - [4, '06:40', '07:52', 400, 472, 72], - [5, '06:42', '07:13', 402, 433, 31], - [6, '06:48', '08:15', 408, 495, 87], - [7, '06:59', '08:07', 419, 487, 68], - [8, '07:20', '07:36', 440, 456, 16], - [9, '07:35', '08:22', 455, 502, 47], - [10, '07:50', '08:55', 470, 535, 65], - [11, '08:00', '09:05', 480, 545, 65], - [12, '08:00', '08:35', 480, 515, 35], - [13, '08:11', '09:41', 491, 581, 90], - [14, '08:28', '08:50', 508, 530, 22], - [15, '08:35', '08:45', 515, 525, 10], - [16, '08:40', '08:50', 520, 530, 10], - [17, '09:03', '10:28', 543, 628, 85], - [18, '09:23', '09:49', 563, 589, 26], - [19, '09:30', '09:40', 570, 580, 10], - [20, '09:57', '10:20', 597, 620, 23], - [21, '10:09', '11:03', 609, 663, 54], - [22, '10:20', '10:30', 620, 630, 10], - [23, '11:00', '11:10', 660, 670, 10], - [24, '11:45', '12:24', 705, 744, 39], - [25, '12:18', '13:00', 738, 780, 42], - [26, '13:18', '14:44', 798, 884, 86], - [27, '13:53', '14:49', 833, 889, 56], - [28, '14:03', '14:50', 843, 890, 47], - [29, '14:28', '15:15', 868, 915, 47], - [30, '14:30', '15:41', 870, 941, 71], - [31, '14:48', '15:35', 888, 935, 47], - [32, '15:03', '15:50', 903, 950, 47], - [33, '15:28', '16:54', 928, 1014, 86], - [34, '15:38', '16:25', 938, 985, 47], - [35, '15:40', '15:56', 940, 956, 16], - [36, '15:58', '16:45', 958, 1005, 47], - [37, '16:04', '17:30', 964, 1050, 86], - [38, '16:28', '17:15', 988, 1035, 47], - [39, '16:36', '17:21', 996, 1041, 45], - [40, '16:50', '17:00', 1010, 1020, 10], - [41, '16:54', '18:20', 1014, 1100, 86], - [42, '17:01', '17:13', 1021, 1033, 12], - [43, '17:19', '18:31', 1039, 1111, 72], - [44, '17:23', '18:10', 1043, 1090, 47], - [45, '17:34', '18:15', 1054, 1095, 41], - [46, '18:04', '19:29', 1084, 1169, 85], - [47, '18:34', '19:58', 1114, 1198, 84], - [48, '19:56', '20:34', 1196, 1234, 38], - [49, '20:05', '20:48', 1205, 1248, 43] + [0, "05:18", "06:00", 318, 360, 42], + [1, "05:26", "06:08", 326, 368, 42], + [2, "05:40", "05:56", 340, 356, 16], + [3, "06:06", "06:51", 366, 411, 45], + [4, "06:40", "07:52", 400, 472, 72], + [5, "06:42", "07:13", 402, 433, 31], + [6, "06:48", "08:15", 408, 495, 87], + [7, "06:59", "08:07", 419, 487, 68], + [8, "07:20", "07:36", 440, 456, 16], + [9, "07:35", "08:22", 455, 502, 47], + [10, "07:50", "08:55", 470, 535, 65], + [11, "08:00", "09:05", 480, 545, 65], + [12, "08:00", "08:35", 480, 515, 35], + [13, "08:11", "09:41", 491, 581, 90], + [14, "08:28", "08:50", 508, 530, 22], + [15, "08:35", "08:45", 515, 525, 10], + [16, "08:40", "08:50", 520, 530, 10], + [17, "09:03", "10:28", 543, 628, 85], + [18, "09:23", "09:49", 563, 589, 26], + [19, "09:30", "09:40", 570, 580, 10], + [20, "09:57", "10:20", 597, 620, 23], + [21, "10:09", "11:03", 609, 663, 54], + [22, "10:20", "10:30", 620, 630, 10], + [23, "11:00", "11:10", 660, 670, 10], + [24, "11:45", "12:24", 705, 744, 39], + [25, "12:18", "13:00", 738, 780, 42], + [26, "13:18", "14:44", 798, 884, 86], + [27, "13:53", "14:49", 833, 889, 56], + [28, "14:03", "14:50", 843, 890, 47], + [29, "14:28", "15:15", 868, 915, 47], + [30, "14:30", "15:41", 870, 941, 71], + [31, "14:48", "15:35", 888, 935, 47], + [32, "15:03", "15:50", 903, 950, 47], + [33, "15:28", "16:54", 928, 1014, 86], + [34, "15:38", "16:25", 938, 985, 47], + [35, "15:40", "15:56", 940, 956, 16], + [36, "15:58", "16:45", 958, 1005, 47], + [37, "16:04", "17:30", 964, 1050, 86], + [38, "16:28", "17:15", 988, 1035, 47], + [39, "16:36", "17:21", 996, 1041, 45], + [40, "16:50", "17:00", 1010, 1020, 10], + [41, "16:54", "18:20", 1014, 1100, 86], + [42, "17:01", "17:13", 1021, 1033, 12], + [43, "17:19", "18:31", 1039, 1111, 72], + [44, "17:23", "18:10", 1043, 1090, 47], + [45, "17:34", "18:15", 1054, 1095, 41], + [46, "18:04", "19:29", 1084, 1169, 85], + [47, "18:34", "19:58", 1114, 1198, 84], + [48, "19:56", "20:34", 1196, 1234, 38], + [49, "20:05", "20:48", 1205, 1248, 43], ] # yapf:disable SAMPLE_SHIFTS_MEDIUM = [ - [0, '04:30', '04:53', 270, 293, 23], - [1, '04:46', '04:56', 286, 296, 10], - [2, '04:52', '05:56', 292, 356, 64], - [3, '04:53', '05:23', 293, 323, 30], - [4, '05:07', '05:44', 307, 344, 37], - [5, '05:10', '06:06', 310, 366, 56], - [6, '05:18', '06:03', 318, 363, 45], - [7, '05:30', '05:40', 330, 340, 10], - [8, '05:30', '05:40', 330, 340, 10], - [9, '05:33', '06:15', 333, 375, 42], - [10, '05:40', '05:50', 340, 350, 10], - [11, '05:43', '06:08', 343, 368, 25], - [12, '05:54', '07:20', 354, 440, 86], - [13, '06:04', '06:37', 364, 397, 33], - [14, '06:13', '06:58', 373, 418, 45], - [15, '06:14', '07:40', 374, 460, 86], - [16, '06:15', '07:15', 375, 435, 60], - [17, '06:16', '06:26', 376, 386, 10], - [18, '06:17', '06:34', 377, 394, 17], - [19, '06:20', '06:36', 380, 396, 16], - [20, '06:22', '07:06', 382, 426, 44], - [21, '06:24', '07:50', 384, 470, 86], - [22, '06:27', '06:44', 387, 404, 17], - [23, '06:30', '06:40', 390, 400, 10], - [24, '06:31', '06:43', 391, 403, 12], - [25, '06:33', '07:53', 393, 473, 80], - [26, '06:34', '07:09', 394, 429, 35], - [27, '06:40', '06:56', 400, 416, 16], - [28, '06:44', '07:17', 404, 437, 33], - [29, '06:46', '06:58', 406, 418, 12], - [30, '06:49', '07:43', 409, 463, 54], - [31, '06:50', '07:05', 410, 425, 15], - [32, '06:52', '07:36', 412, 456, 44], - [33, '06:54', '07:27', 414, 447, 33], - [34, '06:56', '08:23', 416, 503, 87], - [35, '07:04', '07:44', 424, 464, 40], - [36, '07:11', '08:36', 431, 516, 85], - [37, '07:17', '07:35', 437, 455, 18], - [38, '07:22', '08:06', 442, 486, 44], - [39, '07:27', '08:15', 447, 495, 48], - [40, '07:35', '07:45', 455, 465, 10], - [41, '07:43', '08:08', 463, 488, 25], - [42, '07:50', '08:37', 470, 517, 47], - [43, '07:58', '08:45', 478, 525, 47], - [44, '08:00', '08:35', 480, 515, 35], - [45, '08:06', '08:51', 486, 531, 45], - [46, '08:10', '08:45', 490, 525, 35], - [47, '08:15', '08:30', 495, 510, 15], - [48, '08:16', '09:00', 496, 540, 44], - [49, '08:18', '09:16', 498, 556, 58], - [50, '08:20', '08:36', 500, 516, 16], - [51, '08:27', '09:07', 507, 547, 40], - [52, '08:30', '08:45', 510, 525, 15], - [53, '08:35', '09:15', 515, 555, 40], - [54, '08:46', '09:30', 526, 570, 44], - [55, '08:51', '09:17', 531, 557, 26], - [56, '08:55', '09:15', 535, 555, 20], - [57, '08:58', '09:38', 538, 578, 40], - [58, '09:00', '09:35', 540, 575, 35], - [59, '09:00', '09:16', 540, 556, 16], - [60, '09:20', '09:36', 560, 576, 16], - [61, '09:31', '09:43', 571, 583, 12], - [62, '09:33', '10:15', 573, 615, 42], - [63, '09:54', '10:05', 594, 605, 11], - [64, '10:11', '10:38', 611, 638, 27], - [65, '10:18', '11:00', 618, 660, 42], - [66, '10:21', '10:47', 621, 647, 26], - [67, '10:25', '11:04', 625, 664, 39], - [68, '10:26', '11:08', 626, 668, 42], - [69, '10:44', '12:11', 644, 731, 87], - [70, '11:00', '11:16', 660, 676, 16], - [71, '11:15', '11:54', 675, 714, 39], - [72, '11:16', '11:28', 676, 688, 12], - [73, '11:20', '11:30', 680, 690, 10], - [74, '11:21', '11:47', 681, 707, 26], - [75, '11:25', '12:04', 685, 724, 39], - [76, '11:34', '11:45', 694, 705, 11], - [77, '11:35', '12:14', 695, 734, 39], - [78, '11:41', '12:23', 701, 743, 42], - [79, '11:44', '12:35', 704, 755, 51], - [80, '11:46', '11:58', 706, 718, 12], - [81, '12:00', '12:10', 720, 730, 10], - [82, '12:04', '12:15', 724, 735, 11], - [83, '12:04', '13:04', 724, 784, 60], - [84, '12:11', '12:38', 731, 758, 27], - [85, '12:15', '12:54', 735, 774, 39], - [86, '12:25', '13:10', 745, 790, 45], - [87, '12:30', '12:40', 750, 760, 10], - [88, '12:34', '13:58', 754, 838, 84], - [89, '12:38', '13:25', 758, 805, 47], - [90, '12:48', '13:35', 768, 815, 47], - [91, '13:00', '13:16', 780, 796, 16], - [92, '13:05', '13:44', 785, 824, 39], - [93, '13:08', '13:55', 788, 835, 47], - [94, '13:14', '14:38', 794, 878, 84], - [95, '13:23', '13:49', 803, 829, 26], - [96, '13:25', '14:04', 805, 844, 39], - [97, '13:28', '14:54', 808, 894, 86], - [98, '13:31', '13:43', 811, 823, 12], - [99, '13:34', '14:58', 814, 898, 84], - [100, '13:38', '14:25', 818, 865, 47], - [101, '13:38', '15:04', 818, 904, 86], - [102, '13:39', '14:33', 819, 873, 54], - [103, '13:40', '13:50', 820, 830, 10], - [104, '13:43', '14:10', 823, 850, 27], - [105, '13:48', '14:35', 828, 875, 47], - [106, '13:48', '14:35', 828, 875, 47], - [107, '13:53', '14:40', 833, 880, 47], - [108, '13:58', '15:24', 838, 924, 86], - [109, '13:58', '14:25', 838, 865, 27], - [110, '14:00', '14:16', 840, 856, 16], - [111, '14:13', '15:00', 853, 900, 47], - [112, '14:20', '15:31', 860, 931, 71], - [113, '14:25', '15:02', 865, 902, 37], - [114, '14:34', '14:45', 874, 885, 11], - [115, '14:40', '15:51', 880, 951, 71], - [116, '14:40', '14:56', 880, 896, 16], - [117, '14:46', '14:58', 886, 898, 12], - [118, '14:49', '15:43', 889, 943, 54], - [119, '14:52', '15:21', 892, 921, 29], - [120, '14:58', '16:24', 898, 984, 86], - [121, '14:59', '15:53', 899, 953, 54], - [122, '15:00', '15:10', 900, 910, 10], - [123, '15:00', '15:35', 900, 935, 35], - [124, '15:08', '15:45', 908, 945, 37], - [125, '15:12', '15:36', 912, 936, 24], - [126, '15:18', '16:05', 918, 965, 47], - [127, '15:24', '16:05', 924, 965, 41], - [128, '15:31', '15:43', 931, 943, 12], - [129, '15:35', '15:54', 935, 954, 19], - [130, '15:36', '16:21', 936, 981, 45], - [131, '15:39', '16:33', 939, 993, 54], - [132, '15:48', '16:35', 948, 995, 47], - [133, '15:50', '17:01', 950, 1021, 71], - [134, '16:03', '16:50', 963, 1010, 47], - [135, '16:18', '17:44', 978, 1064, 86], - [136, '16:24', '17:05', 984, 1025, 41], - [137, '16:28', '17:15', 988, 1035, 47], - [138, '16:34', '17:15', 994, 1035, 41], - [139, '16:38', '17:25', 998, 1045, 47], - [140, '16:40', '16:56', 1000, 1016, 16], - [141, '16:45', '17:04', 1005, 1024, 19], - [142, '16:52', '17:36', 1012, 1056, 44], - [143, '16:58', '17:45', 1018, 1065, 47], - [144, '17:04', '18:30', 1024, 1110, 86], - [145, '17:04', '17:45', 1024, 1065, 41], - [146, '17:09', '18:03', 1029, 1083, 54], - [147, '17:18', '18:44', 1038, 1124, 86], - [148, '17:28', '18:15', 1048, 1095, 47], - [149, '17:29', '18:41', 1049, 1121, 72], - [150, '17:36', '18:21', 1056, 1101, 45], - [151, '17:38', '18:25', 1058, 1105, 47], - [152, '17:40', '17:56', 1060, 1076, 16], - [153, '17:45', '18:04', 1065, 1084, 19], - [154, '17:46', '17:58', 1066, 1078, 12], - [155, '17:48', '18:35', 1068, 1115, 47], - [156, '17:49', '18:43', 1069, 1123, 54], - [157, '17:55', '18:14', 1075, 1094, 19], - [158, '17:58', '18:45', 1078, 1125, 47], - [159, '18:00', '19:11', 1080, 1151, 71], - [160, '18:04', '18:45', 1084, 1125, 41], - [161, '18:09', '19:03', 1089, 1143, 54], - [162, '18:13', '19:00', 1093, 1140, 47], - [163, '18:13', '18:40', 1093, 1120, 27], - [164, '18:19', '19:13', 1099, 1153, 54], - [165, '18:28', '19:25', 1108, 1165, 57], - [166, '18:48', '19:28', 1128, 1168, 40], - [167, '19:03', '19:45', 1143, 1185, 42], - [168, '19:20', '19:36', 1160, 1176, 16], - [169, '19:21', '19:31', 1161, 1171, 10], - [170, '19:25', '20:04', 1165, 1204, 39], - [171, '19:26', '20:08', 1166, 1208, 42], - [172, '19:30', '19:40', 1170, 1180, 10], - [173, '19:44', '20:33', 1184, 1233, 49], - [174, '19:48', '21:09', 1188, 1269, 81], - [175, '19:53', '21:02', 1193, 1262, 69], - [176, '20:04', '20:29', 1204, 1229, 25], - [177, '20:17', '21:03', 1217, 1263, 46], - [178, '20:20', '20:57', 1220, 1257, 37], - [179, '20:29', '21:18', 1229, 1278, 49], - [180, '20:35', '21:54', 1235, 1314, 79], - [181, '20:40', '20:50', 1240, 1250, 10], - [182, '20:47', '21:42', 1247, 1302, 55], - [183, '21:00', '21:10', 1260, 1270, 10], - [184, '21:07', '21:44', 1267, 1304, 37], - [185, '21:14', '22:03', 1274, 1323, 49], - [186, '21:39', '21:55', 1299, 1315, 16], - [187, '21:40', '22:17', 1300, 1337, 37], - [188, '21:40', '21:50', 1300, 1310, 10], - [189, '21:48', '22:03', 1308, 1323, 15], - [190, '22:17', '23:03', 1337, 1383, 46], - [191, '22:43', '23:08', 1363, 1388, 25], - [192, '23:35', '01:05', 1415, 1505, 90], - [193, '23:46', '00:01', 1426, 1441, 15], - [194, '23:47', '00:33', 1427, 1473, 46], - [195, '23:52', '00:24', 1432, 1464, 32], - [196, '23:58', '00:38', 1438, 1478, 40], - [197, '00:02', '00:12', 1442, 1452, 10], - [198, '00:07', '00:39', 1447, 1479, 32], - [199, '00:25', '01:12', 1465, 1512, 47] + [0, "04:30", "04:53", 270, 293, 23], + [1, "04:46", "04:56", 286, 296, 10], + [2, "04:52", "05:56", 292, 356, 64], + [3, "04:53", "05:23", 293, 323, 30], + [4, "05:07", "05:44", 307, 344, 37], + [5, "05:10", "06:06", 310, 366, 56], + [6, "05:18", "06:03", 318, 363, 45], + [7, "05:30", "05:40", 330, 340, 10], + [8, "05:30", "05:40", 330, 340, 10], + [9, "05:33", "06:15", 333, 375, 42], + [10, "05:40", "05:50", 340, 350, 10], + [11, "05:43", "06:08", 343, 368, 25], + [12, "05:54", "07:20", 354, 440, 86], + [13, "06:04", "06:37", 364, 397, 33], + [14, "06:13", "06:58", 373, 418, 45], + [15, "06:14", "07:40", 374, 460, 86], + [16, "06:15", "07:15", 375, 435, 60], + [17, "06:16", "06:26", 376, 386, 10], + [18, "06:17", "06:34", 377, 394, 17], + [19, "06:20", "06:36", 380, 396, 16], + [20, "06:22", "07:06", 382, 426, 44], + [21, "06:24", "07:50", 384, 470, 86], + [22, "06:27", "06:44", 387, 404, 17], + [23, "06:30", "06:40", 390, 400, 10], + [24, "06:31", "06:43", 391, 403, 12], + [25, "06:33", "07:53", 393, 473, 80], + [26, "06:34", "07:09", 394, 429, 35], + [27, "06:40", "06:56", 400, 416, 16], + [28, "06:44", "07:17", 404, 437, 33], + [29, "06:46", "06:58", 406, 418, 12], + [30, "06:49", "07:43", 409, 463, 54], + [31, "06:50", "07:05", 410, 425, 15], + [32, "06:52", "07:36", 412, 456, 44], + [33, "06:54", "07:27", 414, 447, 33], + [34, "06:56", "08:23", 416, 503, 87], + [35, "07:04", "07:44", 424, 464, 40], + [36, "07:11", "08:36", 431, 516, 85], + [37, "07:17", "07:35", 437, 455, 18], + [38, "07:22", "08:06", 442, 486, 44], + [39, "07:27", "08:15", 447, 495, 48], + [40, "07:35", "07:45", 455, 465, 10], + [41, "07:43", "08:08", 463, 488, 25], + [42, "07:50", "08:37", 470, 517, 47], + [43, "07:58", "08:45", 478, 525, 47], + [44, "08:00", "08:35", 480, 515, 35], + [45, "08:06", "08:51", 486, 531, 45], + [46, "08:10", "08:45", 490, 525, 35], + [47, "08:15", "08:30", 495, 510, 15], + [48, "08:16", "09:00", 496, 540, 44], + [49, "08:18", "09:16", 498, 556, 58], + [50, "08:20", "08:36", 500, 516, 16], + [51, "08:27", "09:07", 507, 547, 40], + [52, "08:30", "08:45", 510, 525, 15], + [53, "08:35", "09:15", 515, 555, 40], + [54, "08:46", "09:30", 526, 570, 44], + [55, "08:51", "09:17", 531, 557, 26], + [56, "08:55", "09:15", 535, 555, 20], + [57, "08:58", "09:38", 538, 578, 40], + [58, "09:00", "09:35", 540, 575, 35], + [59, "09:00", "09:16", 540, 556, 16], + [60, "09:20", "09:36", 560, 576, 16], + [61, "09:31", "09:43", 571, 583, 12], + [62, "09:33", "10:15", 573, 615, 42], + [63, "09:54", "10:05", 594, 605, 11], + [64, "10:11", "10:38", 611, 638, 27], + [65, "10:18", "11:00", 618, 660, 42], + [66, "10:21", "10:47", 621, 647, 26], + [67, "10:25", "11:04", 625, 664, 39], + [68, "10:26", "11:08", 626, 668, 42], + [69, "10:44", "12:11", 644, 731, 87], + [70, "11:00", "11:16", 660, 676, 16], + [71, "11:15", "11:54", 675, 714, 39], + [72, "11:16", "11:28", 676, 688, 12], + [73, "11:20", "11:30", 680, 690, 10], + [74, "11:21", "11:47", 681, 707, 26], + [75, "11:25", "12:04", 685, 724, 39], + [76, "11:34", "11:45", 694, 705, 11], + [77, "11:35", "12:14", 695, 734, 39], + [78, "11:41", "12:23", 701, 743, 42], + [79, "11:44", "12:35", 704, 755, 51], + [80, "11:46", "11:58", 706, 718, 12], + [81, "12:00", "12:10", 720, 730, 10], + [82, "12:04", "12:15", 724, 735, 11], + [83, "12:04", "13:04", 724, 784, 60], + [84, "12:11", "12:38", 731, 758, 27], + [85, "12:15", "12:54", 735, 774, 39], + [86, "12:25", "13:10", 745, 790, 45], + [87, "12:30", "12:40", 750, 760, 10], + [88, "12:34", "13:58", 754, 838, 84], + [89, "12:38", "13:25", 758, 805, 47], + [90, "12:48", "13:35", 768, 815, 47], + [91, "13:00", "13:16", 780, 796, 16], + [92, "13:05", "13:44", 785, 824, 39], + [93, "13:08", "13:55", 788, 835, 47], + [94, "13:14", "14:38", 794, 878, 84], + [95, "13:23", "13:49", 803, 829, 26], + [96, "13:25", "14:04", 805, 844, 39], + [97, "13:28", "14:54", 808, 894, 86], + [98, "13:31", "13:43", 811, 823, 12], + [99, "13:34", "14:58", 814, 898, 84], + [100, "13:38", "14:25", 818, 865, 47], + [101, "13:38", "15:04", 818, 904, 86], + [102, "13:39", "14:33", 819, 873, 54], + [103, "13:40", "13:50", 820, 830, 10], + [104, "13:43", "14:10", 823, 850, 27], + [105, "13:48", "14:35", 828, 875, 47], + [106, "13:48", "14:35", 828, 875, 47], + [107, "13:53", "14:40", 833, 880, 47], + [108, "13:58", "15:24", 838, 924, 86], + [109, "13:58", "14:25", 838, 865, 27], + [110, "14:00", "14:16", 840, 856, 16], + [111, "14:13", "15:00", 853, 900, 47], + [112, "14:20", "15:31", 860, 931, 71], + [113, "14:25", "15:02", 865, 902, 37], + [114, "14:34", "14:45", 874, 885, 11], + [115, "14:40", "15:51", 880, 951, 71], + [116, "14:40", "14:56", 880, 896, 16], + [117, "14:46", "14:58", 886, 898, 12], + [118, "14:49", "15:43", 889, 943, 54], + [119, "14:52", "15:21", 892, 921, 29], + [120, "14:58", "16:24", 898, 984, 86], + [121, "14:59", "15:53", 899, 953, 54], + [122, "15:00", "15:10", 900, 910, 10], + [123, "15:00", "15:35", 900, 935, 35], + [124, "15:08", "15:45", 908, 945, 37], + [125, "15:12", "15:36", 912, 936, 24], + [126, "15:18", "16:05", 918, 965, 47], + [127, "15:24", "16:05", 924, 965, 41], + [128, "15:31", "15:43", 931, 943, 12], + [129, "15:35", "15:54", 935, 954, 19], + [130, "15:36", "16:21", 936, 981, 45], + [131, "15:39", "16:33", 939, 993, 54], + [132, "15:48", "16:35", 948, 995, 47], + [133, "15:50", "17:01", 950, 1021, 71], + [134, "16:03", "16:50", 963, 1010, 47], + [135, "16:18", "17:44", 978, 1064, 86], + [136, "16:24", "17:05", 984, 1025, 41], + [137, "16:28", "17:15", 988, 1035, 47], + [138, "16:34", "17:15", 994, 1035, 41], + [139, "16:38", "17:25", 998, 1045, 47], + [140, "16:40", "16:56", 1000, 1016, 16], + [141, "16:45", "17:04", 1005, 1024, 19], + [142, "16:52", "17:36", 1012, 1056, 44], + [143, "16:58", "17:45", 1018, 1065, 47], + [144, "17:04", "18:30", 1024, 1110, 86], + [145, "17:04", "17:45", 1024, 1065, 41], + [146, "17:09", "18:03", 1029, 1083, 54], + [147, "17:18", "18:44", 1038, 1124, 86], + [148, "17:28", "18:15", 1048, 1095, 47], + [149, "17:29", "18:41", 1049, 1121, 72], + [150, "17:36", "18:21", 1056, 1101, 45], + [151, "17:38", "18:25", 1058, 1105, 47], + [152, "17:40", "17:56", 1060, 1076, 16], + [153, "17:45", "18:04", 1065, 1084, 19], + [154, "17:46", "17:58", 1066, 1078, 12], + [155, "17:48", "18:35", 1068, 1115, 47], + [156, "17:49", "18:43", 1069, 1123, 54], + [157, "17:55", "18:14", 1075, 1094, 19], + [158, "17:58", "18:45", 1078, 1125, 47], + [159, "18:00", "19:11", 1080, 1151, 71], + [160, "18:04", "18:45", 1084, 1125, 41], + [161, "18:09", "19:03", 1089, 1143, 54], + [162, "18:13", "19:00", 1093, 1140, 47], + [163, "18:13", "18:40", 1093, 1120, 27], + [164, "18:19", "19:13", 1099, 1153, 54], + [165, "18:28", "19:25", 1108, 1165, 57], + [166, "18:48", "19:28", 1128, 1168, 40], + [167, "19:03", "19:45", 1143, 1185, 42], + [168, "19:20", "19:36", 1160, 1176, 16], + [169, "19:21", "19:31", 1161, 1171, 10], + [170, "19:25", "20:04", 1165, 1204, 39], + [171, "19:26", "20:08", 1166, 1208, 42], + [172, "19:30", "19:40", 1170, 1180, 10], + [173, "19:44", "20:33", 1184, 1233, 49], + [174, "19:48", "21:09", 1188, 1269, 81], + [175, "19:53", "21:02", 1193, 1262, 69], + [176, "20:04", "20:29", 1204, 1229, 25], + [177, "20:17", "21:03", 1217, 1263, 46], + [178, "20:20", "20:57", 1220, 1257, 37], + [179, "20:29", "21:18", 1229, 1278, 49], + [180, "20:35", "21:54", 1235, 1314, 79], + [181, "20:40", "20:50", 1240, 1250, 10], + [182, "20:47", "21:42", 1247, 1302, 55], + [183, "21:00", "21:10", 1260, 1270, 10], + [184, "21:07", "21:44", 1267, 1304, 37], + [185, "21:14", "22:03", 1274, 1323, 49], + [186, "21:39", "21:55", 1299, 1315, 16], + [187, "21:40", "22:17", 1300, 1337, 37], + [188, "21:40", "21:50", 1300, 1310, 10], + [189, "21:48", "22:03", 1308, 1323, 15], + [190, "22:17", "23:03", 1337, 1383, 46], + [191, "22:43", "23:08", 1363, 1388, 25], + [192, "23:35", "01:05", 1415, 1505, 90], + [193, "23:46", "00:01", 1426, 1441, 15], + [194, "23:47", "00:33", 1427, 1473, 46], + [195, "23:52", "00:24", 1432, 1464, 32], + [196, "23:58", "00:38", 1438, 1478, 40], + [197, "00:02", "00:12", 1442, 1452, 10], + [198, "00:07", "00:39", 1447, 1479, 32], + [199, "00:25", "01:12", 1465, 1512, 47], ] # yapf:disable SAMPLE_SHIFTS_LARGE = [ - [0, '04:18', '05:00', 258, 300, 42], - [1, '04:27', '05:08', 267, 308, 41], - [2, '04:29', '05:26', 269, 326, 57], - [3, '04:29', '04:55', 269, 295, 26], - [4, '04:30', '04:53', 270, 293, 23], - [5, '04:30', '04:51', 270, 291, 21], - [6, '04:31', '04:53', 271, 293, 22], - [7, '04:33', '05:15', 273, 315, 42], - [8, '04:34', '04:44', 274, 284, 10], - [9, '04:34', '05:03', 274, 303, 29], - [10, '04:35', '04:50', 275, 290, 15], - [11, '04:36', '04:46', 276, 286, 10], - [12, '04:37', '05:18', 277, 318, 41], - [13, '04:41', '05:13', 281, 313, 32], - [14, '04:42', '05:23', 282, 323, 41], - [15, '04:43', '04:53', 283, 293, 10], - [16, '04:44', '05:45', 284, 345, 61], - [17, '04:45', '05:11', 285, 311, 26], - [18, '04:46', '05:01', 286, 301, 15], - [19, '04:46', '04:56', 286, 296, 10], - [20, '04:47', '05:14', 287, 314, 27], - [21, '04:48', '05:30', 288, 330, 42], - [22, '04:49', '05:41', 289, 341, 52], - [23, '04:49', '05:18', 289, 318, 29], - [24, '04:50', '05:33', 290, 333, 43], - [25, '04:52', '05:56', 292, 356, 64], - [26, '04:52', '05:07', 292, 307, 15], - [27, '04:53', '05:19', 293, 319, 26], - [28, '04:53', '05:23', 293, 323, 30], - [29, '04:55', '05:27', 295, 327, 32], - [30, '04:57', '05:38', 297, 338, 41], - [31, '05:00', '06:00', 300, 360, 60], - [32, '05:00', '05:54', 300, 354, 54], - [33, '05:01', '05:33', 301, 333, 32], - [34, '05:01', '05:26', 301, 326, 25], - [35, '05:02', '05:29', 302, 329, 27], - [36, '05:02', '05:12', 302, 312, 10], - [37, '05:03', '05:45', 303, 345, 42], - [38, '05:03', '05:18', 303, 318, 15], - [39, '05:03', '06:28', 303, 388, 85], - [40, '05:03', '05:13', 303, 313, 10], - [41, '05:04', '06:24', 304, 384, 80], - [42, '05:07', '05:44', 307, 344, 37], - [43, '05:08', '05:48', 308, 348, 40], - [44, '05:10', '06:06', 310, 366, 56], - [45, '05:11', '05:37', 311, 337, 26], - [46, '05:11', '05:53', 311, 353, 42], - [47, '05:13', '06:15', 313, 375, 62], - [48, '05:13', '05:38', 313, 338, 25], - [49, '05:16', '05:44', 316, 344, 28], - [50, '05:17', '05:27', 317, 327, 10], - [51, '05:18', '06:40', 318, 400, 82], - [52, '05:18', '06:03', 318, 363, 45], - [53, '05:18', '06:11', 318, 371, 53], - [54, '05:18', '06:00', 318, 360, 42], - [55, '05:19', '06:34', 319, 394, 75], - [56, '05:20', '06:17', 320, 377, 57], - [57, '05:22', '05:59', 322, 359, 37], - [58, '05:24', '05:48', 324, 348, 24], - [59, '05:25', '05:40', 325, 340, 15], - [60, '05:26', '06:08', 326, 368, 42], - [61, '05:27', '06:30', 327, 390, 63], - [62, '05:27', '05:54', 327, 354, 27], - [63, '05:28', '05:53', 328, 353, 25], - [64, '05:29', '05:44', 329, 344, 15], - [65, '05:30', '05:40', 330, 340, 10], - [66, '05:30', '05:40', 330, 340, 10], - [67, '05:30', '05:40', 330, 340, 10], - [68, '05:32', '06:53', 332, 413, 81], - [69, '05:33', '07:00', 333, 420, 87], - [70, '05:33', '06:15', 333, 375, 42], - [71, '05:33', '05:47', 333, 347, 14], - [72, '05:37', '06:13', 337, 373, 36], - [73, '05:37', '06:05', 337, 365, 28], - [74, '05:38', '06:33', 338, 393, 55], - [75, '05:38', '06:04', 338, 364, 26], - [76, '05:38', '06:18', 338, 378, 40], - [77, '05:39', '05:54', 339, 354, 15], - [78, '05:40', '05:56', 340, 356, 16], - [79, '05:40', '06:41', 340, 401, 61], - [80, '05:40', '05:50', 340, 350, 10], - [81, '05:41', '06:23', 341, 383, 42], - [82, '05:41', '06:01', 341, 361, 20], - [83, '05:43', '06:08', 343, 368, 25], - [84, '05:44', '07:10', 344, 430, 86], - [85, '05:44', '05:55', 344, 355, 11], - [86, '05:45', '06:44', 345, 404, 59], - [87, '05:47', '06:17', 347, 377, 30], - [88, '05:48', '07:08', 348, 428, 80], - [89, '05:48', '06:30', 348, 390, 42], - [90, '05:50', '06:50', 350, 410, 60], - [91, '05:50', '06:00', 350, 360, 10], - [92, '05:50', '06:00', 350, 360, 10], - [93, '05:50', '06:51', 350, 411, 61], - [94, '05:52', '06:33', 352, 393, 41], - [95, '05:52', '06:36', 352, 396, 44], - [96, '05:52', '06:23', 352, 383, 31], - [97, '05:54', '06:14', 354, 374, 20], - [98, '05:54', '07:20', 354, 440, 86], - [99, '05:55', '06:40', 355, 400, 45], - [100, '05:55', '06:27', 355, 387, 32], - [101, '05:56', '06:35', 356, 395, 39], - [102, '05:56', '06:06', 356, 366, 10], - [103, '05:57', '06:21', 357, 381, 24], - [104, '05:58', '07:23', 358, 443, 85], - [105, '05:58', '06:23', 358, 383, 25], - [106, '05:58', '06:08', 358, 368, 10], - [107, '05:58', '06:43', 358, 403, 45], - [108, '06:00', '06:10', 360, 370, 10], - [109, '06:00', '06:16', 360, 376, 16], - [110, '06:00', '07:01', 360, 421, 61], - [111, '06:01', '07:00', 361, 420, 59], - [112, '06:01', '06:13', 361, 373, 12], - [113, '06:01', '06:45', 361, 405, 44], - [114, '06:03', '06:50', 363, 410, 47], - [115, '06:04', '06:37', 364, 397, 33], - [116, '06:04', '07:30', 364, 450, 86], - [117, '06:05', '06:24', 365, 384, 19], - [118, '06:06', '06:51', 366, 411, 45], - [119, '06:07', '06:43', 367, 403, 36], - [120, '06:08', '07:30', 368, 450, 82], - [121, '06:10', '06:20', 370, 380, 10], - [122, '06:10', '07:17', 370, 437, 67], - [123, '06:11', '06:54', 371, 414, 43], - [124, '06:11', '06:21', 371, 381, 10], - [125, '06:13', '06:38', 373, 398, 25], - [126, '06:13', '06:58', 373, 418, 45], - [127, '06:13', '06:53', 373, 413, 40], - [128, '06:14', '07:03', 374, 423, 49], - [129, '06:14', '06:47', 374, 407, 33], - [130, '06:14', '07:40', 374, 460, 86], - [131, '06:15', '07:15', 375, 435, 60], - [132, '06:16', '06:28', 376, 388, 12], - [133, '06:16', '06:26', 376, 386, 10], - [134, '06:17', '06:34', 377, 394, 17], - [135, '06:18', '07:06', 378, 426, 48], - [136, '06:18', '07:38', 378, 458, 80], - [137, '06:18', '07:02', 378, 422, 44], - [138, '06:19', '06:53', 379, 413, 34], - [139, '06:20', '07:25', 380, 445, 65], - [140, '06:20', '06:36', 380, 396, 16], - [141, '06:20', '06:30', 380, 390, 10], - [142, '06:20', '06:30', 380, 390, 10], - [143, '06:21', '06:49', 381, 409, 28], - [144, '06:22', '07:06', 382, 426, 44], - [145, '06:24', '07:50', 384, 470, 86], - [146, '06:24', '06:57', 384, 417, 33], - [147, '06:26', '07:45', 386, 465, 79], - [148, '06:26', '07:10', 386, 430, 44], - [149, '06:27', '06:44', 387, 404, 17], - [150, '06:28', '06:53', 388, 413, 25], - [151, '06:28', '07:14', 388, 434, 46], - [152, '06:29', '07:03', 389, 423, 34], - [153, '06:30', '06:40', 390, 400, 10], - [154, '06:30', '07:37', 390, 457, 67], - [155, '06:31', '06:43', 391, 403, 12], - [156, '06:33', '07:14', 393, 434, 41], - [157, '06:33', '07:53', 393, 473, 80], - [158, '06:34', '08:16', 394, 496, 102], - [159, '06:34', '07:09', 394, 429, 35], - [160, '06:34', '07:07', 394, 427, 33], - [161, '06:36', '07:21', 396, 441, 45], - [162, '06:37', '07:22', 397, 442, 45], - [163, '06:37', '06:54', 397, 414, 17], - [164, '06:38', '07:30', 398, 450, 52], - [165, '06:38', '07:18', 398, 438, 40], - [166, '06:39', '07:33', 399, 453, 54], - [167, '06:40', '07:52', 400, 472, 72], - [168, '06:40', '06:50', 400, 410, 10], - [169, '06:40', '07:22', 400, 442, 42], - [170, '06:40', '06:56', 400, 416, 16], - [171, '06:41', '08:00', 401, 480, 79], - [172, '06:42', '07:26', 402, 446, 44], - [173, '06:42', '07:13', 402, 433, 31], - [174, '06:43', '07:08', 403, 428, 25], - [175, '06:43', '07:30', 403, 450, 47], - [176, '06:43', '07:23', 403, 443, 40], - [177, '06:44', '07:17', 404, 437, 33], - [178, '06:44', '08:13', 404, 493, 89], - [179, '06:46', '07:01', 406, 421, 15], - [180, '06:46', '06:58', 406, 418, 12], - [181, '06:47', '07:04', 407, 424, 17], - [182, '06:48', '08:15', 408, 495, 87], - [183, '06:48', '07:34', 408, 454, 46], - [184, '06:48', '07:37', 408, 457, 49], - [185, '06:49', '07:43', 409, 463, 54], - [186, '06:50', '08:00', 410, 480, 70], - [187, '06:50', '07:00', 410, 420, 10], - [188, '06:50', '07:05', 410, 425, 15], - [189, '06:51', '07:18', 411, 438, 27], - [190, '06:52', '07:36', 412, 456, 44], - [191, '06:53', '07:37', 413, 457, 44], - [192, '06:54', '08:20', 414, 500, 86], - [193, '06:54', '07:27', 414, 447, 33], - [194, '06:54', '07:20', 414, 440, 26], - [195, '06:56', '08:23', 416, 503, 87], - [196, '06:57', '07:12', 417, 432, 15], - [197, '06:57', '07:58', 417, 478, 61], - [198, '06:57', '07:45', 417, 465, 48], - [199, '06:57', '07:40', 417, 460, 43], - [200, '06:58', '07:23', 418, 443, 25], - [201, '06:59', '07:53', 419, 473, 54], - [202, '06:59', '08:07', 419, 487, 68], - [203, '07:00', '07:10', 420, 430, 10], - [204, '07:00', '07:16', 420, 436, 16], - [205, '07:01', '08:30', 421, 510, 89], - [206, '07:01', '07:13', 421, 433, 12], - [207, '07:01', '07:43', 421, 463, 42], - [208, '07:03', '08:30', 423, 510, 87], - [209, '07:04', '07:37', 424, 457, 33], - [210, '07:04', '07:44', 424, 464, 40], - [211, '07:05', '07:52', 425, 472, 47], - [212, '07:05', '08:05', 425, 485, 60], - [213, '07:05', '07:46', 425, 466, 41], - [214, '07:06', '07:51', 426, 471, 45], - [215, '07:07', '08:08', 427, 488, 61], - [216, '07:07', '07:52', 427, 472, 45], - [217, '07:07', '08:16', 427, 496, 69], - [218, '07:07', '07:27', 427, 447, 20], - [219, '07:09', '07:50', 429, 470, 41], - [220, '07:09', '08:40', 429, 520, 91], - [221, '07:09', '08:03', 429, 483, 54], - [222, '07:10', '07:20', 430, 440, 10], - [223, '07:11', '08:36', 431, 516, 85], - [224, '07:12', '08:00', 432, 480, 48], - [225, '07:12', '07:47', 432, 467, 35], - [226, '07:13', '07:54', 433, 474, 41], - [227, '07:13', '07:38', 433, 458, 25], - [228, '07:14', '07:59', 434, 479, 45], - [229, '07:16', '08:50', 436, 530, 94], - [230, '07:16', '07:28', 436, 448, 12], - [231, '07:17', '07:35', 437, 455, 18], - [232, '07:17', '07:58', 437, 478, 41], - [233, '07:18', '08:06', 438, 486, 48], - [234, '07:18', '08:44', 438, 524, 86], - [235, '07:19', '08:13', 439, 493, 54], - [236, '07:20', '08:02', 440, 482, 42], - [237, '07:20', '08:07', 440, 487, 47], - [238, '07:20', '07:30', 440, 450, 10], - [239, '07:20', '07:57', 440, 477, 37], - [240, '07:20', '07:36', 440, 456, 16], - [241, '07:21', '07:48', 441, 468, 27], - [242, '07:22', '08:06', 442, 486, 44], - [243, '07:22', '08:25', 442, 505, 63], - [244, '07:24', '08:27', 444, 507, 63], - [245, '07:24', '08:05', 444, 485, 41], - [246, '07:26', '08:23', 446, 503, 57], - [247, '07:26', '08:52', 446, 532, 86], - [248, '07:27', '08:07', 447, 487, 40], - [249, '07:27', '07:42', 447, 462, 15], - [250, '07:27', '08:15', 447, 495, 48], - [251, '07:28', '07:53', 448, 473, 25], - [252, '07:28', '08:09', 448, 489, 41], - [253, '07:28', '07:38', 448, 458, 10], - [254, '07:30', '08:35', 450, 515, 65], - [255, '07:31', '07:43', 451, 463, 12], - [256, '07:32', '08:13', 452, 493, 41], - [257, '07:34', '09:00', 454, 540, 86], - [258, '07:34', '08:33', 454, 513, 59], - [259, '07:34', '09:04', 454, 544, 90], - [260, '07:35', '08:22', 455, 502, 47], - [261, '07:35', '07:45', 455, 465, 10], - [262, '07:35', '08:16', 455, 496, 41], - [263, '07:36', '08:17', 456, 497, 41], - [264, '07:36', '08:36', 456, 516, 60], - [265, '07:37', '07:50', 457, 470, 13], - [266, '07:40', '07:56', 460, 476, 16], - [267, '07:40', '08:20', 460, 500, 40], - [268, '07:40', '08:45', 460, 525, 65], - [269, '07:41', '08:39', 461, 519, 58], - [270, '07:41', '07:51', 461, 471, 10], - [271, '07:42', '08:30', 462, 510, 48], - [272, '07:42', '08:21', 462, 501, 39], - [273, '07:43', '08:08', 463, 488, 25], - [274, '07:43', '08:24', 463, 504, 41], - [275, '07:44', '09:10', 464, 550, 86], - [276, '07:44', '08:43', 464, 523, 59], - [277, '07:46', '08:28', 466, 508, 42], - [278, '07:46', '07:58', 466, 478, 12], - [279, '07:47', '08:00', 467, 480, 13], - [280, '07:48', '09:14', 468, 554, 86], - [281, '07:49', '08:32', 469, 512, 43], - [282, '07:50', '08:55', 470, 535, 65], - [283, '07:50', '08:00', 470, 480, 10], - [284, '07:50', '08:37', 470, 517, 47], - [285, '07:50', '08:26', 470, 506, 36], - [286, '07:51', '08:18', 471, 498, 27], - [287, '07:52', '08:21', 472, 501, 29], - [288, '07:53', '08:35', 473, 515, 42], - [289, '07:54', '09:19', 474, 559, 85], - [290, '07:55', '08:53', 475, 533, 58], - [291, '07:56', '08:54', 476, 534, 58], - [292, '07:57', '08:39', 477, 519, 42], - [293, '07:57', '08:10', 477, 490, 13], - [294, '07:58', '08:45', 478, 525, 47], - [295, '07:58', '08:23', 478, 503, 25], - [296, '08:00', '08:10', 480, 490, 10], - [297, '08:00', '09:05', 480, 545, 65], - [298, '08:00', '08:16', 480, 496, 16], - [299, '08:00', '08:35', 480, 515, 35], - [300, '08:01', '08:13', 481, 493, 12], - [301, '08:01', '08:43', 481, 523, 42], - [302, '08:03', '09:26', 483, 566, 83], - [303, '08:04', '09:29', 484, 569, 85], - [304, '08:05', '08:21', 485, 501, 16], - [305, '08:05', '08:47', 485, 527, 42], - [306, '08:06', '08:51', 486, 531, 45], - [307, '08:06', '09:03', 486, 543, 57], - [308, '08:07', '08:20', 487, 500, 13], - [309, '08:08', '08:55', 488, 535, 47], - [310, '08:08', '08:50', 488, 530, 42], - [311, '08:10', '08:45', 490, 525, 35], - [312, '08:10', '09:15', 490, 555, 65], - [313, '08:10', '08:20', 490, 500, 10], - [314, '08:11', '09:41', 491, 581, 90], - [315, '08:12', '08:55', 492, 535, 43], - [316, '08:13', '08:38', 493, 518, 25], - [317, '08:14', '09:38', 494, 578, 84], - [318, '08:15', '08:30', 495, 510, 15], - [319, '08:16', '08:30', 496, 510, 14], - [320, '08:16', '08:28', 496, 508, 12], - [321, '08:16', '09:00', 496, 540, 44], - [322, '08:17', '09:13', 497, 553, 56], - [323, '08:18', '09:16', 498, 556, 58], - [324, '08:18', '09:05', 498, 545, 47], - [325, '08:20', '08:36', 500, 516, 16], - [326, '08:20', '08:55', 500, 535, 35], - [327, '08:20', '09:05', 500, 545, 45], - [328, '08:20', '08:30', 500, 510, 10], - [329, '08:20', '09:25', 500, 565, 65], - [330, '08:21', '08:38', 501, 518, 17], - [331, '08:21', '08:47', 501, 527, 26], - [332, '08:22', '08:45', 502, 525, 23], - [333, '08:23', '09:10', 503, 550, 47], - [334, '08:24', '09:48', 504, 588, 84], - [335, '08:26', '08:46', 506, 526, 20], - [336, '08:27', '09:07', 507, 547, 40], - [337, '08:28', '08:50', 508, 530, 22], - [338, '08:28', '09:56', 508, 596, 88], - [339, '08:28', '09:23', 508, 563, 55], - [340, '08:29', '09:20', 509, 560, 51], - [341, '08:30', '09:05', 510, 545, 35], - [342, '08:30', '08:45', 510, 525, 15], - [343, '08:30', '08:40', 510, 520, 10], - [344, '08:30', '09:35', 510, 575, 65], - [345, '08:31', '08:43', 511, 523, 12], - [346, '08:31', '09:13', 511, 553, 42], - [347, '08:34', '09:58', 514, 598, 84], - [348, '08:35', '08:55', 515, 535, 20], - [349, '08:35', '09:15', 515, 555, 40], - [350, '08:35', '08:45', 515, 525, 10], - [351, '08:36', '08:46', 516, 526, 10], - [352, '08:36', '09:00', 516, 540, 24], - [353, '08:38', '09:20', 518, 560, 42], - [354, '08:38', '09:35', 518, 575, 57], - [355, '08:38', '09:14', 518, 554, 36], - [356, '08:39', '09:33', 519, 573, 54], - [357, '08:40', '09:45', 520, 585, 65], - [358, '08:40', '08:50', 520, 530, 10], - [359, '08:40', '08:56', 520, 536, 16], - [360, '08:42', '09:25', 522, 565, 43], - [361, '08:43', '09:08', 523, 548, 25], - [362, '08:44', '09:35', 524, 575, 51], - [363, '08:45', '09:00', 525, 540, 15], - [364, '08:45', '09:05', 525, 545, 20], - [365, '08:46', '09:24', 526, 564, 38], - [366, '08:46', '08:58', 526, 538, 12], - [367, '08:46', '09:30', 526, 570, 44], - [368, '08:48', '10:11', 528, 611, 83], - [369, '08:48', '10:13', 528, 613, 85], - [370, '08:49', '09:43', 529, 583, 54], - [371, '08:50', '09:30', 530, 570, 40], - [372, '08:50', '10:00', 530, 600, 70], - [373, '08:50', '09:00', 530, 540, 10], - [374, '08:51', '09:17', 531, 557, 26], - [375, '08:53', '09:20', 533, 560, 27], - [376, '08:53', '09:35', 533, 575, 42], - [377, '08:55', '09:34', 535, 574, 39], - [378, '08:55', '09:15', 535, 555, 20], - [379, '08:58', '09:38', 538, 578, 40], - [380, '08:58', '10:26', 538, 626, 88], - [381, '08:59', '09:53', 539, 593, 54], - [382, '08:59', '09:50', 539, 590, 51], - [383, '09:00', '09:35', 540, 575, 35], - [384, '09:00', '09:16', 540, 556, 16], - [385, '09:00', '09:10', 540, 550, 10], - [386, '09:00', '09:16', 540, 556, 16], - [387, '09:01', '09:13', 541, 553, 12], - [388, '09:03', '09:45', 543, 585, 42], - [389, '09:03', '10:28', 543, 628, 85], - [390, '09:05', '09:44', 545, 584, 39], - [391, '09:05', '09:25', 545, 565, 20], - [392, '09:08', '09:53', 548, 593, 45], - [393, '09:08', '10:04', 548, 604, 56], - [394, '09:09', '10:03', 549, 603, 54], - [395, '09:10', '10:15', 550, 615, 65], - [396, '09:10', '09:20', 550, 560, 10], - [397, '09:11', '09:38', 551, 578, 27], - [398, '09:13', '10:00', 553, 600, 47], - [399, '09:14', '09:39', 554, 579, 25], - [400, '09:14', '10:05', 554, 605, 51], - [401, '09:15', '09:54', 555, 594, 39], - [402, '09:16', '09:28', 556, 568, 12], - [403, '09:18', '10:43', 558, 643, 85], - [404, '09:18', '10:41', 558, 641, 83], - [405, '09:18', '09:58', 558, 598, 40], - [406, '09:19', '10:13', 559, 613, 54], - [407, '09:20', '09:30', 560, 570, 10], - [408, '09:20', '09:36', 560, 576, 16], - [409, '09:21', '09:47', 561, 587, 26], - [410, '09:23', '10:30', 563, 630, 67], - [411, '09:23', '10:05', 563, 605, 42], - [412, '09:23', '09:49', 563, 589, 26], - [413, '09:24', '09:35', 564, 575, 11], - [414, '09:25', '09:35', 565, 575, 10], - [415, '09:25', '10:04', 565, 604, 39], - [416, '09:28', '10:08', 568, 608, 40], - [417, '09:29', '09:45', 569, 585, 16], - [418, '09:29', '10:20', 569, 620, 51], - [419, '09:29', '10:56', 569, 656, 87], - [420, '09:29', '10:23', 569, 623, 54], - [421, '09:30', '09:40', 570, 580, 10], - [422, '09:31', '09:43', 571, 583, 12], - [423, '09:33', '10:58', 573, 658, 85], - [424, '09:33', '10:15', 573, 615, 42], - [425, '09:34', '09:45', 574, 585, 11], - [426, '09:35', '10:14', 575, 614, 39], - [427, '09:38', '10:45', 578, 645, 67], - [428, '09:39', '10:33', 579, 633, 54], - [429, '09:40', '09:56', 580, 596, 16], - [430, '09:40', '09:50', 580, 590, 10], - [431, '09:41', '10:08', 581, 608, 27], - [432, '09:41', '10:23', 581, 623, 42], - [433, '09:44', '10:35', 584, 635, 51], - [434, '09:44', '11:11', 584, 671, 87], - [435, '09:44', '09:55', 584, 595, 11], - [436, '09:45', '10:24', 585, 624, 39], - [437, '09:46', '09:58', 586, 598, 12], - [438, '09:48', '10:30', 588, 630, 42], - [439, '09:48', '11:13', 588, 673, 85], - [440, '09:48', '10:04', 588, 604, 16], - [441, '09:49', '10:43', 589, 643, 54], - [442, '09:50', '10:00', 590, 600, 10], - [443, '09:51', '10:17', 591, 617, 26], - [444, '09:53', '10:49', 593, 649, 56], - [445, '09:53', '11:00', 593, 660, 67], - [446, '09:54', '10:05', 594, 605, 11], - [447, '09:55', '10:34', 595, 634, 39], - [448, '09:56', '10:38', 596, 638, 42], - [449, '09:57', '10:20', 597, 620, 23], - [450, '09:59', '11:26', 599, 686, 87], - [451, '09:59', '10:50', 599, 650, 51], - [452, '09:59', '10:53', 599, 653, 54], - [453, '10:00', '10:16', 600, 616, 16], - [454, '10:00', '10:10', 600, 610, 10], - [455, '10:01', '10:13', 601, 613, 12], - [456, '10:03', '11:28', 603, 688, 85], - [457, '10:03', '10:45', 603, 645, 42], - [458, '10:04', '10:15', 604, 615, 11], - [459, '10:05', '10:44', 605, 644, 39], - [460, '10:08', '11:15', 608, 675, 67], - [461, '10:09', '11:03', 609, 663, 54], - [462, '10:10', '10:20', 610, 620, 10], - [463, '10:11', '10:38', 611, 638, 27], - [464, '10:11', '10:53', 611, 653, 42], - [465, '10:14', '11:05', 614, 665, 51], - [466, '10:14', '11:41', 614, 701, 87], - [467, '10:14', '10:25', 614, 625, 11], - [468, '10:15', '10:54', 615, 654, 39], - [469, '10:16', '10:28', 616, 628, 12], - [470, '10:18', '11:43', 618, 703, 85], - [471, '10:18', '11:00', 618, 660, 42], - [472, '10:19', '11:13', 619, 673, 54], - [473, '10:20', '10:30', 620, 630, 10], - [474, '10:20', '10:36', 620, 636, 16], - [475, '10:21', '10:47', 621, 647, 26], - [476, '10:23', '11:30', 623, 690, 67], - [477, '10:23', '10:45', 623, 645, 22], - [478, '10:24', '10:35', 624, 635, 11], - [479, '10:25', '11:04', 625, 664, 39], - [480, '10:26', '11:08', 626, 668, 42], - [481, '10:29', '11:20', 629, 680, 51], - [482, '10:29', '11:23', 629, 683, 54], - [483, '10:29', '11:56', 629, 716, 87], - [484, '10:30', '10:40', 630, 640, 10], - [485, '10:31', '10:43', 631, 643, 12], - [486, '10:33', '11:15', 633, 675, 42], - [487, '10:33', '11:58', 633, 718, 85], - [488, '10:34', '10:45', 634, 645, 11], - [489, '10:35', '11:14', 635, 674, 39], - [490, '10:38', '11:45', 638, 705, 67], - [491, '10:39', '11:33', 639, 693, 54], - [492, '10:40', '10:50', 640, 650, 10], - [493, '10:40', '10:56', 640, 656, 16], - [494, '10:41', '11:23', 641, 683, 42], - [495, '10:41', '11:08', 641, 668, 27], - [496, '10:44', '12:11', 644, 731, 87], - [497, '10:44', '11:35', 644, 695, 51], - [498, '10:44', '10:55', 644, 655, 11], - [499, '10:45', '11:24', 645, 684, 39], - [500, '10:46', '10:58', 646, 658, 12], - [501, '10:48', '12:13', 648, 733, 85], - [502, '10:48', '11:30', 648, 690, 42], - [503, '10:49', '11:43', 649, 703, 54], - [504, '10:50', '11:00', 650, 660, 10], - [505, '10:51', '11:17', 651, 677, 26], - [506, '10:53', '12:00', 653, 720, 67], - [507, '10:53', '11:20', 653, 680, 27], - [508, '10:54', '11:05', 654, 665, 11], - [509, '10:55', '11:34', 655, 694, 39], - [510, '10:56', '11:38', 656, 698, 42], - [511, '10:59', '11:14', 659, 674, 15], - [512, '10:59', '12:26', 659, 746, 87], - [513, '10:59', '11:53', 659, 713, 54], - [514, '10:59', '11:50', 659, 710, 51], - [515, '11:00', '11:16', 660, 676, 16], - [516, '11:00', '11:10', 660, 670, 10], - [517, '11:01', '11:13', 661, 673, 12], - [518, '11:03', '11:45', 663, 705, 42], - [519, '11:03', '12:28', 663, 748, 85], - [520, '11:04', '11:15', 664, 675, 11], - [521, '11:05', '11:44', 665, 704, 39], - [522, '11:08', '12:15', 668, 735, 67], - [523, '11:09', '12:03', 669, 723, 54], - [524, '11:10', '11:20', 670, 680, 10], - [525, '11:11', '11:38', 671, 698, 27], - [526, '11:11', '11:53', 671, 713, 42], - [527, '11:14', '11:25', 674, 685, 11], - [528, '11:14', '12:05', 674, 725, 51], - [529, '11:14', '12:38', 674, 758, 84], - [530, '11:14', '12:41', 674, 761, 87], - [531, '11:15', '11:54', 675, 714, 39], - [532, '11:16', '11:28', 676, 688, 12], - [533, '11:18', '12:00', 678, 720, 42], - [534, '11:19', '12:13', 679, 733, 54], - [535, '11:20', '11:30', 680, 690, 10], - [536, '11:20', '11:36', 680, 696, 16], - [537, '11:21', '11:47', 681, 707, 26], - [538, '11:23', '12:30', 683, 750, 67], - [539, '11:23', '11:49', 683, 709, 26], - [540, '11:24', '12:48', 684, 768, 84], - [541, '11:24', '11:35', 684, 695, 11], - [542, '11:25', '12:04', 685, 724, 39], - [543, '11:26', '12:08', 686, 728, 42], - [544, '11:29', '11:44', 689, 704, 15], - [545, '11:29', '12:23', 689, 743, 54], - [546, '11:29', '12:20', 689, 740, 51], - [547, '11:29', '12:54', 689, 774, 85], - [548, '11:30', '11:40', 690, 700, 10], - [549, '11:31', '11:43', 691, 703, 12], - [550, '11:33', '12:15', 693, 735, 42], - [551, '11:34', '12:58', 694, 778, 84], - [552, '11:34', '11:45', 694, 705, 11], - [553, '11:35', '12:14', 695, 734, 39], - [554, '11:38', '12:45', 698, 765, 67], - [555, '11:39', '12:33', 699, 753, 54], - [556, '11:40', '11:56', 700, 716, 16], - [557, '11:40', '11:50', 700, 710, 10], - [558, '11:41', '12:08', 701, 728, 27], - [559, '11:41', '12:23', 701, 743, 42], - [560, '11:44', '11:55', 704, 715, 11], - [561, '11:44', '13:14', 704, 794, 90], - [562, '11:44', '13:08', 704, 788, 84], - [563, '11:44', '12:35', 704, 755, 51], - [564, '11:45', '12:24', 705, 744, 39], - [565, '11:46', '11:58', 706, 718, 12], - [566, '11:48', '12:30', 708, 750, 42], - [567, '11:49', '12:43', 709, 763, 54], - [568, '11:50', '12:00', 710, 720, 10], - [569, '11:51', '12:17', 711, 737, 26], - [570, '11:53', '12:49', 713, 769, 56], - [571, '11:53', '13:00', 713, 780, 67], - [572, '11:54', '13:18', 714, 798, 84], - [573, '11:54', '12:05', 714, 725, 11], - [574, '11:55', '12:40', 715, 760, 45], - [575, '11:55', '12:34', 715, 754, 39], - [576, '11:56', '12:35', 716, 755, 39], - [577, '11:57', '12:20', 717, 740, 23], - [578, '11:58', '12:29', 718, 749, 31], - [579, '11:59', '12:50', 719, 770, 51], - [580, '11:59', '12:53', 719, 773, 54], - [581, '11:59', '13:24', 719, 804, 85], - [582, '11:59', '12:14', 719, 734, 15], - [583, '12:00', '12:16', 720, 736, 16], - [584, '12:00', '12:10', 720, 730, 10], - [585, '12:01', '12:45', 721, 765, 44], - [586, '12:01', '12:13', 721, 733, 12], - [587, '12:03', '12:50', 723, 770, 47], - [588, '12:04', '12:15', 724, 735, 11], - [589, '12:04', '13:04', 724, 784, 60], - [590, '12:04', '13:28', 724, 808, 84], - [591, '12:05', '12:44', 725, 764, 39], - [592, '12:08', '13:11', 728, 791, 63], - [593, '12:08', '12:39', 728, 759, 31], - [594, '12:09', '13:03', 729, 783, 54], - [595, '12:10', '12:20', 730, 740, 10], - [596, '12:11', '12:55', 731, 775, 44], - [597, '12:11', '12:38', 731, 758, 27], - [598, '12:14', '13:05', 734, 785, 51], - [599, '12:14', '12:25', 734, 745, 11], - [600, '12:14', '13:44', 734, 824, 90], - [601, '12:14', '13:38', 734, 818, 84], - [602, '12:15', '12:54', 735, 774, 39], - [603, '12:16', '12:28', 736, 748, 12], - [604, '12:18', '13:00', 738, 780, 42], - [605, '12:19', '13:13', 739, 793, 54], - [606, '12:20', '12:30', 740, 750, 10], - [607, '12:20', '13:31', 740, 811, 71], - [608, '12:20', '12:30', 740, 750, 10], - [609, '12:20', '12:36', 740, 756, 16], - [610, '12:21', '12:47', 741, 767, 26], - [611, '12:23', '12:45', 743, 765, 22], - [612, '12:24', '12:35', 744, 755, 11], - [613, '12:24', '13:48', 744, 828, 84], - [614, '12:25', '13:10', 745, 790, 45], - [615, '12:25', '13:04', 745, 784, 39], - [616, '12:26', '13:05', 746, 785, 39], - [617, '12:28', '13:54', 748, 834, 86], - [618, '12:28', '12:38', 748, 758, 10], - [619, '12:28', '13:15', 748, 795, 47], - [620, '12:29', '13:23', 749, 803, 54], - [621, '12:30', '13:41', 750, 821, 71], - [622, '12:30', '12:40', 750, 760, 10], - [623, '12:31', '13:15', 751, 795, 44], - [624, '12:31', '12:43', 751, 763, 12], - [625, '12:33', '12:48', 753, 768, 15], - [626, '12:33', '13:20', 753, 800, 47], - [627, '12:34', '13:58', 754, 838, 84], - [628, '12:34', '13:34', 754, 814, 60], - [629, '12:34', '12:45', 754, 765, 11], - [630, '12:35', '13:14', 755, 794, 39], - [631, '12:38', '13:25', 758, 805, 47], - [632, '12:38', '13:25', 758, 805, 47], - [633, '12:38', '14:04', 758, 844, 86], - [634, '12:39', '13:33', 759, 813, 54], - [635, '12:40', '13:51', 760, 831, 71], - [636, '12:40', '12:50', 760, 770, 10], - [637, '12:40', '12:56', 760, 776, 16], - [638, '12:41', '13:08', 761, 788, 27], - [639, '12:43', '13:30', 763, 810, 47], - [640, '12:44', '12:55', 764, 775, 11], - [641, '12:44', '14:08', 764, 848, 84], - [642, '12:45', '13:24', 765, 804, 39], - [643, '12:46', '12:58', 766, 778, 12], - [644, '12:46', '13:21', 766, 801, 35], - [645, '12:48', '14:14', 768, 854, 86], - [646, '12:48', '13:35', 768, 815, 47], - [647, '12:48', '12:58', 768, 778, 10], - [648, '12:48', '13:35', 768, 815, 47], - [649, '12:49', '13:43', 769, 823, 54], - [650, '12:50', '14:01', 770, 841, 71], - [651, '12:50', '13:00', 770, 780, 10], - [652, '12:50', '13:00', 770, 780, 10], - [653, '12:51', '13:17', 771, 797, 26], - [654, '12:53', '13:20', 773, 800, 27], - [655, '12:53', '13:24', 773, 804, 31], - [656, '12:53', '13:40', 773, 820, 47], - [657, '12:54', '14:18', 774, 858, 84], - [658, '12:54', '13:05', 774, 785, 11], - [659, '12:55', '13:34', 775, 814, 39], - [660, '12:58', '14:24', 778, 864, 86], - [661, '12:58', '13:25', 778, 805, 27], - [662, '12:58', '13:45', 778, 825, 47], - [663, '12:58', '13:45', 778, 825, 47], - [664, '12:59', '13:53', 779, 833, 54], - [665, '13:00', '13:10', 780, 790, 10], - [666, '13:00', '13:16', 780, 796, 16], - [667, '13:00', '14:11', 780, 851, 71], - [668, '13:01', '13:13', 781, 793, 12], - [669, '13:03', '13:34', 783, 814, 31], - [670, '13:03', '13:50', 783, 830, 47], - [671, '13:04', '13:15', 784, 795, 11], - [672, '13:04', '14:28', 784, 868, 84], - [673, '13:05', '13:44', 785, 824, 39], - [674, '13:08', '13:55', 788, 835, 47], - [675, '13:08', '14:34', 788, 874, 86], - [676, '13:08', '13:55', 788, 835, 47], - [677, '13:09', '14:03', 789, 843, 54], - [678, '13:10', '13:20', 790, 800, 10], - [679, '13:10', '14:21', 790, 861, 71], - [680, '13:13', '14:00', 793, 840, 47], - [681, '13:13', '13:40', 793, 820, 27], - [682, '13:14', '14:38', 794, 878, 84], - [683, '13:14', '13:25', 794, 805, 11], - [684, '13:15', '13:54', 795, 834, 39], - [685, '13:16', '13:28', 796, 808, 12], - [686, '13:18', '14:05', 798, 845, 47], - [687, '13:18', '14:44', 798, 884, 86], - [688, '13:18', '14:05', 798, 845, 47], - [689, '13:19', '14:13', 799, 853, 54], - [690, '13:20', '13:36', 800, 816, 16], - [691, '13:20', '14:31', 800, 871, 71], - [692, '13:20', '13:30', 800, 810, 10], - [693, '13:21', '13:47', 801, 827, 26], - [694, '13:23', '14:10', 803, 850, 47], - [695, '13:23', '13:49', 803, 829, 26], - [696, '13:24', '14:48', 804, 888, 84], - [697, '13:24', '13:35', 804, 815, 11], - [698, '13:25', '14:04', 805, 844, 39], - [699, '13:28', '14:15', 808, 855, 47], - [700, '13:28', '14:54', 808, 894, 86], - [701, '13:28', '13:55', 808, 835, 27], - [702, '13:28', '14:15', 808, 855, 47], - [703, '13:29', '14:23', 809, 863, 54], - [704, '13:30', '13:40', 810, 820, 10], - [705, '13:30', '14:41', 810, 881, 71], - [706, '13:31', '13:43', 811, 823, 12], - [707, '13:33', '14:20', 813, 860, 47], - [708, '13:34', '14:58', 814, 898, 84], - [709, '13:34', '13:45', 814, 825, 11], - [710, '13:35', '14:14', 815, 854, 39], - [711, '13:38', '14:25', 818, 865, 47], - [712, '13:38', '14:25', 818, 865, 47], - [713, '13:38', '15:04', 818, 904, 86], - [714, '13:39', '14:33', 819, 873, 54], - [715, '13:40', '13:50', 820, 830, 10], - [716, '13:40', '13:56', 820, 836, 16], - [717, '13:40', '14:51', 820, 891, 71], - [718, '13:43', '14:30', 823, 870, 47], - [719, '13:43', '14:10', 823, 850, 27], - [720, '13:44', '15:09', 824, 909, 85], - [721, '13:44', '13:55', 824, 835, 11], - [722, '13:45', '14:24', 825, 864, 39], - [723, '13:46', '13:58', 826, 838, 12], - [724, '13:48', '14:35', 828, 875, 47], - [725, '13:48', '15:14', 828, 914, 86], - [726, '13:48', '14:35', 828, 875, 47], - [727, '13:49', '14:43', 829, 883, 54], - [728, '13:50', '14:00', 830, 840, 10], - [729, '13:50', '15:01', 830, 901, 71], - [730, '13:51', '14:17', 831, 857, 26], - [731, '13:53', '14:40', 833, 880, 47], - [732, '13:53', '14:49', 833, 889, 56], - [733, '13:54', '14:05', 834, 845, 11], - [734, '13:54', '15:19', 834, 919, 85], - [735, '13:55', '14:34', 835, 874, 39], - [736, '13:57', '14:20', 837, 860, 23], - [737, '13:58', '15:24', 838, 924, 86], - [738, '13:58', '14:45', 838, 885, 47], - [739, '13:58', '14:45', 838, 885, 47], - [740, '13:58', '14:25', 838, 865, 27], - [741, '13:59', '14:53', 839, 893, 54], - [742, '14:00', '14:16', 840, 856, 16], - [743, '14:00', '14:10', 840, 850, 10], - [744, '14:00', '15:11', 840, 911, 71], - [745, '14:01', '14:13', 841, 853, 12], - [746, '14:03', '14:50', 843, 890, 47], - [747, '14:04', '14:15', 844, 855, 11], - [748, '14:04', '15:29', 844, 929, 85], - [749, '14:05', '14:44', 845, 884, 39], - [750, '14:08', '14:55', 848, 895, 47], - [751, '14:08', '14:55', 848, 895, 47], - [752, '14:08', '15:34', 848, 934, 86], - [753, '14:09', '15:03', 849, 903, 54], - [754, '14:10', '15:21', 850, 921, 71], - [755, '14:10', '14:20', 850, 860, 10], - [756, '14:13', '15:00', 853, 900, 47], - [757, '14:13', '14:40', 853, 880, 27], - [758, '14:14', '15:40', 854, 940, 86], - [759, '14:14', '14:25', 854, 865, 11], - [760, '14:15', '14:54', 855, 894, 39], - [761, '14:16', '14:28', 856, 868, 12], - [762, '14:18', '15:05', 858, 905, 47], - [763, '14:18', '15:44', 858, 944, 86], - [764, '14:18', '15:05', 858, 905, 47], - [765, '14:19', '15:13', 859, 913, 54], - [766, '14:20', '15:31', 860, 931, 71], - [767, '14:20', '14:30', 860, 870, 10], - [768, '14:20', '14:36', 860, 876, 16], - [769, '14:21', '14:47', 861, 887, 26], - [770, '14:23', '15:10', 863, 910, 47], - [771, '14:23', '14:45', 863, 885, 22], - [772, '14:24', '15:50', 864, 950, 86], - [773, '14:24', '14:35', 864, 875, 11], - [774, '14:25', '15:02', 865, 902, 37], - [775, '14:26', '14:52', 866, 892, 26], - [776, '14:28', '15:15', 868, 915, 47], - [777, '14:28', '14:55', 868, 895, 27], - [778, '14:28', '15:54', 868, 954, 86], - [779, '14:28', '15:15', 868, 915, 47], - [780, '14:29', '15:23', 869, 923, 54], - [781, '14:30', '15:41', 870, 941, 71], - [782, '14:30', '14:40', 870, 880, 10], - [783, '14:31', '14:43', 871, 883, 12], - [784, '14:33', '15:20', 873, 920, 47], - [785, '14:34', '16:00', 874, 960, 86], - [786, '14:34', '14:45', 874, 885, 11], - [787, '14:35', '15:11', 875, 911, 36], - [788, '14:38', '15:25', 878, 925, 47], - [789, '14:38', '15:25', 878, 925, 47], - [790, '14:38', '16:04', 878, 964, 86], - [791, '14:39', '15:33', 879, 933, 54], - [792, '14:40', '14:50', 880, 890, 10], - [793, '14:40', '15:51', 880, 951, 71], - [794, '14:40', '14:56', 880, 896, 16], - [795, '14:43', '15:30', 883, 930, 47], - [796, '14:43', '15:10', 883, 910, 27], - [797, '14:44', '15:00', 884, 900, 16], - [798, '14:44', '16:10', 884, 970, 86], - [799, '14:45', '15:19', 885, 919, 34], - [800, '14:46', '14:58', 886, 898, 12], - [801, '14:48', '15:35', 888, 935, 47], - [802, '14:48', '15:35', 888, 935, 47], - [803, '14:48', '17:04', 888, 1024, 136], - [804, '14:49', '15:43', 889, 943, 54], - [805, '14:50', '16:01', 890, 961, 71], - [806, '14:50', '15:00', 890, 900, 10], - [807, '14:51', '15:17', 891, 917, 26], - [808, '14:52', '15:27', 892, 927, 35], - [809, '14:52', '15:21', 892, 921, 29], - [810, '14:53', '15:40', 893, 940, 47], - [811, '14:54', '15:08', 894, 908, 14], - [812, '14:54', '16:20', 894, 980, 86], - [813, '14:58', '16:24', 898, 984, 86], - [814, '14:58', '15:45', 898, 945, 47], - [815, '14:58', '15:25', 898, 925, 27], - [816, '14:58', '15:45', 898, 945, 47], - [817, '14:59', '15:53', 899, 953, 54], - [818, '15:00', '15:10', 900, 910, 10], - [819, '15:00', '15:35', 900, 935, 35], - [820, '15:00', '16:11', 900, 971, 71], - [821, '15:00', '15:16', 900, 916, 16], - [822, '15:01', '15:13', 901, 913, 12], - [823, '15:02', '15:16', 902, 916, 14], - [824, '15:03', '15:50', 903, 950, 47], - [825, '15:04', '16:30', 904, 990, 86], - [826, '15:08', '16:34', 908, 994, 86], - [827, '15:08', '15:55', 908, 955, 47], - [828, '15:08', '15:55', 908, 955, 47], - [829, '15:08', '15:45', 908, 945, 37], - [830, '15:09', '16:14', 909, 974, 65], - [831, '15:09', '16:03', 909, 963, 54], - [832, '15:10', '16:21', 910, 981, 71], - [833, '15:10', '15:20', 910, 920, 10], - [834, '15:11', '15:24', 911, 924, 13], - [835, '15:12', '15:36', 912, 936, 24], - [836, '15:13', '16:00', 913, 960, 47], - [837, '15:13', '15:40', 913, 940, 27], - [838, '15:14', '16:40', 914, 1000, 86], - [839, '15:16', '15:28', 916, 928, 12], - [840, '15:16', '15:55', 916, 955, 39], - [841, '15:18', '16:05', 918, 965, 47], - [842, '15:18', '16:44', 918, 1004, 86], - [843, '15:18', '16:05', 918, 965, 47], - [844, '15:19', '16:13', 919, 973, 54], - [845, '15:19', '15:34', 919, 934, 15], - [846, '15:20', '15:30', 920, 930, 10], - [847, '15:20', '16:31', 920, 991, 71], - [848, '15:20', '15:36', 920, 936, 16], - [849, '15:21', '15:47', 921, 947, 26], - [850, '15:21', '16:06', 921, 966, 45], - [851, '15:23', '16:10', 923, 970, 47], - [852, '15:24', '16:50', 924, 1010, 86], - [853, '15:24', '16:05', 924, 965, 41], - [854, '15:27', '15:51', 927, 951, 24], - [855, '15:27', '15:44', 927, 944, 17], - [856, '15:28', '16:15', 928, 975, 47], - [857, '15:28', '16:54', 928, 1014, 86], - [858, '15:28', '16:15', 928, 975, 47], - [859, '15:28', '15:55', 928, 955, 27], - [860, '15:29', '16:23', 929, 983, 54], - [861, '15:30', '16:41', 930, 1001, 71], - [862, '15:30', '15:40', 930, 940, 10], - [863, '15:31', '15:43', 931, 943, 12], - [864, '15:33', '16:20', 933, 980, 47], - [865, '15:34', '17:00', 934, 1020, 86], - [866, '15:34', '16:15', 934, 975, 41], - [867, '15:35', '15:54', 935, 954, 19], - [868, '15:36', '16:21', 936, 981, 45], - [869, '15:38', '16:25', 938, 985, 47], - [870, '15:38', '16:25', 938, 985, 47], - [871, '15:38', '16:39', 938, 999, 61], - [872, '15:39', '16:33', 939, 993, 54], - [873, '15:40', '15:50', 940, 950, 10], - [874, '15:40', '16:51', 940, 1011, 71], - [875, '15:40', '15:56', 940, 956, 16], - [876, '15:43', '16:10', 943, 970, 27], - [877, '15:43', '16:30', 943, 990, 47], - [878, '15:44', '17:10', 944, 1030, 86], - [879, '15:44', '16:25', 944, 985, 41], - [880, '15:45', '16:04', 945, 964, 19], - [881, '15:46', '15:58', 946, 958, 12], - [882, '15:48', '16:35', 948, 995, 47], - [883, '15:48', '16:35', 948, 995, 47], - [884, '15:48', '17:14', 948, 1034, 86], - [885, '15:49', '16:43', 949, 1003, 54], - [886, '15:50', '16:00', 950, 960, 10], - [887, '15:50', '17:01', 950, 1021, 71], - [888, '15:51', '16:18', 951, 978, 27], - [889, '15:52', '16:36', 952, 996, 44], - [890, '15:53', '16:40', 953, 1000, 47], - [891, '15:54', '17:20', 954, 1040, 86], - [892, '15:54', '16:35', 954, 995, 41], - [893, '15:55', '16:14', 955, 974, 19], - [894, '15:58', '16:25', 958, 985, 27], - [895, '15:58', '16:45', 958, 1005, 47], - [896, '15:58', '16:45', 958, 1005, 47], - [897, '15:58', '17:24', 958, 1044, 86], - [898, '15:59', '17:11', 959, 1031, 72], - [899, '15:59', '16:53', 959, 1013, 54], - [900, '16:00', '16:10', 960, 970, 10], - [901, '16:00', '16:16', 960, 976, 16], - [902, '16:01', '16:13', 961, 973, 12], - [903, '16:03', '16:50', 963, 1010, 47], - [904, '16:04', '17:30', 964, 1050, 86], - [905, '16:04', '16:45', 964, 1005, 41], - [906, '16:05', '16:24', 965, 984, 19], - [907, '16:06', '16:51', 966, 1011, 45], - [908, '16:08', '16:55', 968, 1015, 47], - [909, '16:08', '17:34', 968, 1054, 86], - [910, '16:08', '16:55', 968, 1015, 47], - [911, '16:09', '17:03', 969, 1023, 54], - [912, '16:09', '17:21', 969, 1041, 72], - [913, '16:10', '16:20', 970, 980, 10], - [914, '16:13', '16:40', 973, 1000, 27], - [915, '16:13', '17:00', 973, 1020, 47], - [916, '16:14', '16:55', 974, 1015, 41], - [917, '16:14', '17:40', 974, 1060, 86], - [918, '16:15', '16:34', 975, 994, 19], - [919, '16:16', '16:28', 976, 988, 12], - [920, '16:18', '17:05', 978, 1025, 47], - [921, '16:18', '17:05', 978, 1025, 47], - [922, '16:18', '17:44', 978, 1064, 86], - [923, '16:19', '17:31', 979, 1051, 72], - [924, '16:19', '17:13', 979, 1033, 54], - [925, '16:20', '16:30', 980, 990, 10], - [926, '16:20', '16:36', 980, 996, 16], - [927, '16:21', '16:48', 981, 1008, 27], - [928, '16:22', '17:06', 982, 1026, 44], - [929, '16:23', '17:10', 983, 1030, 47], - [930, '16:24', '17:05', 984, 1025, 41], - [931, '16:24', '17:50', 984, 1070, 86], - [932, '16:25', '16:44', 985, 1004, 19], - [933, '16:28', '17:15', 988, 1035, 47], - [934, '16:28', '17:15', 988, 1035, 47], - [935, '16:28', '16:55', 988, 1015, 27], - [936, '16:28', '17:54', 988, 1074, 86], - [937, '16:29', '17:23', 989, 1043, 54], - [938, '16:29', '17:41', 989, 1061, 72], - [939, '16:30', '16:40', 990, 1000, 10], - [940, '16:31', '16:43', 991, 1003, 12], - [941, '16:33', '17:20', 993, 1040, 47], - [942, '16:34', '17:15', 994, 1035, 41], - [943, '16:34', '18:00', 994, 1080, 86], - [944, '16:35', '16:54', 995, 1014, 19], - [945, '16:36', '17:21', 996, 1041, 45], - [946, '16:38', '17:25', 998, 1045, 47], - [947, '16:38', '17:25', 998, 1045, 47], - [948, '16:38', '18:04', 998, 1084, 86], - [949, '16:39', '17:33', 999, 1053, 54], - [950, '16:39', '17:51', 999, 1071, 72], - [951, '16:40', '16:56', 1000, 1016, 16], - [952, '16:40', '16:50', 1000, 1010, 10], - [953, '16:43', '17:10', 1003, 1030, 27], - [954, '16:43', '17:30', 1003, 1050, 47], - [955, '16:44', '17:25', 1004, 1045, 41], - [956, '16:44', '18:10', 1004, 1090, 86], - [957, '16:45', '17:04', 1005, 1024, 19], - [958, '16:46', '16:58', 1006, 1018, 12], - [959, '16:48', '18:14', 1008, 1094, 86], - [960, '16:48', '17:35', 1008, 1055, 47], - [961, '16:48', '17:35', 1008, 1055, 47], - [962, '16:49', '18:01', 1009, 1081, 72], - [963, '16:49', '17:43', 1009, 1063, 54], - [964, '16:50', '17:00', 1010, 1020, 10], - [965, '16:51', '17:18', 1011, 1038, 27], - [966, '16:52', '17:36', 1012, 1056, 44], - [967, '16:53', '17:40', 1013, 1060, 47], - [968, '16:54', '18:20', 1014, 1100, 86], - [969, '16:54', '17:35', 1014, 1055, 41], - [970, '16:55', '17:14', 1015, 1034, 19], - [971, '16:58', '17:25', 1018, 1045, 27], - [972, '16:58', '17:45', 1018, 1065, 47], - [973, '16:58', '17:45', 1018, 1065, 47], - [974, '16:58', '18:24', 1018, 1104, 86], - [975, '16:59', '18:11', 1019, 1091, 72], - [976, '16:59', '17:53', 1019, 1073, 54], - [977, '17:00', '17:16', 1020, 1036, 16], - [978, '17:00', '17:10', 1020, 1030, 10], - [979, '17:01', '17:13', 1021, 1033, 12], - [980, '17:03', '17:50', 1023, 1070, 47], - [981, '17:04', '18:30', 1024, 1110, 86], - [982, '17:04', '17:45', 1024, 1065, 41], - [983, '17:05', '17:24', 1025, 1044, 19], - [984, '17:06', '17:51', 1026, 1071, 45], - [985, '17:08', '17:55', 1028, 1075, 47], - [986, '17:08', '17:55', 1028, 1075, 47], - [987, '17:08', '18:34', 1028, 1114, 86], - [988, '17:09', '18:03', 1029, 1083, 54], - [989, '17:09', '18:21', 1029, 1101, 72], - [990, '17:10', '17:20', 1030, 1040, 10], - [991, '17:13', '17:40', 1033, 1060, 27], - [992, '17:13', '18:00', 1033, 1080, 47], - [993, '17:14', '17:55', 1034, 1075, 41], - [994, '17:14', '18:40', 1034, 1120, 86], - [995, '17:15', '17:34', 1035, 1054, 19], - [996, '17:16', '17:28', 1036, 1048, 12], - [997, '17:18', '18:05', 1038, 1085, 47], - [998, '17:18', '18:05', 1038, 1085, 47], - [999, '17:18', '18:44', 1038, 1124, 86], - [1000, '17:19', '18:31', 1039, 1111, 72], - [1001, '17:19', '18:13', 1039, 1093, 54], - [1002, '17:20', '17:36', 1040, 1056, 16], - [1003, '17:20', '17:30', 1040, 1050, 10], - [1004, '17:21', '17:47', 1041, 1067, 26], - [1005, '17:22', '18:06', 1042, 1086, 44], - [1006, '17:23', '18:10', 1043, 1090, 47], - [1007, '17:24', '18:50', 1044, 1130, 86], - [1008, '17:24', '18:05', 1044, 1085, 41], - [1009, '17:25', '17:44', 1045, 1064, 19], - [1010, '17:28', '17:55', 1048, 1075, 27], - [1011, '17:28', '18:15', 1048, 1095, 47], - [1012, '17:28', '18:15', 1048, 1095, 47], - [1013, '17:28', '18:54', 1048, 1134, 86], - [1014, '17:29', '18:41', 1049, 1121, 72], - [1015, '17:29', '18:23', 1049, 1103, 54], - [1016, '17:30', '17:40', 1050, 1060, 10], - [1017, '17:31', '17:43', 1051, 1063, 12], - [1018, '17:33', '18:20', 1053, 1100, 47], - [1019, '17:34', '18:15', 1054, 1095, 41], - [1020, '17:34', '19:00', 1054, 1140, 86], - [1021, '17:35', '17:54', 1055, 1074, 19], - [1022, '17:36', '18:21', 1056, 1101, 45], - [1023, '17:38', '18:25', 1058, 1105, 47], - [1024, '17:38', '19:04', 1058, 1144, 86], - [1025, '17:38', '18:25', 1058, 1105, 47], - [1026, '17:39', '18:51', 1059, 1131, 72], - [1027, '17:39', '18:33', 1059, 1113, 54], - [1028, '17:40', '17:56', 1060, 1076, 16], - [1029, '17:40', '17:50', 1060, 1070, 10], - [1030, '17:43', '18:10', 1063, 1090, 27], - [1031, '17:43', '18:30', 1063, 1110, 47], - [1032, '17:44', '18:25', 1064, 1105, 41], - [1033, '17:44', '19:14', 1064, 1154, 90], - [1034, '17:45', '18:04', 1065, 1084, 19], - [1035, '17:46', '17:58', 1066, 1078, 12], - [1036, '17:48', '18:35', 1068, 1115, 47], - [1037, '17:48', '18:35', 1068, 1115, 47], - [1038, '17:48', '19:14', 1068, 1154, 86], - [1039, '17:49', '19:01', 1069, 1141, 72], - [1040, '17:49', '18:43', 1069, 1123, 54], - [1041, '17:50', '18:00', 1070, 1080, 10], - [1042, '17:51', '18:17', 1071, 1097, 26], - [1043, '17:52', '18:36', 1072, 1116, 44], - [1044, '17:53', '18:40', 1073, 1120, 47], - [1045, '17:54', '18:35', 1074, 1115, 41], - [1046, '17:54', '18:57', 1074, 1137, 63], - [1047, '17:55', '18:14', 1075, 1094, 19], - [1048, '17:58', '18:45', 1078, 1125, 47], - [1049, '17:58', '18:45', 1078, 1125, 47], - [1050, '17:58', '18:25', 1078, 1105, 27], - [1051, '17:58', '19:26', 1078, 1166, 88], - [1052, '17:59', '18:53', 1079, 1133, 54], - [1053, '18:00', '19:11', 1080, 1151, 71], - [1054, '18:00', '18:10', 1080, 1090, 10], - [1055, '18:00', '18:16', 1080, 1096, 16], - [1056, '18:01', '18:13', 1081, 1093, 12], - [1057, '18:03', '18:50', 1083, 1130, 47], - [1058, '18:04', '18:45', 1084, 1125, 41], - [1059, '18:04', '19:29', 1084, 1169, 85], - [1060, '18:05', '18:24', 1085, 1104, 19], - [1061, '18:06', '18:51', 1086, 1131, 45], - [1062, '18:08', '18:55', 1088, 1135, 47], - [1063, '18:08', '19:06', 1088, 1146, 58], - [1064, '18:08', '18:55', 1088, 1135, 47], - [1065, '18:09', '19:03', 1089, 1143, 54], - [1066, '18:10', '18:20', 1090, 1100, 10], - [1067, '18:10', '19:21', 1090, 1161, 71], - [1068, '18:13', '19:00', 1093, 1140, 47], - [1069, '18:13', '18:40', 1093, 1120, 27], - [1070, '18:14', '19:43', 1094, 1183, 89], - [1071, '18:14', '18:55', 1094, 1135, 41], - [1072, '18:15', '18:34', 1095, 1114, 19], - [1073, '18:16', '18:28', 1096, 1108, 12], - [1074, '18:17', '18:27', 1097, 1107, 10], - [1075, '18:18', '19:41', 1098, 1181, 83], - [1076, '18:18', '18:58', 1098, 1138, 40], - [1077, '18:18', '19:05', 1098, 1145, 47], - [1078, '18:19', '19:13', 1099, 1153, 54], - [1079, '18:20', '19:31', 1100, 1171, 71], - [1080, '18:20', '18:36', 1100, 1116, 16], - [1081, '18:20', '18:30', 1100, 1110, 10], - [1082, '18:22', '19:05', 1102, 1145, 43], - [1083, '18:23', '19:05', 1103, 1145, 42], - [1084, '18:24', '19:27', 1104, 1167, 63], - [1085, '18:24', '19:05', 1104, 1145, 41], - [1086, '18:25', '18:44', 1105, 1124, 19], - [1087, '18:28', '19:25', 1108, 1165, 57], - [1088, '18:28', '18:55', 1108, 1135, 27], - [1089, '18:28', '19:08', 1108, 1148, 40], - [1090, '18:28', '19:15', 1108, 1155, 47], - [1091, '18:29', '19:23', 1109, 1163, 54], - [1092, '18:30', '19:05', 1110, 1145, 35], - [1093, '18:30', '18:40', 1110, 1120, 10], - [1094, '18:31', '18:43', 1111, 1123, 12], - [1095, '18:33', '19:15', 1113, 1155, 42], - [1096, '18:34', '19:58', 1114, 1198, 84], - [1097, '18:34', '19:14', 1114, 1154, 40], - [1098, '18:35', '18:55', 1115, 1135, 20], - [1099, '18:36', '19:20', 1116, 1160, 44], - [1100, '18:38', '19:25', 1118, 1165, 47], - [1101, '18:38', '19:23', 1118, 1163, 45], - [1102, '18:38', '19:56', 1118, 1196, 78], - [1103, '18:39', '19:33', 1119, 1173, 54], - [1104, '18:40', '18:50', 1120, 1130, 10], - [1105, '18:40', '19:45', 1120, 1185, 65], - [1106, '18:40', '18:56', 1120, 1136, 16], - [1107, '18:43', '19:10', 1123, 1150, 27], - [1108, '18:43', '19:30', 1123, 1170, 47], - [1109, '18:44', '19:24', 1124, 1164, 40], - [1110, '18:45', '19:05', 1125, 1145, 20], - [1111, '18:46', '18:58', 1126, 1138, 12], - [1112, '18:48', '19:35', 1128, 1175, 47], - [1113, '18:48', '20:12', 1128, 1212, 84], - [1114, '18:48', '20:11', 1128, 1211, 83], - [1115, '18:48', '19:28', 1128, 1168, 40], - [1116, '18:49', '19:43', 1129, 1183, 54], - [1117, '18:50', '19:00', 1130, 1140, 10], - [1118, '18:51', '19:01', 1131, 1141, 10], - [1119, '18:53', '19:35', 1133, 1175, 42], - [1120, '18:53', '19:15', 1133, 1155, 22], - [1121, '18:53', '20:00', 1133, 1200, 67], - [1122, '18:55', '19:15', 1135, 1155, 20], - [1123, '18:55', '19:34', 1135, 1174, 39], - [1124, '18:58', '19:38', 1138, 1178, 40], - [1125, '18:59', '19:53', 1139, 1193, 54], - [1126, '18:59', '19:50', 1139, 1190, 51], - [1127, '18:59', '19:53', 1139, 1193, 54], - [1128, '19:00', '19:16', 1140, 1156, 16], - [1129, '19:00', '19:10', 1140, 1150, 10], - [1130, '19:00', '19:16', 1140, 1156, 16], - [1131, '19:01', '19:13', 1141, 1153, 12], - [1132, '19:03', '20:26', 1143, 1226, 83], - [1133, '19:03', '19:45', 1143, 1185, 42], - [1134, '19:05', '19:44', 1145, 1184, 39], - [1135, '19:05', '19:25', 1145, 1165, 20], - [1136, '19:08', '20:15', 1148, 1215, 67], - [1137, '19:08', '19:35', 1148, 1175, 27], - [1138, '19:09', '19:49', 1149, 1189, 40], - [1139, '19:09', '20:03', 1149, 1203, 54], - [1140, '19:10', '19:20', 1150, 1160, 10], - [1141, '19:10', '19:20', 1150, 1160, 10], - [1142, '19:11', '19:53', 1151, 1193, 42], - [1143, '19:14', '20:26', 1154, 1226, 72], - [1144, '19:14', '19:35', 1154, 1175, 21], - [1145, '19:14', '19:24', 1154, 1164, 10], - [1146, '19:14', '20:05', 1154, 1205, 51], - [1147, '19:15', '19:30', 1155, 1170, 15], - [1148, '19:15', '19:54', 1155, 1194, 39], - [1149, '19:18', '20:39', 1158, 1239, 81], - [1150, '19:18', '20:00', 1158, 1200, 42], - [1151, '19:19', '20:14', 1159, 1214, 55], - [1152, '19:20', '19:30', 1160, 1170, 10], - [1153, '19:20', '19:36', 1160, 1176, 16], - [1154, '19:21', '19:31', 1161, 1171, 10], - [1155, '19:23', '20:30', 1163, 1230, 67], - [1156, '19:23', '19:35', 1163, 1175, 12], - [1157, '19:24', '19:45', 1164, 1185, 21], - [1158, '19:24', '19:45', 1164, 1185, 21], - [1159, '19:25', '20:04', 1165, 1204, 39], - [1160, '19:26', '20:08', 1166, 1208, 42], - [1161, '19:29', '20:02', 1169, 1202, 33], - [1162, '19:29', '20:18', 1169, 1218, 49], - [1163, '19:29', '20:41', 1169, 1241, 72], - [1164, '19:30', '19:40', 1170, 1180, 10], - [1165, '19:33', '20:54', 1173, 1254, 81], - [1166, '19:33', '20:17', 1173, 1217, 44], - [1167, '19:34', '19:55', 1174, 1195, 21], - [1168, '19:35', '20:14', 1175, 1214, 39], - [1169, '19:38', '20:05', 1178, 1205, 27], - [1170, '19:38', '20:45', 1178, 1245, 67], - [1171, '19:39', '20:12', 1179, 1212, 33], - [1172, '19:40', '19:50', 1180, 1190, 10], - [1173, '19:40', '19:56', 1180, 1196, 16], - [1174, '19:41', '20:27', 1181, 1227, 46], - [1175, '19:43', '19:55', 1183, 1195, 12], - [1176, '19:44', '20:05', 1184, 1205, 21], - [1177, '19:44', '20:33', 1184, 1233, 49], - [1178, '19:44', '21:00', 1184, 1260, 76], - [1179, '19:45', '20:24', 1185, 1224, 39], - [1180, '19:48', '20:37', 1188, 1237, 49], - [1181, '19:48', '21:09', 1188, 1269, 81], - [1182, '19:50', '20:00', 1190, 1200, 10], - [1183, '19:52', '20:29', 1192, 1229, 37], - [1184, '19:53', '20:08', 1193, 1208, 15], - [1185, '19:53', '21:02', 1193, 1262, 69], - [1186, '19:53', '20:20', 1193, 1220, 27], - [1187, '19:54', '20:19', 1194, 1219, 25], - [1188, '19:55', '20:34', 1195, 1234, 39], - [1189, '19:56', '20:34', 1196, 1234, 38], - [1190, '19:59', '20:48', 1199, 1248, 49], - [1191, '19:59', '21:20', 1199, 1280, 81], - [1192, '20:00', '20:16', 1200, 1216, 16], - [1193, '20:00', '20:10', 1200, 1210, 10], - [1194, '20:03', '20:42', 1203, 1242, 39], - [1195, '20:03', '21:24', 1203, 1284, 81], - [1196, '20:04', '20:29', 1204, 1229, 25], - [1197, '20:05', '20:48', 1205, 1248, 43], - [1198, '20:07', '20:44', 1207, 1244, 37], - [1199, '20:08', '20:40', 1208, 1240, 32], - [1200, '20:08', '20:35', 1208, 1235, 27], - [1201, '20:10', '20:20', 1210, 1220, 10], - [1202, '20:10', '20:22', 1210, 1222, 12], - [1203, '20:11', '20:47', 1211, 1247, 36], - [1204, '20:14', '21:04', 1214, 1264, 50], - [1205, '20:14', '21:03', 1214, 1263, 49], - [1206, '20:17', '21:03', 1217, 1263, 46], - [1207, '20:18', '21:39', 1218, 1299, 81], - [1208, '20:20', '20:30', 1220, 1230, 10], - [1209, '20:20', '20:57', 1220, 1257, 37], - [1210, '20:20', '20:36', 1220, 1236, 16], - [1211, '20:22', '20:59', 1222, 1259, 37], - [1212, '20:22', '20:42', 1222, 1242, 20], - [1213, '20:24', '20:49', 1224, 1249, 25], - [1214, '20:27', '21:22', 1227, 1282, 55], - [1215, '20:29', '21:18', 1229, 1278, 49], - [1216, '20:30', '21:07', 1230, 1267, 37], - [1217, '20:30', '20:40', 1230, 1240, 10], - [1218, '20:30', '20:40', 1230, 1240, 10], - [1219, '20:30', '21:40', 1230, 1300, 70], - [1220, '20:32', '21:18', 1232, 1278, 46], - [1221, '20:35', '21:54', 1235, 1314, 79], - [1222, '20:37', '21:14', 1237, 1274, 37], - [1223, '20:38', '21:08', 1238, 1268, 30], - [1224, '20:40', '20:50', 1240, 1250, 10], - [1225, '20:40', '21:17', 1240, 1277, 37], - [1226, '20:40', '20:56', 1240, 1256, 16], - [1227, '20:44', '21:33', 1244, 1293, 49], - [1228, '20:47', '21:33', 1247, 1293, 46], - [1229, '20:47', '21:42', 1247, 1302, 55], - [1230, '20:50', '21:00', 1250, 1260, 10], - [1231, '20:50', '22:00', 1250, 1320, 70], - [1232, '20:50', '22:09', 1250, 1329, 79], - [1233, '20:50', '21:27', 1250, 1287, 37], - [1234, '20:52', '21:29', 1252, 1289, 37], - [1235, '20:53', '21:20', 1253, 1280, 27], - [1236, '20:56', '21:11', 1256, 1271, 15], - [1237, '20:59', '21:48', 1259, 1308, 49], - [1238, '21:00', '21:10', 1260, 1270, 10], - [1239, '21:00', '21:37', 1260, 1297, 37], - [1240, '21:02', '21:48', 1262, 1308, 46], - [1241, '21:05', '22:24', 1265, 1344, 79], - [1242, '21:07', '21:44', 1267, 1304, 37], - [1243, '21:07', '22:02', 1267, 1322, 55], - [1244, '21:08', '21:38', 1268, 1298, 30], - [1245, '21:10', '22:25', 1270, 1345, 75], - [1246, '21:10', '21:20', 1270, 1280, 10], - [1247, '21:10', '21:47', 1270, 1307, 37], - [1248, '21:14', '22:03', 1274, 1323, 49], - [1249, '21:17', '22:03', 1277, 1323, 46], - [1250, '21:20', '22:18', 1280, 1338, 58], - [1251, '21:20', '21:57', 1280, 1317, 37], - [1252, '21:20', '21:30', 1280, 1290, 10], - [1253, '21:22', '21:59', 1282, 1319, 37], - [1254, '21:24', '21:49', 1284, 1309, 25], - [1255, '21:27', '22:21', 1287, 1341, 54], - [1256, '21:30', '22:07', 1290, 1327, 37], - [1257, '21:30', '22:20', 1290, 1340, 50], - [1258, '21:30', '21:40', 1290, 1300, 10], - [1259, '21:32', '22:18', 1292, 1338, 46], - [1260, '21:32', '22:01', 1292, 1321, 29], - [1261, '21:35', '22:54', 1295, 1374, 79], - [1262, '21:37', '22:14', 1297, 1334, 37], - [1263, '21:39', '21:55', 1299, 1315, 16], - [1264, '21:40', '22:17', 1300, 1337, 37], - [1265, '21:40', '21:50', 1300, 1310, 10], - [1266, '21:41', '22:08', 1301, 1328, 27], - [1267, '21:47', '22:16', 1307, 1336, 29], - [1268, '21:47', '22:51', 1307, 1371, 64], - [1269, '21:47', '22:33', 1307, 1353, 46], - [1270, '21:48', '22:03', 1308, 1323, 15], - [1271, '21:50', '22:55', 1310, 1375, 65], - [1272, '21:50', '22:27', 1310, 1347, 37], - [1273, '21:50', '22:00', 1310, 1320, 10], - [1274, '21:52', '22:29', 1312, 1349, 37], - [1275, '21:53', '22:19', 1313, 1339, 26], - [1276, '22:00', '22:38', 1320, 1358, 38], - [1277, '22:00', '22:10', 1320, 1330, 10], - [1278, '22:02', '22:12', 1322, 1332, 10], - [1279, '22:02', '22:48', 1322, 1368, 46], - [1280, '22:04', '22:31', 1324, 1351, 27], - [1281, '22:05', '23:24', 1325, 1404, 79], - [1282, '22:07', '22:44', 1327, 1364, 37], - [1283, '22:07', '22:39', 1327, 1359, 32], - [1284, '22:09', '22:25', 1329, 1345, 16], - [1285, '22:10', '23:25', 1330, 1405, 75], - [1286, '22:13', '22:38', 1333, 1358, 25], - [1287, '22:13', '22:53', 1333, 1373, 40], - [1288, '22:17', '22:27', 1337, 1347, 10], - [1289, '22:17', '23:03', 1337, 1383, 46], - [1290, '22:19', '22:46', 1339, 1366, 27], - [1291, '22:22', '22:59', 1342, 1379, 37], - [1292, '22:24', '22:48', 1344, 1368, 24], - [1293, '22:27', '22:52', 1347, 1372, 25], - [1294, '22:27', '23:21', 1347, 1401, 54], - [1295, '22:28', '23:08', 1348, 1388, 40], - [1296, '22:30', '23:17', 1350, 1397, 47], - [1297, '22:32', '22:42', 1352, 1362, 10], - [1298, '22:32', '23:11', 1352, 1391, 39], - [1299, '22:34', '23:01', 1354, 1381, 27], - [1300, '22:35', '23:54', 1355, 1434, 79], - [1301, '22:37', '23:14', 1357, 1394, 37], - [1302, '22:43', '23:23', 1363, 1403, 40], - [1303, '22:43', '23:08', 1363, 1388, 25], - [1304, '22:47', '23:33', 1367, 1413, 46], - [1305, '22:47', '22:57', 1367, 1377, 10], - [1306, '22:49', '23:16', 1369, 1396, 27], - [1307, '22:52', '23:29', 1372, 1409, 37], - [1308, '22:53', '23:15', 1373, 1395, 22], - [1309, '22:55', '23:55', 1375, 1435, 60], - [1310, '22:57', '23:51', 1377, 1431, 54], - [1311, '22:58', '23:38', 1378, 1418, 40], - [1312, '23:02', '23:41', 1382, 1421, 39], - [1313, '23:02', '23:12', 1382, 1392, 10], - [1314, '23:04', '23:31', 1384, 1411, 27], - [1315, '23:05', '00:24', 1385, 1464, 79], - [1316, '23:07', '23:44', 1387, 1424, 37], - [1317, '23:13', '23:53', 1393, 1433, 40], - [1318, '23:13', '23:38', 1393, 1418, 25], - [1319, '23:17', '00:03', 1397, 1443, 46], - [1320, '23:17', '23:27', 1397, 1407, 10], - [1321, '23:19', '23:46', 1399, 1426, 27], - [1322, '23:22', '23:59', 1402, 1439, 37], - [1323, '23:25', '00:25', 1405, 1465, 60], - [1324, '23:27', '00:21', 1407, 1461, 54], - [1325, '23:28', '00:08', 1408, 1448, 40], - [1326, '23:32', '23:42', 1412, 1422, 10], - [1327, '23:34', '00:01', 1414, 1441, 27], - [1328, '23:35', '01:05', 1415, 1505, 90], - [1329, '23:37', '00:09', 1417, 1449, 32], - [1330, '23:43', '00:23', 1423, 1463, 40], - [1331, '23:43', '00:08', 1423, 1448, 25], - [1332, '23:46', '00:01', 1426, 1441, 15], - [1333, '23:47', '23:57', 1427, 1437, 10], - [1334, '23:47', '00:33', 1427, 1473, 46], - [1335, '23:52', '00:24', 1432, 1464, 32], - [1336, '23:55', '00:49', 1435, 1489, 54], - [1337, '23:57', '00:57', 1437, 1497, 60], - [1338, '23:58', '00:38', 1438, 1478, 40], - [1339, '00:02', '00:12', 1442, 1452, 10], - [1340, '00:07', '00:39', 1447, 1479, 32], - [1341, '00:13', '00:38', 1453, 1478, 25], - [1342, '00:13', '00:51', 1453, 1491, 38], - [1343, '00:15', '01:14', 1455, 1514, 59], - [1344, '00:17', '01:23', 1457, 1523, 66], - [1345, '00:23', '00:33', 1463, 1473, 10], - [1346, '00:24', '00:40', 1464, 1480, 16], - [1347, '00:25', '01:12', 1465, 1512, 47], - [1348, '00:28', '01:07', 1468, 1507, 39], - [1349, '00:33', '01:05', 1473, 1505, 32], - [1350, '00:43', '01:21', 1483, 1521, 38], - [1351, '00:44', '00:54', 1484, 1494, 10], - [1352, '00:47', '01:09', 1487, 1509, 22], - [1353, '00:47', '01:26', 1487, 1526, 39], - [1354, '00:54', '01:04', 1494, 1504, 10], - [1355, '00:57', '01:07', 1497, 1507, 10] + [0, "04:18", "05:00", 258, 300, 42], + [1, "04:27", "05:08", 267, 308, 41], + [2, "04:29", "05:26", 269, 326, 57], + [3, "04:29", "04:55", 269, 295, 26], + [4, "04:30", "04:53", 270, 293, 23], + [5, "04:30", "04:51", 270, 291, 21], + [6, "04:31", "04:53", 271, 293, 22], + [7, "04:33", "05:15", 273, 315, 42], + [8, "04:34", "04:44", 274, 284, 10], + [9, "04:34", "05:03", 274, 303, 29], + [10, "04:35", "04:50", 275, 290, 15], + [11, "04:36", "04:46", 276, 286, 10], + [12, "04:37", "05:18", 277, 318, 41], + [13, "04:41", "05:13", 281, 313, 32], + [14, "04:42", "05:23", 282, 323, 41], + [15, "04:43", "04:53", 283, 293, 10], + [16, "04:44", "05:45", 284, 345, 61], + [17, "04:45", "05:11", 285, 311, 26], + [18, "04:46", "05:01", 286, 301, 15], + [19, "04:46", "04:56", 286, 296, 10], + [20, "04:47", "05:14", 287, 314, 27], + [21, "04:48", "05:30", 288, 330, 42], + [22, "04:49", "05:41", 289, 341, 52], + [23, "04:49", "05:18", 289, 318, 29], + [24, "04:50", "05:33", 290, 333, 43], + [25, "04:52", "05:56", 292, 356, 64], + [26, "04:52", "05:07", 292, 307, 15], + [27, "04:53", "05:19", 293, 319, 26], + [28, "04:53", "05:23", 293, 323, 30], + [29, "04:55", "05:27", 295, 327, 32], + [30, "04:57", "05:38", 297, 338, 41], + [31, "05:00", "06:00", 300, 360, 60], + [32, "05:00", "05:54", 300, 354, 54], + [33, "05:01", "05:33", 301, 333, 32], + [34, "05:01", "05:26", 301, 326, 25], + [35, "05:02", "05:29", 302, 329, 27], + [36, "05:02", "05:12", 302, 312, 10], + [37, "05:03", "05:45", 303, 345, 42], + [38, "05:03", "05:18", 303, 318, 15], + [39, "05:03", "06:28", 303, 388, 85], + [40, "05:03", "05:13", 303, 313, 10], + [41, "05:04", "06:24", 304, 384, 80], + [42, "05:07", "05:44", 307, 344, 37], + [43, "05:08", "05:48", 308, 348, 40], + [44, "05:10", "06:06", 310, 366, 56], + [45, "05:11", "05:37", 311, 337, 26], + [46, "05:11", "05:53", 311, 353, 42], + [47, "05:13", "06:15", 313, 375, 62], + [48, "05:13", "05:38", 313, 338, 25], + [49, "05:16", "05:44", 316, 344, 28], + [50, "05:17", "05:27", 317, 327, 10], + [51, "05:18", "06:40", 318, 400, 82], + [52, "05:18", "06:03", 318, 363, 45], + [53, "05:18", "06:11", 318, 371, 53], + [54, "05:18", "06:00", 318, 360, 42], + [55, "05:19", "06:34", 319, 394, 75], + [56, "05:20", "06:17", 320, 377, 57], + [57, "05:22", "05:59", 322, 359, 37], + [58, "05:24", "05:48", 324, 348, 24], + [59, "05:25", "05:40", 325, 340, 15], + [60, "05:26", "06:08", 326, 368, 42], + [61, "05:27", "06:30", 327, 390, 63], + [62, "05:27", "05:54", 327, 354, 27], + [63, "05:28", "05:53", 328, 353, 25], + [64, "05:29", "05:44", 329, 344, 15], + [65, "05:30", "05:40", 330, 340, 10], + [66, "05:30", "05:40", 330, 340, 10], + [67, "05:30", "05:40", 330, 340, 10], + [68, "05:32", "06:53", 332, 413, 81], + [69, "05:33", "07:00", 333, 420, 87], + [70, "05:33", "06:15", 333, 375, 42], + [71, "05:33", "05:47", 333, 347, 14], + [72, "05:37", "06:13", 337, 373, 36], + [73, "05:37", "06:05", 337, 365, 28], + [74, "05:38", "06:33", 338, 393, 55], + [75, "05:38", "06:04", 338, 364, 26], + [76, "05:38", "06:18", 338, 378, 40], + [77, "05:39", "05:54", 339, 354, 15], + [78, "05:40", "05:56", 340, 356, 16], + [79, "05:40", "06:41", 340, 401, 61], + [80, "05:40", "05:50", 340, 350, 10], + [81, "05:41", "06:23", 341, 383, 42], + [82, "05:41", "06:01", 341, 361, 20], + [83, "05:43", "06:08", 343, 368, 25], + [84, "05:44", "07:10", 344, 430, 86], + [85, "05:44", "05:55", 344, 355, 11], + [86, "05:45", "06:44", 345, 404, 59], + [87, "05:47", "06:17", 347, 377, 30], + [88, "05:48", "07:08", 348, 428, 80], + [89, "05:48", "06:30", 348, 390, 42], + [90, "05:50", "06:50", 350, 410, 60], + [91, "05:50", "06:00", 350, 360, 10], + [92, "05:50", "06:00", 350, 360, 10], + [93, "05:50", "06:51", 350, 411, 61], + [94, "05:52", "06:33", 352, 393, 41], + [95, "05:52", "06:36", 352, 396, 44], + [96, "05:52", "06:23", 352, 383, 31], + [97, "05:54", "06:14", 354, 374, 20], + [98, "05:54", "07:20", 354, 440, 86], + [99, "05:55", "06:40", 355, 400, 45], + [100, "05:55", "06:27", 355, 387, 32], + [101, "05:56", "06:35", 356, 395, 39], + [102, "05:56", "06:06", 356, 366, 10], + [103, "05:57", "06:21", 357, 381, 24], + [104, "05:58", "07:23", 358, 443, 85], + [105, "05:58", "06:23", 358, 383, 25], + [106, "05:58", "06:08", 358, 368, 10], + [107, "05:58", "06:43", 358, 403, 45], + [108, "06:00", "06:10", 360, 370, 10], + [109, "06:00", "06:16", 360, 376, 16], + [110, "06:00", "07:01", 360, 421, 61], + [111, "06:01", "07:00", 361, 420, 59], + [112, "06:01", "06:13", 361, 373, 12], + [113, "06:01", "06:45", 361, 405, 44], + [114, "06:03", "06:50", 363, 410, 47], + [115, "06:04", "06:37", 364, 397, 33], + [116, "06:04", "07:30", 364, 450, 86], + [117, "06:05", "06:24", 365, 384, 19], + [118, "06:06", "06:51", 366, 411, 45], + [119, "06:07", "06:43", 367, 403, 36], + [120, "06:08", "07:30", 368, 450, 82], + [121, "06:10", "06:20", 370, 380, 10], + [122, "06:10", "07:17", 370, 437, 67], + [123, "06:11", "06:54", 371, 414, 43], + [124, "06:11", "06:21", 371, 381, 10], + [125, "06:13", "06:38", 373, 398, 25], + [126, "06:13", "06:58", 373, 418, 45], + [127, "06:13", "06:53", 373, 413, 40], + [128, "06:14", "07:03", 374, 423, 49], + [129, "06:14", "06:47", 374, 407, 33], + [130, "06:14", "07:40", 374, 460, 86], + [131, "06:15", "07:15", 375, 435, 60], + [132, "06:16", "06:28", 376, 388, 12], + [133, "06:16", "06:26", 376, 386, 10], + [134, "06:17", "06:34", 377, 394, 17], + [135, "06:18", "07:06", 378, 426, 48], + [136, "06:18", "07:38", 378, 458, 80], + [137, "06:18", "07:02", 378, 422, 44], + [138, "06:19", "06:53", 379, 413, 34], + [139, "06:20", "07:25", 380, 445, 65], + [140, "06:20", "06:36", 380, 396, 16], + [141, "06:20", "06:30", 380, 390, 10], + [142, "06:20", "06:30", 380, 390, 10], + [143, "06:21", "06:49", 381, 409, 28], + [144, "06:22", "07:06", 382, 426, 44], + [145, "06:24", "07:50", 384, 470, 86], + [146, "06:24", "06:57", 384, 417, 33], + [147, "06:26", "07:45", 386, 465, 79], + [148, "06:26", "07:10", 386, 430, 44], + [149, "06:27", "06:44", 387, 404, 17], + [150, "06:28", "06:53", 388, 413, 25], + [151, "06:28", "07:14", 388, 434, 46], + [152, "06:29", "07:03", 389, 423, 34], + [153, "06:30", "06:40", 390, 400, 10], + [154, "06:30", "07:37", 390, 457, 67], + [155, "06:31", "06:43", 391, 403, 12], + [156, "06:33", "07:14", 393, 434, 41], + [157, "06:33", "07:53", 393, 473, 80], + [158, "06:34", "08:16", 394, 496, 102], + [159, "06:34", "07:09", 394, 429, 35], + [160, "06:34", "07:07", 394, 427, 33], + [161, "06:36", "07:21", 396, 441, 45], + [162, "06:37", "07:22", 397, 442, 45], + [163, "06:37", "06:54", 397, 414, 17], + [164, "06:38", "07:30", 398, 450, 52], + [165, "06:38", "07:18", 398, 438, 40], + [166, "06:39", "07:33", 399, 453, 54], + [167, "06:40", "07:52", 400, 472, 72], + [168, "06:40", "06:50", 400, 410, 10], + [169, "06:40", "07:22", 400, 442, 42], + [170, "06:40", "06:56", 400, 416, 16], + [171, "06:41", "08:00", 401, 480, 79], + [172, "06:42", "07:26", 402, 446, 44], + [173, "06:42", "07:13", 402, 433, 31], + [174, "06:43", "07:08", 403, 428, 25], + [175, "06:43", "07:30", 403, 450, 47], + [176, "06:43", "07:23", 403, 443, 40], + [177, "06:44", "07:17", 404, 437, 33], + [178, "06:44", "08:13", 404, 493, 89], + [179, "06:46", "07:01", 406, 421, 15], + [180, "06:46", "06:58", 406, 418, 12], + [181, "06:47", "07:04", 407, 424, 17], + [182, "06:48", "08:15", 408, 495, 87], + [183, "06:48", "07:34", 408, 454, 46], + [184, "06:48", "07:37", 408, 457, 49], + [185, "06:49", "07:43", 409, 463, 54], + [186, "06:50", "08:00", 410, 480, 70], + [187, "06:50", "07:00", 410, 420, 10], + [188, "06:50", "07:05", 410, 425, 15], + [189, "06:51", "07:18", 411, 438, 27], + [190, "06:52", "07:36", 412, 456, 44], + [191, "06:53", "07:37", 413, 457, 44], + [192, "06:54", "08:20", 414, 500, 86], + [193, "06:54", "07:27", 414, 447, 33], + [194, "06:54", "07:20", 414, 440, 26], + [195, "06:56", "08:23", 416, 503, 87], + [196, "06:57", "07:12", 417, 432, 15], + [197, "06:57", "07:58", 417, 478, 61], + [198, "06:57", "07:45", 417, 465, 48], + [199, "06:57", "07:40", 417, 460, 43], + [200, "06:58", "07:23", 418, 443, 25], + [201, "06:59", "07:53", 419, 473, 54], + [202, "06:59", "08:07", 419, 487, 68], + [203, "07:00", "07:10", 420, 430, 10], + [204, "07:00", "07:16", 420, 436, 16], + [205, "07:01", "08:30", 421, 510, 89], + [206, "07:01", "07:13", 421, 433, 12], + [207, "07:01", "07:43", 421, 463, 42], + [208, "07:03", "08:30", 423, 510, 87], + [209, "07:04", "07:37", 424, 457, 33], + [210, "07:04", "07:44", 424, 464, 40], + [211, "07:05", "07:52", 425, 472, 47], + [212, "07:05", "08:05", 425, 485, 60], + [213, "07:05", "07:46", 425, 466, 41], + [214, "07:06", "07:51", 426, 471, 45], + [215, "07:07", "08:08", 427, 488, 61], + [216, "07:07", "07:52", 427, 472, 45], + [217, "07:07", "08:16", 427, 496, 69], + [218, "07:07", "07:27", 427, 447, 20], + [219, "07:09", "07:50", 429, 470, 41], + [220, "07:09", "08:40", 429, 520, 91], + [221, "07:09", "08:03", 429, 483, 54], + [222, "07:10", "07:20", 430, 440, 10], + [223, "07:11", "08:36", 431, 516, 85], + [224, "07:12", "08:00", 432, 480, 48], + [225, "07:12", "07:47", 432, 467, 35], + [226, "07:13", "07:54", 433, 474, 41], + [227, "07:13", "07:38", 433, 458, 25], + [228, "07:14", "07:59", 434, 479, 45], + [229, "07:16", "08:50", 436, 530, 94], + [230, "07:16", "07:28", 436, 448, 12], + [231, "07:17", "07:35", 437, 455, 18], + [232, "07:17", "07:58", 437, 478, 41], + [233, "07:18", "08:06", 438, 486, 48], + [234, "07:18", "08:44", 438, 524, 86], + [235, "07:19", "08:13", 439, 493, 54], + [236, "07:20", "08:02", 440, 482, 42], + [237, "07:20", "08:07", 440, 487, 47], + [238, "07:20", "07:30", 440, 450, 10], + [239, "07:20", "07:57", 440, 477, 37], + [240, "07:20", "07:36", 440, 456, 16], + [241, "07:21", "07:48", 441, 468, 27], + [242, "07:22", "08:06", 442, 486, 44], + [243, "07:22", "08:25", 442, 505, 63], + [244, "07:24", "08:27", 444, 507, 63], + [245, "07:24", "08:05", 444, 485, 41], + [246, "07:26", "08:23", 446, 503, 57], + [247, "07:26", "08:52", 446, 532, 86], + [248, "07:27", "08:07", 447, 487, 40], + [249, "07:27", "07:42", 447, 462, 15], + [250, "07:27", "08:15", 447, 495, 48], + [251, "07:28", "07:53", 448, 473, 25], + [252, "07:28", "08:09", 448, 489, 41], + [253, "07:28", "07:38", 448, 458, 10], + [254, "07:30", "08:35", 450, 515, 65], + [255, "07:31", "07:43", 451, 463, 12], + [256, "07:32", "08:13", 452, 493, 41], + [257, "07:34", "09:00", 454, 540, 86], + [258, "07:34", "08:33", 454, 513, 59], + [259, "07:34", "09:04", 454, 544, 90], + [260, "07:35", "08:22", 455, 502, 47], + [261, "07:35", "07:45", 455, 465, 10], + [262, "07:35", "08:16", 455, 496, 41], + [263, "07:36", "08:17", 456, 497, 41], + [264, "07:36", "08:36", 456, 516, 60], + [265, "07:37", "07:50", 457, 470, 13], + [266, "07:40", "07:56", 460, 476, 16], + [267, "07:40", "08:20", 460, 500, 40], + [268, "07:40", "08:45", 460, 525, 65], + [269, "07:41", "08:39", 461, 519, 58], + [270, "07:41", "07:51", 461, 471, 10], + [271, "07:42", "08:30", 462, 510, 48], + [272, "07:42", "08:21", 462, 501, 39], + [273, "07:43", "08:08", 463, 488, 25], + [274, "07:43", "08:24", 463, 504, 41], + [275, "07:44", "09:10", 464, 550, 86], + [276, "07:44", "08:43", 464, 523, 59], + [277, "07:46", "08:28", 466, 508, 42], + [278, "07:46", "07:58", 466, 478, 12], + [279, "07:47", "08:00", 467, 480, 13], + [280, "07:48", "09:14", 468, 554, 86], + [281, "07:49", "08:32", 469, 512, 43], + [282, "07:50", "08:55", 470, 535, 65], + [283, "07:50", "08:00", 470, 480, 10], + [284, "07:50", "08:37", 470, 517, 47], + [285, "07:50", "08:26", 470, 506, 36], + [286, "07:51", "08:18", 471, 498, 27], + [287, "07:52", "08:21", 472, 501, 29], + [288, "07:53", "08:35", 473, 515, 42], + [289, "07:54", "09:19", 474, 559, 85], + [290, "07:55", "08:53", 475, 533, 58], + [291, "07:56", "08:54", 476, 534, 58], + [292, "07:57", "08:39", 477, 519, 42], + [293, "07:57", "08:10", 477, 490, 13], + [294, "07:58", "08:45", 478, 525, 47], + [295, "07:58", "08:23", 478, 503, 25], + [296, "08:00", "08:10", 480, 490, 10], + [297, "08:00", "09:05", 480, 545, 65], + [298, "08:00", "08:16", 480, 496, 16], + [299, "08:00", "08:35", 480, 515, 35], + [300, "08:01", "08:13", 481, 493, 12], + [301, "08:01", "08:43", 481, 523, 42], + [302, "08:03", "09:26", 483, 566, 83], + [303, "08:04", "09:29", 484, 569, 85], + [304, "08:05", "08:21", 485, 501, 16], + [305, "08:05", "08:47", 485, 527, 42], + [306, "08:06", "08:51", 486, 531, 45], + [307, "08:06", "09:03", 486, 543, 57], + [308, "08:07", "08:20", 487, 500, 13], + [309, "08:08", "08:55", 488, 535, 47], + [310, "08:08", "08:50", 488, 530, 42], + [311, "08:10", "08:45", 490, 525, 35], + [312, "08:10", "09:15", 490, 555, 65], + [313, "08:10", "08:20", 490, 500, 10], + [314, "08:11", "09:41", 491, 581, 90], + [315, "08:12", "08:55", 492, 535, 43], + [316, "08:13", "08:38", 493, 518, 25], + [317, "08:14", "09:38", 494, 578, 84], + [318, "08:15", "08:30", 495, 510, 15], + [319, "08:16", "08:30", 496, 510, 14], + [320, "08:16", "08:28", 496, 508, 12], + [321, "08:16", "09:00", 496, 540, 44], + [322, "08:17", "09:13", 497, 553, 56], + [323, "08:18", "09:16", 498, 556, 58], + [324, "08:18", "09:05", 498, 545, 47], + [325, "08:20", "08:36", 500, 516, 16], + [326, "08:20", "08:55", 500, 535, 35], + [327, "08:20", "09:05", 500, 545, 45], + [328, "08:20", "08:30", 500, 510, 10], + [329, "08:20", "09:25", 500, 565, 65], + [330, "08:21", "08:38", 501, 518, 17], + [331, "08:21", "08:47", 501, 527, 26], + [332, "08:22", "08:45", 502, 525, 23], + [333, "08:23", "09:10", 503, 550, 47], + [334, "08:24", "09:48", 504, 588, 84], + [335, "08:26", "08:46", 506, 526, 20], + [336, "08:27", "09:07", 507, 547, 40], + [337, "08:28", "08:50", 508, 530, 22], + [338, "08:28", "09:56", 508, 596, 88], + [339, "08:28", "09:23", 508, 563, 55], + [340, "08:29", "09:20", 509, 560, 51], + [341, "08:30", "09:05", 510, 545, 35], + [342, "08:30", "08:45", 510, 525, 15], + [343, "08:30", "08:40", 510, 520, 10], + [344, "08:30", "09:35", 510, 575, 65], + [345, "08:31", "08:43", 511, 523, 12], + [346, "08:31", "09:13", 511, 553, 42], + [347, "08:34", "09:58", 514, 598, 84], + [348, "08:35", "08:55", 515, 535, 20], + [349, "08:35", "09:15", 515, 555, 40], + [350, "08:35", "08:45", 515, 525, 10], + [351, "08:36", "08:46", 516, 526, 10], + [352, "08:36", "09:00", 516, 540, 24], + [353, "08:38", "09:20", 518, 560, 42], + [354, "08:38", "09:35", 518, 575, 57], + [355, "08:38", "09:14", 518, 554, 36], + [356, "08:39", "09:33", 519, 573, 54], + [357, "08:40", "09:45", 520, 585, 65], + [358, "08:40", "08:50", 520, 530, 10], + [359, "08:40", "08:56", 520, 536, 16], + [360, "08:42", "09:25", 522, 565, 43], + [361, "08:43", "09:08", 523, 548, 25], + [362, "08:44", "09:35", 524, 575, 51], + [363, "08:45", "09:00", 525, 540, 15], + [364, "08:45", "09:05", 525, 545, 20], + [365, "08:46", "09:24", 526, 564, 38], + [366, "08:46", "08:58", 526, 538, 12], + [367, "08:46", "09:30", 526, 570, 44], + [368, "08:48", "10:11", 528, 611, 83], + [369, "08:48", "10:13", 528, 613, 85], + [370, "08:49", "09:43", 529, 583, 54], + [371, "08:50", "09:30", 530, 570, 40], + [372, "08:50", "10:00", 530, 600, 70], + [373, "08:50", "09:00", 530, 540, 10], + [374, "08:51", "09:17", 531, 557, 26], + [375, "08:53", "09:20", 533, 560, 27], + [376, "08:53", "09:35", 533, 575, 42], + [377, "08:55", "09:34", 535, 574, 39], + [378, "08:55", "09:15", 535, 555, 20], + [379, "08:58", "09:38", 538, 578, 40], + [380, "08:58", "10:26", 538, 626, 88], + [381, "08:59", "09:53", 539, 593, 54], + [382, "08:59", "09:50", 539, 590, 51], + [383, "09:00", "09:35", 540, 575, 35], + [384, "09:00", "09:16", 540, 556, 16], + [385, "09:00", "09:10", 540, 550, 10], + [386, "09:00", "09:16", 540, 556, 16], + [387, "09:01", "09:13", 541, 553, 12], + [388, "09:03", "09:45", 543, 585, 42], + [389, "09:03", "10:28", 543, 628, 85], + [390, "09:05", "09:44", 545, 584, 39], + [391, "09:05", "09:25", 545, 565, 20], + [392, "09:08", "09:53", 548, 593, 45], + [393, "09:08", "10:04", 548, 604, 56], + [394, "09:09", "10:03", 549, 603, 54], + [395, "09:10", "10:15", 550, 615, 65], + [396, "09:10", "09:20", 550, 560, 10], + [397, "09:11", "09:38", 551, 578, 27], + [398, "09:13", "10:00", 553, 600, 47], + [399, "09:14", "09:39", 554, 579, 25], + [400, "09:14", "10:05", 554, 605, 51], + [401, "09:15", "09:54", 555, 594, 39], + [402, "09:16", "09:28", 556, 568, 12], + [403, "09:18", "10:43", 558, 643, 85], + [404, "09:18", "10:41", 558, 641, 83], + [405, "09:18", "09:58", 558, 598, 40], + [406, "09:19", "10:13", 559, 613, 54], + [407, "09:20", "09:30", 560, 570, 10], + [408, "09:20", "09:36", 560, 576, 16], + [409, "09:21", "09:47", 561, 587, 26], + [410, "09:23", "10:30", 563, 630, 67], + [411, "09:23", "10:05", 563, 605, 42], + [412, "09:23", "09:49", 563, 589, 26], + [413, "09:24", "09:35", 564, 575, 11], + [414, "09:25", "09:35", 565, 575, 10], + [415, "09:25", "10:04", 565, 604, 39], + [416, "09:28", "10:08", 568, 608, 40], + [417, "09:29", "09:45", 569, 585, 16], + [418, "09:29", "10:20", 569, 620, 51], + [419, "09:29", "10:56", 569, 656, 87], + [420, "09:29", "10:23", 569, 623, 54], + [421, "09:30", "09:40", 570, 580, 10], + [422, "09:31", "09:43", 571, 583, 12], + [423, "09:33", "10:58", 573, 658, 85], + [424, "09:33", "10:15", 573, 615, 42], + [425, "09:34", "09:45", 574, 585, 11], + [426, "09:35", "10:14", 575, 614, 39], + [427, "09:38", "10:45", 578, 645, 67], + [428, "09:39", "10:33", 579, 633, 54], + [429, "09:40", "09:56", 580, 596, 16], + [430, "09:40", "09:50", 580, 590, 10], + [431, "09:41", "10:08", 581, 608, 27], + [432, "09:41", "10:23", 581, 623, 42], + [433, "09:44", "10:35", 584, 635, 51], + [434, "09:44", "11:11", 584, 671, 87], + [435, "09:44", "09:55", 584, 595, 11], + [436, "09:45", "10:24", 585, 624, 39], + [437, "09:46", "09:58", 586, 598, 12], + [438, "09:48", "10:30", 588, 630, 42], + [439, "09:48", "11:13", 588, 673, 85], + [440, "09:48", "10:04", 588, 604, 16], + [441, "09:49", "10:43", 589, 643, 54], + [442, "09:50", "10:00", 590, 600, 10], + [443, "09:51", "10:17", 591, 617, 26], + [444, "09:53", "10:49", 593, 649, 56], + [445, "09:53", "11:00", 593, 660, 67], + [446, "09:54", "10:05", 594, 605, 11], + [447, "09:55", "10:34", 595, 634, 39], + [448, "09:56", "10:38", 596, 638, 42], + [449, "09:57", "10:20", 597, 620, 23], + [450, "09:59", "11:26", 599, 686, 87], + [451, "09:59", "10:50", 599, 650, 51], + [452, "09:59", "10:53", 599, 653, 54], + [453, "10:00", "10:16", 600, 616, 16], + [454, "10:00", "10:10", 600, 610, 10], + [455, "10:01", "10:13", 601, 613, 12], + [456, "10:03", "11:28", 603, 688, 85], + [457, "10:03", "10:45", 603, 645, 42], + [458, "10:04", "10:15", 604, 615, 11], + [459, "10:05", "10:44", 605, 644, 39], + [460, "10:08", "11:15", 608, 675, 67], + [461, "10:09", "11:03", 609, 663, 54], + [462, "10:10", "10:20", 610, 620, 10], + [463, "10:11", "10:38", 611, 638, 27], + [464, "10:11", "10:53", 611, 653, 42], + [465, "10:14", "11:05", 614, 665, 51], + [466, "10:14", "11:41", 614, 701, 87], + [467, "10:14", "10:25", 614, 625, 11], + [468, "10:15", "10:54", 615, 654, 39], + [469, "10:16", "10:28", 616, 628, 12], + [470, "10:18", "11:43", 618, 703, 85], + [471, "10:18", "11:00", 618, 660, 42], + [472, "10:19", "11:13", 619, 673, 54], + [473, "10:20", "10:30", 620, 630, 10], + [474, "10:20", "10:36", 620, 636, 16], + [475, "10:21", "10:47", 621, 647, 26], + [476, "10:23", "11:30", 623, 690, 67], + [477, "10:23", "10:45", 623, 645, 22], + [478, "10:24", "10:35", 624, 635, 11], + [479, "10:25", "11:04", 625, 664, 39], + [480, "10:26", "11:08", 626, 668, 42], + [481, "10:29", "11:20", 629, 680, 51], + [482, "10:29", "11:23", 629, 683, 54], + [483, "10:29", "11:56", 629, 716, 87], + [484, "10:30", "10:40", 630, 640, 10], + [485, "10:31", "10:43", 631, 643, 12], + [486, "10:33", "11:15", 633, 675, 42], + [487, "10:33", "11:58", 633, 718, 85], + [488, "10:34", "10:45", 634, 645, 11], + [489, "10:35", "11:14", 635, 674, 39], + [490, "10:38", "11:45", 638, 705, 67], + [491, "10:39", "11:33", 639, 693, 54], + [492, "10:40", "10:50", 640, 650, 10], + [493, "10:40", "10:56", 640, 656, 16], + [494, "10:41", "11:23", 641, 683, 42], + [495, "10:41", "11:08", 641, 668, 27], + [496, "10:44", "12:11", 644, 731, 87], + [497, "10:44", "11:35", 644, 695, 51], + [498, "10:44", "10:55", 644, 655, 11], + [499, "10:45", "11:24", 645, 684, 39], + [500, "10:46", "10:58", 646, 658, 12], + [501, "10:48", "12:13", 648, 733, 85], + [502, "10:48", "11:30", 648, 690, 42], + [503, "10:49", "11:43", 649, 703, 54], + [504, "10:50", "11:00", 650, 660, 10], + [505, "10:51", "11:17", 651, 677, 26], + [506, "10:53", "12:00", 653, 720, 67], + [507, "10:53", "11:20", 653, 680, 27], + [508, "10:54", "11:05", 654, 665, 11], + [509, "10:55", "11:34", 655, 694, 39], + [510, "10:56", "11:38", 656, 698, 42], + [511, "10:59", "11:14", 659, 674, 15], + [512, "10:59", "12:26", 659, 746, 87], + [513, "10:59", "11:53", 659, 713, 54], + [514, "10:59", "11:50", 659, 710, 51], + [515, "11:00", "11:16", 660, 676, 16], + [516, "11:00", "11:10", 660, 670, 10], + [517, "11:01", "11:13", 661, 673, 12], + [518, "11:03", "11:45", 663, 705, 42], + [519, "11:03", "12:28", 663, 748, 85], + [520, "11:04", "11:15", 664, 675, 11], + [521, "11:05", "11:44", 665, 704, 39], + [522, "11:08", "12:15", 668, 735, 67], + [523, "11:09", "12:03", 669, 723, 54], + [524, "11:10", "11:20", 670, 680, 10], + [525, "11:11", "11:38", 671, 698, 27], + [526, "11:11", "11:53", 671, 713, 42], + [527, "11:14", "11:25", 674, 685, 11], + [528, "11:14", "12:05", 674, 725, 51], + [529, "11:14", "12:38", 674, 758, 84], + [530, "11:14", "12:41", 674, 761, 87], + [531, "11:15", "11:54", 675, 714, 39], + [532, "11:16", "11:28", 676, 688, 12], + [533, "11:18", "12:00", 678, 720, 42], + [534, "11:19", "12:13", 679, 733, 54], + [535, "11:20", "11:30", 680, 690, 10], + [536, "11:20", "11:36", 680, 696, 16], + [537, "11:21", "11:47", 681, 707, 26], + [538, "11:23", "12:30", 683, 750, 67], + [539, "11:23", "11:49", 683, 709, 26], + [540, "11:24", "12:48", 684, 768, 84], + [541, "11:24", "11:35", 684, 695, 11], + [542, "11:25", "12:04", 685, 724, 39], + [543, "11:26", "12:08", 686, 728, 42], + [544, "11:29", "11:44", 689, 704, 15], + [545, "11:29", "12:23", 689, 743, 54], + [546, "11:29", "12:20", 689, 740, 51], + [547, "11:29", "12:54", 689, 774, 85], + [548, "11:30", "11:40", 690, 700, 10], + [549, "11:31", "11:43", 691, 703, 12], + [550, "11:33", "12:15", 693, 735, 42], + [551, "11:34", "12:58", 694, 778, 84], + [552, "11:34", "11:45", 694, 705, 11], + [553, "11:35", "12:14", 695, 734, 39], + [554, "11:38", "12:45", 698, 765, 67], + [555, "11:39", "12:33", 699, 753, 54], + [556, "11:40", "11:56", 700, 716, 16], + [557, "11:40", "11:50", 700, 710, 10], + [558, "11:41", "12:08", 701, 728, 27], + [559, "11:41", "12:23", 701, 743, 42], + [560, "11:44", "11:55", 704, 715, 11], + [561, "11:44", "13:14", 704, 794, 90], + [562, "11:44", "13:08", 704, 788, 84], + [563, "11:44", "12:35", 704, 755, 51], + [564, "11:45", "12:24", 705, 744, 39], + [565, "11:46", "11:58", 706, 718, 12], + [566, "11:48", "12:30", 708, 750, 42], + [567, "11:49", "12:43", 709, 763, 54], + [568, "11:50", "12:00", 710, 720, 10], + [569, "11:51", "12:17", 711, 737, 26], + [570, "11:53", "12:49", 713, 769, 56], + [571, "11:53", "13:00", 713, 780, 67], + [572, "11:54", "13:18", 714, 798, 84], + [573, "11:54", "12:05", 714, 725, 11], + [574, "11:55", "12:40", 715, 760, 45], + [575, "11:55", "12:34", 715, 754, 39], + [576, "11:56", "12:35", 716, 755, 39], + [577, "11:57", "12:20", 717, 740, 23], + [578, "11:58", "12:29", 718, 749, 31], + [579, "11:59", "12:50", 719, 770, 51], + [580, "11:59", "12:53", 719, 773, 54], + [581, "11:59", "13:24", 719, 804, 85], + [582, "11:59", "12:14", 719, 734, 15], + [583, "12:00", "12:16", 720, 736, 16], + [584, "12:00", "12:10", 720, 730, 10], + [585, "12:01", "12:45", 721, 765, 44], + [586, "12:01", "12:13", 721, 733, 12], + [587, "12:03", "12:50", 723, 770, 47], + [588, "12:04", "12:15", 724, 735, 11], + [589, "12:04", "13:04", 724, 784, 60], + [590, "12:04", "13:28", 724, 808, 84], + [591, "12:05", "12:44", 725, 764, 39], + [592, "12:08", "13:11", 728, 791, 63], + [593, "12:08", "12:39", 728, 759, 31], + [594, "12:09", "13:03", 729, 783, 54], + [595, "12:10", "12:20", 730, 740, 10], + [596, "12:11", "12:55", 731, 775, 44], + [597, "12:11", "12:38", 731, 758, 27], + [598, "12:14", "13:05", 734, 785, 51], + [599, "12:14", "12:25", 734, 745, 11], + [600, "12:14", "13:44", 734, 824, 90], + [601, "12:14", "13:38", 734, 818, 84], + [602, "12:15", "12:54", 735, 774, 39], + [603, "12:16", "12:28", 736, 748, 12], + [604, "12:18", "13:00", 738, 780, 42], + [605, "12:19", "13:13", 739, 793, 54], + [606, "12:20", "12:30", 740, 750, 10], + [607, "12:20", "13:31", 740, 811, 71], + [608, "12:20", "12:30", 740, 750, 10], + [609, "12:20", "12:36", 740, 756, 16], + [610, "12:21", "12:47", 741, 767, 26], + [611, "12:23", "12:45", 743, 765, 22], + [612, "12:24", "12:35", 744, 755, 11], + [613, "12:24", "13:48", 744, 828, 84], + [614, "12:25", "13:10", 745, 790, 45], + [615, "12:25", "13:04", 745, 784, 39], + [616, "12:26", "13:05", 746, 785, 39], + [617, "12:28", "13:54", 748, 834, 86], + [618, "12:28", "12:38", 748, 758, 10], + [619, "12:28", "13:15", 748, 795, 47], + [620, "12:29", "13:23", 749, 803, 54], + [621, "12:30", "13:41", 750, 821, 71], + [622, "12:30", "12:40", 750, 760, 10], + [623, "12:31", "13:15", 751, 795, 44], + [624, "12:31", "12:43", 751, 763, 12], + [625, "12:33", "12:48", 753, 768, 15], + [626, "12:33", "13:20", 753, 800, 47], + [627, "12:34", "13:58", 754, 838, 84], + [628, "12:34", "13:34", 754, 814, 60], + [629, "12:34", "12:45", 754, 765, 11], + [630, "12:35", "13:14", 755, 794, 39], + [631, "12:38", "13:25", 758, 805, 47], + [632, "12:38", "13:25", 758, 805, 47], + [633, "12:38", "14:04", 758, 844, 86], + [634, "12:39", "13:33", 759, 813, 54], + [635, "12:40", "13:51", 760, 831, 71], + [636, "12:40", "12:50", 760, 770, 10], + [637, "12:40", "12:56", 760, 776, 16], + [638, "12:41", "13:08", 761, 788, 27], + [639, "12:43", "13:30", 763, 810, 47], + [640, "12:44", "12:55", 764, 775, 11], + [641, "12:44", "14:08", 764, 848, 84], + [642, "12:45", "13:24", 765, 804, 39], + [643, "12:46", "12:58", 766, 778, 12], + [644, "12:46", "13:21", 766, 801, 35], + [645, "12:48", "14:14", 768, 854, 86], + [646, "12:48", "13:35", 768, 815, 47], + [647, "12:48", "12:58", 768, 778, 10], + [648, "12:48", "13:35", 768, 815, 47], + [649, "12:49", "13:43", 769, 823, 54], + [650, "12:50", "14:01", 770, 841, 71], + [651, "12:50", "13:00", 770, 780, 10], + [652, "12:50", "13:00", 770, 780, 10], + [653, "12:51", "13:17", 771, 797, 26], + [654, "12:53", "13:20", 773, 800, 27], + [655, "12:53", "13:24", 773, 804, 31], + [656, "12:53", "13:40", 773, 820, 47], + [657, "12:54", "14:18", 774, 858, 84], + [658, "12:54", "13:05", 774, 785, 11], + [659, "12:55", "13:34", 775, 814, 39], + [660, "12:58", "14:24", 778, 864, 86], + [661, "12:58", "13:25", 778, 805, 27], + [662, "12:58", "13:45", 778, 825, 47], + [663, "12:58", "13:45", 778, 825, 47], + [664, "12:59", "13:53", 779, 833, 54], + [665, "13:00", "13:10", 780, 790, 10], + [666, "13:00", "13:16", 780, 796, 16], + [667, "13:00", "14:11", 780, 851, 71], + [668, "13:01", "13:13", 781, 793, 12], + [669, "13:03", "13:34", 783, 814, 31], + [670, "13:03", "13:50", 783, 830, 47], + [671, "13:04", "13:15", 784, 795, 11], + [672, "13:04", "14:28", 784, 868, 84], + [673, "13:05", "13:44", 785, 824, 39], + [674, "13:08", "13:55", 788, 835, 47], + [675, "13:08", "14:34", 788, 874, 86], + [676, "13:08", "13:55", 788, 835, 47], + [677, "13:09", "14:03", 789, 843, 54], + [678, "13:10", "13:20", 790, 800, 10], + [679, "13:10", "14:21", 790, 861, 71], + [680, "13:13", "14:00", 793, 840, 47], + [681, "13:13", "13:40", 793, 820, 27], + [682, "13:14", "14:38", 794, 878, 84], + [683, "13:14", "13:25", 794, 805, 11], + [684, "13:15", "13:54", 795, 834, 39], + [685, "13:16", "13:28", 796, 808, 12], + [686, "13:18", "14:05", 798, 845, 47], + [687, "13:18", "14:44", 798, 884, 86], + [688, "13:18", "14:05", 798, 845, 47], + [689, "13:19", "14:13", 799, 853, 54], + [690, "13:20", "13:36", 800, 816, 16], + [691, "13:20", "14:31", 800, 871, 71], + [692, "13:20", "13:30", 800, 810, 10], + [693, "13:21", "13:47", 801, 827, 26], + [694, "13:23", "14:10", 803, 850, 47], + [695, "13:23", "13:49", 803, 829, 26], + [696, "13:24", "14:48", 804, 888, 84], + [697, "13:24", "13:35", 804, 815, 11], + [698, "13:25", "14:04", 805, 844, 39], + [699, "13:28", "14:15", 808, 855, 47], + [700, "13:28", "14:54", 808, 894, 86], + [701, "13:28", "13:55", 808, 835, 27], + [702, "13:28", "14:15", 808, 855, 47], + [703, "13:29", "14:23", 809, 863, 54], + [704, "13:30", "13:40", 810, 820, 10], + [705, "13:30", "14:41", 810, 881, 71], + [706, "13:31", "13:43", 811, 823, 12], + [707, "13:33", "14:20", 813, 860, 47], + [708, "13:34", "14:58", 814, 898, 84], + [709, "13:34", "13:45", 814, 825, 11], + [710, "13:35", "14:14", 815, 854, 39], + [711, "13:38", "14:25", 818, 865, 47], + [712, "13:38", "14:25", 818, 865, 47], + [713, "13:38", "15:04", 818, 904, 86], + [714, "13:39", "14:33", 819, 873, 54], + [715, "13:40", "13:50", 820, 830, 10], + [716, "13:40", "13:56", 820, 836, 16], + [717, "13:40", "14:51", 820, 891, 71], + [718, "13:43", "14:30", 823, 870, 47], + [719, "13:43", "14:10", 823, 850, 27], + [720, "13:44", "15:09", 824, 909, 85], + [721, "13:44", "13:55", 824, 835, 11], + [722, "13:45", "14:24", 825, 864, 39], + [723, "13:46", "13:58", 826, 838, 12], + [724, "13:48", "14:35", 828, 875, 47], + [725, "13:48", "15:14", 828, 914, 86], + [726, "13:48", "14:35", 828, 875, 47], + [727, "13:49", "14:43", 829, 883, 54], + [728, "13:50", "14:00", 830, 840, 10], + [729, "13:50", "15:01", 830, 901, 71], + [730, "13:51", "14:17", 831, 857, 26], + [731, "13:53", "14:40", 833, 880, 47], + [732, "13:53", "14:49", 833, 889, 56], + [733, "13:54", "14:05", 834, 845, 11], + [734, "13:54", "15:19", 834, 919, 85], + [735, "13:55", "14:34", 835, 874, 39], + [736, "13:57", "14:20", 837, 860, 23], + [737, "13:58", "15:24", 838, 924, 86], + [738, "13:58", "14:45", 838, 885, 47], + [739, "13:58", "14:45", 838, 885, 47], + [740, "13:58", "14:25", 838, 865, 27], + [741, "13:59", "14:53", 839, 893, 54], + [742, "14:00", "14:16", 840, 856, 16], + [743, "14:00", "14:10", 840, 850, 10], + [744, "14:00", "15:11", 840, 911, 71], + [745, "14:01", "14:13", 841, 853, 12], + [746, "14:03", "14:50", 843, 890, 47], + [747, "14:04", "14:15", 844, 855, 11], + [748, "14:04", "15:29", 844, 929, 85], + [749, "14:05", "14:44", 845, 884, 39], + [750, "14:08", "14:55", 848, 895, 47], + [751, "14:08", "14:55", 848, 895, 47], + [752, "14:08", "15:34", 848, 934, 86], + [753, "14:09", "15:03", 849, 903, 54], + [754, "14:10", "15:21", 850, 921, 71], + [755, "14:10", "14:20", 850, 860, 10], + [756, "14:13", "15:00", 853, 900, 47], + [757, "14:13", "14:40", 853, 880, 27], + [758, "14:14", "15:40", 854, 940, 86], + [759, "14:14", "14:25", 854, 865, 11], + [760, "14:15", "14:54", 855, 894, 39], + [761, "14:16", "14:28", 856, 868, 12], + [762, "14:18", "15:05", 858, 905, 47], + [763, "14:18", "15:44", 858, 944, 86], + [764, "14:18", "15:05", 858, 905, 47], + [765, "14:19", "15:13", 859, 913, 54], + [766, "14:20", "15:31", 860, 931, 71], + [767, "14:20", "14:30", 860, 870, 10], + [768, "14:20", "14:36", 860, 876, 16], + [769, "14:21", "14:47", 861, 887, 26], + [770, "14:23", "15:10", 863, 910, 47], + [771, "14:23", "14:45", 863, 885, 22], + [772, "14:24", "15:50", 864, 950, 86], + [773, "14:24", "14:35", 864, 875, 11], + [774, "14:25", "15:02", 865, 902, 37], + [775, "14:26", "14:52", 866, 892, 26], + [776, "14:28", "15:15", 868, 915, 47], + [777, "14:28", "14:55", 868, 895, 27], + [778, "14:28", "15:54", 868, 954, 86], + [779, "14:28", "15:15", 868, 915, 47], + [780, "14:29", "15:23", 869, 923, 54], + [781, "14:30", "15:41", 870, 941, 71], + [782, "14:30", "14:40", 870, 880, 10], + [783, "14:31", "14:43", 871, 883, 12], + [784, "14:33", "15:20", 873, 920, 47], + [785, "14:34", "16:00", 874, 960, 86], + [786, "14:34", "14:45", 874, 885, 11], + [787, "14:35", "15:11", 875, 911, 36], + [788, "14:38", "15:25", 878, 925, 47], + [789, "14:38", "15:25", 878, 925, 47], + [790, "14:38", "16:04", 878, 964, 86], + [791, "14:39", "15:33", 879, 933, 54], + [792, "14:40", "14:50", 880, 890, 10], + [793, "14:40", "15:51", 880, 951, 71], + [794, "14:40", "14:56", 880, 896, 16], + [795, "14:43", "15:30", 883, 930, 47], + [796, "14:43", "15:10", 883, 910, 27], + [797, "14:44", "15:00", 884, 900, 16], + [798, "14:44", "16:10", 884, 970, 86], + [799, "14:45", "15:19", 885, 919, 34], + [800, "14:46", "14:58", 886, 898, 12], + [801, "14:48", "15:35", 888, 935, 47], + [802, "14:48", "15:35", 888, 935, 47], + [803, "14:48", "17:04", 888, 1024, 136], + [804, "14:49", "15:43", 889, 943, 54], + [805, "14:50", "16:01", 890, 961, 71], + [806, "14:50", "15:00", 890, 900, 10], + [807, "14:51", "15:17", 891, 917, 26], + [808, "14:52", "15:27", 892, 927, 35], + [809, "14:52", "15:21", 892, 921, 29], + [810, "14:53", "15:40", 893, 940, 47], + [811, "14:54", "15:08", 894, 908, 14], + [812, "14:54", "16:20", 894, 980, 86], + [813, "14:58", "16:24", 898, 984, 86], + [814, "14:58", "15:45", 898, 945, 47], + [815, "14:58", "15:25", 898, 925, 27], + [816, "14:58", "15:45", 898, 945, 47], + [817, "14:59", "15:53", 899, 953, 54], + [818, "15:00", "15:10", 900, 910, 10], + [819, "15:00", "15:35", 900, 935, 35], + [820, "15:00", "16:11", 900, 971, 71], + [821, "15:00", "15:16", 900, 916, 16], + [822, "15:01", "15:13", 901, 913, 12], + [823, "15:02", "15:16", 902, 916, 14], + [824, "15:03", "15:50", 903, 950, 47], + [825, "15:04", "16:30", 904, 990, 86], + [826, "15:08", "16:34", 908, 994, 86], + [827, "15:08", "15:55", 908, 955, 47], + [828, "15:08", "15:55", 908, 955, 47], + [829, "15:08", "15:45", 908, 945, 37], + [830, "15:09", "16:14", 909, 974, 65], + [831, "15:09", "16:03", 909, 963, 54], + [832, "15:10", "16:21", 910, 981, 71], + [833, "15:10", "15:20", 910, 920, 10], + [834, "15:11", "15:24", 911, 924, 13], + [835, "15:12", "15:36", 912, 936, 24], + [836, "15:13", "16:00", 913, 960, 47], + [837, "15:13", "15:40", 913, 940, 27], + [838, "15:14", "16:40", 914, 1000, 86], + [839, "15:16", "15:28", 916, 928, 12], + [840, "15:16", "15:55", 916, 955, 39], + [841, "15:18", "16:05", 918, 965, 47], + [842, "15:18", "16:44", 918, 1004, 86], + [843, "15:18", "16:05", 918, 965, 47], + [844, "15:19", "16:13", 919, 973, 54], + [845, "15:19", "15:34", 919, 934, 15], + [846, "15:20", "15:30", 920, 930, 10], + [847, "15:20", "16:31", 920, 991, 71], + [848, "15:20", "15:36", 920, 936, 16], + [849, "15:21", "15:47", 921, 947, 26], + [850, "15:21", "16:06", 921, 966, 45], + [851, "15:23", "16:10", 923, 970, 47], + [852, "15:24", "16:50", 924, 1010, 86], + [853, "15:24", "16:05", 924, 965, 41], + [854, "15:27", "15:51", 927, 951, 24], + [855, "15:27", "15:44", 927, 944, 17], + [856, "15:28", "16:15", 928, 975, 47], + [857, "15:28", "16:54", 928, 1014, 86], + [858, "15:28", "16:15", 928, 975, 47], + [859, "15:28", "15:55", 928, 955, 27], + [860, "15:29", "16:23", 929, 983, 54], + [861, "15:30", "16:41", 930, 1001, 71], + [862, "15:30", "15:40", 930, 940, 10], + [863, "15:31", "15:43", 931, 943, 12], + [864, "15:33", "16:20", 933, 980, 47], + [865, "15:34", "17:00", 934, 1020, 86], + [866, "15:34", "16:15", 934, 975, 41], + [867, "15:35", "15:54", 935, 954, 19], + [868, "15:36", "16:21", 936, 981, 45], + [869, "15:38", "16:25", 938, 985, 47], + [870, "15:38", "16:25", 938, 985, 47], + [871, "15:38", "16:39", 938, 999, 61], + [872, "15:39", "16:33", 939, 993, 54], + [873, "15:40", "15:50", 940, 950, 10], + [874, "15:40", "16:51", 940, 1011, 71], + [875, "15:40", "15:56", 940, 956, 16], + [876, "15:43", "16:10", 943, 970, 27], + [877, "15:43", "16:30", 943, 990, 47], + [878, "15:44", "17:10", 944, 1030, 86], + [879, "15:44", "16:25", 944, 985, 41], + [880, "15:45", "16:04", 945, 964, 19], + [881, "15:46", "15:58", 946, 958, 12], + [882, "15:48", "16:35", 948, 995, 47], + [883, "15:48", "16:35", 948, 995, 47], + [884, "15:48", "17:14", 948, 1034, 86], + [885, "15:49", "16:43", 949, 1003, 54], + [886, "15:50", "16:00", 950, 960, 10], + [887, "15:50", "17:01", 950, 1021, 71], + [888, "15:51", "16:18", 951, 978, 27], + [889, "15:52", "16:36", 952, 996, 44], + [890, "15:53", "16:40", 953, 1000, 47], + [891, "15:54", "17:20", 954, 1040, 86], + [892, "15:54", "16:35", 954, 995, 41], + [893, "15:55", "16:14", 955, 974, 19], + [894, "15:58", "16:25", 958, 985, 27], + [895, "15:58", "16:45", 958, 1005, 47], + [896, "15:58", "16:45", 958, 1005, 47], + [897, "15:58", "17:24", 958, 1044, 86], + [898, "15:59", "17:11", 959, 1031, 72], + [899, "15:59", "16:53", 959, 1013, 54], + [900, "16:00", "16:10", 960, 970, 10], + [901, "16:00", "16:16", 960, 976, 16], + [902, "16:01", "16:13", 961, 973, 12], + [903, "16:03", "16:50", 963, 1010, 47], + [904, "16:04", "17:30", 964, 1050, 86], + [905, "16:04", "16:45", 964, 1005, 41], + [906, "16:05", "16:24", 965, 984, 19], + [907, "16:06", "16:51", 966, 1011, 45], + [908, "16:08", "16:55", 968, 1015, 47], + [909, "16:08", "17:34", 968, 1054, 86], + [910, "16:08", "16:55", 968, 1015, 47], + [911, "16:09", "17:03", 969, 1023, 54], + [912, "16:09", "17:21", 969, 1041, 72], + [913, "16:10", "16:20", 970, 980, 10], + [914, "16:13", "16:40", 973, 1000, 27], + [915, "16:13", "17:00", 973, 1020, 47], + [916, "16:14", "16:55", 974, 1015, 41], + [917, "16:14", "17:40", 974, 1060, 86], + [918, "16:15", "16:34", 975, 994, 19], + [919, "16:16", "16:28", 976, 988, 12], + [920, "16:18", "17:05", 978, 1025, 47], + [921, "16:18", "17:05", 978, 1025, 47], + [922, "16:18", "17:44", 978, 1064, 86], + [923, "16:19", "17:31", 979, 1051, 72], + [924, "16:19", "17:13", 979, 1033, 54], + [925, "16:20", "16:30", 980, 990, 10], + [926, "16:20", "16:36", 980, 996, 16], + [927, "16:21", "16:48", 981, 1008, 27], + [928, "16:22", "17:06", 982, 1026, 44], + [929, "16:23", "17:10", 983, 1030, 47], + [930, "16:24", "17:05", 984, 1025, 41], + [931, "16:24", "17:50", 984, 1070, 86], + [932, "16:25", "16:44", 985, 1004, 19], + [933, "16:28", "17:15", 988, 1035, 47], + [934, "16:28", "17:15", 988, 1035, 47], + [935, "16:28", "16:55", 988, 1015, 27], + [936, "16:28", "17:54", 988, 1074, 86], + [937, "16:29", "17:23", 989, 1043, 54], + [938, "16:29", "17:41", 989, 1061, 72], + [939, "16:30", "16:40", 990, 1000, 10], + [940, "16:31", "16:43", 991, 1003, 12], + [941, "16:33", "17:20", 993, 1040, 47], + [942, "16:34", "17:15", 994, 1035, 41], + [943, "16:34", "18:00", 994, 1080, 86], + [944, "16:35", "16:54", 995, 1014, 19], + [945, "16:36", "17:21", 996, 1041, 45], + [946, "16:38", "17:25", 998, 1045, 47], + [947, "16:38", "17:25", 998, 1045, 47], + [948, "16:38", "18:04", 998, 1084, 86], + [949, "16:39", "17:33", 999, 1053, 54], + [950, "16:39", "17:51", 999, 1071, 72], + [951, "16:40", "16:56", 1000, 1016, 16], + [952, "16:40", "16:50", 1000, 1010, 10], + [953, "16:43", "17:10", 1003, 1030, 27], + [954, "16:43", "17:30", 1003, 1050, 47], + [955, "16:44", "17:25", 1004, 1045, 41], + [956, "16:44", "18:10", 1004, 1090, 86], + [957, "16:45", "17:04", 1005, 1024, 19], + [958, "16:46", "16:58", 1006, 1018, 12], + [959, "16:48", "18:14", 1008, 1094, 86], + [960, "16:48", "17:35", 1008, 1055, 47], + [961, "16:48", "17:35", 1008, 1055, 47], + [962, "16:49", "18:01", 1009, 1081, 72], + [963, "16:49", "17:43", 1009, 1063, 54], + [964, "16:50", "17:00", 1010, 1020, 10], + [965, "16:51", "17:18", 1011, 1038, 27], + [966, "16:52", "17:36", 1012, 1056, 44], + [967, "16:53", "17:40", 1013, 1060, 47], + [968, "16:54", "18:20", 1014, 1100, 86], + [969, "16:54", "17:35", 1014, 1055, 41], + [970, "16:55", "17:14", 1015, 1034, 19], + [971, "16:58", "17:25", 1018, 1045, 27], + [972, "16:58", "17:45", 1018, 1065, 47], + [973, "16:58", "17:45", 1018, 1065, 47], + [974, "16:58", "18:24", 1018, 1104, 86], + [975, "16:59", "18:11", 1019, 1091, 72], + [976, "16:59", "17:53", 1019, 1073, 54], + [977, "17:00", "17:16", 1020, 1036, 16], + [978, "17:00", "17:10", 1020, 1030, 10], + [979, "17:01", "17:13", 1021, 1033, 12], + [980, "17:03", "17:50", 1023, 1070, 47], + [981, "17:04", "18:30", 1024, 1110, 86], + [982, "17:04", "17:45", 1024, 1065, 41], + [983, "17:05", "17:24", 1025, 1044, 19], + [984, "17:06", "17:51", 1026, 1071, 45], + [985, "17:08", "17:55", 1028, 1075, 47], + [986, "17:08", "17:55", 1028, 1075, 47], + [987, "17:08", "18:34", 1028, 1114, 86], + [988, "17:09", "18:03", 1029, 1083, 54], + [989, "17:09", "18:21", 1029, 1101, 72], + [990, "17:10", "17:20", 1030, 1040, 10], + [991, "17:13", "17:40", 1033, 1060, 27], + [992, "17:13", "18:00", 1033, 1080, 47], + [993, "17:14", "17:55", 1034, 1075, 41], + [994, "17:14", "18:40", 1034, 1120, 86], + [995, "17:15", "17:34", 1035, 1054, 19], + [996, "17:16", "17:28", 1036, 1048, 12], + [997, "17:18", "18:05", 1038, 1085, 47], + [998, "17:18", "18:05", 1038, 1085, 47], + [999, "17:18", "18:44", 1038, 1124, 86], + [1000, "17:19", "18:31", 1039, 1111, 72], + [1001, "17:19", "18:13", 1039, 1093, 54], + [1002, "17:20", "17:36", 1040, 1056, 16], + [1003, "17:20", "17:30", 1040, 1050, 10], + [1004, "17:21", "17:47", 1041, 1067, 26], + [1005, "17:22", "18:06", 1042, 1086, 44], + [1006, "17:23", "18:10", 1043, 1090, 47], + [1007, "17:24", "18:50", 1044, 1130, 86], + [1008, "17:24", "18:05", 1044, 1085, 41], + [1009, "17:25", "17:44", 1045, 1064, 19], + [1010, "17:28", "17:55", 1048, 1075, 27], + [1011, "17:28", "18:15", 1048, 1095, 47], + [1012, "17:28", "18:15", 1048, 1095, 47], + [1013, "17:28", "18:54", 1048, 1134, 86], + [1014, "17:29", "18:41", 1049, 1121, 72], + [1015, "17:29", "18:23", 1049, 1103, 54], + [1016, "17:30", "17:40", 1050, 1060, 10], + [1017, "17:31", "17:43", 1051, 1063, 12], + [1018, "17:33", "18:20", 1053, 1100, 47], + [1019, "17:34", "18:15", 1054, 1095, 41], + [1020, "17:34", "19:00", 1054, 1140, 86], + [1021, "17:35", "17:54", 1055, 1074, 19], + [1022, "17:36", "18:21", 1056, 1101, 45], + [1023, "17:38", "18:25", 1058, 1105, 47], + [1024, "17:38", "19:04", 1058, 1144, 86], + [1025, "17:38", "18:25", 1058, 1105, 47], + [1026, "17:39", "18:51", 1059, 1131, 72], + [1027, "17:39", "18:33", 1059, 1113, 54], + [1028, "17:40", "17:56", 1060, 1076, 16], + [1029, "17:40", "17:50", 1060, 1070, 10], + [1030, "17:43", "18:10", 1063, 1090, 27], + [1031, "17:43", "18:30", 1063, 1110, 47], + [1032, "17:44", "18:25", 1064, 1105, 41], + [1033, "17:44", "19:14", 1064, 1154, 90], + [1034, "17:45", "18:04", 1065, 1084, 19], + [1035, "17:46", "17:58", 1066, 1078, 12], + [1036, "17:48", "18:35", 1068, 1115, 47], + [1037, "17:48", "18:35", 1068, 1115, 47], + [1038, "17:48", "19:14", 1068, 1154, 86], + [1039, "17:49", "19:01", 1069, 1141, 72], + [1040, "17:49", "18:43", 1069, 1123, 54], + [1041, "17:50", "18:00", 1070, 1080, 10], + [1042, "17:51", "18:17", 1071, 1097, 26], + [1043, "17:52", "18:36", 1072, 1116, 44], + [1044, "17:53", "18:40", 1073, 1120, 47], + [1045, "17:54", "18:35", 1074, 1115, 41], + [1046, "17:54", "18:57", 1074, 1137, 63], + [1047, "17:55", "18:14", 1075, 1094, 19], + [1048, "17:58", "18:45", 1078, 1125, 47], + [1049, "17:58", "18:45", 1078, 1125, 47], + [1050, "17:58", "18:25", 1078, 1105, 27], + [1051, "17:58", "19:26", 1078, 1166, 88], + [1052, "17:59", "18:53", 1079, 1133, 54], + [1053, "18:00", "19:11", 1080, 1151, 71], + [1054, "18:00", "18:10", 1080, 1090, 10], + [1055, "18:00", "18:16", 1080, 1096, 16], + [1056, "18:01", "18:13", 1081, 1093, 12], + [1057, "18:03", "18:50", 1083, 1130, 47], + [1058, "18:04", "18:45", 1084, 1125, 41], + [1059, "18:04", "19:29", 1084, 1169, 85], + [1060, "18:05", "18:24", 1085, 1104, 19], + [1061, "18:06", "18:51", 1086, 1131, 45], + [1062, "18:08", "18:55", 1088, 1135, 47], + [1063, "18:08", "19:06", 1088, 1146, 58], + [1064, "18:08", "18:55", 1088, 1135, 47], + [1065, "18:09", "19:03", 1089, 1143, 54], + [1066, "18:10", "18:20", 1090, 1100, 10], + [1067, "18:10", "19:21", 1090, 1161, 71], + [1068, "18:13", "19:00", 1093, 1140, 47], + [1069, "18:13", "18:40", 1093, 1120, 27], + [1070, "18:14", "19:43", 1094, 1183, 89], + [1071, "18:14", "18:55", 1094, 1135, 41], + [1072, "18:15", "18:34", 1095, 1114, 19], + [1073, "18:16", "18:28", 1096, 1108, 12], + [1074, "18:17", "18:27", 1097, 1107, 10], + [1075, "18:18", "19:41", 1098, 1181, 83], + [1076, "18:18", "18:58", 1098, 1138, 40], + [1077, "18:18", "19:05", 1098, 1145, 47], + [1078, "18:19", "19:13", 1099, 1153, 54], + [1079, "18:20", "19:31", 1100, 1171, 71], + [1080, "18:20", "18:36", 1100, 1116, 16], + [1081, "18:20", "18:30", 1100, 1110, 10], + [1082, "18:22", "19:05", 1102, 1145, 43], + [1083, "18:23", "19:05", 1103, 1145, 42], + [1084, "18:24", "19:27", 1104, 1167, 63], + [1085, "18:24", "19:05", 1104, 1145, 41], + [1086, "18:25", "18:44", 1105, 1124, 19], + [1087, "18:28", "19:25", 1108, 1165, 57], + [1088, "18:28", "18:55", 1108, 1135, 27], + [1089, "18:28", "19:08", 1108, 1148, 40], + [1090, "18:28", "19:15", 1108, 1155, 47], + [1091, "18:29", "19:23", 1109, 1163, 54], + [1092, "18:30", "19:05", 1110, 1145, 35], + [1093, "18:30", "18:40", 1110, 1120, 10], + [1094, "18:31", "18:43", 1111, 1123, 12], + [1095, "18:33", "19:15", 1113, 1155, 42], + [1096, "18:34", "19:58", 1114, 1198, 84], + [1097, "18:34", "19:14", 1114, 1154, 40], + [1098, "18:35", "18:55", 1115, 1135, 20], + [1099, "18:36", "19:20", 1116, 1160, 44], + [1100, "18:38", "19:25", 1118, 1165, 47], + [1101, "18:38", "19:23", 1118, 1163, 45], + [1102, "18:38", "19:56", 1118, 1196, 78], + [1103, "18:39", "19:33", 1119, 1173, 54], + [1104, "18:40", "18:50", 1120, 1130, 10], + [1105, "18:40", "19:45", 1120, 1185, 65], + [1106, "18:40", "18:56", 1120, 1136, 16], + [1107, "18:43", "19:10", 1123, 1150, 27], + [1108, "18:43", "19:30", 1123, 1170, 47], + [1109, "18:44", "19:24", 1124, 1164, 40], + [1110, "18:45", "19:05", 1125, 1145, 20], + [1111, "18:46", "18:58", 1126, 1138, 12], + [1112, "18:48", "19:35", 1128, 1175, 47], + [1113, "18:48", "20:12", 1128, 1212, 84], + [1114, "18:48", "20:11", 1128, 1211, 83], + [1115, "18:48", "19:28", 1128, 1168, 40], + [1116, "18:49", "19:43", 1129, 1183, 54], + [1117, "18:50", "19:00", 1130, 1140, 10], + [1118, "18:51", "19:01", 1131, 1141, 10], + [1119, "18:53", "19:35", 1133, 1175, 42], + [1120, "18:53", "19:15", 1133, 1155, 22], + [1121, "18:53", "20:00", 1133, 1200, 67], + [1122, "18:55", "19:15", 1135, 1155, 20], + [1123, "18:55", "19:34", 1135, 1174, 39], + [1124, "18:58", "19:38", 1138, 1178, 40], + [1125, "18:59", "19:53", 1139, 1193, 54], + [1126, "18:59", "19:50", 1139, 1190, 51], + [1127, "18:59", "19:53", 1139, 1193, 54], + [1128, "19:00", "19:16", 1140, 1156, 16], + [1129, "19:00", "19:10", 1140, 1150, 10], + [1130, "19:00", "19:16", 1140, 1156, 16], + [1131, "19:01", "19:13", 1141, 1153, 12], + [1132, "19:03", "20:26", 1143, 1226, 83], + [1133, "19:03", "19:45", 1143, 1185, 42], + [1134, "19:05", "19:44", 1145, 1184, 39], + [1135, "19:05", "19:25", 1145, 1165, 20], + [1136, "19:08", "20:15", 1148, 1215, 67], + [1137, "19:08", "19:35", 1148, 1175, 27], + [1138, "19:09", "19:49", 1149, 1189, 40], + [1139, "19:09", "20:03", 1149, 1203, 54], + [1140, "19:10", "19:20", 1150, 1160, 10], + [1141, "19:10", "19:20", 1150, 1160, 10], + [1142, "19:11", "19:53", 1151, 1193, 42], + [1143, "19:14", "20:26", 1154, 1226, 72], + [1144, "19:14", "19:35", 1154, 1175, 21], + [1145, "19:14", "19:24", 1154, 1164, 10], + [1146, "19:14", "20:05", 1154, 1205, 51], + [1147, "19:15", "19:30", 1155, 1170, 15], + [1148, "19:15", "19:54", 1155, 1194, 39], + [1149, "19:18", "20:39", 1158, 1239, 81], + [1150, "19:18", "20:00", 1158, 1200, 42], + [1151, "19:19", "20:14", 1159, 1214, 55], + [1152, "19:20", "19:30", 1160, 1170, 10], + [1153, "19:20", "19:36", 1160, 1176, 16], + [1154, "19:21", "19:31", 1161, 1171, 10], + [1155, "19:23", "20:30", 1163, 1230, 67], + [1156, "19:23", "19:35", 1163, 1175, 12], + [1157, "19:24", "19:45", 1164, 1185, 21], + [1158, "19:24", "19:45", 1164, 1185, 21], + [1159, "19:25", "20:04", 1165, 1204, 39], + [1160, "19:26", "20:08", 1166, 1208, 42], + [1161, "19:29", "20:02", 1169, 1202, 33], + [1162, "19:29", "20:18", 1169, 1218, 49], + [1163, "19:29", "20:41", 1169, 1241, 72], + [1164, "19:30", "19:40", 1170, 1180, 10], + [1165, "19:33", "20:54", 1173, 1254, 81], + [1166, "19:33", "20:17", 1173, 1217, 44], + [1167, "19:34", "19:55", 1174, 1195, 21], + [1168, "19:35", "20:14", 1175, 1214, 39], + [1169, "19:38", "20:05", 1178, 1205, 27], + [1170, "19:38", "20:45", 1178, 1245, 67], + [1171, "19:39", "20:12", 1179, 1212, 33], + [1172, "19:40", "19:50", 1180, 1190, 10], + [1173, "19:40", "19:56", 1180, 1196, 16], + [1174, "19:41", "20:27", 1181, 1227, 46], + [1175, "19:43", "19:55", 1183, 1195, 12], + [1176, "19:44", "20:05", 1184, 1205, 21], + [1177, "19:44", "20:33", 1184, 1233, 49], + [1178, "19:44", "21:00", 1184, 1260, 76], + [1179, "19:45", "20:24", 1185, 1224, 39], + [1180, "19:48", "20:37", 1188, 1237, 49], + [1181, "19:48", "21:09", 1188, 1269, 81], + [1182, "19:50", "20:00", 1190, 1200, 10], + [1183, "19:52", "20:29", 1192, 1229, 37], + [1184, "19:53", "20:08", 1193, 1208, 15], + [1185, "19:53", "21:02", 1193, 1262, 69], + [1186, "19:53", "20:20", 1193, 1220, 27], + [1187, "19:54", "20:19", 1194, 1219, 25], + [1188, "19:55", "20:34", 1195, 1234, 39], + [1189, "19:56", "20:34", 1196, 1234, 38], + [1190, "19:59", "20:48", 1199, 1248, 49], + [1191, "19:59", "21:20", 1199, 1280, 81], + [1192, "20:00", "20:16", 1200, 1216, 16], + [1193, "20:00", "20:10", 1200, 1210, 10], + [1194, "20:03", "20:42", 1203, 1242, 39], + [1195, "20:03", "21:24", 1203, 1284, 81], + [1196, "20:04", "20:29", 1204, 1229, 25], + [1197, "20:05", "20:48", 1205, 1248, 43], + [1198, "20:07", "20:44", 1207, 1244, 37], + [1199, "20:08", "20:40", 1208, 1240, 32], + [1200, "20:08", "20:35", 1208, 1235, 27], + [1201, "20:10", "20:20", 1210, 1220, 10], + [1202, "20:10", "20:22", 1210, 1222, 12], + [1203, "20:11", "20:47", 1211, 1247, 36], + [1204, "20:14", "21:04", 1214, 1264, 50], + [1205, "20:14", "21:03", 1214, 1263, 49], + [1206, "20:17", "21:03", 1217, 1263, 46], + [1207, "20:18", "21:39", 1218, 1299, 81], + [1208, "20:20", "20:30", 1220, 1230, 10], + [1209, "20:20", "20:57", 1220, 1257, 37], + [1210, "20:20", "20:36", 1220, 1236, 16], + [1211, "20:22", "20:59", 1222, 1259, 37], + [1212, "20:22", "20:42", 1222, 1242, 20], + [1213, "20:24", "20:49", 1224, 1249, 25], + [1214, "20:27", "21:22", 1227, 1282, 55], + [1215, "20:29", "21:18", 1229, 1278, 49], + [1216, "20:30", "21:07", 1230, 1267, 37], + [1217, "20:30", "20:40", 1230, 1240, 10], + [1218, "20:30", "20:40", 1230, 1240, 10], + [1219, "20:30", "21:40", 1230, 1300, 70], + [1220, "20:32", "21:18", 1232, 1278, 46], + [1221, "20:35", "21:54", 1235, 1314, 79], + [1222, "20:37", "21:14", 1237, 1274, 37], + [1223, "20:38", "21:08", 1238, 1268, 30], + [1224, "20:40", "20:50", 1240, 1250, 10], + [1225, "20:40", "21:17", 1240, 1277, 37], + [1226, "20:40", "20:56", 1240, 1256, 16], + [1227, "20:44", "21:33", 1244, 1293, 49], + [1228, "20:47", "21:33", 1247, 1293, 46], + [1229, "20:47", "21:42", 1247, 1302, 55], + [1230, "20:50", "21:00", 1250, 1260, 10], + [1231, "20:50", "22:00", 1250, 1320, 70], + [1232, "20:50", "22:09", 1250, 1329, 79], + [1233, "20:50", "21:27", 1250, 1287, 37], + [1234, "20:52", "21:29", 1252, 1289, 37], + [1235, "20:53", "21:20", 1253, 1280, 27], + [1236, "20:56", "21:11", 1256, 1271, 15], + [1237, "20:59", "21:48", 1259, 1308, 49], + [1238, "21:00", "21:10", 1260, 1270, 10], + [1239, "21:00", "21:37", 1260, 1297, 37], + [1240, "21:02", "21:48", 1262, 1308, 46], + [1241, "21:05", "22:24", 1265, 1344, 79], + [1242, "21:07", "21:44", 1267, 1304, 37], + [1243, "21:07", "22:02", 1267, 1322, 55], + [1244, "21:08", "21:38", 1268, 1298, 30], + [1245, "21:10", "22:25", 1270, 1345, 75], + [1246, "21:10", "21:20", 1270, 1280, 10], + [1247, "21:10", "21:47", 1270, 1307, 37], + [1248, "21:14", "22:03", 1274, 1323, 49], + [1249, "21:17", "22:03", 1277, 1323, 46], + [1250, "21:20", "22:18", 1280, 1338, 58], + [1251, "21:20", "21:57", 1280, 1317, 37], + [1252, "21:20", "21:30", 1280, 1290, 10], + [1253, "21:22", "21:59", 1282, 1319, 37], + [1254, "21:24", "21:49", 1284, 1309, 25], + [1255, "21:27", "22:21", 1287, 1341, 54], + [1256, "21:30", "22:07", 1290, 1327, 37], + [1257, "21:30", "22:20", 1290, 1340, 50], + [1258, "21:30", "21:40", 1290, 1300, 10], + [1259, "21:32", "22:18", 1292, 1338, 46], + [1260, "21:32", "22:01", 1292, 1321, 29], + [1261, "21:35", "22:54", 1295, 1374, 79], + [1262, "21:37", "22:14", 1297, 1334, 37], + [1263, "21:39", "21:55", 1299, 1315, 16], + [1264, "21:40", "22:17", 1300, 1337, 37], + [1265, "21:40", "21:50", 1300, 1310, 10], + [1266, "21:41", "22:08", 1301, 1328, 27], + [1267, "21:47", "22:16", 1307, 1336, 29], + [1268, "21:47", "22:51", 1307, 1371, 64], + [1269, "21:47", "22:33", 1307, 1353, 46], + [1270, "21:48", "22:03", 1308, 1323, 15], + [1271, "21:50", "22:55", 1310, 1375, 65], + [1272, "21:50", "22:27", 1310, 1347, 37], + [1273, "21:50", "22:00", 1310, 1320, 10], + [1274, "21:52", "22:29", 1312, 1349, 37], + [1275, "21:53", "22:19", 1313, 1339, 26], + [1276, "22:00", "22:38", 1320, 1358, 38], + [1277, "22:00", "22:10", 1320, 1330, 10], + [1278, "22:02", "22:12", 1322, 1332, 10], + [1279, "22:02", "22:48", 1322, 1368, 46], + [1280, "22:04", "22:31", 1324, 1351, 27], + [1281, "22:05", "23:24", 1325, 1404, 79], + [1282, "22:07", "22:44", 1327, 1364, 37], + [1283, "22:07", "22:39", 1327, 1359, 32], + [1284, "22:09", "22:25", 1329, 1345, 16], + [1285, "22:10", "23:25", 1330, 1405, 75], + [1286, "22:13", "22:38", 1333, 1358, 25], + [1287, "22:13", "22:53", 1333, 1373, 40], + [1288, "22:17", "22:27", 1337, 1347, 10], + [1289, "22:17", "23:03", 1337, 1383, 46], + [1290, "22:19", "22:46", 1339, 1366, 27], + [1291, "22:22", "22:59", 1342, 1379, 37], + [1292, "22:24", "22:48", 1344, 1368, 24], + [1293, "22:27", "22:52", 1347, 1372, 25], + [1294, "22:27", "23:21", 1347, 1401, 54], + [1295, "22:28", "23:08", 1348, 1388, 40], + [1296, "22:30", "23:17", 1350, 1397, 47], + [1297, "22:32", "22:42", 1352, 1362, 10], + [1298, "22:32", "23:11", 1352, 1391, 39], + [1299, "22:34", "23:01", 1354, 1381, 27], + [1300, "22:35", "23:54", 1355, 1434, 79], + [1301, "22:37", "23:14", 1357, 1394, 37], + [1302, "22:43", "23:23", 1363, 1403, 40], + [1303, "22:43", "23:08", 1363, 1388, 25], + [1304, "22:47", "23:33", 1367, 1413, 46], + [1305, "22:47", "22:57", 1367, 1377, 10], + [1306, "22:49", "23:16", 1369, 1396, 27], + [1307, "22:52", "23:29", 1372, 1409, 37], + [1308, "22:53", "23:15", 1373, 1395, 22], + [1309, "22:55", "23:55", 1375, 1435, 60], + [1310, "22:57", "23:51", 1377, 1431, 54], + [1311, "22:58", "23:38", 1378, 1418, 40], + [1312, "23:02", "23:41", 1382, 1421, 39], + [1313, "23:02", "23:12", 1382, 1392, 10], + [1314, "23:04", "23:31", 1384, 1411, 27], + [1315, "23:05", "00:24", 1385, 1464, 79], + [1316, "23:07", "23:44", 1387, 1424, 37], + [1317, "23:13", "23:53", 1393, 1433, 40], + [1318, "23:13", "23:38", 1393, 1418, 25], + [1319, "23:17", "00:03", 1397, 1443, 46], + [1320, "23:17", "23:27", 1397, 1407, 10], + [1321, "23:19", "23:46", 1399, 1426, 27], + [1322, "23:22", "23:59", 1402, 1439, 37], + [1323, "23:25", "00:25", 1405, 1465, 60], + [1324, "23:27", "00:21", 1407, 1461, 54], + [1325, "23:28", "00:08", 1408, 1448, 40], + [1326, "23:32", "23:42", 1412, 1422, 10], + [1327, "23:34", "00:01", 1414, 1441, 27], + [1328, "23:35", "01:05", 1415, 1505, 90], + [1329, "23:37", "00:09", 1417, 1449, 32], + [1330, "23:43", "00:23", 1423, 1463, 40], + [1331, "23:43", "00:08", 1423, 1448, 25], + [1332, "23:46", "00:01", 1426, 1441, 15], + [1333, "23:47", "23:57", 1427, 1437, 10], + [1334, "23:47", "00:33", 1427, 1473, 46], + [1335, "23:52", "00:24", 1432, 1464, 32], + [1336, "23:55", "00:49", 1435, 1489, 54], + [1337, "23:57", "00:57", 1437, 1497, 60], + [1338, "23:58", "00:38", 1438, 1478, 40], + [1339, "00:02", "00:12", 1442, 1452, 10], + [1340, "00:07", "00:39", 1447, 1479, 32], + [1341, "00:13", "00:38", 1453, 1478, 25], + [1342, "00:13", "00:51", 1453, 1491, 38], + [1343, "00:15", "01:14", 1455, 1514, 59], + [1344, "00:17", "01:23", 1457, 1523, 66], + [1345, "00:23", "00:33", 1463, 1473, 10], + [1346, "00:24", "00:40", 1464, 1480, 16], + [1347, "00:25", "01:12", 1465, 1512, 47], + [1348, "00:28", "01:07", 1468, 1507, 39], + [1349, "00:33", "01:05", 1473, 1505, 32], + [1350, "00:43", "01:21", 1483, 1521, 38], + [1351, "00:44", "00:54", 1484, 1494, 10], + [1352, "00:47", "01:09", 1487, 1509, 22], + [1353, "00:47", "01:26", 1487, 1526, 39], + [1354, "00:54", "01:04", 1494, 1504, 10], + [1355, "00:57", "01:07", 1497, 1507, 10], ] # yapf:disable - def bus_driver_scheduling(minimize_drivers, max_num_drivers): """Optimize the bus driver scheduling problem. - This model has two modes. + This model has two modes. - If minimize_drivers == True, the objective will be to find the minimal - number of drivers, independently of the working times of each drivers. + If minimize_drivers == True, the objective will be to find the minimal + number of drivers, independently of the working times of each drivers. - Otherwise, will will create max_num_drivers non optional drivers, and - minimize the sum of working times of these drivers. + Otherwise, will will create max_num_drivers non optional drivers, and + minimize the sum of working times of these drivers. - Args: - minimize_drivers: A Boolean parameter specifying the objective of the - problem. If True, it tries to minimize the number of used drivers. If - false, it minimizes the sum of working times per workers. - max_num_drivers: This number specifies the exact number of non optional - drivers to use. This is only used if 'minimize_drivers' is False. + Args: + minimize_drivers: A Boolean parameter specifying the objective of the + problem. If True, it tries to minimize the number of used drivers. If + false, it minimizes the sum of working times per workers. + max_num_drivers: This number specifies the exact number of non optional + drivers to use. This is only used if 'minimize_drivers' is False. - Returns: - The objective value of the model. - """ + Returns: + The objective value of the model. + """ shifts = None if _INSTANCE.value == 0: shifts = SAMPLE_SHIFTS_TINY @@ -1750,19 +1753,18 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Computed data. total_driving_time = sum(shift[5] for shift in shifts) - min_num_drivers = int(math.ceil(total_driving_time * 1.0 / - max_driving_time)) + min_num_drivers = int(math.ceil(total_driving_time * 1.0 / max_driving_time)) num_drivers = 2 * min_num_drivers if minimize_drivers else max_num_drivers min_start_time = min(shift[3] for shift in shifts) max_end_time = max(shift[4] for shift in shifts) - print('Bus driver scheduling') - print(' num shifts =', num_shifts) - print(' total driving time =', total_driving_time, 'minutes') - print(' min num drivers =', min_num_drivers) - print(' num drivers =', num_drivers) - print(' min start time =', min_start_time) - print(' max end time =', max_end_time) + print("Bus driver scheduling") + print(" num shifts =", num_shifts) + print(" total driving time =", total_driving_time, "minutes") + print(" min num drivers =", min_num_drivers) + print(" num drivers =", num_drivers) + print(" min start time =", min_start_time) + print(" max end time =", max_end_time) model = cp_model.CpModel() @@ -1804,15 +1806,15 @@ 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.NewIntVar(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)) - driving_times.append( - model.NewIntVar(0, max_driving_time, 'driving_%i' % d)) + model.NewIntVar(min_start_time, max_end_time + cleanup_time, "end_%i" % d) + ) + driving_times.append(model.NewIntVar(0, max_driving_time, "driving_%i" % d)) working_times.append( - model.NewIntVar(0, max_working_time, 'working_times_%i' % d)) + model.NewIntVar(0, max_working_time, "working_times_%i" % d) + ) incoming_literals = collections.defaultdict(list) outgoing_literals = collections.defaultdict(list) @@ -1822,11 +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(0, max_driving_time, - 'dr_%i_%i' % (d, s)) + total_driving[d, s] = model.NewIntVar( + 0, max_driving_time, "dr_%i_%i" % (d, s) + ) no_break_driving[d, s] = model.NewIntVar( - 0, max_driving_time_without_pauses, 'mdr_%i_%i' % (d, s)) - performed[d, s] = model.NewBoolVar('performed_%i_%i' % (d, s)) + 0, max_driving_time_without_pauses, "mdr_%i_%i" % (d, s) + ) + performed[d, s] = model.NewBoolVar("performed_%i_%i" % (d, s)) for s in range(num_shifts): shift = shifts[s] @@ -1835,37 +1839,30 @@ 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.NewBoolVar("%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(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(no_break_driving[d, s] == duration).OnlyEnforceIf(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.NewBoolVar("%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).OnlyEnforceIf(sink_lit) + model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(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()) + 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 @@ -1875,28 +1872,31 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # - add upper bound on start_time # - add lower bound on end_times model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf( - performed[d, s]) + performed[d, s] + ) model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf( - performed[d, s]) + performed[d, s] + ) for o in range(num_shifts): other = shifts[o] 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.NewBoolVar("%i from %i to %i" % (d, s, o)) # Increase driving time - model.Add(total_driving[d, o] == total_driving[d, s] + - other[5]).OnlyEnforceIf(lit) + model.Add( + total_driving[d, o] == total_driving[d, s] + other[5] + ).OnlyEnforceIf(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]).OnlyEnforceIf(lit) else: - model.Add(no_break_driving[d, o] == no_break_driving[d, s] + - other[5]).OnlyEnforceIf(lit) + model.Add( + no_break_driving[d, o] == no_break_driving[d, s] + other[5] + ).OnlyEnforceIf(lit) # Add arc outgoing_literals[s].append(lit) @@ -1912,18 +1912,15 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): 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()) + 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_drivers.append(working) outgoing_source_literals.append(working.Not()) incoming_sink_literals.append(working.Not()) # Conditional working time constraints - model.Add( - working_times[d] >= min_working_time).OnlyEnforceIf(working) + model.Add(working_times[d] >= min_working_time).OnlyEnforceIf(working) model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not()) else: # Working time constraints @@ -1954,16 +1951,17 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): 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.AddImplication(working_drivers[d].Not(), working_drivers[d + 1].Not()) # Redundant constraints: sum of driving times = sum of shift driving times model.Add(cp_model.LinearExpr.Sum(driving_times) == total_driving_time) if not minimize_drivers: 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.Sum(working_times) + == total_driving_time + + num_drivers * (setup_time + cleanup_time) + + cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights) + ) if minimize_drivers: # Minimize the number of working drivers @@ -1971,12 +1969,11 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): else: # 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.WeightedSum(delay_literals, delay_weights)) if not minimize_drivers and _OUTPUT_PROTO.value: - print('Writing proto to %s' % _OUTPUT_PROTO.value) - with open(_OUTPUT_PROTO.value, 'w') as text_file: + print("Writing proto to %s" % _OUTPUT_PROTO.value) + with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) # Solve model. @@ -1992,14 +1989,16 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Display solution if minimize_drivers: max_num_drivers = int(solver.ObjectiveValue()) - print('minimal number of drivers =', max_num_drivers) + 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(' working time =', - solver.Value(working_times[d]) + setup_time + cleanup_time) + print("Driver %i: " % (d + 1)) + print(" total driving time =", solver.Value(driving_times[d])) + print( + " working time =", + solver.Value(working_times[d]) + setup_time + cleanup_time, + ) first = True for s in range(num_shifts): @@ -2012,8 +2011,8 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # 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: - print(' **break**') - print(' shift ', shift[0], ':', shift[1], '-', shift[2]) + print(" **break**") + print(" shift ", shift[0], ":", shift[1], "-", shift[2]) first = False return int(solver.ObjectiveValue()) @@ -2021,14 +2020,14 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): def main(_): """Optimize the bus driver allocation in two passes.""" - print('----------- first pass: minimize the number of drivers') + print("----------- first pass: minimize the number of drivers") num_drivers = bus_driver_scheduling(True, -1) if num_drivers == -1: - print('no solution found, skipping the final step') + print("no solution found, skipping the final step") else: - print('----------- second pass: minimize the sum of working times') + print("----------- second pass: minimize the sum of working times") bus_driver_scheduling(False, num_drivers) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/chemical_balance_sat.py b/examples/python/chemical_balance_sat.py old mode 100755 new mode 100644 index 6adb2fb539..6585e7d12c --- a/examples/python/chemical_balance_sat.py +++ b/examples/python/chemical_balance_sat.py @@ -11,6 +11,7 @@ # 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. + """We are trying to group items in equal sized groups. Each item has a color and a value. We want the sum of values of each group to be @@ -59,9 +60,13 @@ def chemical_balance(): max_set = [ int( math.ceil( - min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1] + min( + max_quantities[q][1] * 1000 / chemical_set[s][q + 1] for q in all_products - if chemical_set[s][q + 1] != 0))) + if chemical_set[s][q + 1] != 0 + ) + ) + ) for s in all_sets ] @@ -71,14 +76,13 @@ def chemical_balance(): for p in all_products: model.Add( - sum( - int(chemical_set[s][p + 1] * 10) * set_vars[s] - for s in all_sets) <= int(max_quantities[p][1] * 10000)) + sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets) + <= int(max_quantities[p][1] * 10000) + ) 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) + 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) @@ -90,15 +94,15 @@ def chemical_balance(): print(f"Optimal objective value = {solver.ObjectiveValue() / 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] - for s in all_sets) + 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 1a13f54575..8b20dee458 100644 --- a/examples/python/clustering_sat.py +++ b/examples/python/clustering_sat.py @@ -11,6 +11,7 @@ # 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. + """Cluster 40 cities in 4 equal groups to minimize sum of crossed distances.""" from typing import Sequence @@ -19,53 +20,1693 @@ from ortools.sat.python import cp_model distance_matrix = [ - [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056], - [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994], - [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589], - [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743], - [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198], - [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078], - [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967], - [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552], - [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422], - [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935], - [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891], - [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002], - [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225], - [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671], - [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475], - [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633], - [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084], - [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315], - [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866], - [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802], - [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682], - [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471], - [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720], - [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674], - [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908], - [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204], - [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000], - [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066], - [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039], - [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721], - [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496], - [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772], - [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614], - [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805], - [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519], - [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614], - [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749], - [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313], - [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042], - [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0], + [ + 0, + 10938, + 4542, + 2835, + 29441, + 2171, + 1611, + 9208, + 9528, + 11111, + 16120, + 22606, + 22127, + 20627, + 21246, + 23387, + 16697, + 33609, + 26184, + 24772, + 22644, + 20655, + 30492, + 23296, + 32979, + 18141, + 19248, + 17129, + 17192, + 15645, + 12658, + 11210, + 12094, + 13175, + 18162, + 4968, + 12308, + 10084, + 13026, + 15056, + ], + [ + 10938, + 0, + 6422, + 9742, + 18988, + 12974, + 11216, + 19715, + 19004, + 18271, + 25070, + 31971, + 31632, + 30571, + 31578, + 33841, + 27315, + 43964, + 36944, + 35689, + 33569, + 31481, + 41360, + 33760, + 43631, + 28730, + 29976, + 27803, + 28076, + 26408, + 23504, + 22025, + 22000, + 13197, + 14936, + 15146, + 23246, + 20956, + 23963, + 25994, + ], + [ + 4542, + 6422, + 0, + 3644, + 25173, + 6552, + 5092, + 13584, + 13372, + 13766, + 19805, + 26537, + 26117, + 24804, + 25590, + 27784, + 21148, + 37981, + 30693, + 29315, + 27148, + 25071, + 34943, + 27472, + 37281, + 22389, + 23592, + 21433, + 21655, + 20011, + 17087, + 15612, + 15872, + 11653, + 15666, + 8842, + 16843, + 14618, + 17563, + 19589, + ], + [ + 2835, + 9742, + 3644, + 0, + 28681, + 3851, + 4341, + 11660, + 12294, + 13912, + 18893, + 25283, + 24777, + 23173, + 23636, + 25696, + 18950, + 35927, + 28233, + 26543, + 24127, + 21864, + 31765, + 24018, + 33904, + 19005, + 20295, + 18105, + 18551, + 16763, + 13958, + 12459, + 12296, + 10370, + 15331, + 5430, + 14044, + 12135, + 14771, + 16743, + ], + [ + 29441, + 18988, + 25173, + 28681, + 0, + 31590, + 29265, + 37173, + 35501, + 32929, + 40239, + 47006, + 46892, + 46542, + 48112, + 50506, + 44539, + 60103, + 54208, + 53557, + 51878, + 50074, + 59849, + 52645, + 62415, + 47544, + 48689, + 46560, + 46567, + 45086, + 42083, + 40648, + 40971, + 29929, + 28493, + 34015, + 41473, + 38935, + 42160, + 44198, + ], + [ + 2171, + 12974, + 6552, + 3851, + 31590, + 0, + 3046, + 7856, + 8864, + 11330, + 15411, + 21597, + 21065, + 19382, + 19791, + 21845, + 15099, + 32076, + 24425, + 22848, + 20600, + 18537, + 28396, + 21125, + 30825, + 15975, + 17101, + 14971, + 15104, + 13503, + 10544, + 9080, + 9983, + 13435, + 18755, + 2947, + 10344, + 8306, + 11069, + 13078, + ], + [ + 1611, + 11216, + 5092, + 4341, + 29265, + 3046, + 0, + 8526, + 8368, + 9573, + 14904, + 21529, + 21085, + 19719, + 20504, + 22713, + 16118, + 32898, + 25728, + 24541, + 22631, + 20839, + 30584, + 23755, + 33278, + 18557, + 19545, + 17490, + 17309, + 15936, + 12881, + 11498, + 12944, + 14711, + 19589, + 5993, + 12227, + 9793, + 12925, + 14967, + ], + [ + 9208, + 19715, + 13584, + 11660, + 37173, + 7856, + 8526, + 0, + 3248, + 7855, + 8245, + 13843, + 13272, + 11526, + 12038, + 14201, + 7599, + 24411, + 17259, + 16387, + 15050, + 13999, + 23134, + 17899, + 26460, + 12894, + 13251, + 11680, + 10455, + 9997, + 7194, + 6574, + 10678, + 20959, + 26458, + 8180, + 5255, + 2615, + 5730, + 7552, + ], + [ + 9528, + 19004, + 13372, + 12294, + 35501, + 8864, + 8368, + 3248, + 0, + 4626, + 6598, + 13168, + 12746, + 11567, + 12731, + 15083, + 9120, + 25037, + 18718, + 18433, + 17590, + 16888, + 25630, + 20976, + 29208, + 16055, + 16300, + 14838, + 13422, + 13165, + 10430, + 9813, + 13777, + 22300, + 27564, + 10126, + 8388, + 5850, + 8778, + 10422, + ], + [ + 11111, + 18271, + 13766, + 13912, + 32929, + 11330, + 9573, + 7855, + 4626, + 0, + 7318, + 14185, + 14005, + 13655, + 15438, + 17849, + 12839, + 27179, + 21947, + 22230, + 21814, + 21366, + 29754, + 25555, + 33535, + 20674, + 20872, + 19457, + 17961, + 17787, + 15048, + 14372, + 18115, + 24280, + 29101, + 13400, + 13008, + 10467, + 13375, + 14935, + ], + [ + 16120, + 25070, + 19805, + 18893, + 40239, + 15411, + 14904, + 8245, + 6598, + 7318, + 0, + 6939, + 6702, + 6498, + 8610, + 10961, + 7744, + 19889, + 15350, + 16403, + 16975, + 17517, + 24357, + 22176, + 28627, + 18093, + 17672, + 16955, + 14735, + 15510, + 13694, + 13768, + 18317, + 28831, + 34148, + 16326, + 11276, + 9918, + 11235, + 11891, + ], + [ + 22606, + 31971, + 26537, + 25283, + 47006, + 21597, + 21529, + 13843, + 13168, + 14185, + 6939, + 0, + 793, + 3401, + 5562, + 6839, + 8923, + 13433, + 11264, + 13775, + 15853, + 17629, + 21684, + 22315, + 26411, + 19539, + 18517, + 18636, + 16024, + 17632, + 16948, + 17587, + 22131, + 34799, + 40296, + 21953, + 14739, + 14568, + 14366, + 14002, + ], + [ + 22127, + 31632, + 26117, + 24777, + 46892, + 21065, + 21085, + 13272, + 12746, + 14005, + 6702, + 793, + 0, + 2608, + 4809, + 6215, + 8151, + 13376, + 10702, + 13094, + 15099, + 16845, + 21039, + 21535, + 25744, + 18746, + 17725, + 17845, + 15232, + 16848, + 16197, + 16859, + 21391, + 34211, + 39731, + 21345, + 14006, + 13907, + 13621, + 13225, + ], + [ + 20627, + 30571, + 24804, + 23173, + 46542, + 19382, + 19719, + 11526, + 11567, + 13655, + 6498, + 3401, + 2608, + 0, + 2556, + 4611, + 5630, + 13586, + 9157, + 11005, + 12681, + 14285, + 19044, + 18996, + 23644, + 16138, + 15126, + 15240, + 12625, + 14264, + 13736, + 14482, + 18958, + 32292, + 37879, + 19391, + 11621, + 11803, + 11188, + 10671, + ], + [ + 21246, + 31578, + 25590, + 23636, + 48112, + 19791, + 20504, + 12038, + 12731, + 15438, + 8610, + 5562, + 4809, + 2556, + 0, + 2411, + 4917, + 12395, + 6757, + 8451, + 10292, + 12158, + 16488, + 16799, + 21097, + 14374, + 13194, + 13590, + 10943, + 12824, + 12815, + 13779, + 18042, + 32259, + 37918, + 19416, + 10975, + 11750, + 10424, + 9475, + ], + [ + 23387, + 33841, + 27784, + 25696, + 50506, + 21845, + 22713, + 14201, + 15083, + 17849, + 10961, + 6839, + 6215, + 4611, + 2411, + 0, + 6760, + 10232, + 4567, + 7010, + 9607, + 12003, + 14846, + 16408, + 19592, + 14727, + 13336, + 14109, + 11507, + 13611, + 14104, + 15222, + 19237, + 34013, + 39703, + 21271, + 12528, + 13657, + 11907, + 10633, + ], + [ + 16697, + 27315, + 21148, + 18950, + 44539, + 15099, + 16118, + 7599, + 9120, + 12839, + 7744, + 8923, + 8151, + 5630, + 4917, + 6760, + 0, + 16982, + 9699, + 9400, + 9302, + 9823, + 16998, + 14534, + 21042, + 10911, + 10190, + 9900, + 7397, + 8758, + 8119, + 8948, + 13353, + 27354, + 33023, + 14542, + 6106, + 6901, + 5609, + 5084, + ], + [ + 33609, + 43964, + 37981, + 35927, + 60103, + 32076, + 32898, + 24411, + 25037, + 27179, + 19889, + 13433, + 13376, + 13586, + 12395, + 10232, + 16982, + 0, + 8843, + 12398, + 16193, + 19383, + 16423, + 22583, + 20997, + 22888, + 21194, + 22640, + 20334, + 22636, + 23801, + 25065, + 28675, + 44048, + 49756, + 31426, + 22528, + 23862, + 21861, + 20315, + ], + [ + 26184, + 36944, + 30693, + 28233, + 54208, + 24425, + 25728, + 17259, + 18718, + 21947, + 15350, + 11264, + 10702, + 9157, + 6757, + 4567, + 9699, + 8843, + 0, + 3842, + 7518, + 10616, + 10666, + 14237, + 15515, + 14053, + 12378, + 13798, + 11537, + 13852, + 15276, + 16632, + 19957, + 35660, + 41373, + 23361, + 14333, + 16125, + 13624, + 11866, + ], + [ + 24772, + 35689, + 29315, + 26543, + 53557, + 22848, + 24541, + 16387, + 18433, + 22230, + 16403, + 13775, + 13094, + 11005, + 8451, + 7010, + 9400, + 12398, + 3842, + 0, + 3795, + 7014, + 8053, + 10398, + 12657, + 10633, + 8889, + 10569, + 8646, + 10938, + 12906, + 14366, + 17106, + 33171, + 38858, + 21390, + 12507, + 14748, + 11781, + 9802, + ], + [ + 22644, + 33569, + 27148, + 24127, + 51878, + 20600, + 22631, + 15050, + 17590, + 21814, + 16975, + 15853, + 15099, + 12681, + 10292, + 9607, + 9302, + 16193, + 7518, + 3795, + 0, + 3250, + 8084, + 6873, + 11763, + 6949, + 5177, + 7050, + 5619, + 7730, + 10187, + 11689, + 13792, + 30012, + 35654, + 18799, + 10406, + 12981, + 9718, + 7682, + ], + [ + 20655, + 31481, + 25071, + 21864, + 50074, + 18537, + 20839, + 13999, + 16888, + 21366, + 17517, + 17629, + 16845, + 14285, + 12158, + 12003, + 9823, + 19383, + 10616, + 7014, + 3250, + 0, + 9901, + 4746, + 12531, + 3737, + 1961, + 4036, + 3588, + 5109, + 7996, + 9459, + 10846, + 27094, + 32690, + 16451, + 8887, + 11624, + 8304, + 6471, + ], + [ + 30492, + 41360, + 34943, + 31765, + 59849, + 28396, + 30584, + 23134, + 25630, + 29754, + 24357, + 21684, + 21039, + 19044, + 16488, + 14846, + 16998, + 16423, + 10666, + 8053, + 8084, + 9901, + 0, + 9363, + 4870, + 13117, + 11575, + 13793, + 13300, + 15009, + 17856, + 19337, + 20454, + 36551, + 42017, + 26352, + 18403, + 21033, + 17737, + 15720, + ], + [ + 23296, + 33760, + 27472, + 24018, + 52645, + 21125, + 23755, + 17899, + 20976, + 25555, + 22176, + 22315, + 21535, + 18996, + 16799, + 16408, + 14534, + 22583, + 14237, + 10398, + 6873, + 4746, + 9363, + 0, + 10020, + 5211, + 4685, + 6348, + 7636, + 8010, + 11074, + 12315, + 11926, + 27537, + 32880, + 18634, + 12644, + 15358, + 12200, + 10674, + ], + [ + 32979, + 43631, + 37281, + 33904, + 62415, + 30825, + 33278, + 26460, + 29208, + 33535, + 28627, + 26411, + 25744, + 23644, + 21097, + 19592, + 21042, + 20997, + 15515, + 12657, + 11763, + 12531, + 4870, + 10020, + 0, + 14901, + 13738, + 15855, + 16118, + 17348, + 20397, + 21793, + 21936, + 37429, + 42654, + 28485, + 21414, + 24144, + 20816, + 18908, + ], + [ + 18141, + 28730, + 22389, + 19005, + 47544, + 15975, + 18557, + 12894, + 16055, + 20674, + 18093, + 19539, + 18746, + 16138, + 14374, + 14727, + 10911, + 22888, + 14053, + 10633, + 6949, + 3737, + 13117, + 5211, + 14901, + 0, + 1777, + 1217, + 3528, + 2896, + 5892, + 7104, + 7338, + 23517, + 29068, + 13583, + 7667, + 10304, + 7330, + 6204, + ], + [ + 19248, + 29976, + 23592, + 20295, + 48689, + 17101, + 19545, + 13251, + 16300, + 20872, + 17672, + 18517, + 17725, + 15126, + 13194, + 13336, + 10190, + 21194, + 12378, + 8889, + 5177, + 1961, + 11575, + 4685, + 13738, + 1777, + 0, + 2217, + 2976, + 3610, + 6675, + 8055, + 8965, + 25197, + 30774, + 14865, + 8007, + 10742, + 7532, + 6000, + ], + [ + 17129, + 27803, + 21433, + 18105, + 46560, + 14971, + 17490, + 11680, + 14838, + 19457, + 16955, + 18636, + 17845, + 15240, + 13590, + 14109, + 9900, + 22640, + 13798, + 10569, + 7050, + 4036, + 13793, + 6348, + 15855, + 1217, + 2217, + 0, + 2647, + 1686, + 4726, + 6000, + 6810, + 23060, + 28665, + 12674, + 6450, + 9094, + 6117, + 5066, + ], + [ + 17192, + 28076, + 21655, + 18551, + 46567, + 15104, + 17309, + 10455, + 13422, + 17961, + 14735, + 16024, + 15232, + 12625, + 10943, + 11507, + 7397, + 20334, + 11537, + 8646, + 5619, + 3588, + 13300, + 7636, + 16118, + 3528, + 2976, + 2647, + 0, + 2320, + 4593, + 6093, + 8479, + 24542, + 30219, + 13194, + 5301, + 8042, + 4735, + 3039, + ], + [ + 15645, + 26408, + 20011, + 16763, + 45086, + 13503, + 15936, + 9997, + 13165, + 17787, + 15510, + 17632, + 16848, + 14264, + 12824, + 13611, + 8758, + 22636, + 13852, + 10938, + 7730, + 5109, + 15009, + 8010, + 17348, + 2896, + 3610, + 1686, + 2320, + 0, + 3086, + 4444, + 6169, + 22301, + 27963, + 11344, + 4780, + 7408, + 4488, + 3721, + ], + [ + 12658, + 23504, + 17087, + 13958, + 42083, + 10544, + 12881, + 7194, + 10430, + 15048, + 13694, + 16948, + 16197, + 13736, + 12815, + 14104, + 8119, + 23801, + 15276, + 12906, + 10187, + 7996, + 17856, + 11074, + 20397, + 5892, + 6675, + 4726, + 4593, + 3086, + 0, + 1501, + 5239, + 20390, + 26101, + 8611, + 2418, + 4580, + 2599, + 3496, + ], + [ + 11210, + 22025, + 15612, + 12459, + 40648, + 9080, + 11498, + 6574, + 9813, + 14372, + 13768, + 17587, + 16859, + 14482, + 13779, + 15222, + 8948, + 25065, + 16632, + 14366, + 11689, + 9459, + 19337, + 12315, + 21793, + 7104, + 8055, + 6000, + 6093, + 4444, + 1501, + 0, + 4608, + 19032, + 24747, + 7110, + 2860, + 4072, + 3355, + 4772, + ], + [ + 12094, + 22000, + 15872, + 12296, + 40971, + 9983, + 12944, + 10678, + 13777, + 18115, + 18317, + 22131, + 21391, + 18958, + 18042, + 19237, + 13353, + 28675, + 19957, + 17106, + 13792, + 10846, + 20454, + 11926, + 21936, + 7338, + 8965, + 6810, + 8479, + 6169, + 5239, + 4608, + 0, + 16249, + 21866, + 7146, + 7403, + 8446, + 7773, + 8614, + ], + [ + 13175, + 13197, + 11653, + 10370, + 29929, + 13435, + 14711, + 20959, + 22300, + 24280, + 28831, + 34799, + 34211, + 32292, + 32259, + 34013, + 27354, + 44048, + 35660, + 33171, + 30012, + 27094, + 36551, + 27537, + 37429, + 23517, + 25197, + 23060, + 24542, + 22301, + 20390, + 19032, + 16249, + 0, + 5714, + 12901, + 21524, + 20543, + 22186, + 23805, + ], + [ + 18162, + 14936, + 15666, + 15331, + 28493, + 18755, + 19589, + 26458, + 27564, + 29101, + 34148, + 40296, + 39731, + 37879, + 37918, + 39703, + 33023, + 49756, + 41373, + 38858, + 35654, + 32690, + 42017, + 32880, + 42654, + 29068, + 30774, + 28665, + 30219, + 27963, + 26101, + 24747, + 21866, + 5714, + 0, + 18516, + 27229, + 26181, + 27895, + 29519, + ], + [ + 4968, + 15146, + 8842, + 5430, + 34015, + 2947, + 5993, + 8180, + 10126, + 13400, + 16326, + 21953, + 21345, + 19391, + 19416, + 21271, + 14542, + 31426, + 23361, + 21390, + 18799, + 16451, + 26352, + 18634, + 28485, + 13583, + 14865, + 12674, + 13194, + 11344, + 8611, + 7110, + 7146, + 12901, + 18516, + 0, + 9029, + 7668, + 9742, + 11614, + ], + [ + 12308, + 23246, + 16843, + 14044, + 41473, + 10344, + 12227, + 5255, + 8388, + 13008, + 11276, + 14739, + 14006, + 11621, + 10975, + 12528, + 6106, + 22528, + 14333, + 12507, + 10406, + 8887, + 18403, + 12644, + 21414, + 7667, + 8007, + 6450, + 5301, + 4780, + 2418, + 2860, + 7403, + 21524, + 27229, + 9029, + 0, + 2747, + 726, + 2749, + ], + [ + 10084, + 20956, + 14618, + 12135, + 38935, + 8306, + 9793, + 2615, + 5850, + 10467, + 9918, + 14568, + 13907, + 11803, + 11750, + 13657, + 6901, + 23862, + 16125, + 14748, + 12981, + 11624, + 21033, + 15358, + 24144, + 10304, + 10742, + 9094, + 8042, + 7408, + 4580, + 4072, + 8446, + 20543, + 26181, + 7668, + 2747, + 0, + 3330, + 5313, + ], + [ + 13026, + 23963, + 17563, + 14771, + 42160, + 11069, + 12925, + 5730, + 8778, + 13375, + 11235, + 14366, + 13621, + 11188, + 10424, + 11907, + 5609, + 21861, + 13624, + 11781, + 9718, + 8304, + 17737, + 12200, + 20816, + 7330, + 7532, + 6117, + 4735, + 4488, + 2599, + 3355, + 7773, + 22186, + 27895, + 9742, + 726, + 3330, + 0, + 2042, + ], + [ + 15056, + 25994, + 19589, + 16743, + 44198, + 13078, + 14967, + 7552, + 10422, + 14935, + 11891, + 14002, + 13225, + 10671, + 9475, + 10633, + 5084, + 20315, + 11866, + 9802, + 7682, + 6471, + 15720, + 10674, + 18908, + 6204, + 6000, + 5066, + 3039, + 3721, + 3496, + 4772, + 8614, + 23805, + 29519, + 11614, + 2749, + 5313, + 2042, + 0, + ], ] # yapf: disable def clustering_sat(): """Entry point of the program.""" num_nodes = len(distance_matrix) - print('Num nodes =', num_nodes) + print("Num nodes =", num_nodes) # Number of groups to split the nodes, must divide num_nodes. num_groups = 4 @@ -80,7 +1721,7 @@ 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.NewBoolVar("neighbors_%i_%i" % (n1, n2)) neighbors[n1, n2] = same obj_vars.append(same) obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1]) @@ -88,23 +1729,24 @@ def clustering_sat(): # Number of neighborss: for n in range(num_nodes): 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) + sum(neighbors[m, n] for m in range(n)) + + sum(neighbors[n, m] for m in range(n + 1, num_nodes)) + == group_size - 1 + ) # Enforce transivity on all triplets. 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(neighbors[n1, n3] + neighbors[n2, n3] + - neighbors[n1, n2] != 2) + 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) # 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() @@ -124,16 +1766,16 @@ def clustering_sat(): for o in range(n + 1, num_nodes): if solver.BooleanValue(neighbors[n, o]): visited.add(o) - output += ' ' + str(o) - print('Group', g, ':', output) + output += " " + str(o) + print("Group", g, ":", output) break def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") clustering_sat() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/cover_rectangle_sat.py b/examples/python/cover_rectangle_sat.py index ba0e63ccdb..add26955b8 100644 --- a/examples/python/cover_rectangle_sat.py +++ b/examples/python/cover_rectangle_sat.py @@ -11,6 +11,7 @@ # 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. + """Fill a 60x50 rectangle by a minimum number of non-overlapping squares.""" from typing import Sequence @@ -34,16 +35,16 @@ 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.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) - 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.NewIntervalVar(start_x, size, end_x, "ix_%i" % i) + interval_y = model.NewIntervalVar(start_y, size, end_y, "iy_%i" % i) - area = model.NewIntVar(1, size_y * size_y, 'area_%i' % i) + area = model.NewIntVar(1, size_y * size_y, "area_%i" % i) model.AddMultiplicationEquality(area, [size, size]) areas.append(area) @@ -68,7 +69,7 @@ def cover_rectangle(num_squares): model.Add(sizes[i] <= sizes[i + 1]) # Define same to be true iff sizes[i] == sizes[i + 1] - same = model.NewBoolVar('') + same = model.NewBoolVar("") model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same) model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not()) @@ -83,36 +84,38 @@ def cover_rectangle(num_squares): solver = cp_model.CpSolver() solver.parameters.num_workers = 8 status = solver.Solve(model) - print('%s found in %0.2fs' % (solver.StatusName(status), solver.WallTime())) + print("%s found in %0.2fs" % (solver.StatusName(status), solver.WallTime())) # Prints solution. if status == cp_model.OPTIMAL: - display = [[' ' for _ in range(size_x)] for _ in range(size_y)] + 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]) - char = format(i, '01x') + char = format(i, "01x") for j in range(sol_s): for k in range(sol_s): - if display[sol_y + j][sol_x + k] != ' ': - print('ERROR between %s and %s' % - (display[sol_y + j][sol_x + k], char)) + if display[sol_y + j][sol_x + k] != " ": + print( + "ERROR between %s and %s" + % (display[sol_y + j][sol_x + k], char) + ) display[sol_y + j][sol_x + k] = char for line in range(size_y): - print(' '.join(display[line])) + print(" ".join(display[line])) return status == cp_model.OPTIMAL def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") for num_squares in range(1, 15): - print('Trying with size =', num_squares) + print("Trying with size =", num_squares) if cover_rectangle(num_squares): break -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/cryptarithm_sat.py b/examples/python/cryptarithm_sat.py index ad255bf394..d96b5c6ad3 100644 --- a/examples/python/cryptarithm_sat.py +++ b/examples/python/cryptarithm_sat.py @@ -11,6 +11,7 @@ # 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. + """Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY. """ @@ -19,28 +20,27 @@ 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.NewIntVar(1, 9, "s") + e = model.NewIntVar(0, 9, "e") + n = model.NewIntVar(0, 9, "n") + d = model.NewIntVar(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.NewIntVar(1, 9, "m") + o = model.NewIntVar(0, 9, "o") + r = model.NewIntVar(0, 9, "r") + y = model.NewIntVar(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.NewBoolVar("c0") + c1 = model.NewBoolVar("c1") + c2 = model.NewBoolVar("c2") + c3 = model.NewBoolVar("c3") # Force all letters to take on different values. model.AddAllDifferent(s, e, n, d, m, o, r, y) @@ -63,20 +63,20 @@ def send_more_money(): # Solve model. solver = cp_model.CpSolver() 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("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)) def main(_): send_more_money() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/flexible_job_shop_sat.py b/examples/python/flexible_job_shop_sat.py old mode 100755 new mode 100644 index 18b7dc0c86..9eea9d24f4 --- a/examples/python/flexible_job_shop_sat.py +++ b/examples/python/flexible_job_shop_sat.py @@ -11,6 +11,7 @@ # 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 a flexible jobshop problems with the CP-SAT solver. A jobshop is a standard scheduling problem when you must sequence a @@ -38,8 +39,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): """Called at each new solution.""" - print('Solution %i, time = %f s, objective = %i' % - (self.__solution_count, self.WallTime(), self.ObjectiveValue())) + print( + "Solution %i, time = %f s, objective = %i" + % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + ) self.__solution_count += 1 @@ -81,7 +84,7 @@ def flexible_jobshop(): max_task_duration = max(max_task_duration, alternative[0]) horizon += max_task_duration - print('Horizon = %i' % horizon) + print("Horizon = %i" % horizon) # Global storage of variables. intervals_per_resources = collections.defaultdict(list) @@ -109,13 +112,15 @@ def flexible_jobshop(): max_duration = max(max_duration, alt_duration) # 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(min_duration, max_duration, - 'duration' + suffix_name) - end = model.NewIntVar(0, horizon, 'end' + suffix_name) - interval = model.NewIntervalVar(start, duration, end, - 'interval' + suffix_name) + suffix_name = "_j%i_t%i" % (job_id, task_id) + start = model.NewIntVar(0, horizon, "start" + suffix_name) + duration = model.NewIntVar( + min_duration, max_duration, "duration" + suffix_name + ) + end = model.NewIntVar(0, horizon, "end" + suffix_name) + interval = model.NewIntervalVar( + start, duration, end, "interval" + suffix_name + ) # Store the start for the solution. starts[(job_id, task_id)] = start @@ -129,14 +134,14 @@ def flexible_jobshop(): if num_alternatives > 1: 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) + 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_duration = task[alt_id][0] - l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix) + l_end = model.NewIntVar(0, horizon, "end" + alt_suffix) l_interval = model.NewOptionalIntervalVar( - l_start, l_duration, l_end, l_presence, - 'interval' + alt_suffix) + 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. @@ -165,7 +170,7 @@ def flexible_jobshop(): model.AddNoOverlap(intervals) # Makespan objective - makespan = model.NewIntVar(0, horizon, 'makespan') + makespan = model.NewIntVar(0, horizon, "makespan") model.AddMaxEquality(makespan, job_ends) model.Minimize(makespan) @@ -176,7 +181,7 @@ def flexible_jobshop(): # Print final solution. for job_id in all_jobs: - print('Job %i:' % job_id) + print("Job %i:" % job_id) for task_id in range(len(jobs[job_id])): start_value = solver.Value(starts[(job_id, task_id)]) machine = -1 @@ -188,15 +193,16 @@ def flexible_jobshop(): machine = jobs[job_id][task_id][alt_id][1] selected = alt_id print( - ' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' % - (job_id, task_id, start_value, selected, machine, duration)) + " task_%i_%i starts at %i (alt %i, machine %i, duration %i)" + % (job_id, task_id, start_value, selected, machine, duration) + ) - print('Solve status: %s' % solver.StatusName(status)) - print('Optimal objective value: %i' % solver.ObjectiveValue()) - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) + print("Solve status: %s" % solver.StatusName(status)) + print("Optimal objective value: %i" % solver.ObjectiveValue()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) flexible_jobshop() diff --git a/examples/python/gate_scheduling_sat.py b/examples/python/gate_scheduling_sat.py old mode 100755 new mode 100644 index 42781ea3be..00aebdeebd --- a/examples/python/gate_scheduling_sat.py +++ b/examples/python/gate_scheduling_sat.py @@ -11,6 +11,7 @@ # 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. + """Gate Scheduling problem. We have a set of jobs to perform (duration, width). @@ -47,7 +48,7 @@ def main(_): [1, 2], [6, 8], [4, 5], - [3, 7] + [3, 7], ] max_width = 10 @@ -66,31 +67,31 @@ def main(_): for i in all_jobs: # Create main interval. - start = model.NewIntVar(0, horizon, 'start_%i' % i) + start = model.NewIntVar(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.NewIntVar(0, horizon, "end_%i" % i) + interval = model.NewIntervalVar(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.NewBoolVar("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, duration, end0, - performed_on_m0, - 'interval_%i_on_m0' % i) + start0 = model.NewIntVar(0, horizon, "start_%i_on_m0" % i) + end0 = model.NewIntVar(0, horizon, "end_%i_on_m0" % i) + interval0 = model.NewOptionalIntervalVar( + 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.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 + ) intervals1.append(interval1) # We only propagate the constraint if the tasks is performed on the machine. @@ -105,7 +106,7 @@ def main(_): model.AddNoOverlap(intervals1) # Objective variable. - makespan = model.NewIntVar(0, horizon, 'makespan') + makespan = model.NewIntVar(0, horizon, "makespan") model.AddMaxEquality(makespan, ends) model.Minimize(makespan) @@ -118,9 +119,8 @@ def main(_): # Output solution. if visualization.RunFromIPython(): - output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, - 40.0) - output.AddTitle('Makespan = %i' % solver.ObjectiveValue()) + output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0) + output.AddTitle("Makespan = %i" % solver.ObjectiveValue()) color_manager = visualization.ColorManager() color_manager.SeedRandomColor(0) @@ -130,25 +130,27 @@ def main(_): d_x = jobs[i][0] d_y = jobs[i][1] s_y = performed_machine * (max_width - d_y) - output.AddRectangle(start, s_y, d_x, d_y, - color_manager.RandomColor(), 'black', 'j%i' % i) + output.AddRectangle( + start, s_y, d_x, d_y, color_manager.RandomColor(), "black", "j%i" % i + ) output.AddXScale() output.AddYScale() output.Display() else: - print('Solution') - print(' - makespan = %i' % solver.ObjectiveValue()) + print("Solution") + print(" - makespan = %i" % solver.ObjectiveValue()) for i in all_jobs: 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( + " - 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()) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/golomb8.py b/examples/python/golomb8.py index 6f5db72443..8f3698f342 100755 --- a/examples/python/golomb8.py +++ b/examples/python/golomb8.py @@ -11,6 +11,7 @@ # 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. + """This is the Golomb ruler problem. This model aims at maximizing radar interferences in a minimum space. @@ -34,13 +35,13 @@ FLAGS = flags.FLAGS def main(_): # Create the solver. - solver = pywrapcp.Solver('golomb ruler') + solver = pywrapcp.Solver("golomb ruler") size = 8 var_max = size * size all_vars = list(range(0, size)) - marks = [solver.IntVar(0, var_max, 'marks_%d' % i) for i in all_vars] + marks = [solver.IntVar(0, var_max, "marks_%d" % i) for i in all_vars] objective = solver.Minimize(marks[size - 1], 1) @@ -62,21 +63,28 @@ def main(_): collector = solver.AllSolutionCollector(solution) solver.Solve( - solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE), [objective, collector]) + solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE), + [objective, collector], + ) for i in range(0, collector.SolutionCount()): obj_value = collector.Value(i, marks[size - 1]) time = collector.WallTime(i) branches = collector.Branches(i) failures = collector.Failures(i) - print(('Solution #%i: value = %i, failures = %i, branches = %i,' - 'time = %i ms') % (i, obj_value, failures, branches, time)) + print( + ("Solution #%i: value = %i, failures = %i, branches = %i," "time = %i ms") + % (i, obj_value, failures, branches, time) + ) time = solver.WallTime() branches = solver.Branches() failures = solver.Failures() - print(('Total run : failures = %i, branches = %i, time = %i ms' % - (failures, branches, time))) + print( + ( + "Total run : failures = %i, branches = %i, time = %i ms" + % (failures, branches, time) + ) + ) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/golomb_sat.py b/examples/python/golomb_sat.py old mode 100755 new mode 100644 index 73b3b1c7d4..c23a11a2b8 --- a/examples/python/golomb_sat.py +++ b/examples/python/golomb_sat.py @@ -11,6 +11,7 @@ # 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. + """This is the Golomb ruler problem. This model aims at maximizing radar interferences in a minimum space. @@ -29,11 +30,12 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -_ORDER = flags.DEFINE_integer('order', 8, 'Order of the ruler.') +_ORDER = flags.DEFINE_integer("order", 8, "Order of the ruler.") _PARAMS = flags.DEFINE_string( - 'params', - 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45', - 'Sat solver parameters.') + "params", + "num_search_workers:16,log_search_progress:true,max_time_in_seconds:45", + "Sat solver parameters.", +) def solve_golomb_ruler(order, params): @@ -44,7 +46,7 @@ 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.NewIntVar(0, var_max, f"marks_{i}") for i in all_vars] model.Add(marks[0] == 0) for i in range(order - 2): @@ -53,7 +55,7 @@ def solve_golomb_ruler(order, params): diffs = [] for i in range(order - 1): for j in range(i + 1, order): - diff = model.NewIntVar(0, var_max, f'diff [{j},{i}]') + diff = model.NewIntVar(0, var_max, f"diff [{j},{i}]") model.Add(diff == marks[j] - marks[i]) diffs.append(diff) model.AddAllDifferent(diffs) @@ -70,29 +72,29 @@ def solve_golomb_ruler(order, params): if params: text_format.Parse(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() - print(f'Golomb ruler(order={order})') + print(f"Golomb ruler(order={order})") status = solver.Solve(model, solution_printer) # Print solution. - print(f'status: {solver.StatusName(status)}') + print(f"status: {solver.StatusName(status)}") if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): for idx, var in enumerate(marks): - print(f'mark[{idx}]: {solver.Value(var)}') + print(f"mark[{idx}]: {solver.Value(var)}") intervals = [solver.Value(diff) for diff in diffs] intervals.sort() - print(f'intervals: {intervals}') + 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("Statistics:") + print(f"- conflicts: {solver.NumConflicts()}") + print(f"- branches : {solver.NumBranches()}") + print(f"- wall time: {solver.WallTime()}s\n") def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") solve_golomb_ruler(_ORDER.value, _PARAMS.value) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/hidato_sat.py b/examples/python/hidato_sat.py old mode 100644 new mode 100755 index c07f681772..02fb4a9d9c --- a/examples/python/hidato_sat.py +++ b/examples/python/hidato_sat.py @@ -11,6 +11,7 @@ # 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 Hidato problem with the CP-SAT solver.""" from absl import app @@ -21,25 +22,29 @@ from ortools.sat.python import cp_model def build_pairs(rows, cols): """Build closeness pairs for consecutive numbers. - Build set of allowed pairs such that two consecutive numbers touch - each other in the grid. + Build set of allowed pairs such that two consecutive numbers touch + each other in the grid. - Returns: - A list of pairs for allowed consecutive position of numbers. + Returns: + A list of pairs for allowed consecutive position of numbers. - Args: - rows: the number of rows in the grid - cols: the number of columns in the grid - """ + Args: + rows: the number of rows in the grid + cols: the number of columns in the grid + """ result = [] for x in range(rows): for y in range(cols): for dx in (-1, 0, 1): for dy in (-1, 0, 1): - if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and - y + dy < cols and (dx != 0 or dy != 0)): - result.append( - (x * cols + y, (x + dx) * cols + (y + dy))) + if ( + x + dx >= 0 + and x + dx < rows + and y + dy >= 0 + and y + dy < cols + and (dx != 0 or dy != 0) + ): + result.append((x * cols + y, (x + dx) * cols + (y + dy))) return result @@ -54,7 +59,7 @@ def print_solution(positions, rows, cols): position = positions[k] board[position // cols][position % cols] = k + 1 # Print the board. - print('Solution') + print("Solution") print_matrix(board) @@ -63,12 +68,12 @@ def print_matrix(game): rows = len(game) cols = len(game[0]) for i in range(rows): - line = '' + line = "" for j in range(cols): if game[i][j] == 0: - line += ' .' + line += " ." else: - line += '% 3s' % game[i][j] + line += "% 3s" % game[i][j] print(line) @@ -84,34 +89,60 @@ def build_puzzle(problem): puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]] elif problem == 2: - puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0], - [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36], - [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7, 0], - [0, 0, 0, 14, 0, 0, 0]] + puzzle = [ + [0, 44, 41, 0, 0, 0, 0], + [0, 43, 0, 28, 29, 0, 0], + [0, 1, 0, 0, 0, 33, 0], + [0, 2, 25, 4, 34, 0, 36], + [49, 16, 0, 23, 0, 0, 0], + [0, 19, 0, 0, 12, 7, 0], + [0, 0, 0, 14, 0, 0, 0], + ] elif problem == 3: # Problems from the book: # Gyora Bededek: "Hidato: 2000 Pure Logic Puzzles" # Problem 1 (Practice) - puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0], - [23, 0, 1, 14, 11], [0, 25, 0, 0, 12]] + puzzle = [ + [0, 0, 20, 0, 0], + [0, 0, 0, 16, 18], + [22, 0, 15, 0, 0], + [23, 0, 1, 14, 11], + [0, 25, 0, 0, 12], + ] elif problem == 4: # problem 2 (Practice) - puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5], - [0, 0, 7, 0, 0], [9, 8, 25, 1, 0]] + puzzle = [ + [0, 0, 0, 0, 14], + [0, 18, 12, 0, 0], + [0, 0, 17, 4, 5], + [0, 0, 7, 0, 0], + [9, 8, 25, 1, 0], + ] elif problem == 5: # problem 3 (Beginner) - puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19], - [31, 23, 0, 0, 14, 0], [0, 33, 8, 0, 15, 1], - [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]] + puzzle = [ + [0, 26, 0, 0, 0, 18], + [0, 0, 27, 0, 0, 19], + [31, 23, 0, 0, 14, 0], + [0, 33, 8, 0, 15, 1], + [0, 0, 0, 5, 0, 0], + [35, 36, 0, 10, 0, 0], + ] elif problem == 6: # Problem 15 (Intermediate) - puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0], - [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0, 50], - [0, 0, 0, 0, 22, 0, 48, 40], [9, 0, 0, 32, 23, 0, 0, 41], - [27, 0, 0, 0, 36, 0, 46, 0], [28, 30, 0, 35, 0, 0, 0, 0]] + puzzle = [ + [64, 0, 0, 0, 0, 0, 0, 0], + [1, 63, 0, 59, 15, 57, 53, 0], + [0, 4, 0, 14, 0, 0, 0, 0], + [3, 0, 11, 0, 20, 19, 0, 50], + [0, 0, 0, 0, 22, 0, 48, 40], + [9, 0, 0, 32, 23, 0, 0, 41], + [27, 0, 0, 0, 36, 0, 46, 0], + [28, 30, 0, 35, 0, 0, 0, 0], + ] return puzzle @@ -123,18 +154,16 @@ def solve_hidato(puzzle, index): r = len(puzzle) c = len(puzzle[0]) if not visualization.RunFromIPython(): - print('') - print('----- Solving problem %i -----' % index) - print('') - print(('Initial game (%i x %i)' % (r, c))) + print("") + print("----- Solving problem %i -----" % index) + print("") + print(("Initial game (%i x %i)" % (r, c))) print_matrix(puzzle) # # declare variables # - positions = [ - model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c) - ] + positions = [model.NewIntVar(0, r * c - 1, "p[%i]" % i) for i in range(r * c)] # # constraints @@ -153,8 +182,7 @@ def solve_hidato(puzzle, index): # 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.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples) # # solution and search @@ -170,12 +198,10 @@ def solve_hidato(puzzle, index): 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)) + 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.WallTime())) output.Display() else: print_solution( @@ -183,10 +209,10 @@ def solve_hidato(puzzle, index): r, c, ) - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) def main(_): @@ -194,5 +220,5 @@ def main(_): solve_hidato(build_puzzle(pb), pb) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/integer_programming.py b/examples/python/integer_programming.py old mode 100644 new mode 100755 index c3c010f9a0..21d62a1985 --- a/examples/python/integer_programming.py +++ b/examples/python/integer_programming.py @@ -11,15 +11,16 @@ # 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. + """Integer programming examples that show how to use the APIs.""" from ortools.linear_solver import pywraplp -from ortools.init import pywrapinit def Announce(solver, api_type): - print('---- Integer programming example with ' + solver + ' (' + api_type + - ') -----') + print( + "---- Integer programming example with " + solver + " (" + api_type + ") -----" + ) def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type): @@ -29,12 +30,12 @@ def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type): if not solver: return - Announce(optimization_problem_type, 'natural language API') + Announce(optimization_problem_type, "natural language API") infinity = solver.infinity() # x1 and x2 are integer non-negative variables. - x1 = solver.IntVar(0.0, infinity, 'x1') - x2 = solver.IntVar(0.0, infinity, 'x2') + x1 = solver.IntVar(0.0, infinity, "x1") + x2 = solver.IntVar(0.0, infinity, "x2") solver.Minimize(x1 + 2 * x2) solver.Add(3 * x1 + 2 * x2 >= 17) @@ -48,12 +49,12 @@ def RunIntegerExampleCppStyleAPI(optimization_problem_type): if not solver: return - Announce(optimization_problem_type, 'C++ style API') + Announce(optimization_problem_type, "C++ style API") infinity = solver.infinity() # x1 and x2 are integer non-negative variables. - x1 = solver.IntVar(0.0, infinity, 'x1') - x2 = solver.IntVar(0.0, infinity, 'x2') + x1 = solver.IntVar(0.0, infinity, "x1") + x2 = solver.IntVar(0.0, infinity, "x2") # Minimize x1 + 2 * x2. objective = solver.Objective() @@ -70,8 +71,8 @@ def RunIntegerExampleCppStyleAPI(optimization_problem_type): def SolveAndPrint(solver, variable_list): """Solve the problem and print the solution.""" - print('Number of variables = %d' % solver.NumVariables()) - print('Number of constraints = %d' % solver.NumConstraints()) + print("Number of variables = %d" % solver.NumVariables()) + print("Number of constraints = %d" % solver.NumConstraints()) result_status = solver.Solve() @@ -82,33 +83,33 @@ def SolveAndPrint(solver, variable_list): # GLOP_LINEAR_PROGRAMMING, verifying the solution is highly recommended!). assert solver.VerifySolution(1e-7, True) - print('Problem solved in %f milliseconds' % solver.wall_time()) + print("Problem solved in %f milliseconds" % solver.wall_time()) # The objective value of the solution. - print('Optimal objective value = %f' % solver.Objective().Value()) + print("Optimal objective value = %f" % solver.Objective().Value()) # The value of each variable in the solution. for variable in variable_list: - print('%s = %f' % (variable.name(), variable.solution_value())) + print("%s = %f" % (variable.name(), variable.solution_value())) - print('Advanced usage:') - print('Problem solved in %d branch-and-bound nodes' % solver.nodes()) + print("Advanced usage:") + print("Problem solved in %d branch-and-bound nodes" % solver.nodes()) def RunAllIntegerExampleNaturalLanguageAPI(): - RunIntegerExampleNaturalLanguageAPI('GLPK') - RunIntegerExampleNaturalLanguageAPI('CBC') - RunIntegerExampleNaturalLanguageAPI('SCIP') - RunIntegerExampleNaturalLanguageAPI('SAT') - RunIntegerExampleNaturalLanguageAPI('Gurobi') + RunIntegerExampleNaturalLanguageAPI("GLPK") + # Disabling due to ASAN errors with CBC. + # RunIntegerExampleNaturalLanguageAPI('CBC') + RunIntegerExampleNaturalLanguageAPI("SCIP") + RunIntegerExampleNaturalLanguageAPI("SAT") def RunAllIntegerExampleCppStyleAPI(): - RunIntegerExampleCppStyleAPI('GLPK') - RunIntegerExampleCppStyleAPI('CBC') - RunIntegerExampleCppStyleAPI('SCIP') - RunIntegerExampleCppStyleAPI('SAT') - RunIntegerExampleCppStyleAPI('Gurobi') + RunIntegerExampleCppStyleAPI("GLPK") + # Disabling due to ASAN errors with CBC. + # RunIntegerExampleCppStyleAPI('CBC') + RunIntegerExampleCppStyleAPI("SCIP") + RunIntegerExampleCppStyleAPI("SAT") def main(): @@ -116,10 +117,5 @@ def main(): RunAllIntegerExampleCppStyleAPI() -if __name__ == '__main__': - pywrapinit.CppBridge.InitLogging('integer_programming.py') - cpp_flags = pywrapinit.CppFlags() - cpp_flags.stderrthreshold = 0 - cpp_flags.log_prefix = False - pywrapinit.CppBridge.SetFlags(cpp_flags) +if __name__ == "__main__": main() diff --git a/examples/python/jobshop_ft06_distance_sat.py b/examples/python/jobshop_ft06_distance_sat.py old mode 100644 new mode 100755 index 67296f3ed2..5fd7a73d74 --- a/examples/python/jobshop_ft06_distance_sat.py +++ b/examples/python/jobshop_ft06_distance_sat.py @@ -11,6 +11,7 @@ # 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. + """This model implements a variation of the ft06 jobshop. A jobshop is a standard scheduling problem when you must sequence a @@ -45,29 +46,42 @@ def jobshop_ft06_distance(): all_machines = range(0, machines_count) all_jobs = range(0, jobs_count) - durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7], - [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]] + durations = [ + [1, 3, 6, 7, 3, 6], + [8, 5, 10, 10, 10, 4], + [5, 4, 8, 9, 1, 7], + [5, 5, 5, 3, 8, 9], + [9, 3, 5, 4, 3, 1], + [3, 3, 9, 10, 4, 1], + ] - machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4], - [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]] + machines = [ + [2, 0, 1, 3, 5, 4], + [1, 2, 4, 5, 0, 3], + [2, 3, 5, 0, 1, 4], + [1, 0, 2, 3, 4, 5], + [2, 1, 4, 5, 0, 3], + [1, 3, 5, 0, 4, 2], + ] # Computes horizon statically. horizon = 150 - task_type = collections.namedtuple('task_type', 'start end interval') + task_type = collections.namedtuple("task_type", "start end interval") # Creates jobs. 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.NewIntVar(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(start_var, duration, end_var, - 'interval_%i_%i' % (i, j)) - all_tasks[(i, j)] = task_type(start=start_var, - end=end_var, - interval=interval_var) + end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.NewIntervalVar( + start_var, duration, end_var, "interval_%i_%i" % (i, j) + ) + all_tasks[(i, j)] = task_type( + start=start_var, end=end_var, interval=interval_var + ) # Create disjuctive constraints. for i in all_machines: @@ -87,23 +101,24 @@ def jobshop_ft06_distance(): 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.NewBoolVar("%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.NewBoolVar("%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.NewBoolVar("%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).OnlyEnforceIf( + lit + ) model.AddCircuit(arcs) @@ -113,9 +128,10 @@ def jobshop_ft06_distance(): model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, 'makespan') + obj_var = model.NewIntVar(0, horizon, "makespan") model.AddMaxEquality( - obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]) + obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] + ) model.Minimize(obj_var) # Solve model. @@ -124,7 +140,7 @@ def jobshop_ft06_distance(): # Output solution. if status == cp_model.OPTIMAL: - print('Optimal makespan: %i' % solver.ObjectiveValue()) + print("Optimal makespan: %i" % solver.ObjectiveValue()) jobshop_ft06_distance() diff --git a/examples/python/jobshop_ft06_sat.py b/examples/python/jobshop_ft06_sat.py old mode 100644 new mode 100755 index f8b519b119..7de58ff934 --- a/examples/python/jobshop_ft06_sat.py +++ b/examples/python/jobshop_ft06_sat.py @@ -11,6 +11,7 @@ # 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. + """This model implements a simple jobshop named ft06. A jobshop is a standard scheduling problem when you must sequence a @@ -38,29 +39,42 @@ def jobshop_ft06(): all_machines = range(0, machines_count) all_jobs = range(0, jobs_count) - durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7], - [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]] + durations = [ + [1, 3, 6, 7, 3, 6], + [8, 5, 10, 10, 10, 4], + [5, 4, 8, 9, 1, 7], + [5, 5, 5, 3, 8, 9], + [9, 3, 5, 4, 3, 1], + [3, 3, 9, 10, 4, 1], + ] - machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4], - [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]] + machines = [ + [2, 0, 1, 3, 5, 4], + [1, 2, 4, 5, 0, 3], + [2, 3, 5, 0, 1, 4], + [1, 0, 2, 3, 4, 5], + [2, 1, 4, 5, 0, 3], + [1, 3, 5, 0, 4, 2], + ] # Computes horizon dynamically. horizon = sum([sum(durations[i]) for i in all_jobs]) - task_type = collections.namedtuple('task_type', 'start end interval') + task_type = collections.namedtuple("task_type", "start end interval") # Creates jobs. 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.NewIntVar(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(start_var, duration, end_var, - 'interval_%i_%i' % (i, j)) - all_tasks[(i, j)] = task_type(start=start_var, - end=end_var, - interval=interval_var) + end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.NewIntervalVar( + start_var, duration, end_var, "interval_%i_%i" % (i, j) + ) + all_tasks[(i, j)] = task_type( + start=start_var, end=end_var, interval=interval_var + ) # Create disjuctive constraints. machine_to_jobs = {} @@ -79,9 +93,10 @@ def jobshop_ft06(): model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, 'makespan') + obj_var = model.NewIntVar(0, horizon, "makespan") model.AddMaxEquality( - obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]) + obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] + ) model.Minimize(obj_var) # Solve model. @@ -92,12 +107,13 @@ def jobshop_ft06(): # Output solution. if status == cp_model.OPTIMAL: if visualization.RunFromIPython(): - starts = [[ - solver.Value(all_tasks[(i, j)][0]) for j in all_machines - ] for i in all_jobs] - visualization.DisplayJobshop(starts, durations, machines, 'FT06') + starts = [ + [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.ObjectiveValue()) jobshop_ft06() diff --git a/examples/python/jobshop_with_maintenance_sat.py b/examples/python/jobshop_with_maintenance_sat.py index 268ccd804d..953ad9380d 100644 --- a/examples/python/jobshop_with_maintenance_sat.py +++ b/examples/python/jobshop_with_maintenance_sat.py @@ -11,6 +11,7 @@ # 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. + """Jobshop with maintenance tasks using the CP-SAT solver.""" import collections @@ -28,8 +29,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): """Called at each new solution.""" - print('Solution %i, time = %f s, objective = %i' % - (self.__solution_count, self.WallTime(), self.ObjectiveValue())) + print( + "Solution %i, time = %f s, objective = %i" + % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + ) self.__solution_count += 1 @@ -51,10 +54,11 @@ def jobshop_with_maintenance(): horizon = sum(task[1] for job in jobs_data for task in job) # Named tuple to store information about created variables. - task_type = collections.namedtuple('Task', 'start end interval') + task_type = collections.namedtuple("Task", "start end interval") # Named tuple to manipulate solution information. - assigned_task_type = collections.namedtuple('assigned_task_type', - 'start job index duration') + assigned_task_type = collections.namedtuple( + "assigned_task_type", "start job index duration" + ) # Creates job intervals and add to the corresponding machine lists. all_tasks = {} @@ -64,18 +68,19 @@ def jobshop_with_maintenance(): for task_id, task in enumerate(job): 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, duration, end_var, - 'interval' + suffix) - all_tasks[job_id, task_id] = task_type(start=start_var, - end=end_var, - interval=interval_var) + 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, duration, end_var, "interval" + suffix + ) + all_tasks[job_id, task_id] = task_type( + start=start_var, end=end_var, interval=interval_var + ) 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.NewIntervalVar(4, 4, 8, "weekend_0")) # Create and add disjunctive constraints. for machine in all_machines: @@ -84,15 +89,16 @@ def jobshop_with_maintenance(): # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): - model.Add(all_tasks[job_id, task_id + - 1].start >= all_tasks[job_id, task_id].end) + 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, [ - all_tasks[job_id, len(job) - 1].end - for job_id, job in enumerate(jobs_data) - ]) + obj_var = model.NewIntVar(0, horizon, "makespan") + model.AddMaxEquality( + obj_var, + [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)], + ) model.Minimize(obj_var) # Solve model. @@ -108,50 +114,52 @@ def jobshop_with_maintenance(): for task_id, task in enumerate(job): machine = task[0] assigned_jobs[machine].append( - assigned_task_type(start=solver.Value( - all_tasks[job_id, task_id].start), - job=job_id, - index=task_id, - duration=task[1])) + assigned_task_type( + start=solver.Value(all_tasks[job_id, task_id].start), + job=job_id, + index=task_id, + duration=task[1], + ) + ) # Create per machine output lines. - output = '' + output = "" for machine in all_machines: # Sort by starting time. assigned_jobs[machine].sort() - sol_line_tasks = 'Machine ' + str(machine) + ': ' - sol_line = ' ' + sol_line_tasks = "Machine " + str(machine) + ": " + sol_line = " " for assigned_task in assigned_jobs[machine]: - name = 'job_%i_%i' % (assigned_task.job, assigned_task.index) + name = "job_%i_%i" % (assigned_task.job, assigned_task.index) # Add spaces to output to align columns. - sol_line_tasks += '%-10s' % name + sol_line_tasks += "%-10s" % name start = assigned_task.start duration = assigned_task.duration - sol_tmp = '[%i,%i]' % (start, start + duration) + sol_tmp = "[%i,%i]" % (start, start + duration) # Add spaces to output to align columns. - sol_line += '%-10s' % sol_tmp + sol_line += "%-10s" % sol_tmp - sol_line += '\n' - sol_line_tasks += '\n' + sol_line += "\n" + sol_line_tasks += "\n" output += sol_line_tasks output += sol_line # Finally print the solution found. - print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) + print("Optimal Schedule Length: %i" % solver.ObjectiveValue()) print(output) - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") jobshop_with_maintenance() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/knapsack_2d_sat.py b/examples/python/knapsack_2d_sat.py old mode 100755 new mode 100644 index ef7cdfac7a..3972a77267 --- a/examples/python/knapsack_2d_sat.py +++ b/examples/python/knapsack_2d_sat.py @@ -11,6 +11,7 @@ # 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. + """Solver a 2D rectangle knapsack problem. This code is adapted from @@ -29,14 +30,16 @@ 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_search_workers:16,log_search_progress:true,max_time_in_seconds:45', - 'Sat solver parameters.', + "output_proto", "", "Output file to write the cp_model proto to." +) +_PARAMS = flags.DEFINE_string( + "params", + "num_search_workers:16,log_search_progress:true,max_time_in_seconds:45", + "Sat solver parameters.", +) +_MODEL = flags.DEFINE_string( + "model", "rotation", "'duplicate' or 'rotation' or 'optional'" ) -_MODEL = flags.DEFINE_string('model', 'rotation', - '\'duplicate\' or \'rotation\' or \'optional\'') def build_data(): @@ -55,25 +58,25 @@ def build_data(): k10 9 11 5 369.560 cyan """ - data = pd.read_table(io.StringIO(data), sep=r'\s+') - print('Input data') + data = pd.read_table(io.StringIO(data), sep=r"\s+") + print("Input data") print(data) max_height = 20 max_width = 30 - print(f'Container max_width:{max_width} max_height:{max_height}') - print(f'#Items: {len(data.index)}') + print(f"Container max_width:{max_width} max_height:{max_height}") + print(f"#Items: {len(data.index)}") 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.""" # Derived data (expanded to individual items). - data_widths = data['width'].to_numpy() - data_heights = data['height'].to_numpy() - data_availability = data['available'].to_numpy() - data_values = data['value'].to_numpy() + data_widths = data["width"].to_numpy() + data_heights = data["height"].to_numpy() + data_availability = data["available"].to_numpy() + data_values = data["value"].to_numpy() # Non duplicated items data. base_item_widths = np.repeat(data_widths, data_availability) @@ -102,21 +105,25 @@ 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.NewBoolVar(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.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}")) ## Interval variables. x_intervals.append( - model.NewIntervalVar(x_starts[i], item_widths[i] * is_used[i], - x_ends[i], f'x_interval{i}')) + model.NewIntervalVar( + 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.NewIntervalVar( + 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()) @@ -136,8 +143,8 @@ def solve_with_duplicate_items(data, max_height, max_width): # Output proto to file. if _OUTPUT_PROTO.value: - print(f'Writing proto to {_OUTPUT_PROTO.value}') - with open(_OUTPUT_PROTO.value, 'w') as text_file: + print(f"Writing proto to {_OUTPUT_PROTO.value}") + with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) # Solve model. @@ -150,25 +157,27 @@ def solve_with_duplicate_items(data, max_height, max_width): # 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])} - 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': [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], - 'item_value': [item_values[i] for i in used] - }) + 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": [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], + "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.""" # Derived data (expanded to individual items). - data_widths = data['width'].to_numpy() - data_heights = data['height'].to_numpy() - data_availability = data['available'].to_numpy() - data_values = data['value'].to_numpy() + data_widths = data["width"].to_numpy() + data_heights = data["height"].to_numpy() + data_availability = data["available"].to_numpy() + data_values = data["value"].to_numpy() # Non duplicated items data. base_item_widths = np.repeat(data_widths, data_availability) @@ -195,22 +204,27 @@ 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.NewBoolVar(f"is_used{i}")) ## Item coordinates. x_starts.append( - model.NewIntVar(0, max_width - int(item_widths[i]), f'x_start{i}')) + model.NewIntVar(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.NewIntVar(0, max_height - int(item_heights[i]), f"y_start{i}") + ) ## Interval variables. x_intervals.append( - model.NewOptionalFixedSizeIntervalVar(x_starts[i], item_widths[i], - is_used[i], f'x_interval{i}')) + model.NewOptionalFixedSizeIntervalVar( + x_starts[i], item_widths[i], is_used[i], f"x_interval{i}" + ) + ) y_intervals.append( - model.NewOptionalFixedSizeIntervalVar(y_starts[i], item_heights[i], - is_used[i], f'y_interval{i}')) + model.NewOptionalFixedSizeIntervalVar( + 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()) @@ -229,8 +243,8 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): # Output proto to file. if _OUTPUT_PROTO.value: - print(f'Writing proto to {_OUTPUT_PROTO.value}') - with open(_OUTPUT_PROTO.value, 'w') as text_file: + print(f"Writing proto to {_OUTPUT_PROTO.value}") + with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) # Solve model. @@ -243,27 +257,27 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): # 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])} - 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': [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 - ], - 'item_value': [item_values[i] for i in used] - }) + 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": [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], + "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.""" # Derived data (expanded to individual items). - data_widths = data['width'].to_numpy() - data_heights = data['height'].to_numpy() - data_availability = data['available'].to_numpy() - data_values = data['value'].to_numpy() + data_widths = data["width"].to_numpy() + data_heights = data["height"].to_numpy() + data_availability = data["available"].to_numpy() + data_values = data["value"].to_numpy() item_widths = np.repeat(data_widths, data_availability) item_heights = np.repeat(data_heights, data_availability) @@ -287,26 +301,26 @@ 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.NewIntVar(0, max_width, f"x_start{i}")) x_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), - f'x_size{i}')) - x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}')) + model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"x_size{i}") + ) + x_ends.append(model.NewIntVar(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.NewIntVar(0, max_height, f"y_start{i}")) y_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), - f'y_size{i}')) - y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}')) + model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"y_size{i}") + ) + y_ends.append(model.NewIntVar(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.NewIntervalVar(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.NewIntervalVar(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. is_used = [] @@ -315,9 +329,9 @@ 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.NewBoolVar(f"not_selected_{i}") + no_rotation = model.NewBoolVar(f"no_rotation_{i}") + rotated = model.NewBoolVar(f"rotated_{i}") ### Exactly one state must be chosen. model.AddExactlyOne(not_selected, no_rotation, rotated) @@ -346,8 +360,8 @@ def solve_with_rotations(data, max_height, max_width): # Output proto to file. if _OUTPUT_PROTO.value: - print(f'Writing proto to {_OUTPUT_PROTO.value}') - with open(_OUTPUT_PROTO.value, 'w') as text_file: + print(f"Writing proto to {_OUTPUT_PROTO.value}") + with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) # Solve model. @@ -360,28 +374,30 @@ def solve_with_rotations(data, max_height, max_width): # 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])} - 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], - 'item_value': [item_values[i] for i in used] - }) + 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], + "item_value": [item_values[i] for i in used], + } + ) print(data) def main(_): """Solve the problem with all models.""" data, max_height, max_width = build_data() - if _MODEL.value == 'duplicate': + if _MODEL.value == "duplicate": solve_with_duplicate_items(data, max_height, max_width) - elif _MODEL.value == 'optional': + elif _MODEL.value == "optional": solve_with_duplicate_optional_items(data, max_height, max_width) else: solve_with_rotations(data, max_height, max_width) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/line_balancing_sat.py b/examples/python/line_balancing_sat.py old mode 100755 new mode 100644 index bce6d93f86..b2999ebfee --- a/examples/python/line_balancing_sat.py +++ b/examples/python/line_balancing_sat.py @@ -11,6 +11,7 @@ # 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. + """Reader and solver of the single assembly line balancing problem. from https://assembly-line-balancing.de/salbp/: @@ -36,12 +37,14 @@ from google.protobuf import text_format from ortools.sat.python import cp_model -_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.') -_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.') +_INPUT = flags.DEFINE_string("input", "", "Input file to parse and solve.") +_PARAMS = flags.DEFINE_string("params", "", "Sat solver parameters.") _OUTPUT_PROTO = flags.DEFINE_string( - 'output_proto', '', 'Output file to write the cp_model proto to.') -_MODEL = flags.DEFINE_string('model', 'boolean', - 'Model used: boolean, scheduling, greedy') + "output_proto", "", "Output file to write the cp_model proto to." +) +_MODEL = flags.DEFINE_string( + "model", "boolean", "Model used: boolean, scheduling, greedy" +) class SectionInfo(object): @@ -54,13 +57,13 @@ class SectionInfo(object): def __str__(self): if self.index_map: - return f'SectionInfo(index_map={self.index_map})' + return f"SectionInfo(index_map={self.index_map})" elif self.set_of_pairs: - return f'SectionInfo(set_of_pairs={self.set_of_pairs})' + return f"SectionInfo(set_of_pairs={self.set_of_pairs})" elif self.value is not None: - return f'SectionInfo(value={self.value})' + return f"SectionInfo(value={self.value})" else: - return 'SectionInfo()' + return "SectionInfo()" def read_model(filename): @@ -69,64 +72,64 @@ def read_model(filename): current_info = SectionInfo() model = {} - with open(filename, 'r') as input_file: - print(f'Reading model from \'{filename}\'') - section_name = '' + with open(filename, "r") as input_file: + print(f"Reading model from '{filename}'") + section_name = "" for line in input_file: stripped_line = line.strip() if not stripped_line: continue - match_section_def = re.match(r'<([\w\s]+)>', stripped_line) + match_section_def = re.match(r"<([\w\s]+)>", stripped_line) if match_section_def: section_name = match_section_def.group(1) - if section_name == 'end': + if section_name == "end": continue current_info = SectionInfo() model[section_name] = current_info continue - match_single_number = re.match(r'^([0-9]+)$', stripped_line) + match_single_number = re.match(r"^([0-9]+)$", stripped_line) if match_single_number: current_info.value = int(match_single_number.group(1)) continue - match_key_value = re.match(r'^([0-9]+)\s+([0-9]+)$', stripped_line) + match_key_value = re.match(r"^([0-9]+)\s+([0-9]+)$", stripped_line) if match_key_value: key = int(match_key_value.group(1)) value = int(match_key_value.group(2)) current_info.index_map[key] = value continue - match_pair = re.match(r'^([0-9]+),([0-9]+)$', stripped_line) + match_pair = re.match(r"^([0-9]+),([0-9]+)$", stripped_line) if match_pair: left = int(match_pair.group(1)) right = int(match_pair.group(2)) current_info.set_of_pairs.add((left, right)) continue - print(f'Unrecognized line \'{stripped_line}\'') + print(f"Unrecognized line '{stripped_line}'") return model def print_stats(model): - print('Model Statistics') + print("Model Statistics") for key, value in model.items(): - print(f' - {key}: {value}') + print(f" - {key}: {value}") def solve_model_greedily(model): """Compute a greedy solution.""" - print('Solving using a Greedy heuristics') + print("Solving using a Greedy heuristics") - num_tasks = model['number of tasks'].value + num_tasks = model["number of tasks"].value all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data. - precedences = model['precedence relations'].set_of_pairs - durations = model['task times'].index_map - cycle_time = model['cycle time'].value + precedences = model["precedence relations"].set_of_pairs + durations = model["task times"].index_map + cycle_time = model["cycle time"].value weights = collections.defaultdict(int) successors = collections.defaultdict(list) @@ -145,7 +148,7 @@ def solve_model_greedily(model): while len(assignment) < num_tasks: if not candidates: - print('error empty') + print("error empty") break best = -1 @@ -175,7 +178,7 @@ def solve_model_greedily(model): candidates.add(succ) del weights[succ] - print(f' greedy solution uses {current_pod + 1} pods.') + print(f" greedy solution uses {current_pod + 1} pods.") return assignment @@ -183,13 +186,13 @@ def solve_model_greedily(model): def solve_boolean_model(model, hint): """Solve the given model.""" - print('Solving using the Boolean model') + print("Solving using the Boolean model") # Model data - num_tasks = model['number of tasks'].value + num_tasks = model["number of tasks"].value all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the model. - durations = model['task times'].index_map - precedences = model['precedence relations'].set_of_pairs - cycle_time = model['cycle time'].value + durations = model["task times"].index_map + precedences = model["precedence relations"].set_of_pairs + cycle_time = model["cycle time"].value num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks - 1 all_pods = range(num_pods) @@ -204,11 +207,11 @@ 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.NewBoolVar(f"assign_{t}_{p}") + possible[t, p] = model.NewBoolVar(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.NewBoolVar(f"active_{p}") for p in all_pods] # Each task is done on exactly one pod. for t in all_tasks: @@ -216,8 +219,7 @@ def solve_boolean_model(model, hint): # 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 @@ -235,8 +237,7 @@ def solve_boolean_model(model, hint): # Precedences. for before, after in precedences: for p in range(1, num_pods): - model.AddImplication(assign[before, p], possible[after, - p - 1].Not()) + model.AddImplication(assign[before, p], possible[after, p - 1].Not()) # Link active variables with the assign one. for p in all_pods: @@ -260,7 +261,7 @@ def solve_boolean_model(model, hint): model.AddHint(assign[t, hint[t]], 1) if _OUTPUT_PROTO.value: - print(f'Writing proto to {_OUTPUT_PROTO.value}') + print(f"Writing proto to {_OUTPUT_PROTO.value}") model.ExportToFile(_OUTPUT_PROTO.value) # Solve model. @@ -274,13 +275,13 @@ def solve_boolean_model(model, hint): def solve_scheduling_model(model, hint): """Solve the given model using a cumutive model.""" - print('Solving using the scheduling model') + print("Solving using the scheduling model") # Model data - num_tasks = model['number of tasks'].value + num_tasks = model["number of tasks"].value all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data. - durations = model['task times'].index_map - precedences = model['precedence relations'].set_of_pairs - cycle_time = model['cycle time'].value + durations = model["task times"].index_map + precedences = model["precedence relations"].set_of_pairs + cycle_time = model["cycle time"].value num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks @@ -289,21 +290,20 @@ 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.NewIntVar(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.NewFixedSizeIntervalVar(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') + 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") intervals.append(obj_interval) demands.append(cycle_time) @@ -322,7 +322,7 @@ def solve_scheduling_model(model, hint): model.AddHint(pods[t], hint[t]) if _OUTPUT_PROTO.value: - print(f'Writing proto to{_OUTPUT_PROTO.value}') + print(f"Writing proto to{_OUTPUT_PROTO.value}") model.ExportToFile(_OUTPUT_PROTO.value) # Solve model. @@ -335,17 +335,17 @@ def solve_scheduling_model(model, hint): def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") model = read_model(_INPUT.value) print_stats(model) greedy_solution = solve_model_greedily(model) - if _MODEL.value == 'boolean': + if _MODEL.value == "boolean": solve_boolean_model(model, greedy_solution) - elif _MODEL.value == 'scheduling': + elif _MODEL.value == "scheduling": solve_scheduling_model(model, greedy_solution) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/linear_assignment_api.py b/examples/python/linear_assignment_api.py index 3713c3d4d3..d4dc5cc234 100644 --- a/examples/python/linear_assignment_api.py +++ b/examples/python/linear_assignment_api.py @@ -11,6 +11,7 @@ # 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. + """Test linear sum assignment on a 4x4 matrix. Example taken from: @@ -27,8 +28,7 @@ def run_assignment_on_4x4_matrix(): """Test linear sum assignment on a 4x4 matrix.""" num_sources = 4 num_targets = 4 - cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], - [45, 110, 95, 115]] + cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]] expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0] assignment = linear_sum_assignment.SimpleLinearSumAssignment() @@ -38,23 +38,24 @@ def run_assignment_on_4x4_matrix(): solve_status = assignment.solve() if solve_status == assignment.OPTIMAL: - print('Successful solve.') - print('Total cost', assignment.optimal_cost(), '/', expected_cost) + print("Successful solve.") + print("Total cost", assignment.optimal_cost(), "/", expected_cost) for i in range(0, assignment.num_nodes()): - print('Left node %d assigned to right node %d with cost %d.' % - (i, assignment.right_mate(i), assignment.assignment_cost(i))) + print( + "Left node %d assigned to right node %d with cost %d." + % (i, assignment.right_mate(i), assignment.assignment_cost(i)) + ) elif solve_status == assignment.INFEASIBLE: - print('No perfect matching exists.') + print("No perfect matching exists.") elif solve_status == assignment.POSSIBLE_OVERFLOW: - print( - 'Some input costs are too large and may cause an integer overflow.') + print("Some input costs are too large and may cause an integer overflow.") def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") run_assignment_on_4x4_matrix() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/linear_programming.py b/examples/python/linear_programming.py index 5b9abd4583..291ebe4f59 100644 --- a/examples/python/linear_programming.py +++ b/examples/python/linear_programming.py @@ -11,14 +11,16 @@ # 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. + """Linear programming examples that show how to use the APIs.""" from ortools.linear_solver import pywraplp def Announce(solver, api_type): - print('---- Linear programming example with ' + solver + ' (' + api_type + - ') -----') + print( + "---- Linear programming example with " + solver + " (" + api_type + ") -----" + ) def RunLinearExampleNaturalLanguageAPI(optimization_problem_type): @@ -28,23 +30,25 @@ def RunLinearExampleNaturalLanguageAPI(optimization_problem_type): if not solver: return - Announce(optimization_problem_type, 'natural language API') + Announce(optimization_problem_type, "natural language API") infinity = solver.infinity() # x1, x2 and x3 are continuous non-negative variables. - x1 = solver.NumVar(0.0, infinity, 'x1') - x2 = solver.NumVar(0.0, infinity, 'x2') - x3 = solver.NumVar(0.0, infinity, 'x3') + x1 = solver.NumVar(0.0, infinity, "x1") + x2 = solver.NumVar(0.0, infinity, "x2") + x3 = solver.NumVar(0.0, infinity, "x3") solver.Maximize(10 * x1 + 6 * x2 + 4 * x3) - c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, 'ConstraintName0') + c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, "ConstraintName0") c1 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300) sum_of_vars = sum([x1, x2, x3]) - c2 = solver.Add(sum_of_vars <= 100.0, 'OtherConstraintName') + c2 = solver.Add(sum_of_vars <= 100.0, "OtherConstraintName") - SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != 'PDLP') + SolveAndPrint( + solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != "PDLP" + ) # Print a linear expression's solution value. - print('Sum of vars: %s = %s' % (sum_of_vars, sum_of_vars.solution_value())) + print("Sum of vars: %s = %s" % (sum_of_vars, sum_of_vars.solution_value())) def RunLinearExampleCppStyleAPI(optimization_problem_type): @@ -53,13 +57,13 @@ def RunLinearExampleCppStyleAPI(optimization_problem_type): if not solver: return - Announce(optimization_problem_type, 'C++ style API') + Announce(optimization_problem_type, "C++ style API") infinity = solver.infinity() # x1, x2 and x3 are continuous non-negative variables. - x1 = solver.NumVar(0.0, infinity, 'x1') - x2 = solver.NumVar(0.0, infinity, 'x2') - x3 = solver.NumVar(0.0, infinity, 'x3') + x1 = solver.NumVar(0.0, infinity, "x1") + x2 = solver.NumVar(0.0, infinity, "x2") + x3 = solver.NumVar(0.0, infinity, "x3") # Maximize 10 * x1 + 6 * x2 + 4 * x3. objective = solver.Objective() @@ -69,31 +73,32 @@ def RunLinearExampleCppStyleAPI(optimization_problem_type): objective.SetMaximization() # x1 + x2 + x3 <= 100. - c0 = solver.Constraint(-infinity, 100.0, 'c0') + c0 = solver.Constraint(-infinity, 100.0, "c0") c0.SetCoefficient(x1, 1) c0.SetCoefficient(x2, 1) c0.SetCoefficient(x3, 1) # 10 * x1 + 4 * x2 + 5 * x3 <= 600. - c1 = solver.Constraint(-infinity, 600.0, 'c1') + c1 = solver.Constraint(-infinity, 600.0, "c1") c1.SetCoefficient(x1, 10) c1.SetCoefficient(x2, 4) c1.SetCoefficient(x3, 5) # 2 * x1 + 2 * x2 + 6 * x3 <= 300. - c2 = solver.Constraint(-infinity, 300.0, 'c2') + c2 = solver.Constraint(-infinity, 300.0, "c2") c2.SetCoefficient(x1, 2) c2.SetCoefficient(x2, 2) c2.SetCoefficient(x3, 6) - SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2], - optimization_problem_type != 'PDLP') + SolveAndPrint( + solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != "PDLP" + ) def SolveAndPrint(solver, variable_list, constraint_list, is_precise): """Solve the problem and print the solution.""" - print('Number of variables = %d' % solver.NumVariables()) - print('Number of constraints = %d' % solver.NumConstraints()) + print("Number of variables = %d" % solver.NumVariables()) + print("Number of constraints = %d" % solver.NumConstraints()) result_status = solver.Solve() @@ -105,38 +110,40 @@ def SolveAndPrint(solver, variable_list, constraint_list, is_precise): if is_precise: assert solver.VerifySolution(1e-7, True) - print('Problem solved in %f milliseconds' % solver.wall_time()) + print("Problem solved in %f milliseconds" % solver.wall_time()) # The objective value of the solution. - print('Optimal objective value = %f' % solver.Objective().Value()) + print("Optimal objective value = %f" % solver.Objective().Value()) # The value of each variable in the solution. for variable in variable_list: - print('%s = %f' % (variable.name(), variable.solution_value())) + print("%s = %f" % (variable.name(), variable.solution_value())) - print('Advanced usage:') - print('Problem solved in %d iterations' % solver.iterations()) + print("Advanced usage:") + print("Problem solved in %d iterations" % solver.iterations()) for variable in variable_list: - print('%s: reduced cost = %f' % - (variable.name(), variable.reduced_cost())) + print("%s: reduced cost = %f" % (variable.name(), variable.reduced_cost())) activities = solver.ComputeConstraintActivities() for i, constraint in enumerate(constraint_list): - print(('constraint %d: dual value = %f\n' - ' activity = %f' % - (i, constraint.dual_value(), activities[constraint.index()]))) + print( + ( + "constraint %d: dual value = %f\n activity = %f" + % (i, constraint.dual_value(), activities[constraint.index()]) + ) + ) def main(): - RunLinearExampleNaturalLanguageAPI('GLOP') - RunLinearExampleNaturalLanguageAPI('GLPK_LP') - RunLinearExampleNaturalLanguageAPI('CLP') - RunLinearExampleNaturalLanguageAPI('PDLP') + RunLinearExampleNaturalLanguageAPI("GLOP") + RunLinearExampleNaturalLanguageAPI("GLPK_LP") + RunLinearExampleNaturalLanguageAPI("CLP") + RunLinearExampleNaturalLanguageAPI("PDLP") - RunLinearExampleCppStyleAPI('GLOP') - RunLinearExampleCppStyleAPI('GLPK_LP') - RunLinearExampleCppStyleAPI('CLP') - RunLinearExampleCppStyleAPI('PDLP') + RunLinearExampleCppStyleAPI("GLOP") + RunLinearExampleCppStyleAPI("GLPK_LP") + RunLinearExampleCppStyleAPI("CLP") + RunLinearExampleCppStyleAPI("PDLP") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/python/magic_sequence_distribute.py b/examples/python/magic_sequence_distribute.py index f98248dd49..a2ba1d4f48 100755 --- a/examples/python/magic_sequence_distribute.py +++ b/examples/python/magic_sequence_distribute.py @@ -11,6 +11,7 @@ # 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. + """Magic sequence problem. This models aims at building a sequence of numbers such that the number of @@ -30,12 +31,12 @@ FLAGS = flags.FLAGS def main(argv): # Create the solver. - solver = pywrapcp.Solver('magic sequence') + solver = pywrapcp.Solver("magic sequence") # Create an array of IntVars to hold the answers. size = int(argv[1]) if len(argv) > 1 else 100 all_values = list(range(0, size)) - all_vars = [solver.IntVar(0, size, 'vars_%d' % i) for i in all_values] + all_vars = [solver.IntVar(0, size, "vars_%d" % i) for i in all_values] # The number of variables equal to j shall be the value of all_vars[j]. solver.Add(solver.Distribute(all_vars, all_values, all_vars)) @@ -45,12 +46,12 @@ def main(argv): solver.Add(solver.Sum(all_vars) == size) solver.NewSearch( - solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE)) + solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) + ) solver.NextSolution() print(all_vars) solver.EndSearch() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/maze_escape_sat.py b/examples/python/maze_escape_sat.py index 95f2119fdd..cbd123bd70 100755 --- a/examples/python/maze_escape_sat.py +++ b/examples/python/maze_escape_sat.py @@ -11,6 +11,7 @@ # 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. + """Excape the maze while collecting treasures in order. The path must begin at the 'start' position, finish at the 'end' position, @@ -27,23 +28,29 @@ 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_search_workers:8,log_search_progress:true', - 'Sat solver parameters.') + "output_proto", "", "Output file to write the cp_model proto to." +) +_PARAMS = flags.DEFINE_string( + "params", "num_search_workers:8,log_search_progress:true", "Sat solver parameters." +) -def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, - arcs): +def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, arcs): """Checks if the neighbor is valid, and adds it to the model.""" - if (x + dx < 0 or x + dx >= size or y + dy < 0 or y + dy >= size or - z + dz < 0 or z + dz >= size): + if ( + x + dx < 0 + or x + dx >= size + or y + dy < 0 + or y + dy >= size + or z + dz < 0 + or z + dz >= size + ): return before_index = index_map[(x, y, z)] 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('') + move_literal = model.NewBoolVar("") model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal) arcs.append((before_index, after_index, move_literal)) @@ -72,8 +79,7 @@ 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.NewIntVar(0, counter - 1, f"rank_{coord}") # Path constraints. model.Add(position_to_rank[start] == 0) @@ -87,18 +93,24 @@ def escape_the_maze(params, output_proto): for x in range(size): for y in range(size): for z in range(size): - add_neighbor(size, x, y, z, -1, 0, 0, model, index_map, - position_to_rank, arcs) - add_neighbor(size, x, y, z, 1, 0, 0, model, index_map, - position_to_rank, arcs) - add_neighbor(size, x, y, z, 0, -1, 0, model, index_map, - position_to_rank, arcs) - add_neighbor(size, x, y, z, 0, 1, 0, model, index_map, - position_to_rank, arcs) - add_neighbor(size, x, y, z, 0, 0, -1, model, index_map, - position_to_rank, arcs) - add_neighbor(size, x, y, z, 0, 0, 1, model, index_map, - position_to_rank, arcs) + add_neighbor( + size, x, y, z, -1, 0, 0, model, index_map, position_to_rank, arcs + ) + add_neighbor( + size, x, y, z, 1, 0, 0, model, index_map, position_to_rank, arcs + ) + add_neighbor( + size, x, y, z, 0, -1, 0, model, index_map, position_to_rank, arcs + ) + add_neighbor( + size, x, y, z, 0, 1, 0, model, index_map, position_to_rank, arcs + ) + add_neighbor( + size, x, y, z, 0, 0, -1, model, index_map, position_to_rank, arcs + ) + add_neighbor( + size, x, y, z, 0, 0, 1, model, index_map, position_to_rank, arcs + ) # Closes the loop as the constraint expects a circuit, not a path. arcs.append((index_map[end], index_map[start], True)) @@ -119,30 +131,30 @@ def escape_the_maze(params, output_proto): # Prints solution. if result == cp_model.OPTIMAL: - path = [''] * counter + path = [""] * counter for x in range(size): for y in range(size): for z in range(size): position = (x, y, z) rank = solver.Value(position_to_rank[position]) - msg = f'({x}, {y}, {z})' + msg = f"({x}, {y}, {z})" if position == start: - msg += ' [start]' + msg += " [start]" elif position == end: - msg += ' [end]' + msg += " [end]" else: for b in range(len(boxes)): if position == boxes[b]: - msg += f' [boxes {b}]' + msg += f" [boxes {b}]" path[rank] = msg print(path) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value) -if __name__ == '__main__': +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 index 55310790a3..4cd2ab0971 100755 --- a/examples/python/no_wait_baking_scheduling_sat.py +++ b/examples/python/no_wait_baking_scheduling_sat.py @@ -11,6 +11,7 @@ # 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. + """Scheduling cooking tasks in a bakery using no-wait jobshop scheduling. We are scheduling a full day of baking: @@ -27,34 +28,35 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -_PARAMS = flags.DEFINE_string('params', - 'num_search_workers:16, max_time_in_seconds:30', - 'Sat solver parameters.') +_PARAMS = flags.DEFINE_string( + "params", "num_search_workers:16, max_time_in_seconds:30", "Sat solver parameters." +) _PROTO_FILE = flags.DEFINE_string( - 'proto_file', '', 'If not empty, output the proto to this file.') + "proto_file", "", "If not empty, output the proto to this file." +) # Recipes -CROISSANT = 'croissant' -APPLE_PIE = 'apple pie' -BRIOCHE = 'brioche' -CHOCOLATE_CAKE = 'chocolate cake' +CROISSANT = "croissant" +APPLE_PIE = "apple pie" +BRIOCHE = "brioche" +CHOCOLATE_CAKE = "chocolate cake" # Skills -BAKING = 'baking' -PROOFING = 'proofing' -COOKING = 'cooking' -COOLING = 'cooling' -DECORATING = 'decorating' -DISPLAY = 'display' +BAKING = "baking" +PROOFING = "proofing" +COOKING = "cooking" +COOLING = "cooling" +DECORATING = "decorating" +DISPLAY = "display" class Task(object): """A unit baking task. - - Simple baking tasks have a fixed duration. They are performed by workers. - - Waiting/cooling/proofing tasks have a min and a max duration. - They are performed by machine or they use space resources. - """ + - Simple baking tasks have a fixed duration. They are performed by workers. + - Waiting/cooling/proofing tasks have a min and a max duration. + They are performed by machine or they use space resources. + """ def __init__(self, name, min_duration, max_duration): self.name = name @@ -86,13 +88,13 @@ class Recipe(object): class Resource(object): """A resource is a worker, a machine, or just some space for cakes to rest. - - Workers have a capacity of 1 and can have variable efficiency. - - Machines and spaces have a capacity greater or equal to one, but the - efficiency is fixed to 100. + - Workers have a capacity of 1 and can have variable efficiency. + - Machines and spaces have a capacity greater or equal to one, but the + efficiency is fixed to 100. - For a worker with efficiency k and a task of duration t, the resulting - work will have a duration `ceil(t * k)`. - """ + For a worker with efficiency k and a task of duration t, the resulting + work will have a duration `ceil(t * k)`. + """ def __init__(self, name, capacity): self.name = name @@ -110,12 +112,12 @@ class Order(object): def __init__(self, unique_id, recipe_name, due_date, quantity): """Builds an order. - Args: - unique_id: A unique identifier for the order. Used to display the result. - recipe_name: The name of the recipe. It must match one of the recipes. - due_date: The due date in minutes since midnight. - quantity: How many cakes to prepare. - """ + Args: + unique_id: A unique identifier for the order. Used to display the result. + recipe_name: The name of the recipe. It must match one of the recipes. + due_date: The due date in minutes since midnight. + quantity: How many cakes to prepare. + """ self.unique_id = unique_id self.recipe_name = recipe_name self.due_date = due_date @@ -151,32 +153,41 @@ def set_up_data(): chocolate_cake_recipe.add_task(DECORATING, 15, 15) chocolate_cake_recipe.add_task(DISPLAY, 5, 5 * 60) recipes = [ - croissant_recipe, apple_pie_recipe, brioche_recipe, - chocolate_cake_recipe + croissant_recipe, + apple_pie_recipe, + brioche_recipe, + chocolate_cake_recipe, ] # Resources. - baker1 = Resource('baker1', 1).add_skill(BAKING, 1.0) - baker2 = Resource('baker2', 1).add_skill(BAKING, 1.0) - decorator1 = Resource('decorator1', 1).add_skill(DECORATING, 1.0) - waiting_space = Resource('waiting_space', 4).add_skill(PROOFING, 1.0) - oven = Resource('oven', 4).add_skill(COOKING, 1.0) - display_space = Resource('display_space', 12).add_skill(DISPLAY, 1.0) + baker1 = Resource("baker1", 1).add_skill(BAKING, 1.0) + baker2 = Resource("baker2", 1).add_skill(BAKING, 1.0) + decorator1 = Resource("decorator1", 1).add_skill(DECORATING, 1.0) + waiting_space = Resource("waiting_space", 4).add_skill(PROOFING, 1.0) + oven = Resource("oven", 4).add_skill(COOKING, 1.0) + display_space = Resource("display_space", 12).add_skill(DISPLAY, 1.0) resources = [baker1, baker2, decorator1, waiting_space, oven, display_space] # Orders - croissant_7am = Order('croissant_7am', CROISSANT, 7 * 60, 3) - croissant_8am = Order('croissant_8am', CROISSANT, 8 * 60, 3) - croissant_9am = Order('croissant_9am', CROISSANT, 9 * 60, 2) - croissant_10am = Order('croissant_10am', CROISSANT, 10 * 60, 1) - croissant_11am = Order('croissant_11am', CROISSANT, 11 * 60, 1) - brioche_10am = Order('brioche_10am', BRIOCHE, 10 * 60, 8) - brioche_12pm = Order('brioche_12pm', BRIOCHE, 12 * 60, 8) - apple_pie_1pm = Order('apple_pie_1pm', APPLE_PIE, 13 * 60, 10) - chocolate_4pm = Order('chocolate_4pm', CHOCOLATE_CAKE, 16 * 60, 10) + croissant_7am = Order("croissant_7am", CROISSANT, 7 * 60, 3) + croissant_8am = Order("croissant_8am", CROISSANT, 8 * 60, 3) + croissant_9am = Order("croissant_9am", CROISSANT, 9 * 60, 2) + croissant_10am = Order("croissant_10am", CROISSANT, 10 * 60, 1) + croissant_11am = Order("croissant_11am", CROISSANT, 11 * 60, 1) + brioche_10am = Order("brioche_10am", BRIOCHE, 10 * 60, 8) + brioche_12pm = Order("brioche_12pm", BRIOCHE, 12 * 60, 8) + apple_pie_1pm = Order("apple_pie_1pm", APPLE_PIE, 13 * 60, 10) + chocolate_4pm = Order("chocolate_4pm", CHOCOLATE_CAKE, 16 * 60, 10) orders = [ - croissant_7am, croissant_8am, croissant_9am, croissant_10am, - croissant_11am, brioche_10am, brioche_12pm, apple_pie_1pm, chocolate_4pm + croissant_7am, + croissant_8am, + croissant_9am, + croissant_10am, + croissant_11am, + brioche_10am, + brioche_12pm, + apple_pie_1pm, + chocolate_4pm, ] return recipes, resources, orders @@ -210,50 +221,48 @@ def solve_with_cp_sat(recipes, resources, orders): tardiness_vars = [] for order in orders: for batch in range(order.quantity): - order_id = f'{order.unique_id}_{batch}' + order_id = f"{order.unique_id}_{batch}" sorted_orders.append(order_id) previous_end = None due_date = order.due_date recipe = recipe_by_name[order.recipe_name] for task in recipe.tasks: skill_name = task.name - suffix = f'_{order.unique_id}_batch{batch}_{skill_name}' + suffix = f"_{order.unique_id}_batch{batch}_{skill_name}" start = None if previous_end is None: - start = model.NewIntVar(start_work, horizon, - f'start{suffix}') + start = model.NewIntVar(start_work, horizon, f"start{suffix}") orders_sequence_of_events[order_id].append( - (start, f'start{suffix}')) + (start, f"start{suffix}") + ) else: start = previous_end - size = model.NewIntVar(task.min_duration, task.max_duration, - f'size{suffix}') + size = model.NewIntVar( + 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.NewIntVar(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}') - orders_sequence_of_events[order_id].append( - (end, f'end{suffix}')) + end = model.NewIntVar(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}') + presence = model.NewBoolVar(f"presence{suffix}_{resource.name}") copy = model.NewOptionalIntervalVar( - start, size, end, presence, - f'interval{suffix}_{resource.name}') + start, size, end, presence, f"interval{suffix}_{resource.name}" + ) interval_list_by_resource_name[resource.name].append(copy) presence_literals.append(presence) @@ -266,8 +275,7 @@ def solve_with_cp_sat(recipes, resources, orders): if resource.capacity == 1: model.AddNoOverlap(intervals) else: - model.AddCumulative(intervals, [1] * len(intervals), - resource.capacity) + model.AddCumulative(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 @@ -283,19 +291,19 @@ def solve_with_cp_sat(recipes, resources, orders): if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for order_id in sorted_orders: - print(f'{order_id}:') + print(f"{order_id}:") for time_expr, event_id in orders_sequence_of_events[order_id]: time = solver.Value(time_expr) - print(f' {event_id} at {time // 60}:{time % 60:02}') + print(f" {event_id} at {time // 60}:{time % 60:02}") def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") recipes, resources, orders = set_up_data() solve_with_cp_sat(recipes, resources, orders) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/nqueens_sat.py b/examples/python/nqueens_sat.py index 36a03ec344..fb93eebb9d 100644 --- a/examples/python/nqueens_sat.py +++ b/examples/python/nqueens_sat.py @@ -11,6 +11,7 @@ # 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. + """CP/SAT model for the N-queens problem.""" import time @@ -19,7 +20,7 @@ from absl import app from absl import flags from ortools.sat.python import cp_model -_SIZE = flags.DEFINE_integer('size', 8, 'Number of queens.') +_SIZE = flags.DEFINE_integer("size", 8, "Number of queens.") class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): @@ -36,8 +37,10 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): current_time = time.time() - print('Solution %i, time = %f s' % - (self.__solution_count, current_time - self.__start_time)) + print( + "Solution %i, time = %f s" + % (self.__solution_count, current_time - self.__start_time) + ) self.__solution_count += 1 all_queens = range(len(self.__queens)) @@ -45,9 +48,9 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): for j in all_queens: if self.Value(self.__queens[j]) == i: # There is a queen in column j, row i. - print('Q', end=' ') + print("Q", end=" ") else: - print('_', end=' ') + print("_", end=" ") print() print() @@ -60,9 +63,7 @@ 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.NewIntVar(0, board_size - 1, "x%i" % i) for i in range(board_size)] ### Creates the constraints. @@ -74,8 +75,8 @@ def main(_): 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.NewIntVar(0, 2 * board_size, "diag1_%i" % i) + q2 = model.NewIntVar(-board_size, board_size, "diag2_%i" % i) diag1.append(q1) diag2.append(q2) model.Add(q1 == queens[i] + i) @@ -92,12 +93,12 @@ def main(_): 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("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()) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/prize_collecting_tsp_sat.py b/examples/python/prize_collecting_tsp_sat.py index d758de266d..2ddff9b380 100755 --- a/examples/python/prize_collecting_tsp_sat.py +++ b/examples/python/prize_collecting_tsp_sat.py @@ -11,6 +11,7 @@ # 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. + """Simple prize collecting TSP problem with a max distance.""" from typing import Sequence @@ -19,46 +20,1686 @@ from ortools.sat.python import cp_model DISTANCE_MATRIX = [ - [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056], - [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994], - [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589], - [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743], - [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198], - [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078], - [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967], - [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552], - [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422], - [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935], - [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891], - [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002], - [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225], - [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671], - [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475], - [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633], - [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084], - [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315], - [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866], - [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802], - [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682], - [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471], - [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720], - [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674], - [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908], - [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204], - [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000], - [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066], - [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039], - [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721], - [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496], - [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772], - [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614], - [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805], - [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519], - [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614], - [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749], - [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313], - [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042], - [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0], + [ + 0, + 10938, + 4542, + 2835, + 29441, + 2171, + 1611, + 9208, + 9528, + 11111, + 16120, + 22606, + 22127, + 20627, + 21246, + 23387, + 16697, + 33609, + 26184, + 24772, + 22644, + 20655, + 30492, + 23296, + 32979, + 18141, + 19248, + 17129, + 17192, + 15645, + 12658, + 11210, + 12094, + 13175, + 18162, + 4968, + 12308, + 10084, + 13026, + 15056, + ], + [ + 10938, + 0, + 6422, + 9742, + 18988, + 12974, + 11216, + 19715, + 19004, + 18271, + 25070, + 31971, + 31632, + 30571, + 31578, + 33841, + 27315, + 43964, + 36944, + 35689, + 33569, + 31481, + 41360, + 33760, + 43631, + 28730, + 29976, + 27803, + 28076, + 26408, + 23504, + 22025, + 22000, + 13197, + 14936, + 15146, + 23246, + 20956, + 23963, + 25994, + ], + [ + 4542, + 6422, + 0, + 3644, + 25173, + 6552, + 5092, + 13584, + 13372, + 13766, + 19805, + 26537, + 26117, + 24804, + 25590, + 27784, + 21148, + 37981, + 30693, + 29315, + 27148, + 25071, + 34943, + 27472, + 37281, + 22389, + 23592, + 21433, + 21655, + 20011, + 17087, + 15612, + 15872, + 11653, + 15666, + 8842, + 16843, + 14618, + 17563, + 19589, + ], + [ + 2835, + 9742, + 3644, + 0, + 28681, + 3851, + 4341, + 11660, + 12294, + 13912, + 18893, + 25283, + 24777, + 23173, + 23636, + 25696, + 18950, + 35927, + 28233, + 26543, + 24127, + 21864, + 31765, + 24018, + 33904, + 19005, + 20295, + 18105, + 18551, + 16763, + 13958, + 12459, + 12296, + 10370, + 15331, + 5430, + 14044, + 12135, + 14771, + 16743, + ], + [ + 29441, + 18988, + 25173, + 28681, + 0, + 31590, + 29265, + 37173, + 35501, + 32929, + 40239, + 47006, + 46892, + 46542, + 48112, + 50506, + 44539, + 60103, + 54208, + 53557, + 51878, + 50074, + 59849, + 52645, + 62415, + 47544, + 48689, + 46560, + 46567, + 45086, + 42083, + 40648, + 40971, + 29929, + 28493, + 34015, + 41473, + 38935, + 42160, + 44198, + ], + [ + 2171, + 12974, + 6552, + 3851, + 31590, + 0, + 3046, + 7856, + 8864, + 11330, + 15411, + 21597, + 21065, + 19382, + 19791, + 21845, + 15099, + 32076, + 24425, + 22848, + 20600, + 18537, + 28396, + 21125, + 30825, + 15975, + 17101, + 14971, + 15104, + 13503, + 10544, + 9080, + 9983, + 13435, + 18755, + 2947, + 10344, + 8306, + 11069, + 13078, + ], + [ + 1611, + 11216, + 5092, + 4341, + 29265, + 3046, + 0, + 8526, + 8368, + 9573, + 14904, + 21529, + 21085, + 19719, + 20504, + 22713, + 16118, + 32898, + 25728, + 24541, + 22631, + 20839, + 30584, + 23755, + 33278, + 18557, + 19545, + 17490, + 17309, + 15936, + 12881, + 11498, + 12944, + 14711, + 19589, + 5993, + 12227, + 9793, + 12925, + 14967, + ], + [ + 9208, + 19715, + 13584, + 11660, + 37173, + 7856, + 8526, + 0, + 3248, + 7855, + 8245, + 13843, + 13272, + 11526, + 12038, + 14201, + 7599, + 24411, + 17259, + 16387, + 15050, + 13999, + 23134, + 17899, + 26460, + 12894, + 13251, + 11680, + 10455, + 9997, + 7194, + 6574, + 10678, + 20959, + 26458, + 8180, + 5255, + 2615, + 5730, + 7552, + ], + [ + 9528, + 19004, + 13372, + 12294, + 35501, + 8864, + 8368, + 3248, + 0, + 4626, + 6598, + 13168, + 12746, + 11567, + 12731, + 15083, + 9120, + 25037, + 18718, + 18433, + 17590, + 16888, + 25630, + 20976, + 29208, + 16055, + 16300, + 14838, + 13422, + 13165, + 10430, + 9813, + 13777, + 22300, + 27564, + 10126, + 8388, + 5850, + 8778, + 10422, + ], + [ + 11111, + 18271, + 13766, + 13912, + 32929, + 11330, + 9573, + 7855, + 4626, + 0, + 7318, + 14185, + 14005, + 13655, + 15438, + 17849, + 12839, + 27179, + 21947, + 22230, + 21814, + 21366, + 29754, + 25555, + 33535, + 20674, + 20872, + 19457, + 17961, + 17787, + 15048, + 14372, + 18115, + 24280, + 29101, + 13400, + 13008, + 10467, + 13375, + 14935, + ], + [ + 16120, + 25070, + 19805, + 18893, + 40239, + 15411, + 14904, + 8245, + 6598, + 7318, + 0, + 6939, + 6702, + 6498, + 8610, + 10961, + 7744, + 19889, + 15350, + 16403, + 16975, + 17517, + 24357, + 22176, + 28627, + 18093, + 17672, + 16955, + 14735, + 15510, + 13694, + 13768, + 18317, + 28831, + 34148, + 16326, + 11276, + 9918, + 11235, + 11891, + ], + [ + 22606, + 31971, + 26537, + 25283, + 47006, + 21597, + 21529, + 13843, + 13168, + 14185, + 6939, + 0, + 793, + 3401, + 5562, + 6839, + 8923, + 13433, + 11264, + 13775, + 15853, + 17629, + 21684, + 22315, + 26411, + 19539, + 18517, + 18636, + 16024, + 17632, + 16948, + 17587, + 22131, + 34799, + 40296, + 21953, + 14739, + 14568, + 14366, + 14002, + ], + [ + 22127, + 31632, + 26117, + 24777, + 46892, + 21065, + 21085, + 13272, + 12746, + 14005, + 6702, + 793, + 0, + 2608, + 4809, + 6215, + 8151, + 13376, + 10702, + 13094, + 15099, + 16845, + 21039, + 21535, + 25744, + 18746, + 17725, + 17845, + 15232, + 16848, + 16197, + 16859, + 21391, + 34211, + 39731, + 21345, + 14006, + 13907, + 13621, + 13225, + ], + [ + 20627, + 30571, + 24804, + 23173, + 46542, + 19382, + 19719, + 11526, + 11567, + 13655, + 6498, + 3401, + 2608, + 0, + 2556, + 4611, + 5630, + 13586, + 9157, + 11005, + 12681, + 14285, + 19044, + 18996, + 23644, + 16138, + 15126, + 15240, + 12625, + 14264, + 13736, + 14482, + 18958, + 32292, + 37879, + 19391, + 11621, + 11803, + 11188, + 10671, + ], + [ + 21246, + 31578, + 25590, + 23636, + 48112, + 19791, + 20504, + 12038, + 12731, + 15438, + 8610, + 5562, + 4809, + 2556, + 0, + 2411, + 4917, + 12395, + 6757, + 8451, + 10292, + 12158, + 16488, + 16799, + 21097, + 14374, + 13194, + 13590, + 10943, + 12824, + 12815, + 13779, + 18042, + 32259, + 37918, + 19416, + 10975, + 11750, + 10424, + 9475, + ], + [ + 23387, + 33841, + 27784, + 25696, + 50506, + 21845, + 22713, + 14201, + 15083, + 17849, + 10961, + 6839, + 6215, + 4611, + 2411, + 0, + 6760, + 10232, + 4567, + 7010, + 9607, + 12003, + 14846, + 16408, + 19592, + 14727, + 13336, + 14109, + 11507, + 13611, + 14104, + 15222, + 19237, + 34013, + 39703, + 21271, + 12528, + 13657, + 11907, + 10633, + ], + [ + 16697, + 27315, + 21148, + 18950, + 44539, + 15099, + 16118, + 7599, + 9120, + 12839, + 7744, + 8923, + 8151, + 5630, + 4917, + 6760, + 0, + 16982, + 9699, + 9400, + 9302, + 9823, + 16998, + 14534, + 21042, + 10911, + 10190, + 9900, + 7397, + 8758, + 8119, + 8948, + 13353, + 27354, + 33023, + 14542, + 6106, + 6901, + 5609, + 5084, + ], + [ + 33609, + 43964, + 37981, + 35927, + 60103, + 32076, + 32898, + 24411, + 25037, + 27179, + 19889, + 13433, + 13376, + 13586, + 12395, + 10232, + 16982, + 0, + 8843, + 12398, + 16193, + 19383, + 16423, + 22583, + 20997, + 22888, + 21194, + 22640, + 20334, + 22636, + 23801, + 25065, + 28675, + 44048, + 49756, + 31426, + 22528, + 23862, + 21861, + 20315, + ], + [ + 26184, + 36944, + 30693, + 28233, + 54208, + 24425, + 25728, + 17259, + 18718, + 21947, + 15350, + 11264, + 10702, + 9157, + 6757, + 4567, + 9699, + 8843, + 0, + 3842, + 7518, + 10616, + 10666, + 14237, + 15515, + 14053, + 12378, + 13798, + 11537, + 13852, + 15276, + 16632, + 19957, + 35660, + 41373, + 23361, + 14333, + 16125, + 13624, + 11866, + ], + [ + 24772, + 35689, + 29315, + 26543, + 53557, + 22848, + 24541, + 16387, + 18433, + 22230, + 16403, + 13775, + 13094, + 11005, + 8451, + 7010, + 9400, + 12398, + 3842, + 0, + 3795, + 7014, + 8053, + 10398, + 12657, + 10633, + 8889, + 10569, + 8646, + 10938, + 12906, + 14366, + 17106, + 33171, + 38858, + 21390, + 12507, + 14748, + 11781, + 9802, + ], + [ + 22644, + 33569, + 27148, + 24127, + 51878, + 20600, + 22631, + 15050, + 17590, + 21814, + 16975, + 15853, + 15099, + 12681, + 10292, + 9607, + 9302, + 16193, + 7518, + 3795, + 0, + 3250, + 8084, + 6873, + 11763, + 6949, + 5177, + 7050, + 5619, + 7730, + 10187, + 11689, + 13792, + 30012, + 35654, + 18799, + 10406, + 12981, + 9718, + 7682, + ], + [ + 20655, + 31481, + 25071, + 21864, + 50074, + 18537, + 20839, + 13999, + 16888, + 21366, + 17517, + 17629, + 16845, + 14285, + 12158, + 12003, + 9823, + 19383, + 10616, + 7014, + 3250, + 0, + 9901, + 4746, + 12531, + 3737, + 1961, + 4036, + 3588, + 5109, + 7996, + 9459, + 10846, + 27094, + 32690, + 16451, + 8887, + 11624, + 8304, + 6471, + ], + [ + 30492, + 41360, + 34943, + 31765, + 59849, + 28396, + 30584, + 23134, + 25630, + 29754, + 24357, + 21684, + 21039, + 19044, + 16488, + 14846, + 16998, + 16423, + 10666, + 8053, + 8084, + 9901, + 0, + 9363, + 4870, + 13117, + 11575, + 13793, + 13300, + 15009, + 17856, + 19337, + 20454, + 36551, + 42017, + 26352, + 18403, + 21033, + 17737, + 15720, + ], + [ + 23296, + 33760, + 27472, + 24018, + 52645, + 21125, + 23755, + 17899, + 20976, + 25555, + 22176, + 22315, + 21535, + 18996, + 16799, + 16408, + 14534, + 22583, + 14237, + 10398, + 6873, + 4746, + 9363, + 0, + 10020, + 5211, + 4685, + 6348, + 7636, + 8010, + 11074, + 12315, + 11926, + 27537, + 32880, + 18634, + 12644, + 15358, + 12200, + 10674, + ], + [ + 32979, + 43631, + 37281, + 33904, + 62415, + 30825, + 33278, + 26460, + 29208, + 33535, + 28627, + 26411, + 25744, + 23644, + 21097, + 19592, + 21042, + 20997, + 15515, + 12657, + 11763, + 12531, + 4870, + 10020, + 0, + 14901, + 13738, + 15855, + 16118, + 17348, + 20397, + 21793, + 21936, + 37429, + 42654, + 28485, + 21414, + 24144, + 20816, + 18908, + ], + [ + 18141, + 28730, + 22389, + 19005, + 47544, + 15975, + 18557, + 12894, + 16055, + 20674, + 18093, + 19539, + 18746, + 16138, + 14374, + 14727, + 10911, + 22888, + 14053, + 10633, + 6949, + 3737, + 13117, + 5211, + 14901, + 0, + 1777, + 1217, + 3528, + 2896, + 5892, + 7104, + 7338, + 23517, + 29068, + 13583, + 7667, + 10304, + 7330, + 6204, + ], + [ + 19248, + 29976, + 23592, + 20295, + 48689, + 17101, + 19545, + 13251, + 16300, + 20872, + 17672, + 18517, + 17725, + 15126, + 13194, + 13336, + 10190, + 21194, + 12378, + 8889, + 5177, + 1961, + 11575, + 4685, + 13738, + 1777, + 0, + 2217, + 2976, + 3610, + 6675, + 8055, + 8965, + 25197, + 30774, + 14865, + 8007, + 10742, + 7532, + 6000, + ], + [ + 17129, + 27803, + 21433, + 18105, + 46560, + 14971, + 17490, + 11680, + 14838, + 19457, + 16955, + 18636, + 17845, + 15240, + 13590, + 14109, + 9900, + 22640, + 13798, + 10569, + 7050, + 4036, + 13793, + 6348, + 15855, + 1217, + 2217, + 0, + 2647, + 1686, + 4726, + 6000, + 6810, + 23060, + 28665, + 12674, + 6450, + 9094, + 6117, + 5066, + ], + [ + 17192, + 28076, + 21655, + 18551, + 46567, + 15104, + 17309, + 10455, + 13422, + 17961, + 14735, + 16024, + 15232, + 12625, + 10943, + 11507, + 7397, + 20334, + 11537, + 8646, + 5619, + 3588, + 13300, + 7636, + 16118, + 3528, + 2976, + 2647, + 0, + 2320, + 4593, + 6093, + 8479, + 24542, + 30219, + 13194, + 5301, + 8042, + 4735, + 3039, + ], + [ + 15645, + 26408, + 20011, + 16763, + 45086, + 13503, + 15936, + 9997, + 13165, + 17787, + 15510, + 17632, + 16848, + 14264, + 12824, + 13611, + 8758, + 22636, + 13852, + 10938, + 7730, + 5109, + 15009, + 8010, + 17348, + 2896, + 3610, + 1686, + 2320, + 0, + 3086, + 4444, + 6169, + 22301, + 27963, + 11344, + 4780, + 7408, + 4488, + 3721, + ], + [ + 12658, + 23504, + 17087, + 13958, + 42083, + 10544, + 12881, + 7194, + 10430, + 15048, + 13694, + 16948, + 16197, + 13736, + 12815, + 14104, + 8119, + 23801, + 15276, + 12906, + 10187, + 7996, + 17856, + 11074, + 20397, + 5892, + 6675, + 4726, + 4593, + 3086, + 0, + 1501, + 5239, + 20390, + 26101, + 8611, + 2418, + 4580, + 2599, + 3496, + ], + [ + 11210, + 22025, + 15612, + 12459, + 40648, + 9080, + 11498, + 6574, + 9813, + 14372, + 13768, + 17587, + 16859, + 14482, + 13779, + 15222, + 8948, + 25065, + 16632, + 14366, + 11689, + 9459, + 19337, + 12315, + 21793, + 7104, + 8055, + 6000, + 6093, + 4444, + 1501, + 0, + 4608, + 19032, + 24747, + 7110, + 2860, + 4072, + 3355, + 4772, + ], + [ + 12094, + 22000, + 15872, + 12296, + 40971, + 9983, + 12944, + 10678, + 13777, + 18115, + 18317, + 22131, + 21391, + 18958, + 18042, + 19237, + 13353, + 28675, + 19957, + 17106, + 13792, + 10846, + 20454, + 11926, + 21936, + 7338, + 8965, + 6810, + 8479, + 6169, + 5239, + 4608, + 0, + 16249, + 21866, + 7146, + 7403, + 8446, + 7773, + 8614, + ], + [ + 13175, + 13197, + 11653, + 10370, + 29929, + 13435, + 14711, + 20959, + 22300, + 24280, + 28831, + 34799, + 34211, + 32292, + 32259, + 34013, + 27354, + 44048, + 35660, + 33171, + 30012, + 27094, + 36551, + 27537, + 37429, + 23517, + 25197, + 23060, + 24542, + 22301, + 20390, + 19032, + 16249, + 0, + 5714, + 12901, + 21524, + 20543, + 22186, + 23805, + ], + [ + 18162, + 14936, + 15666, + 15331, + 28493, + 18755, + 19589, + 26458, + 27564, + 29101, + 34148, + 40296, + 39731, + 37879, + 37918, + 39703, + 33023, + 49756, + 41373, + 38858, + 35654, + 32690, + 42017, + 32880, + 42654, + 29068, + 30774, + 28665, + 30219, + 27963, + 26101, + 24747, + 21866, + 5714, + 0, + 18516, + 27229, + 26181, + 27895, + 29519, + ], + [ + 4968, + 15146, + 8842, + 5430, + 34015, + 2947, + 5993, + 8180, + 10126, + 13400, + 16326, + 21953, + 21345, + 19391, + 19416, + 21271, + 14542, + 31426, + 23361, + 21390, + 18799, + 16451, + 26352, + 18634, + 28485, + 13583, + 14865, + 12674, + 13194, + 11344, + 8611, + 7110, + 7146, + 12901, + 18516, + 0, + 9029, + 7668, + 9742, + 11614, + ], + [ + 12308, + 23246, + 16843, + 14044, + 41473, + 10344, + 12227, + 5255, + 8388, + 13008, + 11276, + 14739, + 14006, + 11621, + 10975, + 12528, + 6106, + 22528, + 14333, + 12507, + 10406, + 8887, + 18403, + 12644, + 21414, + 7667, + 8007, + 6450, + 5301, + 4780, + 2418, + 2860, + 7403, + 21524, + 27229, + 9029, + 0, + 2747, + 726, + 2749, + ], + [ + 10084, + 20956, + 14618, + 12135, + 38935, + 8306, + 9793, + 2615, + 5850, + 10467, + 9918, + 14568, + 13907, + 11803, + 11750, + 13657, + 6901, + 23862, + 16125, + 14748, + 12981, + 11624, + 21033, + 15358, + 24144, + 10304, + 10742, + 9094, + 8042, + 7408, + 4580, + 4072, + 8446, + 20543, + 26181, + 7668, + 2747, + 0, + 3330, + 5313, + ], + [ + 13026, + 23963, + 17563, + 14771, + 42160, + 11069, + 12925, + 5730, + 8778, + 13375, + 11235, + 14366, + 13621, + 11188, + 10424, + 11907, + 5609, + 21861, + 13624, + 11781, + 9718, + 8304, + 17737, + 12200, + 20816, + 7330, + 7532, + 6117, + 4735, + 4488, + 2599, + 3355, + 7773, + 22186, + 27895, + 9742, + 726, + 3330, + 0, + 2042, + ], + [ + 15056, + 25994, + 19589, + 16743, + 44198, + 13078, + 14967, + 7552, + 10422, + 14935, + 11891, + 14002, + 13225, + 10671, + 9475, + 10633, + 5084, + 20315, + 11866, + 9802, + 7682, + 6471, + 15720, + 10674, + 18908, + 6204, + 6000, + 5066, + 3039, + 3721, + 3496, + 4772, + 8614, + 23805, + 29519, + 11614, + 2749, + 5313, + 2042, + 0, + ], ] # yapf: disable MAX_DISTANCE = 80_000 @@ -71,22 +1712,22 @@ VISIT_VALUES[0] = 0 def print_solution(solver, visited_nodes, used_arcs, num_nodes): """Prints solution on console.""" # Display dropped nodes. - dropped_nodes = 'Dropped nodes:' + dropped_nodes = "Dropped nodes:" for i in range(num_nodes): if i == 0: continue if not solver.BooleanValue(visited_nodes[i]): - dropped_nodes += f' {i}({VISIT_VALUES[i]})' + dropped_nodes += f" {i}({VISIT_VALUES[i]})" print(dropped_nodes) # Display routes current_node = 0 - plan_output = 'Route for vehicle 0:\n' + plan_output = "Route for vehicle 0:\n" route_distance = 0 value_collected = 0 route_is_finished = False while not route_is_finished: value_collected += VISIT_VALUES[current_node] - plan_output += f' {current_node} ->' + plan_output += f" {current_node} ->" # find next node for node in range(num_nodes): if node == current_node: @@ -97,9 +1738,9 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes): if current_node == 0: route_is_finished = True 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" {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" print(plan_output) @@ -107,7 +1748,7 @@ def prize_collecting_tsp(): """Entry point of the program.""" num_nodes = len(DISTANCE_MATRIX) all_nodes = range(num_nodes) - print(f'Num nodes = {num_nodes}') + print(f"Num nodes = {num_nodes}") # Model. model = cp_model.CpModel() @@ -120,7 +1761,7 @@ def prize_collecting_tsp(): # Create the circuit constraint. arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f'{i} is visited') + is_visited = model.NewBoolVar(f"{i} is visited") arcs.append([i, i, is_visited.Not()]) obj_vars.append(is_visited) @@ -131,7 +1772,7 @@ def prize_collecting_tsp(): if i == j: used_arcs[i, j] = is_visited.Not() continue - arc_is_used = model.NewBoolVar(f'{j} follows {i}') + arc_is_used = model.NewBoolVar(f"{j} follows {i}") arcs.append([i, j, arc_is_used]) obj_vars.append(arc_is_used) @@ -145,13 +1786,16 @@ def prize_collecting_tsp(): # limit the route distance model.Add( - sum(used_arcs[i, j] * DISTANCE_MATRIX[i][j] + sum( + used_arcs[i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes - for j in all_nodes) <= MAX_DISTANCE) + for j in all_nodes + ) + <= MAX_DISTANCE + ) # 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() @@ -167,9 +1811,9 @@ def prize_collecting_tsp(): def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") prize_collecting_tsp() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/prize_collecting_vrp_sat.py b/examples/python/prize_collecting_vrp_sat.py index 50af42a8d6..41d6ea5d7e 100755 --- a/examples/python/prize_collecting_vrp_sat.py +++ b/examples/python/prize_collecting_vrp_sat.py @@ -11,6 +11,7 @@ # 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. + """Simple prize collecting VRP problem with a max distance.""" from typing import Sequence @@ -19,46 +20,1686 @@ from ortools.sat.python import cp_model DISTANCE_MATRIX = [ - [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056], - [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994], - [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589], - [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743], - [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198], - [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078], - [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967], - [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552], - [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422], - [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935], - [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891], - [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002], - [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225], - [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671], - [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475], - [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633], - [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084], - [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315], - [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866], - [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802], - [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682], - [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471], - [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720], - [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674], - [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908], - [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204], - [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000], - [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066], - [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039], - [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721], - [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496], - [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772], - [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614], - [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805], - [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519], - [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614], - [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749], - [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313], - [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042], - [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0], + [ + 0, + 10938, + 4542, + 2835, + 29441, + 2171, + 1611, + 9208, + 9528, + 11111, + 16120, + 22606, + 22127, + 20627, + 21246, + 23387, + 16697, + 33609, + 26184, + 24772, + 22644, + 20655, + 30492, + 23296, + 32979, + 18141, + 19248, + 17129, + 17192, + 15645, + 12658, + 11210, + 12094, + 13175, + 18162, + 4968, + 12308, + 10084, + 13026, + 15056, + ], + [ + 10938, + 0, + 6422, + 9742, + 18988, + 12974, + 11216, + 19715, + 19004, + 18271, + 25070, + 31971, + 31632, + 30571, + 31578, + 33841, + 27315, + 43964, + 36944, + 35689, + 33569, + 31481, + 41360, + 33760, + 43631, + 28730, + 29976, + 27803, + 28076, + 26408, + 23504, + 22025, + 22000, + 13197, + 14936, + 15146, + 23246, + 20956, + 23963, + 25994, + ], + [ + 4542, + 6422, + 0, + 3644, + 25173, + 6552, + 5092, + 13584, + 13372, + 13766, + 19805, + 26537, + 26117, + 24804, + 25590, + 27784, + 21148, + 37981, + 30693, + 29315, + 27148, + 25071, + 34943, + 27472, + 37281, + 22389, + 23592, + 21433, + 21655, + 20011, + 17087, + 15612, + 15872, + 11653, + 15666, + 8842, + 16843, + 14618, + 17563, + 19589, + ], + [ + 2835, + 9742, + 3644, + 0, + 28681, + 3851, + 4341, + 11660, + 12294, + 13912, + 18893, + 25283, + 24777, + 23173, + 23636, + 25696, + 18950, + 35927, + 28233, + 26543, + 24127, + 21864, + 31765, + 24018, + 33904, + 19005, + 20295, + 18105, + 18551, + 16763, + 13958, + 12459, + 12296, + 10370, + 15331, + 5430, + 14044, + 12135, + 14771, + 16743, + ], + [ + 29441, + 18988, + 25173, + 28681, + 0, + 31590, + 29265, + 37173, + 35501, + 32929, + 40239, + 47006, + 46892, + 46542, + 48112, + 50506, + 44539, + 60103, + 54208, + 53557, + 51878, + 50074, + 59849, + 52645, + 62415, + 47544, + 48689, + 46560, + 46567, + 45086, + 42083, + 40648, + 40971, + 29929, + 28493, + 34015, + 41473, + 38935, + 42160, + 44198, + ], + [ + 2171, + 12974, + 6552, + 3851, + 31590, + 0, + 3046, + 7856, + 8864, + 11330, + 15411, + 21597, + 21065, + 19382, + 19791, + 21845, + 15099, + 32076, + 24425, + 22848, + 20600, + 18537, + 28396, + 21125, + 30825, + 15975, + 17101, + 14971, + 15104, + 13503, + 10544, + 9080, + 9983, + 13435, + 18755, + 2947, + 10344, + 8306, + 11069, + 13078, + ], + [ + 1611, + 11216, + 5092, + 4341, + 29265, + 3046, + 0, + 8526, + 8368, + 9573, + 14904, + 21529, + 21085, + 19719, + 20504, + 22713, + 16118, + 32898, + 25728, + 24541, + 22631, + 20839, + 30584, + 23755, + 33278, + 18557, + 19545, + 17490, + 17309, + 15936, + 12881, + 11498, + 12944, + 14711, + 19589, + 5993, + 12227, + 9793, + 12925, + 14967, + ], + [ + 9208, + 19715, + 13584, + 11660, + 37173, + 7856, + 8526, + 0, + 3248, + 7855, + 8245, + 13843, + 13272, + 11526, + 12038, + 14201, + 7599, + 24411, + 17259, + 16387, + 15050, + 13999, + 23134, + 17899, + 26460, + 12894, + 13251, + 11680, + 10455, + 9997, + 7194, + 6574, + 10678, + 20959, + 26458, + 8180, + 5255, + 2615, + 5730, + 7552, + ], + [ + 9528, + 19004, + 13372, + 12294, + 35501, + 8864, + 8368, + 3248, + 0, + 4626, + 6598, + 13168, + 12746, + 11567, + 12731, + 15083, + 9120, + 25037, + 18718, + 18433, + 17590, + 16888, + 25630, + 20976, + 29208, + 16055, + 16300, + 14838, + 13422, + 13165, + 10430, + 9813, + 13777, + 22300, + 27564, + 10126, + 8388, + 5850, + 8778, + 10422, + ], + [ + 11111, + 18271, + 13766, + 13912, + 32929, + 11330, + 9573, + 7855, + 4626, + 0, + 7318, + 14185, + 14005, + 13655, + 15438, + 17849, + 12839, + 27179, + 21947, + 22230, + 21814, + 21366, + 29754, + 25555, + 33535, + 20674, + 20872, + 19457, + 17961, + 17787, + 15048, + 14372, + 18115, + 24280, + 29101, + 13400, + 13008, + 10467, + 13375, + 14935, + ], + [ + 16120, + 25070, + 19805, + 18893, + 40239, + 15411, + 14904, + 8245, + 6598, + 7318, + 0, + 6939, + 6702, + 6498, + 8610, + 10961, + 7744, + 19889, + 15350, + 16403, + 16975, + 17517, + 24357, + 22176, + 28627, + 18093, + 17672, + 16955, + 14735, + 15510, + 13694, + 13768, + 18317, + 28831, + 34148, + 16326, + 11276, + 9918, + 11235, + 11891, + ], + [ + 22606, + 31971, + 26537, + 25283, + 47006, + 21597, + 21529, + 13843, + 13168, + 14185, + 6939, + 0, + 793, + 3401, + 5562, + 6839, + 8923, + 13433, + 11264, + 13775, + 15853, + 17629, + 21684, + 22315, + 26411, + 19539, + 18517, + 18636, + 16024, + 17632, + 16948, + 17587, + 22131, + 34799, + 40296, + 21953, + 14739, + 14568, + 14366, + 14002, + ], + [ + 22127, + 31632, + 26117, + 24777, + 46892, + 21065, + 21085, + 13272, + 12746, + 14005, + 6702, + 793, + 0, + 2608, + 4809, + 6215, + 8151, + 13376, + 10702, + 13094, + 15099, + 16845, + 21039, + 21535, + 25744, + 18746, + 17725, + 17845, + 15232, + 16848, + 16197, + 16859, + 21391, + 34211, + 39731, + 21345, + 14006, + 13907, + 13621, + 13225, + ], + [ + 20627, + 30571, + 24804, + 23173, + 46542, + 19382, + 19719, + 11526, + 11567, + 13655, + 6498, + 3401, + 2608, + 0, + 2556, + 4611, + 5630, + 13586, + 9157, + 11005, + 12681, + 14285, + 19044, + 18996, + 23644, + 16138, + 15126, + 15240, + 12625, + 14264, + 13736, + 14482, + 18958, + 32292, + 37879, + 19391, + 11621, + 11803, + 11188, + 10671, + ], + [ + 21246, + 31578, + 25590, + 23636, + 48112, + 19791, + 20504, + 12038, + 12731, + 15438, + 8610, + 5562, + 4809, + 2556, + 0, + 2411, + 4917, + 12395, + 6757, + 8451, + 10292, + 12158, + 16488, + 16799, + 21097, + 14374, + 13194, + 13590, + 10943, + 12824, + 12815, + 13779, + 18042, + 32259, + 37918, + 19416, + 10975, + 11750, + 10424, + 9475, + ], + [ + 23387, + 33841, + 27784, + 25696, + 50506, + 21845, + 22713, + 14201, + 15083, + 17849, + 10961, + 6839, + 6215, + 4611, + 2411, + 0, + 6760, + 10232, + 4567, + 7010, + 9607, + 12003, + 14846, + 16408, + 19592, + 14727, + 13336, + 14109, + 11507, + 13611, + 14104, + 15222, + 19237, + 34013, + 39703, + 21271, + 12528, + 13657, + 11907, + 10633, + ], + [ + 16697, + 27315, + 21148, + 18950, + 44539, + 15099, + 16118, + 7599, + 9120, + 12839, + 7744, + 8923, + 8151, + 5630, + 4917, + 6760, + 0, + 16982, + 9699, + 9400, + 9302, + 9823, + 16998, + 14534, + 21042, + 10911, + 10190, + 9900, + 7397, + 8758, + 8119, + 8948, + 13353, + 27354, + 33023, + 14542, + 6106, + 6901, + 5609, + 5084, + ], + [ + 33609, + 43964, + 37981, + 35927, + 60103, + 32076, + 32898, + 24411, + 25037, + 27179, + 19889, + 13433, + 13376, + 13586, + 12395, + 10232, + 16982, + 0, + 8843, + 12398, + 16193, + 19383, + 16423, + 22583, + 20997, + 22888, + 21194, + 22640, + 20334, + 22636, + 23801, + 25065, + 28675, + 44048, + 49756, + 31426, + 22528, + 23862, + 21861, + 20315, + ], + [ + 26184, + 36944, + 30693, + 28233, + 54208, + 24425, + 25728, + 17259, + 18718, + 21947, + 15350, + 11264, + 10702, + 9157, + 6757, + 4567, + 9699, + 8843, + 0, + 3842, + 7518, + 10616, + 10666, + 14237, + 15515, + 14053, + 12378, + 13798, + 11537, + 13852, + 15276, + 16632, + 19957, + 35660, + 41373, + 23361, + 14333, + 16125, + 13624, + 11866, + ], + [ + 24772, + 35689, + 29315, + 26543, + 53557, + 22848, + 24541, + 16387, + 18433, + 22230, + 16403, + 13775, + 13094, + 11005, + 8451, + 7010, + 9400, + 12398, + 3842, + 0, + 3795, + 7014, + 8053, + 10398, + 12657, + 10633, + 8889, + 10569, + 8646, + 10938, + 12906, + 14366, + 17106, + 33171, + 38858, + 21390, + 12507, + 14748, + 11781, + 9802, + ], + [ + 22644, + 33569, + 27148, + 24127, + 51878, + 20600, + 22631, + 15050, + 17590, + 21814, + 16975, + 15853, + 15099, + 12681, + 10292, + 9607, + 9302, + 16193, + 7518, + 3795, + 0, + 3250, + 8084, + 6873, + 11763, + 6949, + 5177, + 7050, + 5619, + 7730, + 10187, + 11689, + 13792, + 30012, + 35654, + 18799, + 10406, + 12981, + 9718, + 7682, + ], + [ + 20655, + 31481, + 25071, + 21864, + 50074, + 18537, + 20839, + 13999, + 16888, + 21366, + 17517, + 17629, + 16845, + 14285, + 12158, + 12003, + 9823, + 19383, + 10616, + 7014, + 3250, + 0, + 9901, + 4746, + 12531, + 3737, + 1961, + 4036, + 3588, + 5109, + 7996, + 9459, + 10846, + 27094, + 32690, + 16451, + 8887, + 11624, + 8304, + 6471, + ], + [ + 30492, + 41360, + 34943, + 31765, + 59849, + 28396, + 30584, + 23134, + 25630, + 29754, + 24357, + 21684, + 21039, + 19044, + 16488, + 14846, + 16998, + 16423, + 10666, + 8053, + 8084, + 9901, + 0, + 9363, + 4870, + 13117, + 11575, + 13793, + 13300, + 15009, + 17856, + 19337, + 20454, + 36551, + 42017, + 26352, + 18403, + 21033, + 17737, + 15720, + ], + [ + 23296, + 33760, + 27472, + 24018, + 52645, + 21125, + 23755, + 17899, + 20976, + 25555, + 22176, + 22315, + 21535, + 18996, + 16799, + 16408, + 14534, + 22583, + 14237, + 10398, + 6873, + 4746, + 9363, + 0, + 10020, + 5211, + 4685, + 6348, + 7636, + 8010, + 11074, + 12315, + 11926, + 27537, + 32880, + 18634, + 12644, + 15358, + 12200, + 10674, + ], + [ + 32979, + 43631, + 37281, + 33904, + 62415, + 30825, + 33278, + 26460, + 29208, + 33535, + 28627, + 26411, + 25744, + 23644, + 21097, + 19592, + 21042, + 20997, + 15515, + 12657, + 11763, + 12531, + 4870, + 10020, + 0, + 14901, + 13738, + 15855, + 16118, + 17348, + 20397, + 21793, + 21936, + 37429, + 42654, + 28485, + 21414, + 24144, + 20816, + 18908, + ], + [ + 18141, + 28730, + 22389, + 19005, + 47544, + 15975, + 18557, + 12894, + 16055, + 20674, + 18093, + 19539, + 18746, + 16138, + 14374, + 14727, + 10911, + 22888, + 14053, + 10633, + 6949, + 3737, + 13117, + 5211, + 14901, + 0, + 1777, + 1217, + 3528, + 2896, + 5892, + 7104, + 7338, + 23517, + 29068, + 13583, + 7667, + 10304, + 7330, + 6204, + ], + [ + 19248, + 29976, + 23592, + 20295, + 48689, + 17101, + 19545, + 13251, + 16300, + 20872, + 17672, + 18517, + 17725, + 15126, + 13194, + 13336, + 10190, + 21194, + 12378, + 8889, + 5177, + 1961, + 11575, + 4685, + 13738, + 1777, + 0, + 2217, + 2976, + 3610, + 6675, + 8055, + 8965, + 25197, + 30774, + 14865, + 8007, + 10742, + 7532, + 6000, + ], + [ + 17129, + 27803, + 21433, + 18105, + 46560, + 14971, + 17490, + 11680, + 14838, + 19457, + 16955, + 18636, + 17845, + 15240, + 13590, + 14109, + 9900, + 22640, + 13798, + 10569, + 7050, + 4036, + 13793, + 6348, + 15855, + 1217, + 2217, + 0, + 2647, + 1686, + 4726, + 6000, + 6810, + 23060, + 28665, + 12674, + 6450, + 9094, + 6117, + 5066, + ], + [ + 17192, + 28076, + 21655, + 18551, + 46567, + 15104, + 17309, + 10455, + 13422, + 17961, + 14735, + 16024, + 15232, + 12625, + 10943, + 11507, + 7397, + 20334, + 11537, + 8646, + 5619, + 3588, + 13300, + 7636, + 16118, + 3528, + 2976, + 2647, + 0, + 2320, + 4593, + 6093, + 8479, + 24542, + 30219, + 13194, + 5301, + 8042, + 4735, + 3039, + ], + [ + 15645, + 26408, + 20011, + 16763, + 45086, + 13503, + 15936, + 9997, + 13165, + 17787, + 15510, + 17632, + 16848, + 14264, + 12824, + 13611, + 8758, + 22636, + 13852, + 10938, + 7730, + 5109, + 15009, + 8010, + 17348, + 2896, + 3610, + 1686, + 2320, + 0, + 3086, + 4444, + 6169, + 22301, + 27963, + 11344, + 4780, + 7408, + 4488, + 3721, + ], + [ + 12658, + 23504, + 17087, + 13958, + 42083, + 10544, + 12881, + 7194, + 10430, + 15048, + 13694, + 16948, + 16197, + 13736, + 12815, + 14104, + 8119, + 23801, + 15276, + 12906, + 10187, + 7996, + 17856, + 11074, + 20397, + 5892, + 6675, + 4726, + 4593, + 3086, + 0, + 1501, + 5239, + 20390, + 26101, + 8611, + 2418, + 4580, + 2599, + 3496, + ], + [ + 11210, + 22025, + 15612, + 12459, + 40648, + 9080, + 11498, + 6574, + 9813, + 14372, + 13768, + 17587, + 16859, + 14482, + 13779, + 15222, + 8948, + 25065, + 16632, + 14366, + 11689, + 9459, + 19337, + 12315, + 21793, + 7104, + 8055, + 6000, + 6093, + 4444, + 1501, + 0, + 4608, + 19032, + 24747, + 7110, + 2860, + 4072, + 3355, + 4772, + ], + [ + 12094, + 22000, + 15872, + 12296, + 40971, + 9983, + 12944, + 10678, + 13777, + 18115, + 18317, + 22131, + 21391, + 18958, + 18042, + 19237, + 13353, + 28675, + 19957, + 17106, + 13792, + 10846, + 20454, + 11926, + 21936, + 7338, + 8965, + 6810, + 8479, + 6169, + 5239, + 4608, + 0, + 16249, + 21866, + 7146, + 7403, + 8446, + 7773, + 8614, + ], + [ + 13175, + 13197, + 11653, + 10370, + 29929, + 13435, + 14711, + 20959, + 22300, + 24280, + 28831, + 34799, + 34211, + 32292, + 32259, + 34013, + 27354, + 44048, + 35660, + 33171, + 30012, + 27094, + 36551, + 27537, + 37429, + 23517, + 25197, + 23060, + 24542, + 22301, + 20390, + 19032, + 16249, + 0, + 5714, + 12901, + 21524, + 20543, + 22186, + 23805, + ], + [ + 18162, + 14936, + 15666, + 15331, + 28493, + 18755, + 19589, + 26458, + 27564, + 29101, + 34148, + 40296, + 39731, + 37879, + 37918, + 39703, + 33023, + 49756, + 41373, + 38858, + 35654, + 32690, + 42017, + 32880, + 42654, + 29068, + 30774, + 28665, + 30219, + 27963, + 26101, + 24747, + 21866, + 5714, + 0, + 18516, + 27229, + 26181, + 27895, + 29519, + ], + [ + 4968, + 15146, + 8842, + 5430, + 34015, + 2947, + 5993, + 8180, + 10126, + 13400, + 16326, + 21953, + 21345, + 19391, + 19416, + 21271, + 14542, + 31426, + 23361, + 21390, + 18799, + 16451, + 26352, + 18634, + 28485, + 13583, + 14865, + 12674, + 13194, + 11344, + 8611, + 7110, + 7146, + 12901, + 18516, + 0, + 9029, + 7668, + 9742, + 11614, + ], + [ + 12308, + 23246, + 16843, + 14044, + 41473, + 10344, + 12227, + 5255, + 8388, + 13008, + 11276, + 14739, + 14006, + 11621, + 10975, + 12528, + 6106, + 22528, + 14333, + 12507, + 10406, + 8887, + 18403, + 12644, + 21414, + 7667, + 8007, + 6450, + 5301, + 4780, + 2418, + 2860, + 7403, + 21524, + 27229, + 9029, + 0, + 2747, + 726, + 2749, + ], + [ + 10084, + 20956, + 14618, + 12135, + 38935, + 8306, + 9793, + 2615, + 5850, + 10467, + 9918, + 14568, + 13907, + 11803, + 11750, + 13657, + 6901, + 23862, + 16125, + 14748, + 12981, + 11624, + 21033, + 15358, + 24144, + 10304, + 10742, + 9094, + 8042, + 7408, + 4580, + 4072, + 8446, + 20543, + 26181, + 7668, + 2747, + 0, + 3330, + 5313, + ], + [ + 13026, + 23963, + 17563, + 14771, + 42160, + 11069, + 12925, + 5730, + 8778, + 13375, + 11235, + 14366, + 13621, + 11188, + 10424, + 11907, + 5609, + 21861, + 13624, + 11781, + 9718, + 8304, + 17737, + 12200, + 20816, + 7330, + 7532, + 6117, + 4735, + 4488, + 2599, + 3355, + 7773, + 22186, + 27895, + 9742, + 726, + 3330, + 0, + 2042, + ], + [ + 15056, + 25994, + 19589, + 16743, + 44198, + 13078, + 14967, + 7552, + 10422, + 14935, + 11891, + 14002, + 13225, + 10671, + 9475, + 10633, + 5084, + 20315, + 11866, + 9802, + 7682, + 6471, + 15720, + 10674, + 18908, + 6204, + 6000, + 5066, + 3039, + 3721, + 3496, + 4772, + 8614, + 23805, + 29519, + 11614, + 2749, + 5313, + 2042, + 0, + ], ] # yapf: disable MAX_DISTANCE = 80_000 @@ -71,29 +1712,28 @@ VISIT_VALUES[0] = 0 def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): """Prints solution on console.""" # Display dropped nodes. - dropped_nodes = 'Dropped nodes:' + dropped_nodes = "Dropped nodes:" for node in range(num_nodes): if node == 0: continue - is_visited = sum([ - solver.BooleanValue(visited_nodes[v][node]) - for v in range(num_vehicles) - ]) + is_visited = sum( + [solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)] + ) if not is_visited: - dropped_nodes += f' {node}({VISIT_VALUES[node]})' + dropped_nodes += f" {node}({VISIT_VALUES[node]})" print(dropped_nodes) # Display routes total_distance = 0 total_value_collected = 0 for v in range(num_vehicles): current_node = 0 - plan_output = f'Route for vehicle {v}:\n' + plan_output = f"Route for vehicle {v}:\n" route_distance = 0 value_collected = 0 route_is_finished = False while not route_is_finished: value_collected += VISIT_VALUES[current_node] - plan_output += f' {current_node} ->' + plan_output += f" {current_node} ->" # find next node for node in range(num_nodes): if node == current_node: @@ -104,21 +1744,21 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): if current_node == 0: route_is_finished = True 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" {current_node}\n" + plan_output += f"Distance of the route: {route_distance}m\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 Distance: {total_distance}m") + print(f"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}") def prize_collecting_vrp(): """Entry point of the program.""" num_nodes = len(DISTANCE_MATRIX) num_vehicles = 4 - print(f'Num nodes = {num_nodes}') + print(f"Num nodes = {num_nodes}") # Model. model = cp_model.CpModel() @@ -135,7 +1775,7 @@ def prize_collecting_vrp(): used_arcs[v] = {} arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f'{i} is visited') + is_visited = model.NewBoolVar(f"{i} is visited") arcs.append([i, i, is_visited.Not()]) obj_vars.append(is_visited) @@ -146,7 +1786,7 @@ def prize_collecting_vrp(): if i == j: used_arcs[v][i, j] = is_visited.Not() continue - arc_is_used = model.NewBoolVar(f'{j} follows {i}') + arc_is_used = model.NewBoolVar(f"{j} follows {i}") arcs.append([i, j, arc_is_used]) obj_vars.append(arc_is_used) @@ -160,18 +1800,20 @@ def prize_collecting_vrp(): # limit the route distance model.Add( - sum(used_arcs[v][i, j] * DISTANCE_MATRIX[i][j] + sum( + used_arcs[v][i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes - for j in all_nodes) <= MAX_DISTANCE) + for j in all_nodes + ) + <= MAX_DISTANCE + ) # 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.AddAtMostOne([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() @@ -181,15 +1823,14 @@ def prize_collecting_vrp(): 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) + print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") prize_collecting_vrp() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/pyflow_example.py b/examples/python/pyflow_example.py index 7d2e5a6eca..5c98372a3e 100644 --- a/examples/python/pyflow_example.py +++ b/examples/python/pyflow_example.py @@ -11,6 +11,7 @@ # 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. + """MaxFlow and MinCostFlow examples.""" from typing import Sequence @@ -21,7 +22,7 @@ from ortools.graph.python import min_cost_flow def max_flow_api(): """MaxFlow simple interface example.""" - print('MaxFlow on a simple network.') + print("MaxFlow on a simple network.") tails = [0, 0, 0, 0, 1, 2, 3, 3, 4] heads = [1, 2, 3, 4, 3, 4, 4, 5, 5] capacities = [5, 8, 5, 3, 4, 5, 6, 6, 4] @@ -30,55 +31,57 @@ def max_flow_api(): for i in range(0, len(tails)): smf.add_arc_with_capacity(tails[i], heads[i], capacities[i]) if smf.solve(0, 5) == smf.OPTIMAL: - print('Total flow', smf.optimal_flow(), '/', expected_total_flow) + print("Total flow", smf.optimal_flow(), "/", expected_total_flow) for i in range(smf.num_arcs()): - print('From source %d to target %d: %d / %d' % - (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i))) - print('Source side min-cut:', smf.get_source_side_min_cut()) - print('Sink side min-cut:', smf.get_sink_side_min_cut()) + print( + "From source %d to target %d: %d / %d" + % (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i)) + ) + print("Source side min-cut:", smf.get_source_side_min_cut()) + print("Sink side min-cut:", smf.get_sink_side_min_cut()) else: - print('There was an issue with the max flow input.') + print("There was an issue with the max flow input.") def min_cost_flow_api(): """MinCostFlow simple interface example. - Note that this example is actually a linear sum assignment example and will - be more efficiently solved with the pywrapgraph.LinearSumAssignment class. - """ - print('MinCostFlow on 4x4 matrix.') + Note that this example is actually a linear sum assignment example and will + be more efficiently solved with the pywrapgraph.LinearSumAssignment class. + """ + print("MinCostFlow on 4x4 matrix.") num_sources = 4 num_targets = 4 - costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], - [45, 110, 95, 115]] + costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]] expected_cost = 275 smcf = min_cost_flow.SimpleMinCostFlow() for source in range(0, num_sources): for target in range(0, num_targets): - smcf.add_arc_with_capacity_and_unit_cost(source, - num_sources + target, 1, - costs[source][target]) + smcf.add_arc_with_capacity_and_unit_cost( + source, num_sources + target, 1, costs[source][target] + ) for node in range(0, num_sources): smcf.set_node_supply(node, 1) smcf.set_node_supply(num_sources + node, -1) status = smcf.solve() if status == smcf.OPTIMAL: - print('Total flow', smcf.optimal_cost(), '/', expected_cost) + print("Total flow", smcf.optimal_cost(), "/", expected_cost) for i in range(0, smcf.num_arcs()): if smcf.flow(i) > 0: - print('From source %d to target %d: cost %d' % - (smcf.tail(i), smcf.head(i) - num_sources, - smcf.unit_cost(i))) + print( + "From source %d to target %d: cost %d" + % (smcf.tail(i), smcf.head(i) - num_sources, smcf.unit_cost(i)) + ) else: - print('There was an issue with the min cost flow input.') + print("There was an issue with the min cost flow input.") def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") max_flow_api() min_cost_flow_api() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/qubo_sat.py b/examples/python/qubo_sat.py index 5d5b1ec88f..fb6c6850a7 100644 --- a/examples/python/qubo_sat.py +++ b/examples/python/qubo_sat.py @@ -11,6 +11,7 @@ # 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 a Qubo program using the CP-SAT solver.""" from typing import Sequence @@ -19,633 +20,5855 @@ from ortools.sat.python import cp_model RAW_DATA = [ [ - 0, 0, 49.774821, -59.5968886, -46.0773896, 0, -65.166109, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 47.0957778, 15.259961, -98.7983264, 0, 0, 0, -20.7757184, - 0, 87.2645672, 0, -22.7888772, 0, 0, 0, -40.4980904, -19.7307486, - -23.222078, 0, -77.5263128, 0, 0, 56.6204008, 0, 0, 0, 0, 0, 0, 0, - 31.378421, 0, 97.3441448, 35.1309806, 0, 0, -40.5727886, -50.7308566, 0, - 0, -69.9304568, 0, 38.5385914, 0, -22.1243232, 0, 0, 0, 0, -62.5102538, - 8.0801276, 46.7998066, -2.3292106, 0, 0, 0, 8.774031, 0, 0, -65.6505736 - ], - [ - -67.2935466, -64.4354852, -96.6712204, 0, 0, -60.7812272, 0, 0, 0, 0, 0, - -7.9966864, 0, 0, 0, 0, 0, 0, 0, 89.7672338, 0, 0, 0, 0, 98.9607046, 0, - 28.6714432, 0, 0, 0, -26.2738856, 0, 0, 68.363956, 0, 0, 0, 0, - 54.7406868, 0, 0, 0, 0, 94.2320734, 0, 0, 0, 0, 0, 0, 0, 0, -2.9647794, - 39.7161716, -54.7931288, 0, 0, 0, 0, -47.2284892, 0, 0, 0, -8.6421808, - -35.399612, 0, 0, 62.1912668, 0, -6.8930716, 0, 0, -17.0801284, 0, 0, - 68.6533416 - ], - [ - 0, 0, 0, 81.165396, 83.773254, 0, -25.1603, 0, 0, 50.225725, 0, - -3.8242274, 0, 0, 36.2078566, 0, 0, 0, 0, 0, 0, 0, 0, 15.551432, 0, 0, - -33.6446236, 0, 0, 0, 36.6171324, 0, 0, 0, 0, 67.9591934, 0, 22.1428016, - 0, -27.2961928, 0, 0, 0, -97.4961564, 90.4062526, 0, 0, 0, -90.0532814, - 0, 98.8332924, 0, 0, -13.8994926, 0, 17.1962884, 0, 0, 0, -55.1654678, - 0, 0, 0, 85.829554, 0, -37.971164, 64.233136, -17.9943296, 0, 0, 0, 0, - -67.7509796, 0, 0, 10.0750712 - ], - [ - 0, 37.2783148, 0, 0, 0, 36.4959506, 0, 0, 0, 0, 0, 0, 61.201323, 0, - 14.4328522, 48.4078064, 0, 0, 0, 0, 0, 0, 0, -47.0969056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 26.720439, 0, 0, 62.1987576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, -65.2085246, 0, 0, 0, 0, 0, 0, 73.3019432, -14.3431238, 0, - 0, 0, 0, 0, 0, 0, 0, 2.1565846, 0, 0, 0.7733644, 0, 5.9090456, 0, - -39.7724192 - ], - [ - 0, 0, 0, 0, -24.4555532, 0, 0, -5.5484574, 0, 25.4685054, 0.7906104, - 4.273133, 0, 52.12973, 0, -12.8040828, 0, 0, 81.888381, 64.0713498, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62.9088768, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 20.869713, 0, 0, -71.5835872, 0, 0, 80.7237808, 0, 0, 0, 0, -60.1883708, - 0, 0, 0, 0, 0, 0, 0, 0, 85.0393326, 23.6045316, -18.8849834, 0, 0, 0, - -90.8065188, -9.5037982, 14.3196654, 0, -28.9290306, 0, 0, -41.5575766 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -8.9478934, 0, - -83.6040618, 0, 0, 0, 0, -14.3874822, 77.2528714, 0, 0, 99.2966066, 0, - 21.7889114, -37.7629282, 0, -11.6026582, 0, 0, 0, 0, 74.422603, 0, 0, - -79.239245, -31.9686324, 0, 0, 0, 0, 0, 0, -29.8797178, 0, 0, - 85.2723062, 0, 0, -8.8031188, 0, 0, -20.043565, 0, 0, -70.733454, 0, - -94.7231762, -85.4584516, 0, 0, 0, 0, -27.6068624, 0, -79.787783, 0, 0, - -55.1894266 - ], - [ - 1.9374354, 0, 1.4807184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 61.298952, 0, 0, 0, -90.5702054, 0, 67.381115, 0, -68.684637, 0, 0, 0, - 0, 0, 0, 0, 0, 93.7807934, 0, 8.8213302, -15.9020466, 0, 0, 0, 0, - 23.8157662, 0, 0, 0, 0, 0, 0, 0, 67.3461972, 0, 0, 0, 0, 0, 43.3263744, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87.7149706, 0, 0, 0, -18.5133574, - -30.0338992 - ], - [ - 6.3937798, 78.7697644, 28.4485838, 0, 0, 0.1352466, 0, 0, 74.5767122, - -13.8340168, 0, 0, 0, 77.2929426, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 17.9243834, 0, 0, 82.2239878, 0, 0, 0, 0, 64.3440016, 90.109577, - 46.8926522, -2.4494366, 0, 84.7413412, 0, 0, -4.216108, 0, 0, - -79.8684776, 0, 0, 74.8706758, -64.4518992, 0, 0, 0, 0, -34.4895004, 0, - 0, 0, -74.1158858, -37.7803516, 0, 0, 0, 0, 0, 80.0054296, 0, 0, 0, 0, - 0, 0, 0, 0, -84.5832026, 0, 0, 71.2540694 - ], - [ - 0, 0, 0, 0, -74.9257454, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 14.9675444, 0, 0, 0, 0, 0, -27.5236912, 0, 0, -80.4993438, 0, - -81.8494538, 0, 0, 0, 0, 0, -18.6802002, 0, 0, 0, 0, 0, 0, 0, - 61.4131076, 0, 0, -55.1421034, 0, 0, -18.576761, 72.3500914, 0, 0, 0, 0, - 0, 0, -23.6460116, 43.1258024, 0, 93.701872, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, -86.5772554 - ], - [ - 0, 0, 93.0762916, 0, 0, 0, 0, 0, 0, 0, -37.2663938, 86.8303764, 0, 0, 0, - -51.9596226, 0, 35.6722618, -91.438259, 0, 0, -70.6277108, 0, - -82.9146992, 58.0327648, 0, 0, 0, 0, 0, 0, 0, 0, 13.3727302, 0, 0, 0, - 23.5719942, 0, -21.5445476, 74.1541634, 60.6365036, 97.4447708, 0, 0, 0, - 0, 82.5869498, 0, 85.1132108, 0, 0, 0, 0, 0, 0, 97.4012534, 0, 0, 0, 0, - -45.1504048, 1.0619934, 59.7140264, 0, 0, 0, 0, 0, 4.177461, 0, 0, 0, - -75.7039276, 0, 0.0421338 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -71.171869, 0, 0, 8.685266, 0, 0, 0, 0, - 33.9089574, 0, -31.6154498, 0, 0, 0, 0, 0, 5.3182746, 0, 0, 0, 0, 0, 0, - 3.0361166, -10.364305, 0, 0, 0, 0, 0, 0, 0, 0, -83.9738444, 0, 0, - -7.9170212, 0, 0, 0, -28.7575682, 0, 0, 0, -29.9216686, 0, 0, - 83.4050918, -39.5247364, -6.7028846, 0, 0, 0, -23.6080482, 0, 0, 0, 0, - 0, 0, -18.380154, 46.9252306, 0, 0, 26.1618372, 99.6235254 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 52.008307, 0, 0, 0, 0, 0, 92.4974102, -76.3015714, - 0, 0, 0, 0, 0, 0, 0, -56.4879132, 0, 0, 0, 50.1473938, 0, 0, 0, 0, 0, 0, - 0, 0, 40.2219566, 0, 0, 0, 84.5162074, 0, 0, 0, -73.3030606, 0, - -10.9258316, 0, 0, 0, 0, 97.5496436, -70.5026182, 0, 62.3611696, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96.4362226, 0, 0, 0, 0, 0, 0, 0, - -6.0104764, 15.7466756 - ], - [ - -38.7678174, 0, 0, 0, 0, 10.5238486, 0, 0, 0, 31.6876676, 79.6111978, 0, - 0, 0, 0, 45.7314046, 0, 0, 0, -10.0125122, 0, 93.3170242, -96.4566224, - -5.853298, 0, -82.2848728, 0, 0, 0, 0, 0, 0, 0, 43.3427638, 24.6186934, - 0, 44.859548, 0, -63.8196424, 0, 32.6630616, 41.48423, 0, -42.9613722, - 0, 0, -68.8954844, 0, 0, 0, 0, 0, 0, 0, 0, 27.7424034, -33.4867534, - -49.1827758, 0, 18.7014116, 0, 0, 59.049662, 0, 0, 0, 0, 0, 0, - -29.6305028, 0, 0, 0, 0, 0, 98.2266078 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -14.6583468, 0, -74.4490466, 0, 0, 0, 0, - 0, 0, 0, 0, 0, -94.9604028, 0, 0, -48.28403, -41.3534342, 0, 0, 0, - 62.9532972, 0, 0, 4.030284, 0, -60.478996, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, -46.5887848, 39.4565458, 0, 0, 0, 0, 0, 0, 0, - -48.5071236, 0, 0, 0, 41.8640204, 0, 0, 0, 74.271524, 0, 15.5769242, 0, - -61.4793904, 52.4500934 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50.8387116, 0, 46.490051, 0, -54.9751352, - 0, 43.0696416, 0, 0, 0, 0, 80.5337704, 0, 0, -16.0325234, 0, 0, 0, 0, 0, - 0, 0, 0, 63.186351, 0, 0, 0, 0, 0, 75.2218604, -27.3783446, 0, 0, 0, 0, - -85.021934, 60.9043202, 55.7344594, 0, 41.1687556, 0, 5.574124, 0, 0, - -5.0028254, -40.2614834, 0, 0, 0, 0, 0, 0, 22.207679, 0, 0, 0, 0, 0, - 65.0504204, 0, -61.4580018, -90.137276, 19.2277196, 0, 0, -73.8615034 - ], - [ - 0, 0, 0, 0, 0, -21.3303052, -75.4586018, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 84.6730538, -3.4478344, 0, -76.083503, - 19.9372656, -38.9938322, 0, 0, 36.0135034, 0, -56.8457144, 0, 0, - 63.4198336, 0, 0, 0, 0, 0, -63.4419798, 28.5629988, 0, 0, 0, 0, - 38.8001126, 0, 0, 47.2148438, 0, -19.256673, -24.8778354, -47.8193252, - 0, 0, -38.7279908, 0, 0, 0, -34.5546658, 0, -96.7675822, 0, 0, 0, 0, 0, - 0, 0, 0, 27.9437518 - ], - [ - 0, -17.8703906, 0, 0, -47.2114204, 0, -73.3595682, 66.668341, 0, 0, 0, - 0, 30.250278, 0, -40.452007, 0, 0, 0, 0, 0, -37.5013244, 34.3045856, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12.898457, - -15.2838318, 0, 0, 0, 0, 0, 16.333021, 0, 72.0452526, 0, 2.285458, 0, 0, - 0, 0, -17.8073046, 0, 0, 0, 81.7271146, 0, 0, -17.7174538, 0, - -62.6694996, 8.7298318, 0, 0, 0, 0, 0, 0, -27.1131974, 0, -68.0964272 - ], - [ - 0, 0, 0, 0, -22.1791216, 16.588597, -19.4545486, 0, -74.8318248, - -74.4252462, 0, 0, 0, -46.1656546, 0, 0, -21.1620788, 0, -25.6883764, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 34.5550634, 0, 0, 0, 0, 93.8894398, 0, - -30.961635, 0, 0, 0, 0, 0, 78.594791, 0, 0, -63.3427616, 52.6543374, 0, - 0, 38.4578962, 0, 0, -56.5589394, 0, 0, 0.6873802, 0, -83.496155, 0, 0, - 0, 13.0737006, 0, -41.7343216, 71.8170636, 69.0276666, 0, 0, 57.722026, - 0, 0, -93.1746526, 0, 0, 0, 0, -49.9838934 - ], - [ - 0, 4.2310134, -77.9001854, -57.1049418, 0, 53.3411444, 0, 0, 62.3456148, - 0, 0, 68.2636062, 0, 0, -97.5234598, 0, 87.5610236, 0, 0, 0, 0, - -77.3855948, 0, 0, -90.724008, 28.2231562, 0, 53.026918, 0, 0, 0, - -76.15995, 0, 0, 0, 15.413813, 0, 0, 0, 0, 0, 0, 0, 0, 13.0272308, 0, 0, - -23.9738128, 38.7553414, 0, 30.9290494, -35.5982432, 0, 0, 0, - -45.1103148, 0, 0, 0, 70.158022, 0, 0, 0, 0, 0, 54.120183, 0, 0, 0, - -41.9285314, 0, 0, 0, 0, 14.1035676, 33.7857218 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 63.3716696, 0, 0, 0, 0, 0, 24.0919054, 0, 0, 0, - 0, 0, 0, 0, 0, 11.3748388, 0, 0, 95.3405052, 93.4694228, 0, 0, - -45.255791, 0, 0, 0, 0, 0, 0, 0, 0, -1.0475536, 60.84603, 0, 0, - -10.47761, 0, 26.1100158, -51.9159084, 0, 0, 0, 0, 0, 0, -65.6123578, 0, - -91.0146766, 0, 0, 0, 0, 0, 0, 0, -21.2845524, 0, 35.7297864, 0, 0, 0, - 0, 0, 0, 15.911098, 0, 0, -12.9287238 - ], - [ - 0, 0, 4.6786386, 0, 0, 1.6495644, 0, 0, 0, 26.96434, 0, 0, 0, - 58.7515752, 0, 0, 0, -47.6494254, 0, -54.2669514, 72.894442, 0, 0, - 95.889445, 0, 68.8888298, -66.11831, 0, -23.7891422, 79.7630012, 0, 0, - 0, -63.9280642, 0, 0, 0, 0, -32.1729936, 0, 0, -44.1408756, 0, 0, 0, 0, - -43.6440432, 0, 0, 0, 0, 0, 0, 0, 0, 9.0521906, 0, 0, -26.1975436, 0, - 45.9278082, 0, 0, 29.678958, 0, 0, 0, 0, 0, 5.9131246, -82.314248, 0, - -56.8775976, -43.6011182, 0, -28.0599468 - ], - [ - 44.0699428, 0, -0.2569744, 0, 0, 0, 10.53932, 0, -89.8739242, 0, 0, 0, - 0, -39.5334882, -60.036911, 96.86551, 0, -59.6306248, 0, 76.9520134, 0, - 0, 0, 0, 0, 55.2369732, 0, 0, 0, 0, -41.8466046, 0, 0, -5.291202, 0, - -18.5051634, 0, 0, 0, 0, 0, 47.1813778, 92.5194464, 90.690835, - 56.7657076, 0, 0, 0, -42.1944794, 0, 0, 0, -69.1124266, 0, 0, 0, 0, 0, - 0, 0, -14.4018142, -36.9699736, 0, 0, 0, 0, 0, 0, 41.4981516, - -1.5870996, 0, -73.7309526, -68.2179518, 0, 5.1895272, -29.7117264 - ], - [ - 90.3158852, 54.7711894, 0, 0, 0, 0, 0, 0, 0, -92.2564004, -20.8178774, - 0, 17.3192726, 0, 2.5685474, 0, 0, 0, 0, -21.96248, 0, -83.8507778, 0, - 0, 0, 0, -81.769375, 0, 0, 0, -73.8973162, 0, 0, 0, 0, 0, 0, - -96.8790628, 0, -29.2883476, 0, 0, -73.2399312, 0, 0, 0, 56.465223, 0, - -10.1549238, 0, 0, 44.7135732, 0, 0, 0, 0, -37.8912668, 61.0703958, 0, - 0, 0, 94.563183, 2.1777518, 0, 0, 0, 0, 0, 0, 69.8987148, 0, 0, 0, - 58.5987754, 0, -73.701682 - ], - [ - 0, 25.7383596, 0, 43.2784374, 0, 0, 0, 0, 0, 0, 0, 65.3498334, 0, - -51.6680898, 0, 0, 0, -32.4960916, 0, -61.8512302, 0, 0, 0, 0, - 66.0087116, 0, 0, 0, 0, 0, -69.5971312, -68.5339006, 0, -87.9115714, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.6412636, 0, -33.3575526, 0, - -34.6876284, 0, 0, 0, 0, 0, -5.195929, 0, 0, 0, 0, 0, 0, -62.551799, 0, - 0, 0, 0, 0, 0, 0, 0, -85.6796076, 0, -69.9796424 - ], - [ - 0, -67.7487338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -89.686036, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35.3396338, -51.2668318, 0, 0, - -9.4925338, 0, 0, 0, 0, 0, 0, 0, 0, -74.049299, 0, 0, 0, 6.669526, 0, 0, - 0, 0, 82.3839076, 0, 0, 0, -63.2986138, 0, 0, 0, 29.4639612, 0, 0, - -75.2754458, 0, 0, 10.6058324, 83.9439366, 48.4539264, 0, 0, 0, 0, - 8.6922024, 17.82273 - ], - [ - -12.5659444, 0, 0, 0, 0, 0, -16.0039068, 0, 0, 0, 0, -64.5579896, 0, - 25.3425712, 0, 0, 0, 0, -73.3525686, 0, 0, 0, 41.4534476, 0, 0, - 35.6355928, 82.0438356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.5500598, 0, - 17.4573382, -30.4230828, 68.6250598, 0, 0, -70.7101786, 0, 0, 0, 0, 0, - 19.070679, 0, 0, 0, 0, 0, 0, 0, -69.0034118, 0, -32.8881618, 0, - 99.6116696, -41.8557658, -36.91302, 0, 0, 0, 0, 0, 25.1313946, 0, 0, 0, - 0, 0, 66.1785624 - ], - [ - -28.5551034, -60.2641954, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.4765054, 0, 0, - 0, 0, 0, 0, 78.1818898, 0, 0, 0, 0, 34.0574966, 0, 0, 0, 3.3327304, 0, - 0, 0, 0, 0, 0, 0, -25.4031686, -6.4345882, 0, 0, 0, 0, 0, 70.724926, 0, - 0, 0, 0, 0, 0, -34.578727, 0, 0, 0, 0, 73.4821434, 0, 0, 0, 0, - -78.7097278, 0, 0, 0, -56.0390914, -77.1810362, 95.2972308, 0, - -88.304829, 0, -11.4076234, 0, 0, 0, 0, 0, -53.8368524 - ], - [ - 0, 0, 53.7291982, 0, 0, 0, 0, 0, 0, 0, 0, 90.7870612, -66.2974882, 0, - -92.2201462, -15.7252186, 0, 0, 0, 0, 0, -25.4072904, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 32.8926792, 36.9923848, 0, 0, 0, 0, 33.293754, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 5.689557, 0, 0, 89.5416534, 40.4300196, 0, 0, - 2.8394972, 90.7550328, 0, 0, 71.835872, 30.8157976, -96.7796296, 0, - 44.1461388, 0, 0, -32.5545222, 0, 75.597677, 0, 0, 0, 0, 33.146892 - ], - [ - 0, -36.157067, 0, 0, -0.4087578, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.1871604, - 0, 60.6086354, 84.4235272, 0, 0, 89.0383032, 0, 0, 0, -43.0195992, 0, - -99.31608, 0, -64.2154682, 0, 0, 0, 76.5304532, 0, 0, 0, 0, 0, - 82.052369, 0, -72.450166, 0, 0, 0, 23.4129134, 0, 0, 0, 0, 0, 0, 0, 0, - 53.3291088, 0, 0, 0, 73.435697, 87.3597806, 0, -94.1974698, 0, 0, 0, 0, - 59.0496292, 0, 0, 0, -13.8506028, 0, 0, -42.6003178, 0, 0, 55.1715212 - ], - [ - 0, 0, 0, -67.8709314, 0, 0, 0, 0, 0, 0, 0, -9.678326, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -28.014314, 0, 0, - 0, 0, 0, 0, 0, 0, -36.8865788, 0, 0, -98.739389, 0, 0, 0, 0, 0, 0, - -86.2168946, 0, 44.1228816, 0, 0, 0, 0, 0, 0, -36.6609072, 0, 0, 0, 0, - 18.3461886, 98.9990466, 30.4109678, 0, 0, 0, -39.2683046 + 0, + 0, + 49.774821, + -59.5968886, + -46.0773896, + 0, + -65.166109, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 47.0957778, + 15.259961, + -98.7983264, + 0, + 0, + 0, + -20.7757184, + 0, + 87.2645672, + 0, + -22.7888772, + 0, + 0, + 0, + -40.4980904, + -19.7307486, + -23.222078, + 0, + -77.5263128, + 0, + 0, + 56.6204008, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 31.378421, + 0, + 97.3441448, + 35.1309806, + 0, + 0, + -40.5727886, + -50.7308566, + 0, + 0, + -69.9304568, + 0, + 38.5385914, + 0, + -22.1243232, + 0, + 0, + 0, + 0, + -62.5102538, + 8.0801276, + 46.7998066, + -2.3292106, + 0, + 0, + 0, + 8.774031, + 0, + 0, + -65.6505736, ], - [ - -49.3558704, 0, 0, -31.3665258, 0, 0, 0, 0, 0, 0, 1.7897898, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -15.7715374, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -50.0629034, 0, 0, 0, 0, 0, 0, 0, 35.8856254, 0, -51.062155, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 78.136688, 0, 0, 0, 0, -41.5917514, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 73.1472674, 0, -77.9015418 + [ + -67.2935466, + -64.4354852, + -96.6712204, + 0, + 0, + -60.7812272, + 0, + 0, + 0, + 0, + 0, + -7.9966864, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 89.7672338, + 0, + 0, + 0, + 0, + 98.9607046, + 0, + 28.6714432, + 0, + 0, + 0, + -26.2738856, + 0, + 0, + 68.363956, + 0, + 0, + 0, + 0, + 54.7406868, + 0, + 0, + 0, + 0, + 94.2320734, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -2.9647794, + 39.7161716, + -54.7931288, + 0, + 0, + 0, + 0, + -47.2284892, + 0, + 0, + 0, + -8.6421808, + -35.399612, + 0, + 0, + 62.1912668, + 0, + -6.8930716, + 0, + 0, + -17.0801284, + 0, + 0, + 68.6533416, + ], + [ + 0, + 0, + 0, + 81.165396, + 83.773254, + 0, + -25.1603, + 0, + 0, + 50.225725, + 0, + -3.8242274, + 0, + 0, + 36.2078566, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 15.551432, + 0, + 0, + -33.6446236, + 0, + 0, + 0, + 36.6171324, + 0, + 0, + 0, + 0, + 67.9591934, + 0, + 22.1428016, + 0, + -27.2961928, + 0, + 0, + 0, + -97.4961564, + 90.4062526, + 0, + 0, + 0, + -90.0532814, + 0, + 98.8332924, + 0, + 0, + -13.8994926, + 0, + 17.1962884, + 0, + 0, + 0, + -55.1654678, + 0, + 0, + 0, + 85.829554, + 0, + -37.971164, + 64.233136, + -17.9943296, + 0, + 0, + 0, + 0, + -67.7509796, + 0, + 0, + 10.0750712, + ], + [ + 0, + 37.2783148, + 0, + 0, + 0, + 36.4959506, + 0, + 0, + 0, + 0, + 0, + 0, + 61.201323, + 0, + 14.4328522, + 48.4078064, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -47.0969056, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 26.720439, + 0, + 0, + 62.1987576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -65.2085246, + 0, + 0, + 0, + 0, + 0, + 0, + 73.3019432, + -14.3431238, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2.1565846, + 0, + 0, + 0.7733644, + 0, + 5.9090456, + 0, + -39.7724192, + ], + [ + 0, + 0, + 0, + 0, + -24.4555532, + 0, + 0, + -5.5484574, + 0, + 25.4685054, + 0.7906104, + 4.273133, + 0, + 52.12973, + 0, + -12.8040828, + 0, + 0, + 81.888381, + 64.0713498, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62.9088768, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20.869713, + 0, + 0, + -71.5835872, + 0, + 0, + 80.7237808, + 0, + 0, + 0, + 0, + -60.1883708, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 85.0393326, + 23.6045316, + -18.8849834, + 0, + 0, + 0, + -90.8065188, + -9.5037982, + 14.3196654, + 0, + -28.9290306, + 0, + 0, + -41.5575766, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -8.9478934, + 0, + -83.6040618, + 0, + 0, + 0, + 0, + -14.3874822, + 77.2528714, + 0, + 0, + 99.2966066, + 0, + 21.7889114, + -37.7629282, + 0, + -11.6026582, + 0, + 0, + 0, + 0, + 74.422603, + 0, + 0, + -79.239245, + -31.9686324, + 0, + 0, + 0, + 0, + 0, + 0, + -29.8797178, + 0, + 0, + 85.2723062, + 0, + 0, + -8.8031188, + 0, + 0, + -20.043565, + 0, + 0, + -70.733454, + 0, + -94.7231762, + -85.4584516, + 0, + 0, + 0, + 0, + -27.6068624, + 0, + -79.787783, + 0, + 0, + -55.1894266, + ], + [ + 1.9374354, + 0, + 1.4807184, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61.298952, + 0, + 0, + 0, + -90.5702054, + 0, + 67.381115, + 0, + -68.684637, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 93.7807934, + 0, + 8.8213302, + -15.9020466, + 0, + 0, + 0, + 0, + 23.8157662, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67.3461972, + 0, + 0, + 0, + 0, + 0, + 43.3263744, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 87.7149706, + 0, + 0, + 0, + -18.5133574, + -30.0338992, + ], + [ + 6.3937798, + 78.7697644, + 28.4485838, + 0, + 0, + 0.1352466, + 0, + 0, + 74.5767122, + -13.8340168, + 0, + 0, + 0, + 77.2929426, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 17.9243834, + 0, + 0, + 82.2239878, + 0, + 0, + 0, + 0, + 64.3440016, + 90.109577, + 46.8926522, + -2.4494366, + 0, + 84.7413412, + 0, + 0, + -4.216108, + 0, + 0, + -79.8684776, + 0, + 0, + 74.8706758, + -64.4518992, + 0, + 0, + 0, + 0, + -34.4895004, + 0, + 0, + 0, + -74.1158858, + -37.7803516, + 0, + 0, + 0, + 0, + 0, + 80.0054296, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -84.5832026, + 0, + 0, + 71.2540694, + ], + [ + 0, + 0, + 0, + 0, + -74.9257454, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 14.9675444, + 0, + 0, + 0, + 0, + 0, + -27.5236912, + 0, + 0, + -80.4993438, + 0, + -81.8494538, + 0, + 0, + 0, + 0, + 0, + -18.6802002, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61.4131076, + 0, + 0, + -55.1421034, + 0, + 0, + -18.576761, + 72.3500914, + 0, + 0, + 0, + 0, + 0, + 0, + -23.6460116, + 43.1258024, + 0, + 93.701872, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -86.5772554, + ], + [ + 0, + 0, + 93.0762916, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -37.2663938, + 86.8303764, + 0, + 0, + 0, + -51.9596226, + 0, + 35.6722618, + -91.438259, + 0, + 0, + -70.6277108, + 0, + -82.9146992, + 58.0327648, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 13.3727302, + 0, + 0, + 0, + 23.5719942, + 0, + -21.5445476, + 74.1541634, + 60.6365036, + 97.4447708, + 0, + 0, + 0, + 0, + 82.5869498, + 0, + 85.1132108, + 0, + 0, + 0, + 0, + 0, + 0, + 97.4012534, + 0, + 0, + 0, + 0, + -45.1504048, + 1.0619934, + 59.7140264, + 0, + 0, + 0, + 0, + 0, + 4.177461, + 0, + 0, + 0, + -75.7039276, + 0, + 0.0421338, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -71.171869, + 0, + 0, + 8.685266, + 0, + 0, + 0, + 0, + 33.9089574, + 0, + -31.6154498, + 0, + 0, + 0, + 0, + 0, + 5.3182746, + 0, + 0, + 0, + 0, + 0, + 0, + 3.0361166, + -10.364305, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -83.9738444, + 0, + 0, + -7.9170212, + 0, + 0, + 0, + -28.7575682, + 0, + 0, + 0, + -29.9216686, + 0, + 0, + 83.4050918, + -39.5247364, + -6.7028846, + 0, + 0, + 0, + -23.6080482, + 0, + 0, + 0, + 0, + 0, + 0, + -18.380154, + 46.9252306, + 0, + 0, + 26.1618372, + 99.6235254, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52.008307, + 0, + 0, + 0, + 0, + 0, + 92.4974102, + -76.3015714, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -56.4879132, + 0, + 0, + 0, + 50.1473938, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40.2219566, + 0, + 0, + 0, + 84.5162074, + 0, + 0, + 0, + -73.3030606, + 0, + -10.9258316, + 0, + 0, + 0, + 0, + 97.5496436, + -70.5026182, + 0, + 62.3611696, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96.4362226, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -6.0104764, + 15.7466756, + ], + [ + -38.7678174, + 0, + 0, + 0, + 0, + 10.5238486, + 0, + 0, + 0, + 31.6876676, + 79.6111978, + 0, + 0, + 0, + 0, + 45.7314046, + 0, + 0, + 0, + -10.0125122, + 0, + 93.3170242, + -96.4566224, + -5.853298, + 0, + -82.2848728, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 43.3427638, + 24.6186934, + 0, + 44.859548, + 0, + -63.8196424, + 0, + 32.6630616, + 41.48423, + 0, + -42.9613722, + 0, + 0, + -68.8954844, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 27.7424034, + -33.4867534, + -49.1827758, + 0, + 18.7014116, + 0, + 0, + 59.049662, + 0, + 0, + 0, + 0, + 0, + 0, + -29.6305028, + 0, + 0, + 0, + 0, + 0, + 98.2266078, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -14.6583468, + 0, + -74.4490466, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -94.9604028, + 0, + 0, + -48.28403, + -41.3534342, + 0, + 0, + 0, + 62.9532972, + 0, + 0, + 4.030284, + 0, + -60.478996, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -46.5887848, + 39.4565458, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -48.5071236, + 0, + 0, + 0, + 41.8640204, + 0, + 0, + 0, + 74.271524, + 0, + 15.5769242, + 0, + -61.4793904, + 52.4500934, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50.8387116, + 0, + 46.490051, + 0, + -54.9751352, + 0, + 43.0696416, + 0, + 0, + 0, + 0, + 80.5337704, + 0, + 0, + -16.0325234, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63.186351, + 0, + 0, + 0, + 0, + 0, + 75.2218604, + -27.3783446, + 0, + 0, + 0, + 0, + -85.021934, + 60.9043202, + 55.7344594, + 0, + 41.1687556, + 0, + 5.574124, + 0, + 0, + -5.0028254, + -40.2614834, + 0, + 0, + 0, + 0, + 0, + 0, + 22.207679, + 0, + 0, + 0, + 0, + 0, + 65.0504204, + 0, + -61.4580018, + -90.137276, + 19.2277196, + 0, + 0, + -73.8615034, + ], + [ + 0, + 0, + 0, + 0, + 0, + -21.3303052, + -75.4586018, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 84.6730538, + -3.4478344, + 0, + -76.083503, + 19.9372656, + -38.9938322, + 0, + 0, + 36.0135034, + 0, + -56.8457144, + 0, + 0, + 63.4198336, + 0, + 0, + 0, + 0, + 0, + -63.4419798, + 28.5629988, + 0, + 0, + 0, + 0, + 38.8001126, + 0, + 0, + 47.2148438, + 0, + -19.256673, + -24.8778354, + -47.8193252, + 0, + 0, + -38.7279908, + 0, + 0, + 0, + -34.5546658, + 0, + -96.7675822, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 27.9437518, + ], + [ + 0, + -17.8703906, + 0, + 0, + -47.2114204, + 0, + -73.3595682, + 66.668341, + 0, + 0, + 0, + 0, + 30.250278, + 0, + -40.452007, + 0, + 0, + 0, + 0, + 0, + -37.5013244, + 34.3045856, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 12.898457, + -15.2838318, + 0, + 0, + 0, + 0, + 0, + 16.333021, + 0, + 72.0452526, + 0, + 2.285458, + 0, + 0, + 0, + 0, + -17.8073046, + 0, + 0, + 0, + 81.7271146, + 0, + 0, + -17.7174538, + 0, + -62.6694996, + 8.7298318, + 0, + 0, + 0, + 0, + 0, + 0, + -27.1131974, + 0, + -68.0964272, + ], + [ + 0, + 0, + 0, + 0, + -22.1791216, + 16.588597, + -19.4545486, + 0, + -74.8318248, + -74.4252462, + 0, + 0, + 0, + -46.1656546, + 0, + 0, + -21.1620788, + 0, + -25.6883764, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 34.5550634, + 0, + 0, + 0, + 0, + 93.8894398, + 0, + -30.961635, + 0, + 0, + 0, + 0, + 0, + 78.594791, + 0, + 0, + -63.3427616, + 52.6543374, + 0, + 0, + 38.4578962, + 0, + 0, + -56.5589394, + 0, + 0, + 0.6873802, + 0, + -83.496155, + 0, + 0, + 0, + 13.0737006, + 0, + -41.7343216, + 71.8170636, + 69.0276666, + 0, + 0, + 57.722026, + 0, + 0, + -93.1746526, + 0, + 0, + 0, + 0, + -49.9838934, ], [ - 0, -6.5048614, -28.611729, 0, 0, -97.9680564, 33.7007078, -70.5347856, - 0, 17.197908, 0, 19.8776858, -24.4246618, 0, 0, 53.363481, 0, 0, 0, - -44.7872848, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -74.0599438, - -81.2162694, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -68.6473592, 0, 39.894997, 0, 0, -38.5305628, 0, -5.244101, 0, 0, - 46.6040974, 2.4384956, 0, 0, -26.8264528, 0, 0, -98.5537452, 2.6463192, - 0, 0, 0, 0.4769732 + 0, + 4.2310134, + -77.9001854, + -57.1049418, + 0, + 53.3411444, + 0, + 0, + 62.3456148, + 0, + 0, + 68.2636062, + 0, + 0, + -97.5234598, + 0, + 87.5610236, + 0, + 0, + 0, + 0, + -77.3855948, + 0, + 0, + -90.724008, + 28.2231562, + 0, + 53.026918, + 0, + 0, + 0, + -76.15995, + 0, + 0, + 0, + 15.413813, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 13.0272308, + 0, + 0, + -23.9738128, + 38.7553414, + 0, + 30.9290494, + -35.5982432, + 0, + 0, + 0, + -45.1103148, + 0, + 0, + 0, + 70.158022, + 0, + 0, + 0, + 0, + 0, + 54.120183, + 0, + 0, + 0, + -41.9285314, + 0, + 0, + 0, + 0, + 14.1035676, + 33.7857218, ], [ - 0, 0, 0, 0, -52.7430298, 1.8510158, -39.691072, 0, 0, 0, 0, 0, - 95.6497418, 0, 0, -48.04896, 0, -26.6728378, 0, 0, 0, 0, 0, -12.3921976, - -65.5861706, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, -57.9815088, 0, 77.6808808, 0, 0, 0, 94.6506526, 0, 0, 0, - 55.8427672, 0, 0, -0.6995066, 0, -78.3071326, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -67.9654476 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63.3716696, + 0, + 0, + 0, + 0, + 0, + 24.0919054, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11.3748388, + 0, + 0, + 95.3405052, + 93.4694228, + 0, + 0, + -45.255791, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -1.0475536, + 60.84603, + 0, + 0, + -10.47761, + 0, + 26.1100158, + -51.9159084, + 0, + 0, + 0, + 0, + 0, + 0, + -65.6123578, + 0, + -91.0146766, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -21.2845524, + 0, + 35.7297864, + 0, + 0, + 0, + 0, + 0, + 0, + 15.911098, + 0, + 0, + -12.9287238, ], [ - 0, -23.0019946, 0, 0, 95.4877116, 0, 0, 0, 0, 0, -36.573767, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90.1862622, -36.4728966, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86.6126918, 0, 0, 0, 0, 0, 0, - 0, -80.1067184, 0, 31.8472788, 27.496628, -66.6206162, 0, 0, 9.1957296, - 0, 37.2257526, 0, 0, 0, 0, 0, 0, 0, -39.1637322, 0, 0, 74.4924622 + 0, + 0, + 4.6786386, + 0, + 0, + 1.6495644, + 0, + 0, + 0, + 26.96434, + 0, + 0, + 0, + 58.7515752, + 0, + 0, + 0, + -47.6494254, + 0, + -54.2669514, + 72.894442, + 0, + 0, + 95.889445, + 0, + 68.8888298, + -66.11831, + 0, + -23.7891422, + 79.7630012, + 0, + 0, + 0, + -63.9280642, + 0, + 0, + 0, + 0, + -32.1729936, + 0, + 0, + -44.1408756, + 0, + 0, + 0, + 0, + -43.6440432, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9.0521906, + 0, + 0, + -26.1975436, + 0, + 45.9278082, + 0, + 0, + 29.678958, + 0, + 0, + 0, + 0, + 0, + 5.9131246, + -82.314248, + 0, + -56.8775976, + -43.6011182, + 0, + -28.0599468, ], [ - 0, 0, -25.4147588, 6.2424662, 0, 0, 0, 0, 0, 0, 92.5623938, -92.810452, - 0, 0, 0, 0, 0, 0, 0, 0, 0, -45.0048688, 0, 0, -32.1678062, 0, 0, 0, - 9.8719598, -33.7145476, -16.3449354, 0, 70.462643, 0, 0, 14.5356206, 0, - 0, 0, 0, -95.1218374, 0, 0, 0, 0, -0.8077516, 0, 0, 0, 0, 0, 53.7434994, - 0, 0, 0, 0, 0, 5.376533, 0, 0, 0, 0, 0, 0, -1.125953, 75.3929928, 0, 0, - 0, 0, 0, -17.8555478, 0, 0, 87.130332, -46.977091 + 44.0699428, + 0, + -0.2569744, + 0, + 0, + 0, + 10.53932, + 0, + -89.8739242, + 0, + 0, + 0, + 0, + -39.5334882, + -60.036911, + 96.86551, + 0, + -59.6306248, + 0, + 76.9520134, + 0, + 0, + 0, + 0, + 0, + 55.2369732, + 0, + 0, + 0, + 0, + -41.8466046, + 0, + 0, + -5.291202, + 0, + -18.5051634, + 0, + 0, + 0, + 0, + 0, + 47.1813778, + 92.5194464, + 90.690835, + 56.7657076, + 0, + 0, + 0, + -42.1944794, + 0, + 0, + 0, + -69.1124266, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -14.4018142, + -36.9699736, + 0, + 0, + 0, + 0, + 0, + 0, + 41.4981516, + -1.5870996, + 0, + -73.7309526, + -68.2179518, + 0, + 5.1895272, + -29.7117264, ], [ - -57.0064908, 0, -61.469472, 0, 0, 94.2906142, 0, 10.1214686, 0, 0, 0, 0, - 0, 0, 0, 0, 90.8859632, 0, -31.3550928, 25.4391198, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, -30.6974596, 0, 16.8162692, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -40.946388, -23.914437, -39.0760436, 0, 0, 12.4664916, 0, 0, 0, - 59.3854694, 0, 0, -79.029102, 0, 48.0444832, 0, 0, 0, 0, 0, 0, 0, - -34.447419, 0, 0, 0, 0, 0, 0, 0, 0, 0, -42.8371356 - ], + 90.3158852, + 54.7711894, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -92.2564004, + -20.8178774, + 0, + 17.3192726, + 0, + 2.5685474, + 0, + 0, + 0, + 0, + -21.96248, + 0, + -83.8507778, + 0, + 0, + 0, + 0, + -81.769375, + 0, + 0, + 0, + -73.8973162, + 0, + 0, + 0, + 0, + 0, + 0, + -96.8790628, + 0, + -29.2883476, + 0, + 0, + -73.2399312, + 0, + 0, + 0, + 56.465223, + 0, + -10.1549238, + 0, + 0, + 44.7135732, + 0, + 0, + 0, + 0, + -37.8912668, + 61.0703958, + 0, + 0, + 0, + 94.563183, + 2.1777518, + 0, + 0, + 0, + 0, + 0, + 0, + 69.8987148, + 0, + 0, + 0, + 58.5987754, + 0, + -73.701682, + ], [ - -46.3742298, 0, 0, 0, 0, 6.4096268, 0, 0, 0, 0, 0, 0, 0, 0, 53.7055136, - 41.0589284, 0, 0, 0, 0, 0, 0, -59.494163, 78.2644798, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 3.5467882, 0, 0, 0, 0, 0, -33.8146284, 0, 81.1209896, - 0, 0, 0, 0, 0, 0, -59.120982, 0, 0, 0, 0, 20.5082176, 0, 0, -32.2137818, - 41.6679682, 98.4426286, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 18.7911844 - ], + 0, + 25.7383596, + 0, + 43.2784374, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65.3498334, + 0, + -51.6680898, + 0, + 0, + 0, + -32.4960916, + 0, + -61.8512302, + 0, + 0, + 0, + 0, + 66.0087116, + 0, + 0, + 0, + 0, + 0, + -69.5971312, + -68.5339006, + 0, + -87.9115714, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 13.6412636, + 0, + -33.3575526, + 0, + -34.6876284, + 0, + 0, + 0, + 0, + 0, + -5.195929, + 0, + 0, + 0, + 0, + 0, + 0, + -62.551799, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -85.6796076, + 0, + -69.9796424, + ], [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 22.5196126, -44.92426, 0, 0, 0, 0, -78.1154748, 95.3654376, 0, 0, 0, - -42.4266782, 73.3850132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94.1878774, - 90.3854666, 0, 0, 0, 0, 0, -15.9053536, -74.9423846, 47.214, 0, 0, - 7.477562, 0, 46.2206928, 19.1508454, 41.6978146, 39.03286, 0, 0, 0, 0, - 0, -14.259302, 0, 54.0542232, 0, 0, 44.5438142 + 0, + -67.7487338, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -89.686036, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 35.3396338, + -51.2668318, + 0, + 0, + -9.4925338, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -74.049299, + 0, + 0, + 0, + 6.669526, + 0, + 0, + 0, + 0, + 82.3839076, + 0, + 0, + 0, + -63.2986138, + 0, + 0, + 0, + 29.4639612, + 0, + 0, + -75.2754458, + 0, + 0, + 10.6058324, + 83.9439366, + 48.4539264, + 0, + 0, + 0, + 0, + 8.6922024, + 17.82273, ], [ - 95.3632006, 43.6928354, 75.8291588, -81.2577418, 0, 0, -91.248437, 0, 0, - 22.476879, -77.967431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.0308978, - -22.0727056, 0, 0, 0, 0, 0, 0, 0, 17.126454, 0, -45.7583606, 0, 0, - 32.9187018, 0, 0, 0, 0, 0, -58.6847152, 0, 0, 39.113676, 0, 0, 0, 0, 0, - 0, -80.1176538, 0, -86.9570556, 0, 0, 0, -9.3462492, 0, 49.3616864, 0, - 60.4773586, 0, 0, 48.9766746, 17.5735282, 75.126033, 0, -50.8306992, 0, - 0, 61.3438194, 0, 0, -95.9051914, 0, 25.6497354 + -12.5659444, + 0, + 0, + 0, + 0, + 0, + -16.0039068, + 0, + 0, + 0, + 0, + -64.5579896, + 0, + 25.3425712, + 0, + 0, + 0, + 0, + -73.3525686, + 0, + 0, + 0, + 41.4534476, + 0, + 0, + 35.6355928, + 82.0438356, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 16.5500598, + 0, + 17.4573382, + -30.4230828, + 68.6250598, + 0, + 0, + -70.7101786, + 0, + 0, + 0, + 0, + 0, + 19.070679, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -69.0034118, + 0, + -32.8881618, + 0, + 99.6116696, + -41.8557658, + -36.91302, + 0, + 0, + 0, + 0, + 0, + 25.1313946, + 0, + 0, + 0, + 0, + 0, + 66.1785624, ], [ - -89.5581772, 0, 0, 0, 0, -37.7576814, 0, 0, 0, -50.475431, 0, 0, 0, 0, - -75.575654, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6.7066106, 0, 0, 0, 68.702018, 0, - 0, 44.2841378, 0, 0, 0, 0, 0, 0, 0, 42.4183476, 80.6872178, 72.028214, - 0, 0, 0, -50.6912368, 0, 0, 0, 53.1913708, 0, 0, 0, 0, -50.0798868, 0, - 0, 0, 0, 0, 0, -99.54189, 0, 0, 0, 0, 87.8828622, 7.144766, 0, 0, 0, - 71.8161494, 91.0414654, 0, 0, -7.240427 - ], + -28.5551034, + -60.2641954, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11.4765054, + 0, + 0, + 0, + 0, + 0, + 0, + 78.1818898, + 0, + 0, + 0, + 0, + 34.0574966, + 0, + 0, + 0, + 3.3327304, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -25.4031686, + -6.4345882, + 0, + 0, + 0, + 0, + 0, + 70.724926, + 0, + 0, + 0, + 0, + 0, + 0, + -34.578727, + 0, + 0, + 0, + 0, + 73.4821434, + 0, + 0, + 0, + 0, + -78.7097278, + 0, + 0, + 0, + -56.0390914, + -77.1810362, + 95.2972308, + 0, + -88.304829, + 0, + -11.4076234, + 0, + 0, + 0, + 0, + 0, + -53.8368524, + ], + [ + 0, + 0, + 53.7291982, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 90.7870612, + -66.2974882, + 0, + -92.2201462, + -15.7252186, + 0, + 0, + 0, + 0, + 0, + -25.4072904, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32.8926792, + 36.9923848, + 0, + 0, + 0, + 0, + 33.293754, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5.689557, + 0, + 0, + 89.5416534, + 40.4300196, + 0, + 0, + 2.8394972, + 90.7550328, + 0, + 0, + 71.835872, + 30.8157976, + -96.7796296, + 0, + 44.1461388, + 0, + 0, + -32.5545222, + 0, + 75.597677, + 0, + 0, + 0, + 0, + 33.146892, + ], + [ + 0, + -36.157067, + 0, + 0, + -0.4087578, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 13.1871604, + 0, + 60.6086354, + 84.4235272, + 0, + 0, + 89.0383032, + 0, + 0, + 0, + -43.0195992, + 0, + -99.31608, + 0, + -64.2154682, + 0, + 0, + 0, + 76.5304532, + 0, + 0, + 0, + 0, + 0, + 82.052369, + 0, + -72.450166, + 0, + 0, + 0, + 23.4129134, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53.3291088, + 0, + 0, + 0, + 73.435697, + 87.3597806, + 0, + -94.1974698, + 0, + 0, + 0, + 0, + 59.0496292, + 0, + 0, + 0, + -13.8506028, + 0, + 0, + -42.6003178, + 0, + 0, + 55.1715212, + ], + [ + 0, + 0, + 0, + -67.8709314, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -9.678326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -28.014314, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -36.8865788, + 0, + 0, + -98.739389, + 0, + 0, + 0, + 0, + 0, + 0, + -86.2168946, + 0, + 44.1228816, + 0, + 0, + 0, + 0, + 0, + 0, + -36.6609072, + 0, + 0, + 0, + 0, + 18.3461886, + 98.9990466, + 30.4109678, + 0, + 0, + 0, + -39.2683046, + ], + [ + -49.3558704, + 0, + 0, + -31.3665258, + 0, + 0, + 0, + 0, + 0, + 0, + 1.7897898, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -15.7715374, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -50.0629034, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 35.8856254, + 0, + -51.062155, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 78.136688, + 0, + 0, + 0, + 0, + -41.5917514, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 73.1472674, + 0, + -77.9015418, + ], + [ + 0, + -6.5048614, + -28.611729, + 0, + 0, + -97.9680564, + 33.7007078, + -70.5347856, + 0, + 17.197908, + 0, + 19.8776858, + -24.4246618, + 0, + 0, + 53.363481, + 0, + 0, + 0, + -44.7872848, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -74.0599438, + -81.2162694, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -68.6473592, + 0, + 39.894997, + 0, + 0, + -38.5305628, + 0, + -5.244101, + 0, + 0, + 46.6040974, + 2.4384956, + 0, + 0, + -26.8264528, + 0, + 0, + -98.5537452, + 2.6463192, + 0, + 0, + 0, + 0.4769732, + ], + [ + 0, + 0, + 0, + 0, + -52.7430298, + 1.8510158, + -39.691072, + 0, + 0, + 0, + 0, + 0, + 95.6497418, + 0, + 0, + -48.04896, + 0, + -26.6728378, + 0, + 0, + 0, + 0, + 0, + -12.3921976, + -65.5861706, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -57.9815088, + 0, + 77.6808808, + 0, + 0, + 0, + 94.6506526, + 0, + 0, + 0, + 55.8427672, + 0, + 0, + -0.6995066, + 0, + -78.3071326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -67.9654476, + ], + [ + 0, + -23.0019946, + 0, + 0, + 95.4877116, + 0, + 0, + 0, + 0, + 0, + -36.573767, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 90.1862622, + -36.4728966, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 86.6126918, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -80.1067184, + 0, + 31.8472788, + 27.496628, + -66.6206162, + 0, + 0, + 9.1957296, + 0, + 37.2257526, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -39.1637322, + 0, + 0, + 74.4924622, + ], + [ + 0, + 0, + -25.4147588, + 6.2424662, + 0, + 0, + 0, + 0, + 0, + 0, + 92.5623938, + -92.810452, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -45.0048688, + 0, + 0, + -32.1678062, + 0, + 0, + 0, + 9.8719598, + -33.7145476, + -16.3449354, + 0, + 70.462643, + 0, + 0, + 14.5356206, + 0, + 0, + 0, + 0, + -95.1218374, + 0, + 0, + 0, + 0, + -0.8077516, + 0, + 0, + 0, + 0, + 0, + 53.7434994, + 0, + 0, + 0, + 0, + 0, + 5.376533, + 0, + 0, + 0, + 0, + 0, + 0, + -1.125953, + 75.3929928, + 0, + 0, + 0, + 0, + 0, + -17.8555478, + 0, + 0, + 87.130332, + -46.977091, + ], + [ + -57.0064908, + 0, + -61.469472, + 0, + 0, + 94.2906142, + 0, + 10.1214686, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 90.8859632, + 0, + -31.3550928, + 25.4391198, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -30.6974596, + 0, + 16.8162692, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -40.946388, + -23.914437, + -39.0760436, + 0, + 0, + 12.4664916, + 0, + 0, + 0, + 59.3854694, + 0, + 0, + -79.029102, + 0, + 48.0444832, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -34.447419, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -42.8371356, + ], + [ + -46.3742298, + 0, + 0, + 0, + 0, + 6.4096268, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53.7055136, + 41.0589284, + 0, + 0, + 0, + 0, + 0, + 0, + -59.494163, + 78.2644798, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3.5467882, + 0, + 0, + 0, + 0, + 0, + -33.8146284, + 0, + 81.1209896, + 0, + 0, + 0, + 0, + 0, + 0, + -59.120982, + 0, + 0, + 0, + 0, + 20.5082176, + 0, + 0, + -32.2137818, + 41.6679682, + 98.4426286, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 18.7911844, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 22.5196126, + -44.92426, + 0, + 0, + 0, + 0, + -78.1154748, + 95.3654376, + 0, + 0, + 0, + -42.4266782, + 73.3850132, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 94.1878774, + 90.3854666, + 0, + 0, + 0, + 0, + 0, + -15.9053536, + -74.9423846, + 47.214, + 0, + 0, + 7.477562, + 0, + 46.2206928, + 19.1508454, + 41.6978146, + 39.03286, + 0, + 0, + 0, + 0, + 0, + -14.259302, + 0, + 54.0542232, + 0, + 0, + 44.5438142, + ], + [ + 95.3632006, + 43.6928354, + 75.8291588, + -81.2577418, + 0, + 0, + -91.248437, + 0, + 0, + 22.476879, + -77.967431, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3.0308978, + -22.0727056, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 17.126454, + 0, + -45.7583606, + 0, + 0, + 32.9187018, + 0, + 0, + 0, + 0, + 0, + -58.6847152, + 0, + 0, + 39.113676, + 0, + 0, + 0, + 0, + 0, + 0, + -80.1176538, + 0, + -86.9570556, + 0, + 0, + 0, + -9.3462492, + 0, + 49.3616864, + 0, + 60.4773586, + 0, + 0, + 48.9766746, + 17.5735282, + 75.126033, + 0, + -50.8306992, + 0, + 0, + 61.3438194, + 0, + 0, + -95.9051914, + 0, + 25.6497354, + ], + [ + -89.5581772, + 0, + 0, + 0, + 0, + -37.7576814, + 0, + 0, + 0, + -50.475431, + 0, + 0, + 0, + 0, + -75.575654, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6.7066106, + 0, + 0, + 0, + 68.702018, + 0, + 0, + 44.2841378, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 42.4183476, + 80.6872178, + 72.028214, + 0, + 0, + 0, + -50.6912368, + 0, + 0, + 0, + 53.1913708, + 0, + 0, + 0, + 0, + -50.0798868, + 0, + 0, + 0, + 0, + 0, + 0, + -99.54189, + 0, + 0, + 0, + 0, + 87.8828622, + 7.144766, + 0, + 0, + 0, + 71.8161494, + 91.0414654, + 0, + 0, + -7.240427, + ], + [ + 0, + -73.397517, + 0, + 0, + 0, + 0, + -42.4633614, + 0, + 0, + 0, + 0, + -2.3988294, + -60.1970288, + -31.1370786, + -16.4428054, + 0, + -86.5694254, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -33.759363, + 0, + 88.9440556, + 0, + 0, + 0, + 48.8687358, + 0, + 0, + 60.1841648, + 0, + 0, + 81.5798018, + 0, + 0, + 0, + 0, + 22.265044, + 0, + 0, + 0, + 0, + -98.612946, + 0, + 0, + 0, + -49.44052, + 0, + 46.9606012, + 0, + 0, + 0, + -23.7990468, + 0, + 0, + 0, + 0, + -72.1702852, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 26.2623134, + 0, + -17.3386012, + ], + [ + 0, + 16.4596174, + 0, + 0, + 0, + 0, + -85.6599392, + 0, + 0, + 0, + 0, + 0, + 0, + 93.708794, + 0, + -37.5698758, + 0, + 0, + 0, + 0, + 0, + 0, + -82.8927936, + 0, + 82.4183808, + 0, + 0, + 0, + 0, + 0, + 0, + 55.028266, + 0, + 0, + 11.3484192, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -60.7651522, + -4.822581, + 0, + 29.0627604, + 0, + 0, + 61.7042716, + 41.5363722, + 73.9967868, + 0, + 0, + 0, + 0, + 50.2308124, + 0, + 33.8231702, + 0, + 0, + 0, + 0, + 0, + -14.7226996, + 14.5401778, + 0, + -72.8145596, + 19.9220286, + -76.4609286, + ], [ - 0, -73.397517, 0, 0, 0, 0, -42.4633614, 0, 0, 0, 0, -2.3988294, - -60.1970288, -31.1370786, -16.4428054, 0, -86.5694254, 0, 0, 0, 0, 0, 0, - 0, 0, -33.759363, 0, 88.9440556, 0, 0, 0, 48.8687358, 0, 0, 60.1841648, - 0, 0, 81.5798018, 0, 0, 0, 0, 22.265044, 0, 0, 0, 0, -98.612946, 0, 0, - 0, -49.44052, 0, 46.9606012, 0, 0, 0, -23.7990468, 0, 0, 0, 0, - -72.1702852, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26.2623134, 0, -17.3386012 - ], - [ - 0, 16.4596174, 0, 0, 0, 0, -85.6599392, 0, 0, 0, 0, 0, 0, 93.708794, 0, - -37.5698758, 0, 0, 0, 0, 0, 0, -82.8927936, 0, 82.4183808, 0, 0, 0, 0, - 0, 0, 55.028266, 0, 0, 11.3484192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, -60.7651522, -4.822581, 0, 29.0627604, 0, 0, 61.7042716, - 41.5363722, 73.9967868, 0, 0, 0, 0, 50.2308124, 0, 33.8231702, 0, 0, 0, - 0, 0, -14.7226996, 14.5401778, 0, -72.8145596, 19.9220286, -76.4609286 - ], - [ - 11.1687196, 0, 0, 0, 0, 0, 0, 0, -37.365205, 0, 0, 0, 52.0314298, 0, - -58.0558462, 0, 0, 18.6738906, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 59.6046802, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.5944948, 0, 0, 0, - 77.0271992, 46.521059, 61.8258634, 0, 0, 36.079546, 0, -14.3080494, 0, - -50.1618478, 0, 0, 95.9445656, 0, 0, 0, 0, 0, 0, 0, 0, 0, -53.478982, - -90.3623976, 0, 0, -37.1575466 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 78.882051, 0, 0, 57.8056512, 7.2056626, 0, - 2.822132, 40.311822, 0, 0, 0, 83.8935006, 0, 0, 91.2774482, 3.160849, - 91.7410132, 0, 0, 0, 0, 0, 2.7544652, 0, 0, 0, 0, 0, 16.8419108, 0, 0, - 84.1171174, 0, 21.3119752, -69.869284, 0, 0, 0, 0, 0, 92.1087118, 0, 0, - 0, 0, 34.3473744, 21.9890278, 0, -36.3139526, 0, 0, 0, 0, 0, 25.613497, - -2.989159, 0, 0, 0, 0, -49.2456622, 0, 0, 27.3140788, 0, 49.210258, 0, - 0, -90.6896972 - ], - [ - 0, 0, 0, 0, 0, 0, -56.1947046, 0, 0, 52.6617176, 61.6283016, 0, 0, 0, 0, - 0, 0, 0, 0, 0, -34.0759312, 0, 92.0200254, 0, 0, -9.7999914, 0, 0, - 34.0572616, 0, 0, 0, 0, 0, 59.7637334, 0, 0, 0, 0, 0, 43.5437732, - 29.9690024, 0, 0, 93.9438036, 0, 0, 0, 52.3867986, 0, 0, 0, 38.0567542, - 0, -63.9851954, 0, 73.1679634, 0, 0, -28.6636244, 0, 0, 0, 0, 0, - 39.2894916, 0, -28.4364668, 0, 0, 0, 0, 0, 0, 0, -32.2899456 - ], - [ - 15.3399588, 0, 0, 0, 0, 0, 0, -66.2540916, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 14.7613734, 0, 0, 0, -32.9550622, 0, 0, -17.2710502, - 0, 0, 0, -66.7611862, 76.5708962, 93.3868072, 12.036471, 0, 0, 0, - -74.2189184, 0, 0, 0, 0, 58.0343866, 0, 71.0145124, 0, 0, 0, 0, 0, 0, 0, - 80.7131208, 0, -49.5191686, 40.2489602, 0, 0, 39.9664558, 0, 0, 0, 0, 0, - 0, 51.704979, 0, 20.9209752, 0, 0, -63.5800814 - ], - [ - 0, 0, 35.1676872, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.326191, 0, 0, - 0, -42.1405488, 0, 0, 0, 0, -64.9203894, 0, 0, 0, 86.5653072, 0, - 17.1250418, 0, 15.224998, -32.788739, 0, 0, 0, 13.1550612, 0, 0, 0, - 93.3049176, 65.5504482, 0, 35.8126186, -16.2474202, 0, -76.7788518, - -51.9001008, 0, -89.725966, 53.4895596, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -82.6077236, 0, 0, 0, -57.075872, -52.5166376, 99.9479554, 0, 0, - -89.7491862, 0, 18.6163306, 0, 29.1454254 - ], - [ - -18.8864514, 0, 0, 0, 0, 0, -46.6968628, -83.8123266, 0, 0, 0, 0, 0, 0, - -91.631792, 0, 0, 0, 0, -57.3455082, 66.459894, 0, 0, 0, -73.9341878, 0, - 0, 0, 81.8859882, 0, 0, 0, 0, 0, 28.0747908, 0, 0, 99.7796988, 0, - 87.0296656, 0, 0, 43.7722526, -60.65313, 98.6287198, 0, 20.8634118, 0, - 0, 25.6519386, 0.4939554, 0, 0, 0, 0, 0, 0, 0, 0, -74.9266526, 0, 0, - -25.4234142, 0, -91.054582, 0, -42.534282, 0, 0, 0, 0, 0, 0, 0, 0, - 12.1491094 - ], - [ - 0, -90.5331764, 0, 0, 0, 0, 0, -8.1166918, 0, 0, 0, 0, 0, -34.0013692, - 21.9272646, 0, 0, 0, 0, 0, 93.2245032, 0, 21.8275426, 0, 0, 0, - 38.5279608, -6.0022692, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 39.644863, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -93.7962256, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 11.5363598, 70.354452, 0, 0, 0, 32.2282298, 0, - 36.659058, 0, 0, 53.992344 - ], - [ - -74.4430478, 0, 0, -93.4496218, 14.6437598, 0, 0, 0, -33.8297902, 0, 0, - 0, 0, 0, 0, -35.949213, 0, 0, -93.1678628, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, -22.8806328, 0, 0, 0, 0, 0, 0, 0, 45.1466816, 88.4821604, 0, 0, - -49.7769388, 0, 0, 0, 0, 0, 0, 53.6290382, -40.4388272 - ], - [ - 71.2065308, 94.4966808, 0, 0, 0, 0, -84.2404402, 0, 0, 0, 0, 0, - 61.7641462, 0, 0, 0, -0.608018, -94.7698384, 0, 0, 0, 0, -3.4562834, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.0708126, -73.3657794, - 26.6482556, 0, 0, 0, 0, 0, 0, -66.9699546, -98.6726948, 0, 0, - -25.4137958, 0, -88.7254432, -81.2735544, 0, -23.3081526, 0, - -51.8917252, 0, 0, 0, 0, 0, 0, 0, 0, -52.3004828, 0, 0, 0, 0, - 67.8542398, 0, -67.7489638, 32.2212522 - ], - [ - 0, 77.0252092, 0, 0, 0, 0, 0, -85.3271924, 0, 0, 0, -2.9308596, 0, - 83.448547, 0, 4.7835886, 0, 0, 0, 0, 76.590577, 0, 0, 0, 0, 0, - 86.0180794, 0, 0, -88.4030016, 0, 0, 0, -13.770426, 57.6068646, 0, 0, 0, - 0, 0, 12.6896788, 0, 0, 0, -78.1078136, -92.3074796, 0, 0, 0, 0, - -94.3200626, 0, 0, -7.987837, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 52.7655986, 0, -78.2727176, 0, 74.4309632, 48.3867546, -85.4142468, 0, - 91.3888914, -6.0372808, 51.1643348 - ], - [ - -77.4193002, 0, 0, 0, 0, 0, 0, 0, 41.0846988, -78.3265056, 0, 0, 0, 0, - 0, 0, 0, -20.408123, 10.7055032, 0, -19.5848354, 0, -32.624054, 0, - 47.333306, 0, -41.6545398, 0, 0, 0, 0, 0, 46.8131656, 0, 52.5387768, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -75.3511542, 0, 0, 0, 0, -27.6140242, 0, 0, - -63.3032372, 0, 0, 0, 0, 0, 0, 0, 0, -93.1854838, 0, 0, 0, 0, - 32.3353436, -75.8659438, 89.852816, 0, 9.7044216, 0, 94.9239572, 0, - -57.0391726, 25.4894998 - ], - [ - 0, 0, 0, -27.2226392, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -89.0780816, - 0, 0, 0, 0, 6.6342408, 0, 0, 92.3634346, -41.5559582, 0, 0, 0, 0, 0, 0, - -4.3499792, 18.9554522, 0, 29.992962, 0, 0, 0, 0, 0, 0, 0, 0, - -51.9952794, -40.3571344, 0, -61.0098328, 0, 0, 0, 0, 71.8571838, - 97.3056784, 0, 0, 27.798026, 0, 0, 20.3198544, 0, 0, 0, -82.1043534, 0, - 0, 0, 0, 0, 55.2807134, 0, 0, 0, 0, -90.8562594, -43.2271604 - ], - [ - 0, 0, 0, 0, 0, 15.2015056, 18.3740448, -11.2614058, 0, 0, 0, 0, 0, - 53.0086248, 0, -91.038908, 0, 0, 88.2707986, 3.2580272, 0, 0, 0, 0, 0, - 0, 0, 0, 88.8189254, 0, 0, 0, 0, -3.742353, 0, 0, 0, -41.4198488, - 90.0966416, 0, -22.474875, 0, -25.610863, -79.5943706, 0, 0, 0, 0, 0, 0, - 0, -3.5616438, 0, 0, 0, 88.5984932, 0, 0, 0, 0, 0, 0, 0, 22.4398688, 0, - 0, 0, 0, 0, 0, 0, -63.7422676, 0, 0, 0, -82.7150752 - ], - [ - 0, 0, 75.634243, -60.420708, 0, 0, 0, 0.8383984, 0, 0, -72.2791914, 0, - 0, 0, 17.1476022, 0, 0, -14.5930276, 9.4352026, 0, -30.1475326, 0, - -53.7249052, 0, 19.3293368, 0, 0, 0, -80.867335, 0, -3.344608, - 71.3546388, -91.098817, 0, 0, 0, -26.83234, 0, 3.7009, 0, 0, 0, 0, 0, - 28.4006138, 0, 0, 57.1433046, 14.4086186, 0, 0, 0, 49.5828354, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 17.0012008, 0, 30.5581294, -98.7868224, 0, 0, 0, - 70.0467486, -9.4444084, 0, 19.9250998, 0, 0, -79.4970662 - ], - [ - 0, 54.0067274, 0, 0, 0, 37.9936634, 0, -20.1071476, -58.981429, 0, 0, - -83.9927614, 0, 0, 0, 0, 0, 0, -70.8571636, 0, 0, 68.4686496, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, -8.1687508, 0, 0, 16.6835288, -10.1286606, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 86.4747972, 0, -34.6316042, 0.9788672, 0, 0, - -41.4513542, 0, 0, 0, 0, 0, 0, 0, 0, -28.4021576, -74.9076708, 0, - -5.0425708, 0, 0, 0, 78.9139852, -50.7082204, 0, 34.0684852, 28.2502302 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 57.544994, 0, 0, -22.4411072, 0, -94.2568866, - -80.7849138, 0, 0, -10.0010618, 33.3160792, 0, 0, 0, 0, 0, 0, - -82.6811248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -11.0390072, 0, 0, - 32.0523166, 0, -80.6937396, 0, -94.2671164, 0, 0, -16.2170198, 0, 0, 0, - -5.7387768, 0, 0, 0, 0, -83.6048238, 0, 0, 0, 0, 0, 0, 33.6877392, 0, - -41.9784856, 0, 0, 0, -50.6512854, 0, 78.737702, 0, 0, 0, 0, 0, - 9.0618996 - ], - [ - 0, 0, 0, 0, 0, -94.8072128, 0, 21.9767716, 0, 0, 0, 57.7817378, - 35.3840102, 0, 0, 0, 60.4679182, -94.9978498, 0, -18.9377882, - -42.3270372, 0, 0, 0, 0, -3.9058912, 0, 0, 44.440235, 0, 0.41471, 0, 0, - -88.0148602, 53.9755254, 0, 0, 74.7772774, -19.633854, -66.6129164, - -25.4637816, -13.1642082, 0, 0, 0, 0, 57.6068044, 0, 0, -75.9060014, - 24.0447026, 0, 0, 29.9750648, 0, 0, 0, -54.938857, -4.4090126, 0, 0, 0, - 0, 0, 0, -57.1202194, 0, -11.130658, 0, 0, 95.6177756, -78.5403868, 0, - 0, 30.4589622, -93.6950296 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -50.308444, 0, 0, 0, 0, 0, 0, 0, - -20.3462364, 0, 0, 25.6081088, 0, 0, 0, 0, 0, 0, 90.9877902, 0, - -56.0922542, 99.2456868, 0, 45.4316172, 0, 70.1339288, -54.576692, 0, 0, - 0, -73.6910292, 0, 0, 0, 0, 0, 0, 38.6032202, 0, 0, 0, 60.7387286, 0, 0, - 0, 0, 0, 0, 0, 0, 95.207832, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 58.4721284 - ], - [ - 0.3163476, 0, 0, 13.0918568, 5.13089, 0, 40.7266308, 0, 0, 0, 0, 0, - -98.7077428, 0, 81.000503, 0, 0, 76.3945372, 0, 0, 0, 0, 0, 0, - -10.2289084, -70.592702, 0, 0, 0, 0, 0, 0, 69.59571, 0, 0, 0, 0, 0, - -61.4746908, 53.4041094, 0, 90.4397278, -33.1874988, 0, 0, 71.5512744, - 5.846105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56.7568638, 0, 0, - 66.7588344, 0, 0, 0, 99.284785, 0, 0, 0, 0, 0, 0, -99.2850048, - -85.4466004 - ], - [ - 0, 0, -34.544278, 0, 0, -36.135124, -33.4259354, 0, 0, -60.4256326, 0, - 44.7734168, 0, 0, 0, 0, -88.6744704, 0, 0, 0, 0, 0, 0, 0, 0, - -95.3752508, 4.596855, 85.2924422, 70.9081648, 22.0390844, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1.6872456, 0, 0, 0, 0, 0, 0, 0, -37.4782422, 0, 0, 0, 0, - 0, 0, 0, 0, 4.7642096, 0, 0, -33.6313218, 0, 0, 72.977526, 0, 0, 0, 0, - 0, 0, 2.5063252, 0, 0, 0, 0, 0, -64.8677902 - ], - [ - 0, 0, 0, 3.718989, 0, 0, 49.3814782, 77.868826, 0, 0, 0, 0, 0, 0, 0, 0, - -49.7008846, 0, 4.696494, 0, 0, 0, 0, 0, 0, 0, -59.4928602, 0, 0, 0, 0, - 0, 82.6840644, -0.996624, -15.8014198, -93.098215, 0, 0, 41.2709314, 0, - -90.9633198, -56.911012, 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.02585, 0, 0, 0, - 0, 0, 0, 61.2353938, 0, 0, 0, 0, 21.376448, 0, 0, 0, 0, 0, 0, 0, - 97.0578858, 0, 0, 0, 5.1265226 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -19.5643656, 0, - 0, -92.0307416, 0, 0, 0, 0, 55.003856, 0, 0, -20.4708104, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54.4588824, 0, 0, 0, 0, 0, 0, 0, - 0, -57.0182252, 0, 0, -48.3222128, 0, 0, 0, -41.1121382, 0, 0, 0, 0, 0, - 0, -12.2773602, 0, -28.6924808, 50.0415338 - ], - [ - 0, 0, 0, -73.9719246, 0, 0, 0, 0, 0, 0, 0, 39.4110332, 0, 0, - -21.6757132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 74.8739354, 0, 0, -52.9210426, 0, 46.7750368, 63.6311858, 0, 0, 0, 0, - 25.5310902, 0, -3.0369126, 0, 32.5437816, 0, 0, 78.9107496, 32.0416448, - 0, 0, 15.9408846, 0, 0, 0, 0, -67.7582854, -52.6103086, 0, 0, 0, 0, 0, - 0, 11.8676176, 0, 0, -41.8820812, 43.608357, 0, 0, -64.3752572 - ], - [ - 0, -15.1648222, -57.5273074, -94.2919124, -3.0777222, -77.345826, - 91.2777856, 0, 0, 0, 0, 13.5044774, 0, 0, 0, 0, 0, 0, 0, 0, 33.5509618, - 0, 0, -33.291092, -60.3050638, 0, 0, 0, 94.610171, 0, 0, 0, 0, 0, 0, - -47.270154, -81.403122, 0, 0, 47.829269, 0, 0, 0, 0, -47.4699122, 0, 0, - -31.0161918, 0, 0, -54.993563, -9.6544012, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -17.0644648, 0, 0, 0, 0, 0, 0, 49.0555938, -82.4686508, 0, 0, - 97.7299292, 0, 0, -93.1718028 - ], - [ - 0, 0, 0, 45.5639954, 0, 0, 0, 0, 0, 92.3127826, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, -5.8746296, 22.9079182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 25.2727812, 0, 0, 0, 0, 0, 0, 65.0012614, 0, 67.4376806, 0, 0, - 14.0298002, 0, 0, 0, 0, 0, 33.432514, 43.000429, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 20.2757068, 4.0567932, 0, 0, -15.8737758, 0, 0, 0, 0, 29.8957398, - 50.0218946, 17.0262346 - ], - [ - 46.1658228, 19.630724, 0, 0, 0, 0, 0, 80.6073204, 24.5091878, 0, 0, 0, - 73.9258848, 0, 0, 0, -98.2044476, 0, 0, 0, 0, -59.2213968, 0, - -70.1450004, 0, 0, 0, 42.4721292, 0, -73.0346074, 0, 67.9829686, 0, 0, - 0, 0, 6.6597886, 39.7215082, 0, 0, 0, 52.505292, 95.9204336, 0, 0, - 5.8797776, 0, 0, 0, 36.0738246, 0, -15.2667614, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 50.963131, 0, 0, 0, 49.3354032, 13.7868492, 0, 77.3708062, - 26.0888548, 0, 0, 0, 0, -60.0881692, 24.2459968 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56.562023, 0, 0, 24.9145996, 0, 0, 0, - 0, 32.8196724, -75.8879774, -12.4005106, 0, 0, 0, 0, -18.824482, - -45.0330046, 0, 0, -47.5493022, 0, 0, 0, 8.1879572, 0, 0, 4.5700222, - -28.6731266, 0, 0, 0, 0, -83.2735948, 5.4624948, 0, 0, 0, 0, 0, 0, 0, - 83.664375, -52.8630198, 58.5805764, 0, 0, 0, 45.5781336, 0, 0, - -1.9768322, 0, 0, 0, 0, 91.1151128, 0, 0, 0, 0, 0, 0, -65.5158586, 0, - -22.12762 - ], - [ - 0, 0, 0, 0, 0, -50.8301006, 0, -57.183698, -24.666489, 43.0557936, 0, 0, - 0, -44.0812116, 0, 0, 0, 97.1670056, 0, 0, 0, 0, 83.4511366, 0, - -96.8912356, -6.1095452, 87.6643268, 0, 0, 0, -32.5223614, 0, 0, 0, 0, - -18.5596964, 0, 56.1790224, 0, 25.6035684, 0, 0, 0, 0, -46.8740154, 0, - 0, 0, 0, 0, 0, 0, -44.4329434, -34.719678, 85.6384822, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 48.9669952, 0, 0, 0, 0, 0, 0, 0, 0, 0, -28.2571694 - ], - [ - 0, 0, 0, 0, 42.1192158, 18.3800218, 0, 0, 33.4475094, 0, -39.4713432, 0, - 54.9744572, 0, -6.6305664, 79.0252374, 77.6908818, -61.5099746, 0, 0, - 38.4501814, 0, 0, -96.4250704, -27.809694, -34.9250884, 63.2997728, 0, - -98.8800042, 0, -7.682428, 0, 0, 0, 0, 0, 0, 7.7256608, 0, 48.5546152, - 0, 0, 0, 0, 0, 0, -58.963373, 0, 0, 0, 0, 0, 86.5062664, 7.0006556, 0, - 0, 0, 0, 0, 0, 3.1958572, 0, -46.2861844, 0, 0, 0, 0, 30.0710902, - 46.6948898, 0, 68.4681908, 0, 0, 0, 0, -51.3930766 - ], - [ - 7.1969236, 0, -79.6160432, -39.947334, -15.2636342, 0, 10.0645898, - -57.8968238, -31.9945476, 0, 91.5839662, -52.777566, 0, 0, 0, 0, - -40.9252974, 0, 0, 0, -45.5113982, 70.585349, 0, 0, 0, 0, 0, 0, 0, 0, - -66.2523308, 0, 0, 0, 0, 0, 0, 0, 0, 85.7454324, -7.1148372, 0, - 93.6584844, 0, 0, 0, 0, 0, 0, -8.0414788, 0, 0, 0, 0, 51.3263388, 0, 0, - 79.892422, 0, 0, -64.6934098, 0, 0, -45.9577622, 0, 0, 0, -46.8290526, - 0, 48.2417506, 0, 0, 0, 0, -2.4115812, -50.7156922 - ], - [ - 62.9870494, -81.5584552, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53.7333104, 0, - 50.6438202, -57.7938262, 0, 0, 0, 0, -78.1108892, 0, 0, 0, 0, 0, 0, 0, - 87.6584692, 0, 58.2960112, -17.5355398, 12.0497204, -60.5414862, 0, 0, - 0, 0, 0, 0, 0, 0, 38.586472, -19.7940052, 94.423359, -89.557933, 0, 0, - -77.0715722, 0, -87.707166, 70.8585278, 0, 9.0616914, 91.333051, 0, 0, - 0, 5.8166112, 0, 24.7793442, -51.000038, 0, 0, 0, 0, 0, 0, 79.9657776, - 97.4969126, 0, 0, 0, 0, -73.8994394 - ], - [ - 10.133741, 0, 0, 0, 95.6910336, 0, 0, 0, 0, -22.6696236, 0, 0, 0, 0, - 15.137996, 35.7088464, 0, -16.1971956, 0, -29.4834358, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -89.868739, 24.0040126, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 40.7292396, 35.7709458, 0, 34.690347, 22.3083532, 0, 0, 0, 0, 0, - -48.4224164, 0, 0, 0, 0, 91.7670744, 0, 0, 69.4045014, 0, 60.5937114, - -38.9993134, 0, 0, 0, 0, 0, 55.4599018, 0, 86.689944 - ], - [ - 0, 0, -24.842169, -52.997003, 0, 0, 0, 0, 0, 0, 0, 5.9075842, 0, 0, - -91.1447252, -5.3147106, 0, 0, 0, 4.4670454, 34.97343, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, -5.3957052, 0, 0, 0, 0, -19.2118838, 0, 0, 0, 0, 0, 0, 0, 0, - 64.9559324, 0, 0, -10.0586402, 0, -74.8523334, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 84.8495464, 0, 0, 0, 0, 0, 0, 33.0825986, 46.995148, 0, 0, 0, - -4.243203, 0, 0, -24.0124188 - ] + 11.1687196, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -37.365205, + 0, + 0, + 0, + 52.0314298, + 0, + -58.0558462, + 0, + 0, + 18.6738906, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 59.6046802, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -26.5944948, + 0, + 0, + 0, + 77.0271992, + 46.521059, + 61.8258634, + 0, + 0, + 36.079546, + 0, + -14.3080494, + 0, + -50.1618478, + 0, + 0, + 95.9445656, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -53.478982, + -90.3623976, + 0, + 0, + -37.1575466, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 78.882051, + 0, + 0, + 57.8056512, + 7.2056626, + 0, + 2.822132, + 40.311822, + 0, + 0, + 0, + 83.8935006, + 0, + 0, + 91.2774482, + 3.160849, + 91.7410132, + 0, + 0, + 0, + 0, + 0, + 2.7544652, + 0, + 0, + 0, + 0, + 0, + 16.8419108, + 0, + 0, + 84.1171174, + 0, + 21.3119752, + -69.869284, + 0, + 0, + 0, + 0, + 0, + 92.1087118, + 0, + 0, + 0, + 0, + 34.3473744, + 21.9890278, + 0, + -36.3139526, + 0, + 0, + 0, + 0, + 0, + 25.613497, + -2.989159, + 0, + 0, + 0, + 0, + -49.2456622, + 0, + 0, + 27.3140788, + 0, + 49.210258, + 0, + 0, + -90.6896972, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + -56.1947046, + 0, + 0, + 52.6617176, + 61.6283016, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -34.0759312, + 0, + 92.0200254, + 0, + 0, + -9.7999914, + 0, + 0, + 34.0572616, + 0, + 0, + 0, + 0, + 0, + 59.7637334, + 0, + 0, + 0, + 0, + 0, + 43.5437732, + 29.9690024, + 0, + 0, + 93.9438036, + 0, + 0, + 0, + 52.3867986, + 0, + 0, + 0, + 38.0567542, + 0, + -63.9851954, + 0, + 73.1679634, + 0, + 0, + -28.6636244, + 0, + 0, + 0, + 0, + 0, + 39.2894916, + 0, + -28.4364668, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -32.2899456, + ], + [ + 15.3399588, + 0, + 0, + 0, + 0, + 0, + 0, + -66.2540916, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 14.7613734, + 0, + 0, + 0, + -32.9550622, + 0, + 0, + -17.2710502, + 0, + 0, + 0, + -66.7611862, + 76.5708962, + 93.3868072, + 12.036471, + 0, + 0, + 0, + -74.2189184, + 0, + 0, + 0, + 0, + 58.0343866, + 0, + 71.0145124, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 80.7131208, + 0, + -49.5191686, + 40.2489602, + 0, + 0, + 39.9664558, + 0, + 0, + 0, + 0, + 0, + 0, + 51.704979, + 0, + 20.9209752, + 0, + 0, + -63.5800814, + ], + [ + 0, + 0, + 35.1676872, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8.326191, + 0, + 0, + 0, + -42.1405488, + 0, + 0, + 0, + 0, + -64.9203894, + 0, + 0, + 0, + 86.5653072, + 0, + 17.1250418, + 0, + 15.224998, + -32.788739, + 0, + 0, + 0, + 13.1550612, + 0, + 0, + 0, + 93.3049176, + 65.5504482, + 0, + 35.8126186, + -16.2474202, + 0, + -76.7788518, + -51.9001008, + 0, + -89.725966, + 53.4895596, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -82.6077236, + 0, + 0, + 0, + -57.075872, + -52.5166376, + 99.9479554, + 0, + 0, + -89.7491862, + 0, + 18.6163306, + 0, + 29.1454254, + ], + [ + -18.8864514, + 0, + 0, + 0, + 0, + 0, + -46.6968628, + -83.8123266, + 0, + 0, + 0, + 0, + 0, + 0, + -91.631792, + 0, + 0, + 0, + 0, + -57.3455082, + 66.459894, + 0, + 0, + 0, + -73.9341878, + 0, + 0, + 0, + 81.8859882, + 0, + 0, + 0, + 0, + 0, + 28.0747908, + 0, + 0, + 99.7796988, + 0, + 87.0296656, + 0, + 0, + 43.7722526, + -60.65313, + 98.6287198, + 0, + 20.8634118, + 0, + 0, + 25.6519386, + 0.4939554, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -74.9266526, + 0, + 0, + -25.4234142, + 0, + -91.054582, + 0, + -42.534282, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 12.1491094, + ], + [ + 0, + -90.5331764, + 0, + 0, + 0, + 0, + 0, + -8.1166918, + 0, + 0, + 0, + 0, + 0, + -34.0013692, + 21.9272646, + 0, + 0, + 0, + 0, + 0, + 93.2245032, + 0, + 21.8275426, + 0, + 0, + 0, + 38.5279608, + -6.0022692, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39.644863, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -93.7962256, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11.5363598, + 70.354452, + 0, + 0, + 0, + 32.2282298, + 0, + 36.659058, + 0, + 0, + 53.992344, + ], + [ + -74.4430478, + 0, + 0, + -93.4496218, + 14.6437598, + 0, + 0, + 0, + -33.8297902, + 0, + 0, + 0, + 0, + 0, + 0, + -35.949213, + 0, + 0, + -93.1678628, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -22.8806328, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 45.1466816, + 88.4821604, + 0, + 0, + -49.7769388, + 0, + 0, + 0, + 0, + 0, + 0, + 53.6290382, + -40.4388272, + ], + [ + 71.2065308, + 94.4966808, + 0, + 0, + 0, + 0, + -84.2404402, + 0, + 0, + 0, + 0, + 0, + 61.7641462, + 0, + 0, + 0, + -0.608018, + -94.7698384, + 0, + 0, + 0, + 0, + -3.4562834, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -26.0708126, + -73.3657794, + 26.6482556, + 0, + 0, + 0, + 0, + 0, + 0, + -66.9699546, + -98.6726948, + 0, + 0, + -25.4137958, + 0, + -88.7254432, + -81.2735544, + 0, + -23.3081526, + 0, + -51.8917252, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -52.3004828, + 0, + 0, + 0, + 0, + 67.8542398, + 0, + -67.7489638, + 32.2212522, + ], + [ + 0, + 77.0252092, + 0, + 0, + 0, + 0, + 0, + -85.3271924, + 0, + 0, + 0, + -2.9308596, + 0, + 83.448547, + 0, + 4.7835886, + 0, + 0, + 0, + 0, + 76.590577, + 0, + 0, + 0, + 0, + 0, + 86.0180794, + 0, + 0, + -88.4030016, + 0, + 0, + 0, + -13.770426, + 57.6068646, + 0, + 0, + 0, + 0, + 0, + 12.6896788, + 0, + 0, + 0, + -78.1078136, + -92.3074796, + 0, + 0, + 0, + 0, + -94.3200626, + 0, + 0, + -7.987837, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52.7655986, + 0, + -78.2727176, + 0, + 74.4309632, + 48.3867546, + -85.4142468, + 0, + 91.3888914, + -6.0372808, + 51.1643348, + ], + [ + -77.4193002, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41.0846988, + -78.3265056, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -20.408123, + 10.7055032, + 0, + -19.5848354, + 0, + -32.624054, + 0, + 47.333306, + 0, + -41.6545398, + 0, + 0, + 0, + 0, + 0, + 46.8131656, + 0, + 52.5387768, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -75.3511542, + 0, + 0, + 0, + 0, + -27.6140242, + 0, + 0, + -63.3032372, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -93.1854838, + 0, + 0, + 0, + 0, + 32.3353436, + -75.8659438, + 89.852816, + 0, + 9.7044216, + 0, + 94.9239572, + 0, + -57.0391726, + 25.4894998, + ], + [ + 0, + 0, + 0, + -27.2226392, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -89.0780816, + 0, + 0, + 0, + 0, + 6.6342408, + 0, + 0, + 92.3634346, + -41.5559582, + 0, + 0, + 0, + 0, + 0, + 0, + -4.3499792, + 18.9554522, + 0, + 29.992962, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -51.9952794, + -40.3571344, + 0, + -61.0098328, + 0, + 0, + 0, + 0, + 71.8571838, + 97.3056784, + 0, + 0, + 27.798026, + 0, + 0, + 20.3198544, + 0, + 0, + 0, + -82.1043534, + 0, + 0, + 0, + 0, + 0, + 55.2807134, + 0, + 0, + 0, + 0, + -90.8562594, + -43.2271604, + ], + [ + 0, + 0, + 0, + 0, + 0, + 15.2015056, + 18.3740448, + -11.2614058, + 0, + 0, + 0, + 0, + 0, + 53.0086248, + 0, + -91.038908, + 0, + 0, + 88.2707986, + 3.2580272, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 88.8189254, + 0, + 0, + 0, + 0, + -3.742353, + 0, + 0, + 0, + -41.4198488, + 90.0966416, + 0, + -22.474875, + 0, + -25.610863, + -79.5943706, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -3.5616438, + 0, + 0, + 0, + 88.5984932, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 22.4398688, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -63.7422676, + 0, + 0, + 0, + -82.7150752, + ], + [ + 0, + 0, + 75.634243, + -60.420708, + 0, + 0, + 0, + 0.8383984, + 0, + 0, + -72.2791914, + 0, + 0, + 0, + 17.1476022, + 0, + 0, + -14.5930276, + 9.4352026, + 0, + -30.1475326, + 0, + -53.7249052, + 0, + 19.3293368, + 0, + 0, + 0, + -80.867335, + 0, + -3.344608, + 71.3546388, + -91.098817, + 0, + 0, + 0, + -26.83234, + 0, + 3.7009, + 0, + 0, + 0, + 0, + 0, + 28.4006138, + 0, + 0, + 57.1433046, + 14.4086186, + 0, + 0, + 0, + 49.5828354, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 17.0012008, + 0, + 30.5581294, + -98.7868224, + 0, + 0, + 0, + 70.0467486, + -9.4444084, + 0, + 19.9250998, + 0, + 0, + -79.4970662, + ], + [ + 0, + 54.0067274, + 0, + 0, + 0, + 37.9936634, + 0, + -20.1071476, + -58.981429, + 0, + 0, + -83.9927614, + 0, + 0, + 0, + 0, + 0, + 0, + -70.8571636, + 0, + 0, + 68.4686496, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -8.1687508, + 0, + 0, + 16.6835288, + -10.1286606, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 86.4747972, + 0, + -34.6316042, + 0.9788672, + 0, + 0, + -41.4513542, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -28.4021576, + -74.9076708, + 0, + -5.0425708, + 0, + 0, + 0, + 78.9139852, + -50.7082204, + 0, + 34.0684852, + 28.2502302, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 57.544994, + 0, + 0, + -22.4411072, + 0, + -94.2568866, + -80.7849138, + 0, + 0, + -10.0010618, + 33.3160792, + 0, + 0, + 0, + 0, + 0, + 0, + -82.6811248, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -11.0390072, + 0, + 0, + 32.0523166, + 0, + -80.6937396, + 0, + -94.2671164, + 0, + 0, + -16.2170198, + 0, + 0, + 0, + -5.7387768, + 0, + 0, + 0, + 0, + -83.6048238, + 0, + 0, + 0, + 0, + 0, + 0, + 33.6877392, + 0, + -41.9784856, + 0, + 0, + 0, + -50.6512854, + 0, + 78.737702, + 0, + 0, + 0, + 0, + 0, + 9.0618996, + ], + [ + 0, + 0, + 0, + 0, + 0, + -94.8072128, + 0, + 21.9767716, + 0, + 0, + 0, + 57.7817378, + 35.3840102, + 0, + 0, + 0, + 60.4679182, + -94.9978498, + 0, + -18.9377882, + -42.3270372, + 0, + 0, + 0, + 0, + -3.9058912, + 0, + 0, + 44.440235, + 0, + 0.41471, + 0, + 0, + -88.0148602, + 53.9755254, + 0, + 0, + 74.7772774, + -19.633854, + -66.6129164, + -25.4637816, + -13.1642082, + 0, + 0, + 0, + 0, + 57.6068044, + 0, + 0, + -75.9060014, + 24.0447026, + 0, + 0, + 29.9750648, + 0, + 0, + 0, + -54.938857, + -4.4090126, + 0, + 0, + 0, + 0, + 0, + 0, + -57.1202194, + 0, + -11.130658, + 0, + 0, + 95.6177756, + -78.5403868, + 0, + 0, + 30.4589622, + -93.6950296, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -50.308444, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -20.3462364, + 0, + 0, + 25.6081088, + 0, + 0, + 0, + 0, + 0, + 0, + 90.9877902, + 0, + -56.0922542, + 99.2456868, + 0, + 45.4316172, + 0, + 70.1339288, + -54.576692, + 0, + 0, + 0, + -73.6910292, + 0, + 0, + 0, + 0, + 0, + 0, + 38.6032202, + 0, + 0, + 0, + 60.7387286, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 95.207832, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58.4721284, + ], + [ + 0.3163476, + 0, + 0, + 13.0918568, + 5.13089, + 0, + 40.7266308, + 0, + 0, + 0, + 0, + 0, + -98.7077428, + 0, + 81.000503, + 0, + 0, + 76.3945372, + 0, + 0, + 0, + 0, + 0, + 0, + -10.2289084, + -70.592702, + 0, + 0, + 0, + 0, + 0, + 0, + 69.59571, + 0, + 0, + 0, + 0, + 0, + -61.4746908, + 53.4041094, + 0, + 90.4397278, + -33.1874988, + 0, + 0, + 71.5512744, + 5.846105, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56.7568638, + 0, + 0, + 66.7588344, + 0, + 0, + 0, + 99.284785, + 0, + 0, + 0, + 0, + 0, + 0, + -99.2850048, + -85.4466004, + ], + [ + 0, + 0, + -34.544278, + 0, + 0, + -36.135124, + -33.4259354, + 0, + 0, + -60.4256326, + 0, + 44.7734168, + 0, + 0, + 0, + 0, + -88.6744704, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -95.3752508, + 4.596855, + 85.2924422, + 70.9081648, + 22.0390844, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1.6872456, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -37.4782422, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4.7642096, + 0, + 0, + -33.6313218, + 0, + 0, + 72.977526, + 0, + 0, + 0, + 0, + 0, + 0, + 2.5063252, + 0, + 0, + 0, + 0, + 0, + -64.8677902, + ], + [ + 0, + 0, + 0, + 3.718989, + 0, + 0, + 49.3814782, + 77.868826, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -49.7008846, + 0, + 4.696494, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -59.4928602, + 0, + 0, + 0, + 0, + 0, + 82.6840644, + -0.996624, + -15.8014198, + -93.098215, + 0, + 0, + 41.2709314, + 0, + -90.9633198, + -56.911012, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -26.02585, + 0, + 0, + 0, + 0, + 0, + 0, + 61.2353938, + 0, + 0, + 0, + 0, + 21.376448, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 97.0578858, + 0, + 0, + 0, + 5.1265226, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -19.5643656, + 0, + 0, + -92.0307416, + 0, + 0, + 0, + 0, + 55.003856, + 0, + 0, + -20.4708104, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54.4588824, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -57.0182252, + 0, + 0, + -48.3222128, + 0, + 0, + 0, + -41.1121382, + 0, + 0, + 0, + 0, + 0, + 0, + -12.2773602, + 0, + -28.6924808, + 50.0415338, + ], + [ + 0, + 0, + 0, + -73.9719246, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39.4110332, + 0, + 0, + -21.6757132, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 74.8739354, + 0, + 0, + -52.9210426, + 0, + 46.7750368, + 63.6311858, + 0, + 0, + 0, + 0, + 25.5310902, + 0, + -3.0369126, + 0, + 32.5437816, + 0, + 0, + 78.9107496, + 32.0416448, + 0, + 0, + 15.9408846, + 0, + 0, + 0, + 0, + -67.7582854, + -52.6103086, + 0, + 0, + 0, + 0, + 0, + 0, + 11.8676176, + 0, + 0, + -41.8820812, + 43.608357, + 0, + 0, + -64.3752572, + ], + [ + 0, + -15.1648222, + -57.5273074, + -94.2919124, + -3.0777222, + -77.345826, + 91.2777856, + 0, + 0, + 0, + 0, + 13.5044774, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 33.5509618, + 0, + 0, + -33.291092, + -60.3050638, + 0, + 0, + 0, + 94.610171, + 0, + 0, + 0, + 0, + 0, + 0, + -47.270154, + -81.403122, + 0, + 0, + 47.829269, + 0, + 0, + 0, + 0, + -47.4699122, + 0, + 0, + -31.0161918, + 0, + 0, + -54.993563, + -9.6544012, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -17.0644648, + 0, + 0, + 0, + 0, + 0, + 0, + 49.0555938, + -82.4686508, + 0, + 0, + 97.7299292, + 0, + 0, + -93.1718028, + ], + [ + 0, + 0, + 0, + 45.5639954, + 0, + 0, + 0, + 0, + 0, + 92.3127826, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -5.8746296, + 22.9079182, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 25.2727812, + 0, + 0, + 0, + 0, + 0, + 0, + 65.0012614, + 0, + 67.4376806, + 0, + 0, + 14.0298002, + 0, + 0, + 0, + 0, + 0, + 33.432514, + 43.000429, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20.2757068, + 4.0567932, + 0, + 0, + -15.8737758, + 0, + 0, + 0, + 0, + 29.8957398, + 50.0218946, + 17.0262346, + ], + [ + 46.1658228, + 19.630724, + 0, + 0, + 0, + 0, + 0, + 80.6073204, + 24.5091878, + 0, + 0, + 0, + 73.9258848, + 0, + 0, + 0, + -98.2044476, + 0, + 0, + 0, + 0, + -59.2213968, + 0, + -70.1450004, + 0, + 0, + 0, + 42.4721292, + 0, + -73.0346074, + 0, + 67.9829686, + 0, + 0, + 0, + 0, + 6.6597886, + 39.7215082, + 0, + 0, + 0, + 52.505292, + 95.9204336, + 0, + 0, + 5.8797776, + 0, + 0, + 0, + 36.0738246, + 0, + -15.2667614, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50.963131, + 0, + 0, + 0, + 49.3354032, + 13.7868492, + 0, + 77.3708062, + 26.0888548, + 0, + 0, + 0, + 0, + -60.0881692, + 24.2459968, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56.562023, + 0, + 0, + 24.9145996, + 0, + 0, + 0, + 0, + 32.8196724, + -75.8879774, + -12.4005106, + 0, + 0, + 0, + 0, + -18.824482, + -45.0330046, + 0, + 0, + -47.5493022, + 0, + 0, + 0, + 8.1879572, + 0, + 0, + 4.5700222, + -28.6731266, + 0, + 0, + 0, + 0, + -83.2735948, + 5.4624948, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 83.664375, + -52.8630198, + 58.5805764, + 0, + 0, + 0, + 45.5781336, + 0, + 0, + -1.9768322, + 0, + 0, + 0, + 0, + 91.1151128, + 0, + 0, + 0, + 0, + 0, + 0, + -65.5158586, + 0, + -22.12762, + ], + [ + 0, + 0, + 0, + 0, + 0, + -50.8301006, + 0, + -57.183698, + -24.666489, + 43.0557936, + 0, + 0, + 0, + -44.0812116, + 0, + 0, + 0, + 97.1670056, + 0, + 0, + 0, + 0, + 83.4511366, + 0, + -96.8912356, + -6.1095452, + 87.6643268, + 0, + 0, + 0, + -32.5223614, + 0, + 0, + 0, + 0, + -18.5596964, + 0, + 56.1790224, + 0, + 25.6035684, + 0, + 0, + 0, + 0, + -46.8740154, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -44.4329434, + -34.719678, + 85.6384822, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 48.9669952, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -28.2571694, + ], + [ + 0, + 0, + 0, + 0, + 42.1192158, + 18.3800218, + 0, + 0, + 33.4475094, + 0, + -39.4713432, + 0, + 54.9744572, + 0, + -6.6305664, + 79.0252374, + 77.6908818, + -61.5099746, + 0, + 0, + 38.4501814, + 0, + 0, + -96.4250704, + -27.809694, + -34.9250884, + 63.2997728, + 0, + -98.8800042, + 0, + -7.682428, + 0, + 0, + 0, + 0, + 0, + 0, + 7.7256608, + 0, + 48.5546152, + 0, + 0, + 0, + 0, + 0, + 0, + -58.963373, + 0, + 0, + 0, + 0, + 0, + 86.5062664, + 7.0006556, + 0, + 0, + 0, + 0, + 0, + 0, + 3.1958572, + 0, + -46.2861844, + 0, + 0, + 0, + 0, + 30.0710902, + 46.6948898, + 0, + 68.4681908, + 0, + 0, + 0, + 0, + -51.3930766, + ], + [ + 7.1969236, + 0, + -79.6160432, + -39.947334, + -15.2636342, + 0, + 10.0645898, + -57.8968238, + -31.9945476, + 0, + 91.5839662, + -52.777566, + 0, + 0, + 0, + 0, + -40.9252974, + 0, + 0, + 0, + -45.5113982, + 70.585349, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -66.2523308, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 85.7454324, + -7.1148372, + 0, + 93.6584844, + 0, + 0, + 0, + 0, + 0, + 0, + -8.0414788, + 0, + 0, + 0, + 0, + 51.3263388, + 0, + 0, + 79.892422, + 0, + 0, + -64.6934098, + 0, + 0, + -45.9577622, + 0, + 0, + 0, + -46.8290526, + 0, + 48.2417506, + 0, + 0, + 0, + 0, + -2.4115812, + -50.7156922, + ], + [ + 62.9870494, + -81.5584552, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53.7333104, + 0, + 50.6438202, + -57.7938262, + 0, + 0, + 0, + 0, + -78.1108892, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 87.6584692, + 0, + 58.2960112, + -17.5355398, + 12.0497204, + -60.5414862, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 38.586472, + -19.7940052, + 94.423359, + -89.557933, + 0, + 0, + -77.0715722, + 0, + -87.707166, + 70.8585278, + 0, + 9.0616914, + 91.333051, + 0, + 0, + 0, + 5.8166112, + 0, + 24.7793442, + -51.000038, + 0, + 0, + 0, + 0, + 0, + 0, + 79.9657776, + 97.4969126, + 0, + 0, + 0, + 0, + -73.8994394, + ], + [ + 10.133741, + 0, + 0, + 0, + 95.6910336, + 0, + 0, + 0, + 0, + -22.6696236, + 0, + 0, + 0, + 0, + 15.137996, + 35.7088464, + 0, + -16.1971956, + 0, + -29.4834358, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -89.868739, + 24.0040126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40.7292396, + 35.7709458, + 0, + 34.690347, + 22.3083532, + 0, + 0, + 0, + 0, + 0, + -48.4224164, + 0, + 0, + 0, + 0, + 91.7670744, + 0, + 0, + 69.4045014, + 0, + 60.5937114, + -38.9993134, + 0, + 0, + 0, + 0, + 0, + 55.4599018, + 0, + 86.689944, + ], + [ + 0, + 0, + -24.842169, + -52.997003, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5.9075842, + 0, + 0, + -91.1447252, + -5.3147106, + 0, + 0, + 0, + 4.4670454, + 34.97343, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -5.3957052, + 0, + 0, + 0, + 0, + -19.2118838, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64.9559324, + 0, + 0, + -10.0586402, + 0, + -74.8523334, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 84.8495464, + 0, + 0, + 0, + 0, + 0, + 0, + 33.0825986, + 46.995148, + 0, + 0, + 0, + -4.243203, + 0, + 0, + -24.0124188, + ], ] @@ -657,7 +5880,7 @@ def solve_qubo(): num_vars = len(RAW_DATA) all_vars = range(num_vars) - variables = [model.NewBoolVar('x_%i' % i) for i in all_vars] + variables = [model.NewBoolVar("x_%i" % i) for i in all_vars] obj_vars = [] obj_coeffs = [] @@ -669,7 +5892,7 @@ def solve_qubo(): if coeff == 0.0: continue x_j = variables[j] - var = model.NewBoolVar('') + var = model.NewBoolVar("") model.AddBoolOr([x_i.Not(), x_j.Not(), var]) model.AddImplication(var, x_i) model.AddImplication(var, x_j) @@ -682,8 +5905,7 @@ 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() @@ -695,9 +5917,9 @@ def solve_qubo(): def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") solve_qubo() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/shift_scheduling_sat.py b/examples/python/shift_scheduling_sat.py old mode 100755 new mode 100644 index 1cad5f2f9a..96b36c80e6 --- a/examples/python/shift_scheduling_sat.py +++ b/examples/python/shift_scheduling_sat.py @@ -11,6 +11,7 @@ # 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. + """Creates a shift scheduling problem and solves it.""" from absl import app @@ -20,28 +21,30 @@ from ortools.sat.python import cp_model from google.protobuf import text_format _OUTPUT_PROTO = flags.DEFINE_string( - 'output_proto', '', 'Output file to write the cp_model proto to.') -_PARAMS = flags.DEFINE_string('params', 'max_time_in_seconds:10.0', - 'Sat solver parameters.') + "output_proto", "", "Output file to write the cp_model proto to." +) +_PARAMS = flags.DEFINE_string( + "params", "max_time_in_seconds:10.0", "Sat solver parameters." +) def negated_bounded_span(works, start, length): """Filters an isolated sub-sequence of variables assined to True. - Extract the span of Boolean variables [start, start + length), negate them, - and if there is variables to the left/right of this span, surround the span by - them in non negated form. + Extract the span of Boolean variables [start, start + length), negate them, + and if there is variables to the left/right of this span, surround the span by + them in non negated form. - Args: - works: a list of variables to extract the span from. - start: the start to the span. - length: the length of the span. + Args: + works: a list of variables to extract the span from. + start: the start to the span. + length: the length of the span. - Returns: - a list of variables which conjunction will be false if the sub-list is - assigned to True, and correctly bounded by variables assigned to False, - or by the start or end of works. - """ + Returns: + a list of variables which conjunction will be false if the sub-list is + assigned to True, and correctly bounded by variables assigned to False, + or by the start or end of works. + """ sequence = [] # Left border (start of works, or works[start - 1]) if start > 0: @@ -54,35 +57,36 @@ def negated_bounded_span(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): +def add_soft_sequence_constraint( + model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix +): """Sequence constraint on true variables with soft and hard bounds. - This constraint look at every maximal contiguous sequence of variables - assigned to true. If forbids sequence of length < hard_min or > hard_max. - Then it creates penalty terms if the length is < soft_min or > soft_max. + This constraint look at every maximal contiguous sequence of variables + assigned to true. If forbids sequence of length < hard_min or > hard_max. + Then it creates penalty terms if the length is < soft_min or > soft_max. - Args: - model: the sequence constraint is built on this model. - works: a list of Boolean variables. - hard_min: any sequence of true variables must have a length of at least - hard_min. - soft_min: any sequence should have a length of at least soft_min, or a - linear penalty on the delta will be added to the objective. - min_cost: the coefficient of the linear penalty if the length is less than - soft_min. - soft_max: any sequence should have a length of at most soft_max, or a linear - penalty on the delta will be added to the objective. - hard_max: any sequence of true variables must have a length of at most - hard_max. - max_cost: the coefficient of the linear penalty if the length is more than - soft_max. - prefix: a base name for penalty literals. + Args: + model: the sequence constraint is built on this model. + works: a list of Boolean variables. + hard_min: any sequence of true variables must have a length of at least + hard_min. + soft_min: any sequence should have a length of at least soft_min, or a + linear penalty on the delta will be added to the objective. + min_cost: the coefficient of the linear penalty if the length is less than + soft_min. + soft_max: any sequence should have a length of at most soft_max, or a linear + penalty on the delta will be added to the objective. + hard_max: any sequence of true variables must have a length of at most + hard_max. + max_cost: the coefficient of the linear penalty if the length is more than + soft_max. + prefix: a base name for penalty literals. - Returns: - a tuple (variables_list, coefficient_list) containing the different - penalties created by the sequence constraint. - """ + Returns: + a tuple (variables_list, coefficient_list) containing the different + penalties created by the sequence constraint. + """ cost_literals = [] cost_coefficients = [] @@ -96,7 +100,7 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost, for length in range(hard_min, soft_min): for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) - name = ': under_span(start=%i, length=%i)' % (start, length) + name = ": under_span(start=%i, length=%i)" % (start, length) lit = model.NewBoolVar(prefix + name) span.append(lit) model.AddBoolOr(span) @@ -110,7 +114,7 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost, for length in range(soft_max + 1, hard_max + 1): for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) - name = ': over_span(start=%i, length=%i)' % (start, length) + name = ": over_span(start=%i, length=%i)" % (start, length) lit = model.NewBoolVar(prefix + name) span.append(lit) model.AddBoolOr(span) @@ -120,61 +124,61 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost, # 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.AddBoolOr([works[i].Not() 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): +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. - This constraint counts the variables assigned to true from works. - If forbids sum < hard_min or > hard_max. - Then it creates penalty terms if the sum is < soft_min or > soft_max. + This constraint counts the variables assigned to true from works. + If forbids sum < hard_min or > hard_max. + Then it creates penalty terms if the sum is < soft_min or > soft_max. - Args: - model: the sequence constraint is built on this model. - works: a list of Boolean variables. - hard_min: any sequence of true variables must have a sum of at least - hard_min. - soft_min: any sequence should have a sum of at least soft_min, or a linear - penalty on the delta will be added to the objective. - min_cost: the coefficient of the linear penalty if the sum is less than - soft_min. - soft_max: any sequence should have a sum of at most soft_max, or a linear - penalty on the delta will be added to the objective. - hard_max: any sequence of true variables must have a sum of at most - hard_max. - max_cost: the coefficient of the linear penalty if the sum is more than - soft_max. - prefix: a base name for penalty variables. + Args: + model: the sequence constraint is built on this model. + works: a list of Boolean variables. + hard_min: any sequence of true variables must have a sum of at least + hard_min. + soft_min: any sequence should have a sum of at least soft_min, or a linear + penalty on the delta will be added to the objective. + min_cost: the coefficient of the linear penalty if the sum is less than + soft_min. + soft_max: any sequence should have a sum of at most soft_max, or a linear + penalty on the delta will be added to the objective. + hard_max: any sequence of true variables must have a sum of at most + hard_max. + max_cost: the coefficient of the linear penalty if the sum is more than + soft_max. + prefix: a base name for penalty variables. - Returns: - a tuple (variables_list, coefficient_list) containing the different - penalties created by the sequence constraint. - """ + Returns: + a tuple (variables_list, coefficient_list) containing the different + penalties created by the sequence constraint. + """ cost_variables = [] cost_coefficients = [] - sum_var = model.NewIntVar(hard_min, hard_max, '') + sum_var = model.NewIntVar(hard_min, hard_max, "") # This adds the hard constraints on the sum. 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), '') + delta = model.NewIntVar(-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') + excess = model.NewIntVar(0, 7, prefix + ": under_sum") model.AddMaxEquality(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, '') + delta = model.NewIntVar(-7, 7, "") model.Add(delta == sum_var - soft_max) - excess = model.NewIntVar(0, 7, prefix + ': over_sum') + excess = model.NewIntVar(0, 7, prefix + ": over_sum") model.AddMaxEquality(excess, [delta, 0]) cost_variables.append(excess) cost_coefficients.append(max_cost) @@ -187,7 +191,7 @@ def solve_shift_scheduling(params, output_proto): # Data num_employees = 8 num_weeks = 3 - shifts = ['O', 'M', 'A', 'N'] + shifts = ["O", "M", "A", "N"] # Fixed assignment: (employee, shift, day). # This fixes the first 2 days of the schedule. @@ -220,7 +224,7 @@ def solve_shift_scheduling(params, output_proto): (5, 3, 10, -2), # Employee 2 does not want a night shift on the first Friday (positive # weight). - (2, 3, 4, 4) + (2, 3, 4, 4), ] # Shift constraints on continuous sequence : @@ -277,7 +281,7 @@ 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.NewBoolVar("work%i_%i_%i" % (e, s, d)) # Linear terms of the objective in a minimization context. obj_int_vars = [] @@ -305,9 +309,16 @@ def solve_shift_scheduling(params, output_proto): for e in range(num_employees): works = [work[e, shift, d] for d in range(num_days)] variables, coeffs = add_soft_sequence_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, hard_max, + model, + works, + hard_min, + soft_min, + min_cost, + soft_max, + hard_max, max_cost, - 'shift_constraint(employee %i, shift %i)' % (e, shift)) + "shift_constraint(employee %i, shift %i)" % (e, shift), + ) obj_bool_vars.extend(variables) obj_bool_coeffs.extend(coeffs) @@ -318,10 +329,17 @@ def solve_shift_scheduling(params, output_proto): for w in range(num_weeks): works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, - hard_max, max_cost, - 'weekly_sum_constraint(employee %i, shift %i, week %i)' % - (e, shift, w)) + model, + works, + hard_min, + soft_min, + min_cost, + soft_max, + hard_max, + max_cost, + "weekly_sum_constraint(employee %i, shift %i, week %i)" + % (e, shift, w), + ) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) @@ -330,14 +348,15 @@ 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].Not(), + work[e, next_shift, d + 1].Not(), ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( - 'transition (employee=%i, day=%i)' % (e, d)) + "transition (employee=%i, day=%i)" % (e, d) + ) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) @@ -350,28 +369,25 @@ 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, '') + worked = model.NewIntVar(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) + 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) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective 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)))) + 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))) + ) if output_proto: - print('Writing proto to %s' % output_proto) - with open(output_proto, 'w') as text_file: + print("Writing proto to %s" % output_proto) + with open(output_proto, "w") as text_file: text_file.write(str(model)) # Solve the model. @@ -384,43 +400,45 @@ def solve_shift_scheduling(params, output_proto): # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print() - header = ' ' + header = " " for w in range(num_weeks): - header += 'M T W T F S S ' + header += "M T W T F S S " print(header) for e in range(num_employees): - schedule = '' + schedule = "" for d in range(num_days): for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): - schedule += shifts[s] + ' ' - print('worker %i: %s' % (e, schedule)) + schedule += shifts[s] + " " + print("worker %i: %s" % (e, schedule)) print() - print('Penalties:') + print("Penalties:") for i, var in enumerate(obj_bool_vars): if solver.BooleanValue(var): penalty = obj_bool_coeffs[i] if penalty > 0: - print(' %s violated, penalty=%i' % (var.Name(), penalty)) + print(" %s violated, penalty=%i" % (var.Name(), penalty)) else: - print(' %s fulfilled, gain=%i' % (var.Name(), -penalty)) + print(" %s fulfilled, gain=%i" % (var.Name(), -penalty)) for i, var in enumerate(obj_int_vars): if solver.Value(var) > 0: - print(' %s violated by %i, linear penalty=%i' % - (var.Name(), solver.Value(var), obj_int_coeffs[i])) + print( + " %s violated by %i, linear penalty=%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("Statistics") + print(" - status : %s" % solver.StatusName(status)) + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) def main(_): solve_shift_scheduling(_PARAMS.value, _OUTPUT_PROTO.value) -if __name__ == '__main__': +if __name__ == "__main__": app.run(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 aa65933bbd..b82ed3719d 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 @@ -11,6 +11,7 @@ # 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. + """Single machine jobshop with setup times, release dates and due dates.""" from typing import Sequence @@ -19,19 +20,22 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -#---------------------------------------------------------------------------- +# ---------------------------------------------------------------------------- # Command line arguments. _OUTPUT_PROTO = flags.DEFINE_string( - 'output_proto', '', 'Output file to write the cp_model proto to.') + "output_proto", "", "Output file to write the cp_model proto to." +) _PARAMS = flags.DEFINE_string( - 'params', - 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45', - 'Sat solver parameters.') -_PREPROCESS = flags.DEFINE_bool('--preprocess_times', True, - 'Preprocess setup times and durations') + "params", + "num_search_workers:16,log_search_progress:true,max_time_in_seconds:45", + "Sat solver parameters.", +) +_PREPROCESS = flags.DEFINE_bool( + "--preprocess_times", True, "Preprocess setup times and durations" +) -#---------------------------------------------------------------------------- +# ---------------------------------------------------------------------------- # Intermediate solution printer class SolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" @@ -42,8 +46,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): """Called after each new solution found.""" - print('Solution %i, time = %f s, objective = %i' % - (self.__solution_count, self.WallTime(), self.ObjectiveValue())) + print( + "Solution %i, time = %f s, objective = %i" + % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + ) self.__solution_count += 1 @@ -53,110 +59,343 @@ def single_machine_scheduling(): parameters = _PARAMS.value output_proto_file = _OUTPUT_PROTO.value - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Data. job_durations = [ - 2546, 8589, 5953, 3710, 3630, 3016, 4148, 8706, 1604, 5502, 9983, 6209, - 9920, 7860, 2176 + 2546, + 8589, + 5953, + 3710, + 3630, + 3016, + 4148, + 8706, + 1604, + 5502, + 9983, + 6209, + 9920, + 7860, + 2176, ] setup_times = [ [ - 3559, 1638, 2000, 3676, 2741, 2439, 2406, 1526, 1600, 3356, 4324, - 1923, 3663, 4103, 2215 + 3559, + 1638, + 2000, + 3676, + 2741, + 2439, + 2406, + 1526, + 1600, + 3356, + 4324, + 1923, + 3663, + 4103, + 2215, ], [ - 1442, 3010, 1641, 4490, 2060, 2143, 3376, 3891, 3513, 2855, 2653, - 1471, 2257, 1186, 2354 + 1442, + 3010, + 1641, + 4490, + 2060, + 2143, + 3376, + 3891, + 3513, + 2855, + 2653, + 1471, + 2257, + 1186, + 2354, ], [ - 1728, 3583, 3243, 4080, 2191, 3644, 4023, 3510, 2135, 1346, 1410, - 3565, 3181, 1126, 4169 + 1728, + 3583, + 3243, + 4080, + 2191, + 3644, + 4023, + 3510, + 2135, + 1346, + 1410, + 3565, + 3181, + 1126, + 4169, ], [ - 1291, 1703, 3103, 4001, 1712, 1137, 3341, 3485, 2557, 2435, 1972, - 1986, 1522, 4734, 2520 + 1291, + 1703, + 3103, + 4001, + 1712, + 1137, + 3341, + 3485, + 2557, + 2435, + 1972, + 1986, + 1522, + 4734, + 2520, ], [ - 4134, 2200, 1502, 3995, 1277, 1808, 1020, 2078, 2999, 1605, 1697, - 2323, 2268, 2288, 4856 + 4134, + 2200, + 1502, + 3995, + 1277, + 1808, + 1020, + 2078, + 2999, + 1605, + 1697, + 2323, + 2268, + 2288, + 4856, ], [ - 4974, 2480, 2492, 4088, 2587, 4652, 1478, 3942, 1222, 3305, 1206, - 1024, 2605, 3080, 3516 + 4974, + 2480, + 2492, + 4088, + 2587, + 4652, + 1478, + 3942, + 1222, + 3305, + 1206, + 1024, + 2605, + 3080, + 3516, ], [ - 1903, 2584, 2104, 1609, 4745, 2691, 1539, 2544, 2499, 2074, 4793, - 1756, 2190, 1298, 2605 + 1903, + 2584, + 2104, + 1609, + 4745, + 2691, + 1539, + 2544, + 2499, + 2074, + 4793, + 1756, + 2190, + 1298, + 2605, ], [ - 1407, 2536, 2296, 1769, 1449, 3386, 3046, 1180, 4132, 4783, 3386, - 3429, 2450, 3376, 3719 + 1407, + 2536, + 2296, + 1769, + 1449, + 3386, + 3046, + 1180, + 4132, + 4783, + 3386, + 3429, + 2450, + 3376, + 3719, ], [ - 3026, 1637, 3628, 3096, 1498, 4947, 1912, 3703, 4107, 4730, 1805, - 2189, 1789, 1985, 3586 + 3026, + 1637, + 3628, + 3096, + 1498, + 4947, + 1912, + 3703, + 4107, + 4730, + 1805, + 2189, + 1789, + 1985, + 3586, ], [ - 3940, 1342, 1601, 2737, 1748, 3771, 4052, 1619, 2558, 3782, 4383, - 3451, 4904, 1108, 1750 + 3940, + 1342, + 1601, + 2737, + 1748, + 3771, + 4052, + 1619, + 2558, + 3782, + 4383, + 3451, + 4904, + 1108, + 1750, ], [ - 1348, 3162, 1507, 3936, 1453, 2953, 4182, 2968, 3134, 1042, 3175, - 2805, 4901, 1735, 1654 + 1348, + 3162, + 1507, + 3936, + 1453, + 2953, + 4182, + 2968, + 3134, + 1042, + 3175, + 2805, + 4901, + 1735, + 1654, ], [ - 1099, 1711, 1245, 1067, 4343, 3407, 1108, 1784, 4803, 2342, 3377, - 2037, 3563, 1621, 2840 + 1099, + 1711, + 1245, + 1067, + 4343, + 3407, + 1108, + 1784, + 4803, + 2342, + 3377, + 2037, + 3563, + 1621, + 2840, ], [ - 2573, 4222, 3164, 2563, 3231, 4731, 2395, 1033, 4795, 3288, 2335, - 4935, 4066, 1440, 4979 + 2573, + 4222, + 3164, + 2563, + 3231, + 4731, + 2395, + 1033, + 4795, + 3288, + 2335, + 4935, + 4066, + 1440, + 4979, ], [ - 3321, 1666, 3573, 2377, 4649, 4600, 1065, 2475, 3658, 3374, 1138, - 4367, 4728, 3032, 2198 + 3321, + 1666, + 3573, + 2377, + 4649, + 4600, + 1065, + 2475, + 3658, + 3374, + 1138, + 4367, + 4728, + 3032, + 2198, ], [ - 2986, 1180, 4095, 3132, 3987, 3880, 3526, 1460, 4885, 3827, 4945, - 4419, 3486, 3805, 3804 + 2986, + 1180, + 4095, + 3132, + 3987, + 3880, + 3526, + 1460, + 4885, + 3827, + 4945, + 4419, + 3486, + 3805, + 3804, ], [ - 4163, 3441, 1217, 2941, 1210, 3794, 1779, 1904, 4255, 4967, 4003, - 3873, 1002, 2055, 4295 + 4163, + 3441, + 1217, + 2941, + 1210, + 3794, + 1779, + 1904, + 4255, + 4967, + 4003, + 3873, + 1002, + 2055, + 4295, ], ] due_dates = [ - -1, -1, 28569, -1, 98104, 27644, 55274, 57364, -1, -1, 60875, 96637, - 77888, -1, -1 - ] - release_dates = [ - 0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0 + -1, + -1, + 28569, + -1, + 98104, + 27644, + 55274, + 57364, + -1, + -1, + 60875, + 96637, + 77888, + -1, + -1, ] + release_dates = [0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0] precedences = [(0, 2), (1, 2)] - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Helper data. num_jobs = len(job_durations) all_jobs = range(num_jobs) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Preprocess. if _PREPROCESS.value: for job_id in all_jobs: min_incoming_setup = min( - setup_times[j][job_id] for j in range(num_jobs + 1)) + setup_times[j][job_id] for j in range(num_jobs + 1) + ) if release_dates[job_id] != 0: - min_incoming_setup = min(min_incoming_setup, - release_dates[job_id]) + min_incoming_setup = min(min_incoming_setup, release_dates[job_id]) if min_incoming_setup == 0: continue - print('job %i has a min incoming setup of %i' % - (job_id, min_incoming_setup)) + print( + "job %i has a min incoming setup of %i" % (job_id, min_incoming_setup) + ) # We can transfer some setup times to the duration of the job. job_durations[job_id] += min_incoming_setup # Decrease corresponding incoming setup times. @@ -166,36 +405,37 @@ def single_machine_scheduling(): if release_dates[job_id] != 0: release_dates[job_id] -= min_incoming_setup - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Model. model = cp_model.CpModel() - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Compute a maximum makespan greedily. horizon = sum(job_durations) + sum( - max(setup_times[i][j] - for i in range(num_jobs + 1)) - for j in range(num_jobs)) - print('Greedy horizon =', horizon) + max(setup_times[i][j] for i in range(num_jobs + 1)) for j in range(num_jobs) + ) + print("Greedy horizon =", horizon) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Global storage of variables. intervals = [] starts = [] ends = [] - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Scan the jobs and create the relevant variables and intervals. for job_id in all_jobs: duration = job_durations[job_id] release_date = release_dates[job_id] due_date = due_dates[job_id] if due_dates[job_id] != -1 else horizon - print('job %2i: start = %5i, duration = %4i, end = %6i' % - (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) + print( + "job %2i: start = %5i, duration = %4i, end = %6i" + % (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) starts.append(start) ends.append(end) intervals.append(interval) @@ -203,24 +443,24 @@ def single_machine_scheduling(): # No overlap constraint. model.AddNoOverlap(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.NewBoolVar("") 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) # Final arc from an arc to the dummy node. - arcs.append([i + 1, 0, model.NewBoolVar('')]) + arcs.append([i + 1, 0, model.NewBoolVar("")]) for j in all_jobs: if i == j: continue - lit = model.NewBoolVar('%i follows %i' % (j, i)) + lit = model.NewBoolVar("%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 @@ -228,34 +468,36 @@ 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(lit) + model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + lit + ) else: - model.Add(starts[j] >= ends[i] + - setup_times[i + 1][j]).OnlyEnforceIf(lit) + model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + lit + ) model.AddCircuit(arcs) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Precedences. for before, after in precedences: - print('job %i is after job %i' % (after, before)) + print("job %i is after job %i" % (after, before)) model.Add(ends[before] <= starts[after]) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Objective. - makespan = model.NewIntVar(0, horizon, 'makespan') + makespan = model.NewIntVar(0, horizon, "makespan") model.AddMaxEquality(makespan, ends) model.Minimize(makespan) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Write problem to file. if output_proto_file: - print('Writing proto to %s' % output_proto_file) - with open(output_proto_file, 'w') as text_file: + print("Writing proto to %s" % output_proto_file) + with open(output_proto_file, "w") as text_file: text_file.write(str(model)) - #---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # Solve. solver = cp_model.CpSolver() if parameters: @@ -264,15 +506,16 @@ def single_machine_scheduling(): 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 %i starts at %i end ends at %i" + % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id])) + ) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") single_machine_scheduling() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/spread_robots_sat.py b/examples/python/spread_robots_sat.py index fe367499f1..fb5011b316 100644 --- a/examples/python/spread_robots_sat.py +++ b/examples/python/spread_robots_sat.py @@ -11,6 +11,7 @@ # 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. + """Maximize the minimum of pairwise distances between n robots in a square space.""" import math @@ -21,14 +22,14 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -_NUM_ROBOTS = flags.DEFINE_integer('num_robots', 8, - 'Number of robots to place.') -_ROOM_SIZE = flags.DEFINE_integer('room_size', 20, - 'Size of the square room where robots are.') +_NUM_ROBOTS = flags.DEFINE_integer("num_robots", 8, "Number of robots to place.") +_ROOM_SIZE = flags.DEFINE_integer( + "room_size", 20, "Size of the square room where robots are." +) _PARAMS = flags.DEFINE_string( - 'params', - 'num_search_workers:16, max_time_in_seconds:20', - 'Sat solver parameters.', + "params", + "num_search_workers:16, max_time_in_seconds:20", + "Sat solver parameters.", ) @@ -37,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.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)] # The specification of the problem is to maximize the minimum euclidian # distance between any two robots. Unfortunately, the euclidian distance @@ -57,30 +58,30 @@ 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(0, 2 * scaling * room_size**2, - 'scaled_min_square_distance') + scaled_min_square_distance = model.NewIntVar( + 0, 2 * scaling * room_size**2, "scaled_min_square_distance" + ) # Build intermediate variables and get the list of squared distances on # each dimension. 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}') + 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]) # 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}') + 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) # 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): @@ -99,20 +100,22 @@ def spread_robots(num_robots: int, room_size: int, params: str): # 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)}') + print( + f"Spread {num_robots} with a min pairwise distance of" + f" {math.sqrt(solver.ObjectiveValue() / 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.') + print("No solution found.") def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") spread_robots(_NUM_ROBOTS.value, _ROOM_SIZE.value, _PARAMS.value) -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/steel_mill_slab_sat.py b/examples/python/steel_mill_slab_sat.py index 10fb286dbf..2f99ca928a 100755 --- a/examples/python/steel_mill_slab_sat.py +++ b/examples/python/steel_mill_slab_sat.py @@ -11,6 +11,7 @@ # 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 Stell Mill Slab problem with 4 different techniques.""" # overloaded sum() clashes with pytype. @@ -20,21 +21,54 @@ import time from absl import app from absl import flags +from google.protobuf import text_format from ortools.sat.python import cp_model -_PROBLEM = flags.DEFINE_integer('problem', 2, 'Problem id to solve.') + +_PROBLEM = flags.DEFINE_integer("problem", 2, "Problem id to solve.") _BREAK_SYMMETRIES = flags.DEFINE_boolean( - 'break_symmetries', True, 'Break symmetries between equivalent orders.') + "break_symmetries", True, "Break symmetries between equivalent orders." +) _SOLVER = flags.DEFINE_string( - 'solver', 'sat_column', 'Method used to solve: sat, sat_table, sat_column.') + "solver", "sat_column", "Method used to solve: sat, sat_table, sat_column." +) +_PARAMS = flags.DEFINE_string( + "params", + "max_time_in_seconds:20,num_workers:8,log_search_progress:true", + "CP-SAT parameters.", +) def build_problem(problem_id): """Build problem data.""" + capacities = None + num_colors = None + num_slabs = None + orders = None + if problem_id == 0: capacities = [ - 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, - 39, 42, 43, 44 + 0, + 12, + 14, + 17, + 18, + 19, + 20, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 32, + 35, + 39, + 42, + 43, + 44, ] num_colors = 88 num_slabs = 111 @@ -149,7 +183,7 @@ def build_problem(problem_id): (27, 85), (27, 86), (10, 87), - (3, 88) + (3, 88), ] elif problem_id == 1: capacities = [0, 17, 44] @@ -191,7 +225,7 @@ def build_problem(problem_id): (22, 20), (5, 21), (4, 22), - (10, 23) + (10, 23), ] elif problem_id == 2: capacities = [0, 17, 44] @@ -225,7 +259,7 @@ def build_problem(problem_id): (3, 6), (22, 14), (19, 15), - (19, 15) + (19, 15), ] elif problem_id == 3: @@ -243,7 +277,7 @@ def build_problem(problem_id): (4, 7), (7, 4), (7, 8), - (3, 6) + (3, 6), ] return (num_slabs, capacities, num_colors, orders) @@ -267,21 +301,29 @@ class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback): """Called on each new solution.""" current_time = time.time() 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)) + 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]) - ] for s in self.__all_slabs] + orders_in_slab = [ + [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])) + line = " - slab %i, load = %i, loss = %i, orders = [" % ( + s, + self.Value(self.__load[s]), + self.Value(self.__loss[s]), + ) for o in orders_in_slab[s]: - line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0], - self.__orders[o][1]) - line += ']' + line += "#%i(w%i, c%i) " % ( + o, + self.__orders[o][0], + self.__orders[o][1], + ) + line += "]" print(line) @@ -295,16 +337,17 @@ def steel_mill_slab(problem, break_symmetries): all_slabs = range(num_slabs) all_colors = range(num_colors) all_orders = range(len(orders)) - print('Solving steel mill with %i orders, %i slabs, and %i capacities' % - (num_orders, num_slabs, num_capacities - 1)) + print( + "Solving steel mill with %i orders, %i slabs, and %i capacities" + % (num_orders, num_slabs, num_capacities - 1) + ) # Compute auxiliary data. widths = [x[0] for x in orders] colors = [x[1] for x in orders] max_capacity = max(capacities) loss_array = [ - min(x for x in capacities if x >= c) - c for c in range(max_capacity + - 1) + min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1) ] max_loss = max(loss_array) orders_per_color = [ @@ -318,16 +361,15 @@ 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 - ] for o in all_orders] - loads = [ - model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s) + assign = [ + [model.NewBoolVar("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] + color_is_in_slab = [ + [model.NewBoolVar("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors] for s in all_slabs ] - color_is_in_slab = [[ - model.NewBoolVar('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: @@ -345,8 +387,7 @@ def steel_mill_slab(problem, break_symmetries): 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.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not()) # At most two colors per slab. for s in all_slabs: @@ -381,35 +422,39 @@ def steel_mill_slab(problem, break_symmetries): if w not in local_width_to_order: local_width_to_order[w] = [] local_width_to_order[w].append(o) - for w, os in local_width_to_order.items(): + for _, os in local_width_to_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) - for w, os in width_to_unique_color_order.items(): + for _, os in width_to_unique_color_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) # Create position variables if there are symmetries to be broken. if break_symmetries and ordered_equivalent_orders: - print(' - creating %i symmetry breaking constraints' % - len(ordered_equivalent_orders)) + print( + " - creating %i symmetry breaking constraints" + % len(ordered_equivalent_orders) + ) positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: - positions[p[0]] = model.NewIntVar(0, num_slabs - 1, - 'position_of_slab_%i' % p[0]) + positions[p[0]] = model.NewIntVar( + 0, num_slabs - 1, "position_of_slab_%i" % p[0] + ) model.AddMapDomain(positions[p[0]], assign[p[0]]) if p[1] not in positions: - positions[p[1]] = model.NewIntVar(0, num_slabs - 1, - 'position_of_slab_%i' % p[1]) + positions[p[1]] = model.NewIntVar( + 0, num_slabs - 1, "position_of_slab_%i" % p[1] + ) model.AddMapDomain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. 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.NewIntVar(0, num_slabs * max_loss, "obj") + losses = [model.NewIntVar(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)) @@ -417,17 +462,19 @@ def steel_mill_slab(problem, break_symmetries): ### Solve model. solver = cp_model.CpSolver() - solver.parameters.num_search_workers = 8 + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) objective_printer = cp_model.ObjectiveSolutionPrinter() 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())) + "Loss = %i, time = %f s, %i conflicts" + % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + ) else: - print('No solution') + print("No solution") def collect_valid_slabs_dp(capacities, colors, widths, loss_array): @@ -436,13 +483,11 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array): max_capacity = max(capacities) - valid_assignment = collections.namedtuple('valid_assignment', - 'orders load colors') + valid_assignment = collections.namedtuple("valid_assignment", "orders load colors") all_valid_assignments = [valid_assignment(orders=[], load=0, colors=[])] - for order_id in range(len(colors)): + for order_id, new_color in enumerate(colors): new_width = widths[order_id] - new_color = colors[order_id] new_assignments = [] for assignment in all_valid_assignments: if assignment.load + new_width > max_capacity: @@ -452,18 +497,21 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array): new_colors.append(new_color) if len(new_colors) > 2: continue - new_assignment = valid_assignment(orders=assignment.orders + - [order_id], - load=assignment.load + new_width, - colors=new_colors) + new_assignment = valid_assignment( + orders=assignment.orders + [order_id], + load=assignment.load + new_width, + colors=new_colors, + ) new_assignments.append(new_assignment) all_valid_assignments.extend(new_assignments) - print('%i assignments created in %.2f s' % - (len(all_valid_assignments), time.time() - start_time)) + print( + "%i assignments created in %.2f s" + % (len(all_valid_assignments), time.time() - start_time) + ) tuples = [] for assignment in all_valid_assignments: - solution = [0 for _ in range(len(colors))] + solution = [0] * len(colors) for i in assignment.orders: solution[i] = 1 solution.append(loss_array[assignment.load]) @@ -483,16 +531,17 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): all_slabs = range(num_slabs) all_colors = range(num_colors) all_orders = range(len(orders)) - print('Solving steel mill with %i orders, %i slabs, and %i capacities' % - (num_orders, num_slabs, num_capacities - 1)) + print( + "Solving steel mill with %i orders, %i slabs, and %i capacities" + % (num_orders, num_slabs, num_capacities - 1) + ) # Compute auxiliary data. widths = [x[0] for x in orders] colors = [x[1] for x in orders] max_capacity = max(capacities) loss_array = [ - min(x for x in capacities if x >= c) - c for c in range(max_capacity + - 1) + min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1) ] max_loss = max(loss_array) @@ -500,21 +549,23 @@ 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 - ] 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] + assign = [ + [model.NewBoolVar("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] - unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths, - loss_array) + unsorted_valid_slabs = collect_valid_slabs_dp( + capacities, colors, widths, loss_array + ) # Sort slab by descending load/loss. Remove duplicates. - valid_slabs = sorted(unsorted_valid_slabs, - key=lambda c: 1000 * c[-1] + c[-2]) + valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2]) for s in all_slabs: - model.AddAllowedAssignments([assign[o][s] for o in all_orders] + - [losses[s], loads[s]], valid_slabs) + model.AddAllowedAssignments( + [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: @@ -529,7 +580,7 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): # Collect equivalent orders. if break_symmetries: - print('Breaking symmetries') + print("Breaking symmetries") width_to_unique_color_order = {} ordered_equivalent_orders = [] orders_per_color = [ @@ -553,28 +604,32 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): if w not in local_width_to_order: local_width_to_order[w] = [] local_width_to_order[w].append(o) - for w, os in local_width_to_order.items(): + for _, os in local_width_to_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) - for w, os in width_to_unique_color_order.items(): + for _, os in width_to_unique_color_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) # Create position variables if there are symmetries to be broken. if ordered_equivalent_orders: - print(' - creating %i symmetry breaking constraints' % - len(ordered_equivalent_orders)) + print( + " - creating %i symmetry breaking constraints" + % len(ordered_equivalent_orders) + ) positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: positions[p[0]] = model.NewIntVar( - 0, num_slabs - 1, 'position_of_slab_%i' % p[0]) + 0, num_slabs - 1, "position_of_slab_%i" % p[0] + ) model.AddMapDomain(positions[p[0]], assign[p[0]]) if p[1] not in positions: positions[p[1]] = model.NewIntVar( - 0, num_slabs - 1, 'position_of_slab_%i' % p[1]) + 0, num_slabs - 1, "position_of_slab_%i" % p[1] + ) model.AddMapDomain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. model.Add(positions[p[0]] <= positions[p[1]]) @@ -582,22 +637,24 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): # Objective. model.Minimize(sum(losses)) - print('Model created') + print("Model created") ### Solve model. solver = cp_model.CpSolver() - solver.num_search_workers = 8 - solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, - losses) + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) + + solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses) 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())) + "Loss = %i, time = %.2f s, %i conflicts" + % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + ) else: - print('No solution') + print("No solution") def steel_mill_slab_with_column_generation(problem): @@ -608,77 +665,77 @@ def steel_mill_slab_with_column_generation(problem): num_orders = len(orders) num_capacities = len(capacities) all_orders = range(len(orders)) - print('Solving steel mill with %i orders, %i slabs, and %i capacities' % - (num_orders, num_slabs, num_capacities - 1)) + print( + "Solving steel mill with %i orders, %i slabs, and %i capacities" + % (num_orders, num_slabs, num_capacities - 1) + ) # Compute auxiliary data. widths = [x[0] for x in orders] colors = [x[1] for x in orders] max_capacity = max(capacities) loss_array = [ - min(x for x in capacities if x >= c) - c for c in range(max_capacity + - 1) + min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1) ] ### Model problem. # Generate all valid slabs (columns) - unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths, - loss_array) + unsorted_valid_slabs = collect_valid_slabs_dp( + capacities, colors, widths, loss_array + ) # Sort slab by descending load/loss. Remove duplicates. - valid_slabs = sorted(unsorted_valid_slabs, - key=lambda c: 1000 * c[-1] + c[-2]) + valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2]) all_valid_slabs = range(len(valid_slabs)) # create model and decision variables. model = cp_model.CpModel() - selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs] + selected = [model.NewBoolVar("selected_%i" % i) for i in all_valid_slabs] for order_id in all_orders: model.Add( - sum(selected[i] - for i, slab in enumerate(valid_slabs) - if slab[order_id]) == 1) + 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( - sum(selected[i] * valid_slabs[i][-1] - for i in all_valid_slabs) == sum(widths)) + 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') + print("Model created") ### Solve model. solver = cp_model.CpSolver() - solver.parameters.num_search_workers = 8 - solver.parameters.log_search_progress = True + if _PARAMS.value: + text_format.Parse(_PARAMS.value, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() 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())) + "Loss = %i, time = %.2f s, %i conflicts" + % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + ) else: - print('No solution') + print("No solution") def main(_): - if _SOLVER.value == 'sat': + if _SOLVER.value == "sat": steel_mill_slab(_PROBLEM.value, _BREAK_SYMMETRIES.value) - elif _SOLVER.value == 'sat_table': - steel_mill_slab_with_valid_slabs(_PROBLEM.value, - _BREAK_SYMMETRIES.value) - elif _SOLVER.value == 'sat_column': + elif _SOLVER.value == "sat_table": + steel_mill_slab_with_valid_slabs(_PROBLEM.value, _BREAK_SYMMETRIES.value) + elif _SOLVER.value == "sat_column": steel_mill_slab_with_column_generation(_PROBLEM.value) else: - print(f'Unknown model {_SOLVER.value}') + print(f"Unknown model {_SOLVER.value}") -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/sudoku_sat.py b/examples/python/sudoku_sat.py index cfd65e26a2..bf4b391ca9 100644 --- a/examples/python/sudoku_sat.py +++ b/examples/python/sudoku_sat.py @@ -11,6 +11,7 @@ # 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. + """This model implements a sudoku solver.""" from ortools.sat.python import cp_model @@ -26,16 +27,22 @@ def solve_sudoku(): line = list(range(0, line_size)) cell = list(range(0, cell_size)) - initial_grid = [[0, 6, 0, 0, 5, 0, 0, 2, 0], [0, 0, 0, 3, 0, 0, 0, 9, 0], - [7, 0, 0, 6, 0, 0, 0, 1, 0], [0, 0, 6, 0, 3, 0, 4, 0, 0], - [0, 0, 4, 0, 7, 0, 1, 0, 0], [0, 0, 5, 0, 9, 0, 8, 0, 0], - [0, 4, 0, 0, 0, 1, 0, 0, 6], [0, 3, 0, 0, 0, 8, 0, 0, 0], - [0, 2, 0, 0, 4, 0, 0, 5, 0]] + initial_grid = [ + [0, 6, 0, 0, 5, 0, 0, 2, 0], + [0, 0, 0, 3, 0, 0, 0, 9, 0], + [7, 0, 0, 6, 0, 0, 0, 1, 0], + [0, 0, 6, 0, 3, 0, 4, 0, 0], + [0, 0, 4, 0, 7, 0, 1, 0, 0], + [0, 0, 5, 0, 9, 0, 8, 0, 0], + [0, 4, 0, 0, 0, 1, 0, 0, 6], + [0, 3, 0, 0, 0, 8, 0, 0, 0], + [0, 2, 0, 0, 4, 0, 0, 5, 0], + ] 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.NewIntVar(1, line_size, "grid %i %i" % (i, j)) # AllDifferent on rows. for i in line: @@ -51,8 +58,7 @@ def solve_sudoku(): one_cell = [] for di in cell: for dj in cell: - one_cell.append(grid[(i * cell_size + di, - j * cell_size + dj)]) + one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)]) model.AddAllDifferent(one_cell) diff --git a/examples/python/task_allocation_sat.py b/examples/python/task_allocation_sat.py index 0a623d1798..15d26ac1ab 100644 --- a/examples/python/task_allocation_sat.py +++ b/examples/python/task_allocation_sat.py @@ -11,6 +11,7 @@ # 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. + """CP-SAT model for task allocation problem. see http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc- @@ -25,256 +26,2608 @@ from ortools.sat.python import cp_model def task_allocation_sat(): """Solves the task allocation problem.""" # Availability matrix. - available = [[ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0 - ], - [ - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 1, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]] + available = [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ], + [ + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ] ntasks = len(available) nslots = len(available[0]) @@ -291,30 +2644,31 @@ 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.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] for task in all_tasks: model.Add( - sum(assign[(task, slot)] - for slot in all_slots - if available[task][slot] == 1) == 1) + sum( + assign[(task, slot)] for slot in all_slots if available[task][slot] == 1 + ) + == 1 + ) for slot in all_slots: model.Add( - sum(assign[(task, slot)] - for task in all_tasks - if available[task][slot] == 1) <= capacity) - model.AddBoolOr([ - assign[(task, slot)] - for task in all_tasks - if available[task][slot] == 1 - ]).OnlyEnforceIf(slot_used[slot]) + sum( + assign[(task, slot)] for task in all_tasks if available[task][slot] == 1 + ) + <= capacity + ) + model.AddBoolOr( + [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1] + ).OnlyEnforceIf(slot_used[slot]) for task in all_tasks: if available[task][slot] == 1: - model.AddImplication(slot_used[slot].Not(), - assign[(task, slot)].Not()) + model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not()) else: model.Add(assign[(task, slot)] == 0) @@ -331,17 +2685,17 @@ def task_allocation_sat(): solver.parameters.num_search_workers = 16 status = solver.Solve(model) - print('Statistics') - print(' - status =', solver.StatusName(status)) - print(' - optimal solution =', solver.ObjectiveValue()) - print(' - wall time : %f s' % solver.WallTime()) + print("Statistics") + print(" - status =", solver.StatusName(status)) + print(" - optimal solution =", solver.ObjectiveValue()) + print(" - wall time : %f s" % solver.WallTime()) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") task_allocation_sat() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/tasks_and_workers_assignment_sat.py b/examples/python/tasks_and_workers_assignment_sat.py index ee604335e0..0960f59c5c 100644 --- a/examples/python/tasks_and_workers_assignment_sat.py +++ b/examples/python/tasks_and_workers_assignment_sat.py @@ -11,6 +11,7 @@ # 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. + """Tasks and workers to group assignment to average sum(cost) / #workers.""" from typing import Sequence @@ -26,8 +27,10 @@ class ObjectivePrinter(cp_model.CpSolverSolutionCallback): self.__solution_count = 0 def on_solution_callback(self): - print('Solution %i, time = %f s, objective = %i' % - (self.__solution_count, self.WallTime(), self.ObjectiveValue())) + print( + "Solution %i, time = %f s, objective = %i" + % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + ) self.__solution_count += 1 @@ -50,13 +53,13 @@ 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.NewBoolVar("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.NewBoolVar("x[%i,%i]" % (k, j)) # Constraints @@ -75,13 +78,11 @@ def tasks_and_workers_assignment_sat(): 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) + 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) + 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) + a = model.NewIntVar(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j) model.AddDivisionEquality(a, c, n) averages.append(a) @@ -92,7 +93,7 @@ def tasks_and_workers_assignment_sat(): model.Add(sum(num_workers_in_group) == num_workers) # Objective. - obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj') + obj = model.NewIntVar(0, sum_of_costs * scaling, "obj") model.AddMaxEquality(obj, averages) model.Minimize(obj) @@ -105,17 +106,18 @@ def tasks_and_workers_assignment_sat(): if status == cp_model.OPTIMAL: for j in all_groups: - print('Group %i' % j) + print("Group %i" % j) for i in all_workers: if solver.BooleanValue(x[i, j]): - print(' - worker %i' % i) + print(" - worker %i" % i) for k in all_tasks: if solver.BooleanValue(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)) - print(' - average cost = %f' % - (solver.Value(averages[j]) * 1.0 / scaling)) + 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) + ) + print(" - average cost = %f" % (solver.Value(averages[j]) * 1.0 / scaling)) tasks_and_workers_assignment_sat() @@ -123,9 +125,9 @@ tasks_and_workers_assignment_sat() def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") tasks_and_workers_assignment_sat() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/tsp_sat.py b/examples/python/tsp_sat.py index 3d12f22478..ebca05fcc2 100644 --- a/examples/python/tsp_sat.py +++ b/examples/python/tsp_sat.py @@ -11,51 +11,1692 @@ # 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. + """Simple travelling salesman problem between cities.""" from ortools.sat.python import cp_model DISTANCE_MATRIX = [ - [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056], - [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994], - [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589], - [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743], - [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198], - [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078], - [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967], - [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552], - [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422], - [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935], - [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891], - [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002], - [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225], - [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671], - [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475], - [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633], - [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084], - [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315], - [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866], - [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802], - [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682], - [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471], - [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720], - [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674], - [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908], - [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204], - [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000], - [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066], - [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039], - [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721], - [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496], - [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772], - [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614], - [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805], - [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519], - [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614], - [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749], - [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313], - [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042], - [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0], + [ + 0, + 10938, + 4542, + 2835, + 29441, + 2171, + 1611, + 9208, + 9528, + 11111, + 16120, + 22606, + 22127, + 20627, + 21246, + 23387, + 16697, + 33609, + 26184, + 24772, + 22644, + 20655, + 30492, + 23296, + 32979, + 18141, + 19248, + 17129, + 17192, + 15645, + 12658, + 11210, + 12094, + 13175, + 18162, + 4968, + 12308, + 10084, + 13026, + 15056, + ], + [ + 10938, + 0, + 6422, + 9742, + 18988, + 12974, + 11216, + 19715, + 19004, + 18271, + 25070, + 31971, + 31632, + 30571, + 31578, + 33841, + 27315, + 43964, + 36944, + 35689, + 33569, + 31481, + 41360, + 33760, + 43631, + 28730, + 29976, + 27803, + 28076, + 26408, + 23504, + 22025, + 22000, + 13197, + 14936, + 15146, + 23246, + 20956, + 23963, + 25994, + ], + [ + 4542, + 6422, + 0, + 3644, + 25173, + 6552, + 5092, + 13584, + 13372, + 13766, + 19805, + 26537, + 26117, + 24804, + 25590, + 27784, + 21148, + 37981, + 30693, + 29315, + 27148, + 25071, + 34943, + 27472, + 37281, + 22389, + 23592, + 21433, + 21655, + 20011, + 17087, + 15612, + 15872, + 11653, + 15666, + 8842, + 16843, + 14618, + 17563, + 19589, + ], + [ + 2835, + 9742, + 3644, + 0, + 28681, + 3851, + 4341, + 11660, + 12294, + 13912, + 18893, + 25283, + 24777, + 23173, + 23636, + 25696, + 18950, + 35927, + 28233, + 26543, + 24127, + 21864, + 31765, + 24018, + 33904, + 19005, + 20295, + 18105, + 18551, + 16763, + 13958, + 12459, + 12296, + 10370, + 15331, + 5430, + 14044, + 12135, + 14771, + 16743, + ], + [ + 29441, + 18988, + 25173, + 28681, + 0, + 31590, + 29265, + 37173, + 35501, + 32929, + 40239, + 47006, + 46892, + 46542, + 48112, + 50506, + 44539, + 60103, + 54208, + 53557, + 51878, + 50074, + 59849, + 52645, + 62415, + 47544, + 48689, + 46560, + 46567, + 45086, + 42083, + 40648, + 40971, + 29929, + 28493, + 34015, + 41473, + 38935, + 42160, + 44198, + ], + [ + 2171, + 12974, + 6552, + 3851, + 31590, + 0, + 3046, + 7856, + 8864, + 11330, + 15411, + 21597, + 21065, + 19382, + 19791, + 21845, + 15099, + 32076, + 24425, + 22848, + 20600, + 18537, + 28396, + 21125, + 30825, + 15975, + 17101, + 14971, + 15104, + 13503, + 10544, + 9080, + 9983, + 13435, + 18755, + 2947, + 10344, + 8306, + 11069, + 13078, + ], + [ + 1611, + 11216, + 5092, + 4341, + 29265, + 3046, + 0, + 8526, + 8368, + 9573, + 14904, + 21529, + 21085, + 19719, + 20504, + 22713, + 16118, + 32898, + 25728, + 24541, + 22631, + 20839, + 30584, + 23755, + 33278, + 18557, + 19545, + 17490, + 17309, + 15936, + 12881, + 11498, + 12944, + 14711, + 19589, + 5993, + 12227, + 9793, + 12925, + 14967, + ], + [ + 9208, + 19715, + 13584, + 11660, + 37173, + 7856, + 8526, + 0, + 3248, + 7855, + 8245, + 13843, + 13272, + 11526, + 12038, + 14201, + 7599, + 24411, + 17259, + 16387, + 15050, + 13999, + 23134, + 17899, + 26460, + 12894, + 13251, + 11680, + 10455, + 9997, + 7194, + 6574, + 10678, + 20959, + 26458, + 8180, + 5255, + 2615, + 5730, + 7552, + ], + [ + 9528, + 19004, + 13372, + 12294, + 35501, + 8864, + 8368, + 3248, + 0, + 4626, + 6598, + 13168, + 12746, + 11567, + 12731, + 15083, + 9120, + 25037, + 18718, + 18433, + 17590, + 16888, + 25630, + 20976, + 29208, + 16055, + 16300, + 14838, + 13422, + 13165, + 10430, + 9813, + 13777, + 22300, + 27564, + 10126, + 8388, + 5850, + 8778, + 10422, + ], + [ + 11111, + 18271, + 13766, + 13912, + 32929, + 11330, + 9573, + 7855, + 4626, + 0, + 7318, + 14185, + 14005, + 13655, + 15438, + 17849, + 12839, + 27179, + 21947, + 22230, + 21814, + 21366, + 29754, + 25555, + 33535, + 20674, + 20872, + 19457, + 17961, + 17787, + 15048, + 14372, + 18115, + 24280, + 29101, + 13400, + 13008, + 10467, + 13375, + 14935, + ], + [ + 16120, + 25070, + 19805, + 18893, + 40239, + 15411, + 14904, + 8245, + 6598, + 7318, + 0, + 6939, + 6702, + 6498, + 8610, + 10961, + 7744, + 19889, + 15350, + 16403, + 16975, + 17517, + 24357, + 22176, + 28627, + 18093, + 17672, + 16955, + 14735, + 15510, + 13694, + 13768, + 18317, + 28831, + 34148, + 16326, + 11276, + 9918, + 11235, + 11891, + ], + [ + 22606, + 31971, + 26537, + 25283, + 47006, + 21597, + 21529, + 13843, + 13168, + 14185, + 6939, + 0, + 793, + 3401, + 5562, + 6839, + 8923, + 13433, + 11264, + 13775, + 15853, + 17629, + 21684, + 22315, + 26411, + 19539, + 18517, + 18636, + 16024, + 17632, + 16948, + 17587, + 22131, + 34799, + 40296, + 21953, + 14739, + 14568, + 14366, + 14002, + ], + [ + 22127, + 31632, + 26117, + 24777, + 46892, + 21065, + 21085, + 13272, + 12746, + 14005, + 6702, + 793, + 0, + 2608, + 4809, + 6215, + 8151, + 13376, + 10702, + 13094, + 15099, + 16845, + 21039, + 21535, + 25744, + 18746, + 17725, + 17845, + 15232, + 16848, + 16197, + 16859, + 21391, + 34211, + 39731, + 21345, + 14006, + 13907, + 13621, + 13225, + ], + [ + 20627, + 30571, + 24804, + 23173, + 46542, + 19382, + 19719, + 11526, + 11567, + 13655, + 6498, + 3401, + 2608, + 0, + 2556, + 4611, + 5630, + 13586, + 9157, + 11005, + 12681, + 14285, + 19044, + 18996, + 23644, + 16138, + 15126, + 15240, + 12625, + 14264, + 13736, + 14482, + 18958, + 32292, + 37879, + 19391, + 11621, + 11803, + 11188, + 10671, + ], + [ + 21246, + 31578, + 25590, + 23636, + 48112, + 19791, + 20504, + 12038, + 12731, + 15438, + 8610, + 5562, + 4809, + 2556, + 0, + 2411, + 4917, + 12395, + 6757, + 8451, + 10292, + 12158, + 16488, + 16799, + 21097, + 14374, + 13194, + 13590, + 10943, + 12824, + 12815, + 13779, + 18042, + 32259, + 37918, + 19416, + 10975, + 11750, + 10424, + 9475, + ], + [ + 23387, + 33841, + 27784, + 25696, + 50506, + 21845, + 22713, + 14201, + 15083, + 17849, + 10961, + 6839, + 6215, + 4611, + 2411, + 0, + 6760, + 10232, + 4567, + 7010, + 9607, + 12003, + 14846, + 16408, + 19592, + 14727, + 13336, + 14109, + 11507, + 13611, + 14104, + 15222, + 19237, + 34013, + 39703, + 21271, + 12528, + 13657, + 11907, + 10633, + ], + [ + 16697, + 27315, + 21148, + 18950, + 44539, + 15099, + 16118, + 7599, + 9120, + 12839, + 7744, + 8923, + 8151, + 5630, + 4917, + 6760, + 0, + 16982, + 9699, + 9400, + 9302, + 9823, + 16998, + 14534, + 21042, + 10911, + 10190, + 9900, + 7397, + 8758, + 8119, + 8948, + 13353, + 27354, + 33023, + 14542, + 6106, + 6901, + 5609, + 5084, + ], + [ + 33609, + 43964, + 37981, + 35927, + 60103, + 32076, + 32898, + 24411, + 25037, + 27179, + 19889, + 13433, + 13376, + 13586, + 12395, + 10232, + 16982, + 0, + 8843, + 12398, + 16193, + 19383, + 16423, + 22583, + 20997, + 22888, + 21194, + 22640, + 20334, + 22636, + 23801, + 25065, + 28675, + 44048, + 49756, + 31426, + 22528, + 23862, + 21861, + 20315, + ], + [ + 26184, + 36944, + 30693, + 28233, + 54208, + 24425, + 25728, + 17259, + 18718, + 21947, + 15350, + 11264, + 10702, + 9157, + 6757, + 4567, + 9699, + 8843, + 0, + 3842, + 7518, + 10616, + 10666, + 14237, + 15515, + 14053, + 12378, + 13798, + 11537, + 13852, + 15276, + 16632, + 19957, + 35660, + 41373, + 23361, + 14333, + 16125, + 13624, + 11866, + ], + [ + 24772, + 35689, + 29315, + 26543, + 53557, + 22848, + 24541, + 16387, + 18433, + 22230, + 16403, + 13775, + 13094, + 11005, + 8451, + 7010, + 9400, + 12398, + 3842, + 0, + 3795, + 7014, + 8053, + 10398, + 12657, + 10633, + 8889, + 10569, + 8646, + 10938, + 12906, + 14366, + 17106, + 33171, + 38858, + 21390, + 12507, + 14748, + 11781, + 9802, + ], + [ + 22644, + 33569, + 27148, + 24127, + 51878, + 20600, + 22631, + 15050, + 17590, + 21814, + 16975, + 15853, + 15099, + 12681, + 10292, + 9607, + 9302, + 16193, + 7518, + 3795, + 0, + 3250, + 8084, + 6873, + 11763, + 6949, + 5177, + 7050, + 5619, + 7730, + 10187, + 11689, + 13792, + 30012, + 35654, + 18799, + 10406, + 12981, + 9718, + 7682, + ], + [ + 20655, + 31481, + 25071, + 21864, + 50074, + 18537, + 20839, + 13999, + 16888, + 21366, + 17517, + 17629, + 16845, + 14285, + 12158, + 12003, + 9823, + 19383, + 10616, + 7014, + 3250, + 0, + 9901, + 4746, + 12531, + 3737, + 1961, + 4036, + 3588, + 5109, + 7996, + 9459, + 10846, + 27094, + 32690, + 16451, + 8887, + 11624, + 8304, + 6471, + ], + [ + 30492, + 41360, + 34943, + 31765, + 59849, + 28396, + 30584, + 23134, + 25630, + 29754, + 24357, + 21684, + 21039, + 19044, + 16488, + 14846, + 16998, + 16423, + 10666, + 8053, + 8084, + 9901, + 0, + 9363, + 4870, + 13117, + 11575, + 13793, + 13300, + 15009, + 17856, + 19337, + 20454, + 36551, + 42017, + 26352, + 18403, + 21033, + 17737, + 15720, + ], + [ + 23296, + 33760, + 27472, + 24018, + 52645, + 21125, + 23755, + 17899, + 20976, + 25555, + 22176, + 22315, + 21535, + 18996, + 16799, + 16408, + 14534, + 22583, + 14237, + 10398, + 6873, + 4746, + 9363, + 0, + 10020, + 5211, + 4685, + 6348, + 7636, + 8010, + 11074, + 12315, + 11926, + 27537, + 32880, + 18634, + 12644, + 15358, + 12200, + 10674, + ], + [ + 32979, + 43631, + 37281, + 33904, + 62415, + 30825, + 33278, + 26460, + 29208, + 33535, + 28627, + 26411, + 25744, + 23644, + 21097, + 19592, + 21042, + 20997, + 15515, + 12657, + 11763, + 12531, + 4870, + 10020, + 0, + 14901, + 13738, + 15855, + 16118, + 17348, + 20397, + 21793, + 21936, + 37429, + 42654, + 28485, + 21414, + 24144, + 20816, + 18908, + ], + [ + 18141, + 28730, + 22389, + 19005, + 47544, + 15975, + 18557, + 12894, + 16055, + 20674, + 18093, + 19539, + 18746, + 16138, + 14374, + 14727, + 10911, + 22888, + 14053, + 10633, + 6949, + 3737, + 13117, + 5211, + 14901, + 0, + 1777, + 1217, + 3528, + 2896, + 5892, + 7104, + 7338, + 23517, + 29068, + 13583, + 7667, + 10304, + 7330, + 6204, + ], + [ + 19248, + 29976, + 23592, + 20295, + 48689, + 17101, + 19545, + 13251, + 16300, + 20872, + 17672, + 18517, + 17725, + 15126, + 13194, + 13336, + 10190, + 21194, + 12378, + 8889, + 5177, + 1961, + 11575, + 4685, + 13738, + 1777, + 0, + 2217, + 2976, + 3610, + 6675, + 8055, + 8965, + 25197, + 30774, + 14865, + 8007, + 10742, + 7532, + 6000, + ], + [ + 17129, + 27803, + 21433, + 18105, + 46560, + 14971, + 17490, + 11680, + 14838, + 19457, + 16955, + 18636, + 17845, + 15240, + 13590, + 14109, + 9900, + 22640, + 13798, + 10569, + 7050, + 4036, + 13793, + 6348, + 15855, + 1217, + 2217, + 0, + 2647, + 1686, + 4726, + 6000, + 6810, + 23060, + 28665, + 12674, + 6450, + 9094, + 6117, + 5066, + ], + [ + 17192, + 28076, + 21655, + 18551, + 46567, + 15104, + 17309, + 10455, + 13422, + 17961, + 14735, + 16024, + 15232, + 12625, + 10943, + 11507, + 7397, + 20334, + 11537, + 8646, + 5619, + 3588, + 13300, + 7636, + 16118, + 3528, + 2976, + 2647, + 0, + 2320, + 4593, + 6093, + 8479, + 24542, + 30219, + 13194, + 5301, + 8042, + 4735, + 3039, + ], + [ + 15645, + 26408, + 20011, + 16763, + 45086, + 13503, + 15936, + 9997, + 13165, + 17787, + 15510, + 17632, + 16848, + 14264, + 12824, + 13611, + 8758, + 22636, + 13852, + 10938, + 7730, + 5109, + 15009, + 8010, + 17348, + 2896, + 3610, + 1686, + 2320, + 0, + 3086, + 4444, + 6169, + 22301, + 27963, + 11344, + 4780, + 7408, + 4488, + 3721, + ], + [ + 12658, + 23504, + 17087, + 13958, + 42083, + 10544, + 12881, + 7194, + 10430, + 15048, + 13694, + 16948, + 16197, + 13736, + 12815, + 14104, + 8119, + 23801, + 15276, + 12906, + 10187, + 7996, + 17856, + 11074, + 20397, + 5892, + 6675, + 4726, + 4593, + 3086, + 0, + 1501, + 5239, + 20390, + 26101, + 8611, + 2418, + 4580, + 2599, + 3496, + ], + [ + 11210, + 22025, + 15612, + 12459, + 40648, + 9080, + 11498, + 6574, + 9813, + 14372, + 13768, + 17587, + 16859, + 14482, + 13779, + 15222, + 8948, + 25065, + 16632, + 14366, + 11689, + 9459, + 19337, + 12315, + 21793, + 7104, + 8055, + 6000, + 6093, + 4444, + 1501, + 0, + 4608, + 19032, + 24747, + 7110, + 2860, + 4072, + 3355, + 4772, + ], + [ + 12094, + 22000, + 15872, + 12296, + 40971, + 9983, + 12944, + 10678, + 13777, + 18115, + 18317, + 22131, + 21391, + 18958, + 18042, + 19237, + 13353, + 28675, + 19957, + 17106, + 13792, + 10846, + 20454, + 11926, + 21936, + 7338, + 8965, + 6810, + 8479, + 6169, + 5239, + 4608, + 0, + 16249, + 21866, + 7146, + 7403, + 8446, + 7773, + 8614, + ], + [ + 13175, + 13197, + 11653, + 10370, + 29929, + 13435, + 14711, + 20959, + 22300, + 24280, + 28831, + 34799, + 34211, + 32292, + 32259, + 34013, + 27354, + 44048, + 35660, + 33171, + 30012, + 27094, + 36551, + 27537, + 37429, + 23517, + 25197, + 23060, + 24542, + 22301, + 20390, + 19032, + 16249, + 0, + 5714, + 12901, + 21524, + 20543, + 22186, + 23805, + ], + [ + 18162, + 14936, + 15666, + 15331, + 28493, + 18755, + 19589, + 26458, + 27564, + 29101, + 34148, + 40296, + 39731, + 37879, + 37918, + 39703, + 33023, + 49756, + 41373, + 38858, + 35654, + 32690, + 42017, + 32880, + 42654, + 29068, + 30774, + 28665, + 30219, + 27963, + 26101, + 24747, + 21866, + 5714, + 0, + 18516, + 27229, + 26181, + 27895, + 29519, + ], + [ + 4968, + 15146, + 8842, + 5430, + 34015, + 2947, + 5993, + 8180, + 10126, + 13400, + 16326, + 21953, + 21345, + 19391, + 19416, + 21271, + 14542, + 31426, + 23361, + 21390, + 18799, + 16451, + 26352, + 18634, + 28485, + 13583, + 14865, + 12674, + 13194, + 11344, + 8611, + 7110, + 7146, + 12901, + 18516, + 0, + 9029, + 7668, + 9742, + 11614, + ], + [ + 12308, + 23246, + 16843, + 14044, + 41473, + 10344, + 12227, + 5255, + 8388, + 13008, + 11276, + 14739, + 14006, + 11621, + 10975, + 12528, + 6106, + 22528, + 14333, + 12507, + 10406, + 8887, + 18403, + 12644, + 21414, + 7667, + 8007, + 6450, + 5301, + 4780, + 2418, + 2860, + 7403, + 21524, + 27229, + 9029, + 0, + 2747, + 726, + 2749, + ], + [ + 10084, + 20956, + 14618, + 12135, + 38935, + 8306, + 9793, + 2615, + 5850, + 10467, + 9918, + 14568, + 13907, + 11803, + 11750, + 13657, + 6901, + 23862, + 16125, + 14748, + 12981, + 11624, + 21033, + 15358, + 24144, + 10304, + 10742, + 9094, + 8042, + 7408, + 4580, + 4072, + 8446, + 20543, + 26181, + 7668, + 2747, + 0, + 3330, + 5313, + ], + [ + 13026, + 23963, + 17563, + 14771, + 42160, + 11069, + 12925, + 5730, + 8778, + 13375, + 11235, + 14366, + 13621, + 11188, + 10424, + 11907, + 5609, + 21861, + 13624, + 11781, + 9718, + 8304, + 17737, + 12200, + 20816, + 7330, + 7532, + 6117, + 4735, + 4488, + 2599, + 3355, + 7773, + 22186, + 27895, + 9742, + 726, + 3330, + 0, + 2042, + ], + [ + 15056, + 25994, + 19589, + 16743, + 44198, + 13078, + 14967, + 7552, + 10422, + 14935, + 11891, + 14002, + 13225, + 10671, + 9475, + 10633, + 5084, + 20315, + 11866, + 9802, + 7682, + 6471, + 15720, + 10674, + 18908, + 6204, + 6000, + 5066, + 3039, + 3721, + 3496, + 4772, + 8614, + 23805, + 29519, + 11614, + 2749, + 5313, + 2042, + 0, + ], ] # yapf: disable @@ -63,7 +1704,7 @@ def main(): """Entry point of the program.""" num_nodes = len(DISTANCE_MATRIX) all_nodes = range(num_nodes) - print('Num nodes =', num_nodes) + print("Num nodes =", num_nodes) # Model. model = cp_model.CpModel() @@ -79,7 +1720,7 @@ def main(): if i == j: continue - lit = model.NewBoolVar('%i follows %i' % (j, i)) + lit = model.NewBoolVar("%i follows %i" % (j, i)) arcs.append([i, j, lit]) arc_literals[i, j] = lit @@ -89,8 +1730,7 @@ def main(): model.AddCircuit(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() @@ -102,7 +1742,7 @@ def main(): print(solver.ResponseStats()) current_node = 0 - str_route = '%i' % current_node + str_route = "%i" % current_node route_is_finished = False route_distance = 0 while not route_is_finished: @@ -110,16 +1750,16 @@ def main(): if i == current_node: continue if solver.BooleanValue(arc_literals[current_node, i]): - str_route += ' -> %i' % i + str_route += " -> %i" % i route_distance += DISTANCE_MATRIX[current_node][i] current_node = i if current_node == 0: route_is_finished = True break - print('Route:', str_route) - print('Travelled distance:', route_distance) + print("Route:", str_route) + print("Travelled distance:", route_distance) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/python/vendor_scheduling_sat.py b/examples/python/vendor_scheduling_sat.py index a9cdb2d0c3..ac65975e47 100644 --- a/examples/python/vendor_scheduling_sat.py +++ b/examples/python/vendor_scheduling_sat.py @@ -11,6 +11,7 @@ # 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 a simple shift scheduling problem.""" from typing import Sequence @@ -21,8 +22,15 @@ from ortools.sat.python import cp_model class SolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, num_vendors, num_hours, possible_schedules, - selected_schedules, hours_stat, min_vendors): + def __init__( + self, + num_vendors, + num_hours, + possible_schedules, + selected_schedules, + hours_stat, + min_vendors, + ): cp_model.CpSolverSolutionCallback.__init__(self) self.__solution_count = 0 self.__num_vendors = num_vendors @@ -35,17 +43,18 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): """Called at each new solution.""" self.__solution_count += 1 - print('Solution %i: ', self.__solution_count) - print(' min vendors:', self.__min_vendors) + print("Solution %i: ", self.__solution_count) + print(" min vendors:", self.__min_vendors) for i in range(self.__num_vendors): print( - ' - vendor %i: ' % i, self.__possible_schedules[self.Value( - self.__selected_schedules[i])]) + " - vendor %i: " % 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(" - # workers on day%2i: " % j, end=" ") + print(self.Value(self.__hours_stat[j]), end=" ") print() print() @@ -72,12 +81,14 @@ def vendor_scheduling_sat(): # Last columns are : # index_of_the_schedule, sum of worked hours (per work type). # The index is useful for branching. - possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8], - [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4], - [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5], - [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4], - [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]] + possible_schedules = [ + [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8], + [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4], + [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5], + [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4], + [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0], + ] num_possible_schedules = len(possible_schedules) selected_schedules = [] @@ -97,11 +108,10 @@ def vendor_scheduling_sat(): 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.NewIntVar(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.NewIntVar(0, num_possible_schedules - 1, "s[%i]" % v) + hours = model.NewIntVar(0, num_hours, "h[%i]" % v) selected_schedules.append(selected_schedule) vendors_stat.append(hours) tmp.append(selected_schedule) @@ -113,7 +123,7 @@ def vendor_scheduling_sat(): # Statistics and constraints for each hour # for h in all_hours: - workers = model.NewIntVar(0, 1000, 'workers[%i]' % h) + workers = model.NewIntVar(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]) @@ -127,25 +137,29 @@ def vendor_scheduling_sat(): # Solve model. solver = cp_model.CpSolver() solver.parameters.enumerate_all_solutions = True - solution_printer = SolutionPrinter(num_vendors, num_hours, - possible_schedules, selected_schedules, - hours_stat, min_vendors) + solution_printer = SolutionPrinter( + num_vendors, + num_hours, + possible_schedules, + selected_schedules, + hours_stat, + min_vendors, + ) status = solver.Solve(model, solution_printer) - print('Status = %s' % solver.StatusName(status)) + print("Status = %s" % solver.StatusName(status)) - print('Statistics') - print(' - conflicts : %i' % solver.NumConflicts()) - print(' - branches : %i' % solver.NumBranches()) - print(' - wall time : %f s' % solver.WallTime()) - print(' - number of solutions found: %i' % - solution_printer.solution_count()) + print("Statistics") + print(" - conflicts : %i" % solver.NumConflicts()) + print(" - branches : %i" % solver.NumBranches()) + print(" - wall time : %f s" % solver.WallTime()) + print(" - number of solutions found: %i" % solution_printer.solution_count()) def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") vendor_scheduling_sat() -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/wedding_optimal_chart_sat.py b/examples/python/wedding_optimal_chart_sat.py index 4dba30d4f2..a9c0cf8521 100644 --- a/examples/python/wedding_optimal_chart_sat.py +++ b/examples/python/wedding_optimal_chart_sat.py @@ -11,6 +11,7 @@ # 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. + """Finding an optimal wedding seating chart. From @@ -56,9 +57,10 @@ class WeddingChartPrinter(cp_model.CpSolverSolutionCallback): def on_solution_callback(self): current_time = time.time() objective = self.ObjectiveValue() - print("Solution %i, time = %f s, objective = %i" % - (self.__solution_count, current_time - self.__start_time, - objective)) + print( + "Solution %i, time = %f s, objective = %i" + % (self.__solution_count, current_time - self.__start_time, objective) + ) self.__solution_count += 1 for t in range(self.__num_tables): @@ -85,38 +87,52 @@ def build_data(): # Connection matrix: who knows who, and how strong # is the relation - connections = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]] + connections = [ + [1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + ] # Names of the guests. B: Bride side, G: Groom side names = [ - "Deb (B)", "John (B)", "Martha (B)", "Travis (B)", "Allan (B)", - "Lois (B)", "Jayne (B)", "Brad (B)", "Abby (B)", "Mary Helen (G)", - "Lee (G)", "Annika (G)", "Carl (G)", "Colin (G)", "Shirley (G)", - "DeAnn (G)", "Lori (G)" + "Deb (B)", + "John (B)", + "Martha (B)", + "Travis (B)", + "Allan (B)", + "Lois (B)", + "Jayne (B)", + "Brad (B)", + "Abby (B)", + "Mary Helen (G)", + "Lee (G)", + "Annika (G)", + "Carl (G)", + "Colin (G)", + "Shirley (G)", + "DeAnn (G)", + "Lori (G)", ] return num_tables, table_capacity, min_known_neighbors, connections, names def solve_with_discrete_model(): """Discrete approach.""" - num_tables, table_capacity, min_known_neighbors, connections, names = build_data( - ) + num_tables, table_capacity, min_known_neighbors, connections, names = build_data() num_guests = len(connections) @@ -132,28 +148,32 @@ 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.NewBoolVar("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( - "guest %i seats with guest %i" % (g1, g2)) + "guest %i seats with guest %i" % (g1, g2) + ) same_table = {} 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( - "guest %i seats with guest %i on table %i" % (g1, g2, t)) + "guest %i seats with guest %i on table %i" % (g1, g2, t) + ) # Objective model.Maximize( - sum(connections[g1][g2] * colocated[g1, g2] + sum( + connections[g1][g2] * colocated[g1, g2] for g1 in range(num_guests - 1) for g2 in range(g1 + 1, num_guests) - if connections[g1][g2] > 0)) + if connections[g1][g2] > 0 + ) + ) # # Constraints @@ -172,29 +192,38 @@ def solve_with_discrete_model(): for g2 in range(g1 + 1, num_guests): for t in all_tables: # Link same_table and seats. - model.AddBoolOr([ - seats[(t, g1)].Not(), seats[(t, g2)].Not(), - same_table[(g1, g2, t)] - ]) + model.AddBoolOr( + [ + seats[(t, g1)].Not(), + seats[(t, g2)].Not(), + 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)]) # Link colocated and same_table. model.Add( - sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[( - g1, g2)]) + 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( - sum(same_table[(g, g2, t)] + sum( + same_table[(g, g2, t)] for g2 in range(g + 1, num_guests) for t in all_tables - if connections[g][g2] > 0) + - sum(same_table[(g1, g, t)] + if connections[g][g2] > 0 + ) + + sum( + same_table[(g1, g, t)] for g1 in range(g) for t in all_tables - if connections[g1][g] > 0) >= min_known_neighbors) + if connections[g1][g] > 0 + ) + >= min_known_neighbors + ) # Symmetry breaking. First guest seats on the first table. model.Add(seats[(0, 0)] == 1) diff --git a/examples/python/weighted_latency_problem_sat.py b/examples/python/weighted_latency_problem_sat.py index 01c78ffa3c..3db149a385 100644 --- a/examples/python/weighted_latency_problem_sat.py +++ b/examples/python/weighted_latency_problem_sat.py @@ -11,6 +11,7 @@ # 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. + """Solve a random Weighted Latency problem with the CP-SAT solver.""" import random @@ -21,16 +22,16 @@ from absl import flags from google.protobuf import text_format from ortools.sat.python import cp_model -_NUM_NODES = flags.DEFINE_integer('num_nodes', 12, 'Number of nodes to visit.') -_GRID_SIZE = flags.DEFINE_integer('grid_size', 20, - 'Size of the grid where nodes are.') -_PROFIT_RANGE = flags.DEFINE_integer('profit_range', 50, 'Range of profit.') -_SEED = flags.DEFINE_integer('seed', 0, 'Random seed.') -_PARAMS = flags.DEFINE_string('params', - 'num_search_workers:16, max_time_in_seconds:5', - 'Sat solver parameters.') +_NUM_NODES = flags.DEFINE_integer("num_nodes", 12, "Number of nodes to visit.") +_GRID_SIZE = flags.DEFINE_integer("grid_size", 20, "Size of the grid where nodes are.") +_PROFIT_RANGE = flags.DEFINE_integer("profit_range", 50, "Range of profit.") +_SEED = flags.DEFINE_integer("seed", 0, "Random seed.") +_PARAMS = flags.DEFINE_string( + "params", "num_search_workers:16, max_time_in_seconds:5", "Sat solver parameters." +) _PROTO_FILE = flags.DEFINE_string( - 'proto_file', '', 'If not empty, output the proto to this file.') + "proto_file", "", "If not empty, output the proto to this file." +) def build_model(): @@ -60,10 +61,7 @@ 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.NewIntVar(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1)] # Node 0 is the start node. model.Add(times[0] == 0) @@ -76,7 +74,7 @@ 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.NewBoolVar(f"{i}_to_{j}") arcs.append((i, j, lit)) # Add transitions between nodes. @@ -103,12 +101,12 @@ def solve_with_cp_sat(x, y, profits): def main(argv: Sequence[str]) -> None: if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') + raise app.UsageError("Too many command-line arguments.") x, y, profits = build_model() solve_with_cp_sat(x, y, profits) # TODO(user): Implement routing model. -if __name__ == '__main__': +if __name__ == "__main__": app.run(main) diff --git a/examples/python/zebra_sat.py b/examples/python/zebra_sat.py index 4208ec2962..b0e4ba01e8 100644 --- a/examples/python/zebra_sat.py +++ b/examples/python/zebra_sat.py @@ -11,6 +11,7 @@ # 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. + """This is the zebra problem as invented by Lewis Caroll. There are five houses. @@ -43,42 +44,41 @@ 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.NewIntVar(1, 5, "red") + green = model.NewIntVar(1, 5, "green") + yellow = model.NewIntVar(1, 5, "yellow") + blue = model.NewIntVar(1, 5, "blue") + ivory = model.NewIntVar(1, 5, "ivory") - englishman = model.NewIntVar(1, 5, 'englishman') - spaniard = model.NewIntVar(1, 5, 'spaniard') - japanese = model.NewIntVar(1, 5, 'japanese') - ukrainian = model.NewIntVar(1, 5, 'ukrainian') - norwegian = model.NewIntVar(1, 5, 'norwegian') + englishman = model.NewIntVar(1, 5, "englishman") + spaniard = model.NewIntVar(1, 5, "spaniard") + japanese = model.NewIntVar(1, 5, "japanese") + ukrainian = model.NewIntVar(1, 5, "ukrainian") + norwegian = model.NewIntVar(1, 5, "norwegian") - dog = model.NewIntVar(1, 5, 'dog') - snails = model.NewIntVar(1, 5, 'snails') - fox = model.NewIntVar(1, 5, 'fox') - zebra = model.NewIntVar(1, 5, 'zebra') - horse = model.NewIntVar(1, 5, 'horse') + dog = model.NewIntVar(1, 5, "dog") + snails = model.NewIntVar(1, 5, "snails") + fox = model.NewIntVar(1, 5, "fox") + zebra = model.NewIntVar(1, 5, "zebra") + horse = model.NewIntVar(1, 5, "horse") - tea = model.NewIntVar(1, 5, 'tea') - coffee = model.NewIntVar(1, 5, 'coffee') - water = model.NewIntVar(1, 5, 'water') - milk = model.NewIntVar(1, 5, 'milk') - fruit_juice = model.NewIntVar(1, 5, 'fruit juice') + tea = model.NewIntVar(1, 5, "tea") + coffee = model.NewIntVar(1, 5, "coffee") + water = model.NewIntVar(1, 5, "water") + milk = model.NewIntVar(1, 5, "milk") + fruit_juice = model.NewIntVar(1, 5, "fruit juice") - old_gold = model.NewIntVar(1, 5, 'old gold') - kools = model.NewIntVar(1, 5, 'kools') - chesterfields = model.NewIntVar(1, 5, 'chesterfields') - lucky_strike = model.NewIntVar(1, 5, 'lucky strike') - parliaments = model.NewIntVar(1, 5, 'parliaments') + old_gold = model.NewIntVar(1, 5, "old gold") + kools = model.NewIntVar(1, 5, "kools") + chesterfields = model.NewIntVar(1, 5, "chesterfields") + lucky_strike = model.NewIntVar(1, 5, "lucky strike") + parliaments = model.NewIntVar(1, 5, "parliaments") model.AddAllDifferent(red, green, yellow, blue, ivory) model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian) model.AddAllDifferent(dog, snails, fox, zebra, horse) model.AddAllDifferent(tea, coffee, water, milk, fruit_juice) - model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, - old_gold) + model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold) model.Add(englishman == red) model.Add(spaniard == dog) @@ -90,18 +90,18 @@ def solve_zebra(): model.Add(milk == 3) model.Add(norwegian == 1) - diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields') + diff_fox_chesterfields = model.NewIntVar(-4, 4, "diff_fox_chesterfields") model.Add(diff_fox_chesterfields == fox - chesterfields) model.AddAbsEquality(1, diff_fox_chesterfields) - diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools') + diff_horse_kools = model.NewIntVar(-4, 4, "diff_horse_kools") model.Add(diff_horse_kools == horse - kools) model.AddAbsEquality(1, diff_horse_kools) model.Add(lucky_strike == fruit_juice) model.Add(japanese == parliaments) - diff_norwegian_blue = model.NewIntVar(-4, 4, 'diff_norwegian_blue') + diff_norwegian_blue = model.NewIntVar(-4, 4, "diff_norwegian_blue") model.Add(diff_norwegian_blue == norwegian - blue) model.AddAbsEquality(1, diff_norwegian_blue) @@ -111,16 +111,12 @@ def solve_zebra(): 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!') + print("No solutions to the zebra problem, this is unusual!") solve_zebra()