From 9179bad0ae25b7bfd0ed27a24d3e170c694378d3 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Thu, 13 Jul 2023 14:34:43 +0200 Subject: [PATCH] examples: Bump notebooks --- examples/notebook/algorithms/knapsack.ipynb | 46 +- .../algorithms/simple_knapsack_program.ipynb | 24 +- .../constraint_solver/cp_is_fun_cp.ipynb | 47 +- .../constraint_solver/cvrptw_break.ipynb | 248 +- .../constraint_solver/nqueens_cp.ipynb | 23 +- .../constraint_solver/simple_cp_program.ipynb | 27 +- .../simple_routing_program.ipynb | 14 +- examples/notebook/constraint_solver/tsp.ipynb | 57 +- .../constraint_solver/tsp_circuit_board.ipynb | 130 +- .../constraint_solver/tsp_cities.ipynb | 28 +- .../tsp_distance_matrix.ipynb | 114 +- examples/notebook/constraint_solver/vrp.ipynb | 123 +- .../constraint_solver/vrp_breaks.ipynb | 72 +- .../constraint_solver/vrp_capacity.ipynb | 145 +- .../constraint_solver/vrp_drop_nodes.ipynb | 151 +- .../constraint_solver/vrp_global_span.ipynb | 128 +- .../vrp_initial_routes.ipynb | 135 +- .../constraint_solver/vrp_node_max.ipynb | 171 +- .../vrp_pickup_delivery.ipynb | 138 +- .../vrp_pickup_delivery_fifo.ipynb | 141 +- .../vrp_pickup_delivery_lifo.ipynb | 141 +- .../constraint_solver/vrp_resources.ipynb | 89 +- .../vrp_solution_callback.ipynb | 149 +- .../constraint_solver/vrp_starts_ends.ipynb | 129 +- .../constraint_solver/vrp_time_windows.ipynb | 68 +- .../constraint_solver/vrp_tokens.ipynb | 61 +- .../vrp_with_time_limit.ipynb | 26 +- .../notebook/constraint_solver/vrpgs.ipynb | 83 +- .../vrptw_store_solution_data.ipynb | 71 +- examples/notebook/examples/appointments.ipynb | 193 +- .../assignment_with_constraints_sat.ipynb | 69 +- .../notebook/examples/balance_group_sat.ipynb | 71 +- .../examples/bus_driver_scheduling_sat.ipynb | 3447 ++++++++--------- .../examples/chemical_balance_sat.ipynb | 28 +- .../notebook/examples/clustering_sat.ipynb | 30 +- .../examples/cover_rectangle_sat.ipynb | 45 +- .../notebook/examples/cryptarithm_sat.ipynb | 46 +- .../examples/flexible_job_shop_sat.ipynb | 58 +- .../examples/gate_scheduling_sat.ipynb | 60 +- examples/notebook/examples/golomb8.ipynb | 24 +- examples/notebook/examples/golomb_sat.ipynb | 32 +- examples/notebook/examples/hidato_sat.ipynb | 124 +- .../examples/integer_programming.ipynb | 58 +- .../examples/jobshop_ft06_distance_sat.ipynb | 60 +- .../notebook/examples/jobshop_ft06_sat.ipynb | 54 +- .../jobshop_with_maintenance_sat.ipynb | 90 +- .../notebook/examples/knapsack_2d_sat.ipynb | 217 +- .../examples/line_balancing_sat.ipynb | 110 +- .../examples/linear_assignment_api.ipynb | 21 +- .../examples/linear_programming.ipynb | 85 +- .../examples/magic_sequence_distribute.ipynb | 9 +- .../notebook/examples/maze_escape_sat.ipynb | 70 +- .../no_wait_baking_scheduling_sat.ipynb | 148 +- examples/notebook/examples/nqueens_sat.ipynb | 31 +- .../examples/prize_collecting_tsp_sat.ipynb | 42 +- .../examples/prize_collecting_vrp_sat.ipynb | 59 +- .../notebook/examples/pyflow_example.ipynb | 47 +- examples/notebook/examples/qubo_sat.ipynb | 12 +- examples/notebook/examples/rcpsp_sat.ipynb | 438 ++- .../examples/shift_scheduling_sat.ipynb | 242 +- ...ing_with_setup_release_due_dates_sat.ipynb | 427 +- .../notebook/examples/spread_robots_sat.ipynb | 47 +- .../examples/steel_mill_slab_sat.ipynb | 492 +-- examples/notebook/examples/sudoku_sat.ipynb | 22 +- .../examples/task_allocation_sat.ipynb | 496 ++- .../tasks_and_workers_assignment_sat.ipynb | 38 +- examples/notebook/examples/tsp_sat.ipynb | 22 +- .../examples/vendor_scheduling_sat.ipynb | 74 +- .../examples/wedding_optimal_chart_sat.ipynb | 113 +- .../weighted_latency_problem_sat.ipynb | 28 +- examples/notebook/examples/zebra_sat.ipynb | 74 +- .../assignment_linear_sum_assignment.ipynb | 30 +- .../notebook/graph/assignment_min_flow.ipynb | 42 +- .../notebook/graph/balance_min_flow.ipynb | 76 +- .../graph/simple_max_flow_program.ipynb | 16 +- .../graph/simple_min_cost_flow_program.ipynb | 15 +- .../linear_solver/assignment_groups_mip.ipynb | 33 +- .../linear_solver/assignment_mb.ipynb | 72 +- .../linear_solver/assignment_mip.ipynb | 12 +- .../assignment_task_sizes_mip.ipynb | 25 +- .../linear_solver/assignment_teams_mip.ipynb | 22 +- .../linear_solver/basic_example.ipynb | 28 +- .../linear_solver/bin_packing_mb.ipynb | 102 +- .../linear_solver/bin_packing_mip.ipynb | 52 +- .../integer_programming_example.ipynb | 13 +- .../linear_programming_example.ipynb | 27 +- .../linear_solver/mip_var_array.ipynb | 47 +- .../linear_solver/multiple_knapsack_mip.ipynb | 67 +- .../linear_solver/simple_lp_program.ipynb | 29 +- .../linear_solver/simple_lp_program_mb.ipynb | 28 +- .../linear_solver/simple_mip_program.ipynb | 31 +- .../linear_solver/simple_mip_program_mb.ipynb | 24 +- .../notebook/linear_solver/stigler_diet.ipynb | 304 +- .../notebook/pdlp/simple_pdlp_program.ipynb | 68 +- .../notebook/sat/assignment_groups_sat.ipynb | 17 +- examples/notebook/sat/assignment_sat.ipynb | 73 +- .../sat/assignment_task_sizes_sat.ipynb | 17 +- .../notebook/sat/assignment_teams_sat.ipynb | 12 +- .../notebook/sat/assumptions_sample_sat.ipynb | 21 +- examples/notebook/sat/bin_packing_sat.ipynb | 193 + .../notebook/sat/binpacking_problem_sat.ipynb | 22 +- .../notebook/sat/bool_or_sample_sat.ipynb | 8 +- .../sat/boolean_product_sample_sat.ipynb | 14 +- .../notebook/sat/channeling_sample_sat.ipynb | 15 +- .../notebook/sat/copy_model_sample_sat.ipynb | 13 +- examples/notebook/sat/cp_is_fun_sat.ipynb | 40 +- examples/notebook/sat/cp_sat_example.ipynb | 26 +- .../earliness_tardiness_cost_sample_sat.ipynb | 17 +- .../notebook/sat/interval_sample_sat.ipynb | 22 +- .../notebook/sat/literal_sample_sat.ipynb | 6 +- .../notebook/sat/minimal_jobshop_sat.ipynb | 89 +- .../notebook/sat/multiple_knapsack_sat.ipynb | 64 +- .../notebook/sat/no_overlap_sample_sat.ipynb | 40 +- examples/notebook/sat/non_linear_sat.ipynb | 15 +- examples/notebook/sat/nqueens_sat.ipynb | 24 +- examples/notebook/sat/nurses_sat.ipynb | 30 +- .../sat/optional_interval_sample_sat.ipynb | 25 +- .../overlapping_intervals_sample_sat.ipynb | 35 +- .../sat/rabbits_and_pheasants_sat.ipynb | 8 +- .../notebook/sat/ranking_sample_sat.ipynb | 70 +- .../notebook/sat/reified_sample_sat.ipynb | 7 +- .../notebook/sat/schedule_requests_sat.ipynb | 54 +- .../scheduling_with_calendar_sample_sat.ipynb | 20 +- .../search_for_all_solutions_sample_sat.ipynb | 13 +- .../notebook/sat/simple_sat_program.ipynb | 14 +- .../sat/solution_hinting_sample_sat.ipynb | 11 +- ...nt_intermediate_solutions_sample_sat.ipynb | 17 +- .../solve_with_time_limit_sample_sat.ipynb | 12 +- .../sat/step_function_sample_sat.ipynb | 23 +- .../stop_after_n_solutions_sample_sat.ipynb | 14 +- 130 files changed, 6534 insertions(+), 6362 deletions(-) create mode 100644 examples/notebook/sat/bin_packing_sat.ipynb diff --git a/examples/notebook/algorithms/knapsack.ipynb b/examples/notebook/algorithms/knapsack.ipynb index d8c7c861d3..9bdd73f181 100644 --- a/examples/notebook/algorithms/knapsack.ipynb +++ b/examples/notebook/algorithms/knapsack.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "A simple knapsack problem." ] }, @@ -82,43 +83,48 @@ "metadata": {}, "outputs": [], "source": [ - "from ortools.algorithms import pywrapknapsack_solver\n", + "from ortools.algorithms.python import knapsack_solver\n", "\n", "\n", "def main():\n", " # Create the solver.\n", - " solver = pywrapknapsack_solver.KnapsackSolver(\n", - " pywrapknapsack_solver.KnapsackSolver.\n", - " KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, 'KnapsackExample')\n", + " solver = knapsack_solver.KnapsackSolver(\n", + " knapsack_solver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,\n", + " \"KnapsackExample\",\n", + " )\n", "\n", " values = [\n", - " 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,\n", - " 78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,\n", - " 87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,\n", - " 312\n", + " # fmt:off\n", + " 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,\n", + " 78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,\n", + " 87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,\n", + " 312\n", + " # fmt:on\n", + " ]\n", + " weights = [\n", + " # fmt: off\n", + " [7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,\n", + " 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71, 3,\n", + " 86, 66, 31, 65, 0, 79, 20, 65, 52, 13],\n", + " # fmt: on\n", " ]\n", - " weights = [[\n", - " 7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,\n", - " 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,\n", - " 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13\n", - " ]]\n", " capacities = [850]\n", "\n", - " solver.Init(values, weights, capacities)\n", - " computed_value = solver.Solve()\n", + " solver.init(values, weights, capacities)\n", + " computed_value = solver.solve()\n", "\n", " packed_items = []\n", " packed_weights = []\n", " total_weight = 0\n", - " print('Total value =', computed_value)\n", + " print(\"Total value =\", computed_value)\n", " for i in range(len(values)):\n", - " if solver.BestSolutionContains(i):\n", + " if solver.best_solution_contains(i):\n", " packed_items.append(i)\n", " packed_weights.append(weights[0][i])\n", " total_weight += weights[0][i]\n", - " print('Total weight:', total_weight)\n", - " print('Packed items:', packed_items)\n", - " print('Packed_weights:', packed_weights)\n", + " print(\"Total weight:\", total_weight)\n", + " print(\"Packed items:\", packed_items)\n", + " print(\"Packed_weights:\", packed_weights)\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/algorithms/simple_knapsack_program.ipynb b/examples/notebook/algorithms/simple_knapsack_program.ipynb index 871ce6577c..9b7d94f209 100644 --- a/examples/notebook/algorithms/simple_knapsack_program.ipynb +++ b/examples/notebook/algorithms/simple_knapsack_program.ipynb @@ -83,27 +83,29 @@ "metadata": {}, "outputs": [], "source": [ - "from ortools.algorithms import pywrapknapsack_solver\n", + "from ortools.algorithms.python import knapsack_solver\n", "\n", "\n", "def main():\n", " # Create the solver.\n", - " solver = pywrapknapsack_solver.KnapsackSolver(\n", - " pywrapknapsack_solver.KnapsackSolver.\n", - " KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, \"test\")\n", + " solver = knapsack_solver.KnapsackSolver(\n", + " knapsack_solver.SolverType.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER,\n", + " \"test\",\n", + " )\n", "\n", - " weights = [[\n", - " 565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293,\n", - " 712, 147, 421, 255\n", - " ]]\n", + " weights = [\n", + " # fmt:off\n", + " [565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293, 712, 147, 421, 255],\n", + " # fmt:on\n", + " ]\n", " capacities = [850]\n", " values = weights[0]\n", "\n", - " solver.Init(values, weights, capacities)\n", - " computed_value = solver.Solve()\n", + " solver.init(values, weights, capacities)\n", + " computed_value = solver.solve()\n", "\n", " packed_items = [\n", - " x for x in range(0, len(weights[0])) if solver.BestSolutionContains(x)\n", + " x for x in range(0, len(weights[0])) if solver.best_solution_contains(x)\n", " ]\n", " packed_weights = [weights[0][i] for i in packed_items]\n", "\n", diff --git a/examples/notebook/constraint_solver/cp_is_fun_cp.ipynb b/examples/notebook/constraint_solver/cp_is_fun_cp.ipynb index 76f3a21b6c..19e95c9817 100644 --- a/examples/notebook/constraint_solver/cp_is_fun_cp.ipynb +++ b/examples/notebook/constraint_solver/cp_is_fun_cp.ipynb @@ -93,23 +93,23 @@ "\n", "def main():\n", " # Constraint programming engine\n", - " solver = pywrapcp.Solver('CP is fun!')\n", + " solver = pywrapcp.Solver(\"CP is fun!\")\n", "\n", " base = 10\n", "\n", " # Decision variables.\n", " digits = list(range(0, base))\n", " digits_without_zero = list(range(1, base))\n", - " c = solver.IntVar(digits_without_zero, 'C')\n", - " p = solver.IntVar(digits, 'P')\n", - " i = solver.IntVar(digits_without_zero, 'I')\n", - " s = solver.IntVar(digits, 'S')\n", - " f = solver.IntVar(digits_without_zero, 'F')\n", - " u = solver.IntVar(digits, 'U')\n", - " n = solver.IntVar(digits, 'N')\n", - " t = solver.IntVar(digits_without_zero, 'T')\n", - " r = solver.IntVar(digits, 'R')\n", - " e = solver.IntVar(digits, 'E')\n", + " c = solver.IntVar(digits_without_zero, \"C\")\n", + " p = solver.IntVar(digits, \"P\")\n", + " i = solver.IntVar(digits_without_zero, \"I\")\n", + " s = solver.IntVar(digits, \"S\")\n", + " f = solver.IntVar(digits_without_zero, \"F\")\n", + " u = solver.IntVar(digits, \"U\")\n", + " n = solver.IntVar(digits, \"N\")\n", + " t = solver.IntVar(digits_without_zero, \"T\")\n", + " r = solver.IntVar(digits, \"R\")\n", + " e = solver.IntVar(digits, \"E\")\n", "\n", " # We need to group variables in a list to use the constraint AllDifferent.\n", " letters = [c, p, i, s, f, u, n, t, r, e]\n", @@ -121,8 +121,10 @@ " solver.Add(solver.AllDifferent(letters))\n", "\n", " # CP + IS + FUN = TRUE\n", - " solver.Add(p + s + n + base * (c + i + u) + base * base * f == e +\n", - " base * u + base * base * r + base * base * base * t)\n", + " solver.Add(\n", + " p + s + n + base * (c + i + u) + base * base * f\n", + " == e + base * u + base * base * r + base * base * base * t\n", + " )\n", "\n", " solution_count = 0\n", " db = solver.Phase(letters, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", @@ -130,13 +132,22 @@ " while solver.NextSolution():\n", " print(letters)\n", " # Is CP + IS + FUN = TRUE?\n", - " assert (base * c.Value() + p.Value() + base * i.Value() + s.Value() +\n", - " base * base * f.Value() + base * u.Value() +\n", - " n.Value() == base * base * base * t.Value() +\n", - " base * base * r.Value() + base * u.Value() + e.Value())\n", + " assert (\n", + " base * c.Value()\n", + " + p.Value()\n", + " + base * i.Value()\n", + " + s.Value()\n", + " + base * base * f.Value()\n", + " + base * u.Value()\n", + " + n.Value()\n", + " == base * base * base * t.Value()\n", + " + base * base * r.Value()\n", + " + base * u.Value()\n", + " + e.Value()\n", + " )\n", " solution_count += 1\n", " solver.EndSearch()\n", - " print(f'Number of solutions found: {solution_count}')\n", + " print(f\"Number of solutions found: {solution_count}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/cvrptw_break.ipynb b/examples/notebook/constraint_solver/cvrptw_break.ipynb index 6914f32231..2bc9441182 100644 --- a/examples/notebook/constraint_solver/cvrptw_break.ipynb +++ b/examples/notebook/constraint_solver/cvrptw_break.ipynb @@ -100,81 +100,91 @@ " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", " # Locations in block unit\n", - " locations_ = \\\n", - " [(4, 4), # depot\n", - " (2, 0), (8, 0), # locations to visit\n", - " (0, 1), (1, 1),\n", - " (5, 2), (7, 2),\n", - " (3, 3), (6, 3),\n", - " (5, 5), (8, 5),\n", - " (1, 6), (2, 6),\n", - " (3, 7), (6, 7),\n", - " (0, 8), (7, 8)]\n", + " locations_ = [\n", + " # fmt: off\n", + " (4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8),\n", + " # fmt: on\n", + " ]\n", " # Compute locations in meters using the block dimension defined as follow\n", " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", " # here we use: 114m x 80m city block\n", " # src: https://nyti.ms/2GDoRIe \"NY Times: Know Your distance\"\n", - " data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations_]\n", - " data['numlocations_'] = len(data['locations'])\n", - " data['time_windows'] = \\\n", - " [(0, 0),\n", - " (75, 85), (75, 85), # 1, 2\n", - " (60, 70), (45, 55), # 3, 4\n", - " (0, 8), (50, 60), # 5, 6\n", - " (0, 10), (10, 20), # 7, 8\n", - " (0, 10), (75, 85), # 9, 10\n", - " (85, 95), (5, 15), # 11, 12\n", - " (15, 25), (10, 20), # 13, 14\n", - " (45, 55), (30, 40)] # 15, 16\n", - " data['demands'] = \\\n", - " [0, # depot\n", - " 1, 1, # 1, 2\n", - " 2, 4, # 3, 4\n", - " 2, 4, # 5, 6\n", - " 8, 8, # 7, 8\n", - " 1, 2, # 9,10\n", - " 1, 2, # 11,12\n", - " 4, 4, # 13, 14\n", - " 8, 8] # 15, 16\n", - " data['time_per_demand_unit'] = 5 # 5 minutes/unit\n", - " data['num_vehicles'] = 4\n", - " data['breaks'] = [(2, False), (2, False), (2, False), (2, False)]\n", - " data['vehicle_capacity'] = 15\n", - " data['vehicle_speed'] = 83 # Travel speed: 5km/h converted in m/min\n", - " data['depot'] = 0\n", + " data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations_]\n", + " data[\"numlocations_\"] = len(data[\"locations\"])\n", + " data[\"time_windows\"] = [\n", + " # fmt: off\n", + " (0, 0), # depot\n", + " (75, 85), (75, 85), # 1, 2\n", + " (60, 70), (45, 55), # 3, 4\n", + " (0, 8), (50, 60), # 5, 6\n", + " (0, 10), (10, 20), # 7, 8\n", + " (0, 10), (75, 85), # 9, 10\n", + " (85, 95), (5, 15), # 11, 12\n", + " (15, 25), (10, 20), # 13, 14\n", + " (45, 55), (30, 40),\n", + " # 15, 16\n", + " # fmt: on\n", + " ]\n", + " data[\"demands\"] = [\n", + " # fmt: off\n", + " 0, # depot\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9, 10\n", + " 1, 2, # 11, 12\n", + " 4, 4, # 13, 14\n", + " 8, 8,\n", + " # 15, 16\n", + " # fmt: on\n", + " ]\n", + " data[\"time_per_demand_unit\"] = 5 # 5 minutes/unit\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"breaks\"] = [(2, False), (2, False), (2, False), (2, False)]\n", + " data[\"vehicle_capacity\"] = 15\n", + " data[\"vehicle_speed\"] = 83 # Travel speed: 5km/h converted in m/min\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def manhattan_distance(position_1, position_2):\n", " \"\"\"Computes the Manhattan distance between two points.\"\"\"\n", - " return (abs(position_1[0] - position_2[0]) +\n", - " abs(position_1[1] - position_2[1]))\n", + " return abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])\n", "\n", "\n", "def create_distance_evaluator(data):\n", " \"\"\"Creates callback to return distance between points.\"\"\"\n", " distances_ = {}\n", " # precompute distance between location to have distance callback in O(1)\n", - " for from_node in range(data['numlocations_']):\n", + " for from_node in range(data[\"numlocations_\"]):\n", " distances_[from_node] = {}\n", - " for to_node in range(data['numlocations_']):\n", + " for to_node in range(data[\"numlocations_\"]):\n", " if from_node == to_node:\n", " distances_[from_node][to_node] = 0\n", " else:\n", - " distances_[from_node][to_node] = (manhattan_distance(\n", - " data['locations'][from_node], data['locations'][to_node]))\n", + " distances_[from_node][to_node] = manhattan_distance(\n", + " data[\"locations\"][from_node], data[\"locations\"][to_node]\n", + " )\n", "\n", " def distance_evaluator(manager, from_node, to_node):\n", " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", - " return distances_[manager.IndexToNode(from_node)][manager.IndexToNode(\n", - " to_node)]\n", + " return distances_[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n", "\n", " return distance_evaluator\n", "\n", "\n", "def create_demand_evaluator(data):\n", " \"\"\"Creates callback to get demands at each location.\"\"\"\n", - " demands_ = data['demands']\n", + " demands_ = data[\"demands\"]\n", "\n", " def demand_evaluator(manager, node):\n", " \"\"\"Returns the demand of the current node.\"\"\"\n", @@ -185,13 +195,14 @@ "\n", "def add_capacity_constraints(routing, data, demand_evaluator_index):\n", " \"\"\"Adds capacity constraint.\"\"\"\n", - " capacity = 'Capacity'\n", + " capacity = \"Capacity\"\n", " routing.AddDimension(\n", " demand_evaluator_index,\n", " 0, # null capacity slack\n", - " data['vehicle_capacity'],\n", + " data[\"vehicle_capacity\"],\n", " True, # start cumul to zero\n", - " capacity)\n", + " capacity,\n", + " )\n", "\n", "\n", "def create_time_evaluator(data):\n", @@ -199,63 +210,68 @@ "\n", " def service_time(data, node):\n", " \"\"\"Gets the service time for the specified location.\"\"\"\n", - " return data['demands'][node] * data['time_per_demand_unit']\n", + " return data[\"demands\"][node] * data[\"time_per_demand_unit\"]\n", "\n", " def travel_time(data, from_node, to_node):\n", " \"\"\"Gets the travel times between two locations.\"\"\"\n", " if from_node == to_node:\n", " travel_time = 0\n", " else:\n", - " travel_time = manhattan_distance(\n", - " data['locations'][from_node],\n", - " data['locations'][to_node]) / data['vehicle_speed']\n", + " travel_time = (\n", + " manhattan_distance(\n", + " data[\"locations\"][from_node], data[\"locations\"][to_node]\n", + " )\n", + " / data[\"vehicle_speed\"]\n", + " )\n", " return travel_time\n", "\n", " total_time_ = {}\n", " # precompute total time to have time callback in O(1)\n", - " for from_node in range(data['numlocations_']):\n", + " for from_node in range(data[\"numlocations_\"]):\n", " total_time_[from_node] = {}\n", - " for to_node in range(data['numlocations_']):\n", + " for to_node in range(data[\"numlocations_\"]):\n", " if from_node == to_node:\n", " total_time_[from_node][to_node] = 0\n", " else:\n", " total_time_[from_node][to_node] = int(\n", - " service_time(data, from_node) +\n", - " travel_time(data, from_node, to_node))\n", + " service_time(data, from_node)\n", + " + travel_time(data, from_node, to_node)\n", + " )\n", "\n", " def time_evaluator(manager, from_node, to_node):\n", " \"\"\"Returns the total time between the two nodes.\"\"\"\n", - " return total_time_[manager.IndexToNode(from_node)][manager.IndexToNode(\n", - " to_node)]\n", + " return total_time_[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n", "\n", " return time_evaluator\n", "\n", "\n", "def add_time_window_constraints(routing, manager, data, time_evaluator_index):\n", " \"\"\"Add Global Span constraint.\"\"\"\n", - " time = 'Time'\n", + " time = \"Time\"\n", " horizon = 120\n", " routing.AddDimension(\n", " time_evaluator_index,\n", " horizon, # allow waiting time\n", " horizon, # maximum time per vehicle\n", " False, # don't force start cumul to zero\n", - " time)\n", + " time,\n", + " )\n", " time_dimension = routing.GetDimensionOrDie(time)\n", " # Add time window constraints for each location except depot\n", " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", - " for location_idx, time_window in enumerate(data['time_windows']):\n", - " if location_idx == data['depot']:\n", + " for location_idx, time_window in enumerate(data[\"time_windows\"]):\n", + " if location_idx == data[\"depot\"]:\n", " continue\n", " index = manager.NodeToIndex(location_idx)\n", " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", " routing.AddToAssignment(time_dimension.SlackVar(index))\n", " # Add time window constraints for each vehicle start node\n", " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", - " data['time_windows'][0][1])\n", + " time_dimension.CumulVar(index).SetRange(\n", + " data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n", + " )\n", " routing.AddToAssignment(time_dimension.SlackVar(index))\n", " # The time window at the end node was impliclty set in the time dimension\n", " # definition to be [0, horizon].\n", @@ -263,28 +279,32 @@ " # be added to the assignment.\n", "\n", "\n", - "def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n", + "def print_solution(\n", + " data, manager, routing, assignment\n", + "): # pylint:disable=too-many-locals\n", " \"\"\"Prints assignment on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", "\n", - " print('Breaks:')\n", + " print(\"Breaks:\")\n", " intervals = assignment.IntervalVarContainer()\n", " for i in range(intervals.Size()):\n", " brk = intervals.Element(i)\n", " if brk.PerformedValue() == 1:\n", - " print(f'{brk.Var().Name()}:'\n", - " f' Start({brk.StartValue()}) Duration({brk.DurationValue()})')\n", + " print(\n", + " f\"{brk.Var().Name()}:\"\n", + " f\" Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n", + " )\n", " else:\n", - " print(f'{brk.Var().Name()}: Unperformed')\n", + " print(f\"{brk.Var().Name()}: Unperformed\")\n", "\n", " total_distance = 0\n", " total_load = 0\n", " total_time = 0\n", - " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", - " time_dimension = routing.GetDimensionOrDie('Time')\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " capacity_dimension = routing.GetDimensionOrDie(\"Capacity\")\n", + " time_dimension = routing.GetDimensionOrDie(\"Time\")\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " distance = 0\n", " while not routing.IsEnd(index):\n", " load_var = capacity_dimension.CumulVar(index)\n", @@ -292,32 +312,33 @@ " slack_var = time_dimension.SlackVar(index)\n", " node = manager.IndexToNode(index)\n", " plan_output += (\n", - " f' {node}'\n", - " f' Load({assignment.Value(load_var)})'\n", - " f' Time({assignment.Min(time_var)}, {assignment.Max(time_var)})'\n", - " f' Slack({assignment.Min(slack_var)}, {assignment.Max(slack_var)})'\n", - " ' ->')\n", + " f\" {node}\"\n", + " f\" Load({assignment.Value(load_var)})\"\n", + " f\" Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\"\n", + " f\" Slack({assignment.Min(slack_var)}, {assignment.Max(slack_var)})\"\n", + " \" ->\"\n", + " )\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", - " distance += routing.GetArcCostForVehicle(previous_index, index,\n", - " vehicle_id)\n", + " distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)\n", " load_var = capacity_dimension.CumulVar(index)\n", " time_var = time_dimension.CumulVar(index)\n", " node = manager.IndexToNode(index)\n", " plan_output += (\n", - " f' {node}'\n", - " f' Load({assignment.Value(load_var)})'\n", - " f' Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\\n')\n", - " plan_output += f'Distance of the route: {distance}m\\n'\n", - " plan_output += f'Load of the route: {assignment.Value(load_var)}\\n'\n", - " plan_output += f'Time of the route: {assignment.Value(time_var)}\\n'\n", + " f\" {node}\"\n", + " f\" Load({assignment.Value(load_var)})\"\n", + " f\" Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\\n\"\n", + " )\n", + " plan_output += f\"Distance of the route: {distance}m\\n\"\n", + " plan_output += f\"Load of the route: {assignment.Value(load_var)}\\n\"\n", + " plan_output += f\"Time of the route: {assignment.Value(time_var)}\\n\"\n", " print(plan_output)\n", " total_distance += distance\n", " total_load += assignment.Value(load_var)\n", " total_time += assignment.Value(time_var)\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", - " print(f'Total Load of all routes: {total_load}')\n", - " print(f'Total Time of all routes: {total_time}min')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", + " print(f\"Total Load of all routes: {total_load}\")\n", + " print(f\"Total Time of all routes: {total_time}min\")\n", "\n", "\n", "def main():\n", @@ -326,50 +347,57 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager\n", - " manager = pywrapcp.RoutingIndexManager(data['numlocations_'],\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " data[\"numlocations_\"], data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", " # Define weight of each edge\n", " distance_evaluator_index = routing.RegisterTransitCallback(\n", - " functools.partial(create_distance_evaluator(data), manager))\n", + " functools.partial(create_distance_evaluator(data), manager)\n", + " )\n", " routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", "\n", " # Add Capacity constraint\n", " demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n", - " functools.partial(create_demand_evaluator(data), manager))\n", + " functools.partial(create_demand_evaluator(data), manager)\n", + " )\n", " add_capacity_constraints(routing, data, demand_evaluator_index)\n", "\n", " # Add Time Window constraint\n", " time_evaluator_index = routing.RegisterTransitCallback(\n", - " functools.partial(create_time_evaluator(data), manager))\n", + " functools.partial(create_time_evaluator(data), manager)\n", + " )\n", " add_time_window_constraints(routing, manager, data, time_evaluator_index)\n", "\n", " # Add breaks\n", - " time_dimension = routing.GetDimensionOrDie('Time')\n", + " time_dimension = routing.GetDimensionOrDie(\"Time\")\n", " node_visit_transit = {}\n", " for index in range(routing.Size()):\n", " node = manager.IndexToNode(index)\n", - " node_visit_transit[index] = int(data['demands'][node] *\n", - " data['time_per_demand_unit'])\n", + " node_visit_transit[index] = int(\n", + " data[\"demands\"][node] * data[\"time_per_demand_unit\"]\n", + " )\n", "\n", " break_intervals = {}\n", - " for v in range(data['num_vehicles']):\n", - " vehicle_break = data['breaks'][v]\n", + " for v in range(data[\"num_vehicles\"]):\n", + " vehicle_break = data[\"breaks\"][v]\n", " break_intervals[v] = [\n", - " routing.solver().FixedDurationIntervalVar(15, 100, vehicle_break[0],\n", - " vehicle_break[1],\n", - " f'Break for vehicle {v}')\n", + " routing.solver().FixedDurationIntervalVar(\n", + " 15, 100, vehicle_break[0], vehicle_break[1], f\"Break for vehicle {v}\"\n", + " )\n", " ]\n", - " time_dimension.SetBreakIntervalsOfVehicle(break_intervals[v], v,\n", - " node_visit_transit.values())\n", + " time_dimension.SetBreakIntervalsOfVehicle(\n", + " break_intervals[v], v, node_visit_transit.values()\n", + " )\n", "\n", " # Setting first solution heuristic (cheapest addition).\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " ) # pylint: disable=no-member\n", "\n", " # Solve the problem.\n", " assignment = routing.SolveWithParameters(search_parameters)\n", @@ -378,7 +406,7 @@ " if assignment:\n", " print_solution(data, manager, routing, assignment)\n", " else:\n", - " print('No solution found!')\n", + " print(\"No solution found!\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/nqueens_cp.ipynb b/examples/notebook/constraint_solver/nqueens_cp.ipynb index fc48174720..890cda4ff3 100644 --- a/examples/notebook/constraint_solver/nqueens_cp.ipynb +++ b/examples/notebook/constraint_solver/nqueens_cp.ipynb @@ -89,13 +89,11 @@ "\n", "def main(board_size):\n", " # Creates the solver.\n", - " solver = pywrapcp.Solver('n-queens')\n", + " solver = pywrapcp.Solver(\"n-queens\")\n", "\n", " # Creates the variables.\n", " # The array index is the column, and the value is the row.\n", - " queens = [\n", - " solver.IntVar(0, board_size - 1, f'x{i}') for i in range(board_size)\n", - " ]\n", + " queens = [solver.IntVar(0, board_size - 1, f\"x{i}\") for i in range(board_size)]\n", "\n", " # Creates the constraints.\n", " # All rows must be different.\n", @@ -105,8 +103,7 @@ " solver.Add(solver.AllDifferent([queens[i] + i for i in range(board_size)]))\n", " solver.Add(solver.AllDifferent([queens[i] - i for i in range(board_size)]))\n", "\n", - " db = solver.Phase(queens, solver.CHOOSE_FIRST_UNBOUND,\n", - " solver.ASSIGN_MIN_VALUE)\n", + " db = solver.Phase(queens, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", "\n", " # Iterates through the solutions, displaying each.\n", " num_solutions = 0\n", @@ -117,20 +114,20 @@ " for j in range(board_size):\n", " if queens[j].Value() == i:\n", " # There is a queen in column j, row i.\n", - " print('Q', end=' ')\n", + " print(\"Q\", end=\" \")\n", " else:\n", - " print('_', end=' ')\n", + " print(\"_\", end=\" \")\n", " print()\n", " print()\n", " num_solutions += 1\n", " solver.EndSearch()\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(f' failures: {solver.Failures()}')\n", - " print(f' branches: {solver.Branches()}')\n", - " print(f' wall time: {solver.WallTime()} ms')\n", - " print(f' Solutions found: {num_solutions}')\n", + " print(\"\\nStatistics\")\n", + " print(f\" failures: {solver.Failures()}\")\n", + " print(f\" branches: {solver.Branches()}\")\n", + " print(f\" wall time: {solver.WallTime()} ms\")\n", + " print(f\" Solutions found: {num_solutions}\")\n", "\n", "\n", "# By default, solve the 8x8 problem.\n", diff --git a/examples/notebook/constraint_solver/simple_cp_program.ipynb b/examples/notebook/constraint_solver/simple_cp_program.ipynb index 2cf555b238..cfb85a3c86 100644 --- a/examples/notebook/constraint_solver/simple_cp_program.ipynb +++ b/examples/notebook/constraint_solver/simple_cp_program.ipynb @@ -89,37 +89,38 @@ "def main():\n", " \"\"\"Entry point of the program.\"\"\"\n", " # Instantiate the solver.\n", - " solver = pywrapcp.Solver('CPSimple')\n", + " solver = pywrapcp.Solver(\"CPSimple\")\n", "\n", " # Create the variables.\n", " num_vals = 3\n", - " x = solver.IntVar(0, num_vals - 1, 'x')\n", - " y = solver.IntVar(0, num_vals - 1, 'y')\n", - " z = solver.IntVar(0, num_vals - 1, 'z')\n", + " x = solver.IntVar(0, num_vals - 1, \"x\")\n", + " y = solver.IntVar(0, num_vals - 1, \"y\")\n", + " z = solver.IntVar(0, num_vals - 1, \"z\")\n", "\n", " # Constraint 0: x != y.\n", " solver.Add(x != y)\n", - " print('Number of constraints: ', solver.Constraints())\n", + " print(\"Number of constraints: \", solver.Constraints())\n", "\n", " # Solve the problem.\n", - " decision_builder = solver.Phase([x, y, z], solver.CHOOSE_FIRST_UNBOUND,\n", - " solver.ASSIGN_MIN_VALUE)\n", + " decision_builder = solver.Phase(\n", + " [x, y, z], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE\n", + " )\n", "\n", " # Print solution on console.\n", " count = 0\n", " solver.NewSearch(decision_builder)\n", " while solver.NextSolution():\n", " count += 1\n", - " solution = f'Solution {count}:\\n'\n", + " solution = f\"Solution {count}:\\n\"\n", " for var in [x, y, z]:\n", - " solution += f' {var.Name()} = {var.Value()}'\n", + " solution += f\" {var.Name()} = {var.Value()}\"\n", " print(solution)\n", " solver.EndSearch()\n", - " print(f'Number of solutions found: {count}')\n", + " print(f\"Number of solutions found: {count}\")\n", "\n", - " print('Advanced usage:')\n", - " print(f'Problem solved in {solver.WallTime()}ms')\n", - " print(f'Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes')\n", + " print(\"Advanced usage:\")\n", + " print(f\"Problem solved in {solver.WallTime()}ms\")\n", + " print(f\"Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/simple_routing_program.ipynb b/examples/notebook/constraint_solver/simple_routing_program.ipynb index df39ff5edb..f513f5b927 100644 --- a/examples/notebook/constraint_solver/simple_routing_program.ipynb +++ b/examples/notebook/constraint_solver/simple_routing_program.ipynb @@ -100,7 +100,6 @@ " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the absolute difference between the two nodes.\"\"\"\n", @@ -117,23 +116,24 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " ) # pylint: disable=no-member\n", "\n", " # Solve the problem.\n", " assignment = routing.SolveWithParameters(search_parameters)\n", "\n", " # Print solution on console.\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " index = routing.Start(0)\n", - " plan_output = 'Route for vehicle 0:\\n'\n", + " plan_output = \"Route for vehicle 0:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f'{manager.IndexToNode(index)} -> '\n", + " plan_output += f\"{manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", "\n", "\n", diff --git a/examples/notebook/constraint_solver/tsp.ipynb b/examples/notebook/constraint_solver/tsp.ipynb index 402d20ff1b..3ee35985f9 100644 --- a/examples/notebook/constraint_solver/tsp.ipynb +++ b/examples/notebook/constraint_solver/tsp.ipynb @@ -95,20 +95,23 @@ " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", " # Locations in block units\n", - " locations = \\\n", - " [(4, 4), # depot\n", - " (2, 0), (8, 0), # locations to visit\n", - " (0, 1), (1, 1),\n", - " (5, 2), (7, 2),\n", - " (3, 3), (6, 3),\n", - " (5, 5), (8, 5),\n", - " (1, 6), (2, 6),\n", - " (3, 7), (6, 7),\n", - " (0, 8), (7, 8),]\n", + " locations = [\n", + " # fmt:off\n", + " (4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)\n", + " # fmt:on\n", + " ]\n", " # Convert locations in meters using a city block dimension of 114m x 80m.\n", - " data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations]\n", - " data['num_vehicles'] = 1\n", - " data['depot'] = 0\n", + " data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations]\n", + " data[\"num_vehicles\"] = 1\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", @@ -117,15 +120,15 @@ " distances_ = {}\n", " index_manager_ = manager\n", " # precompute distance between location to have distance callback in O(1)\n", - " for from_counter, from_node in enumerate(data['locations']):\n", + " for from_counter, from_node in enumerate(data[\"locations\"]):\n", " distances_[from_counter] = {}\n", - " for to_counter, to_node in enumerate(data['locations']):\n", + " for to_counter, to_node in enumerate(data[\"locations\"]):\n", " if from_counter == to_counter:\n", " distances_[from_counter][to_counter] = 0\n", " else:\n", - " distances_[from_counter][to_counter] = (\n", - " abs(from_node[0] - to_node[0]) +\n", - " abs(from_node[1] - to_node[1]))\n", + " distances_[from_counter][to_counter] = abs(\n", + " from_node[0] - to_node[0]\n", + " ) + abs(from_node[1] - to_node[1])\n", "\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", @@ -139,17 +142,17 @@ "\n", "def print_solution(manager, routing, assignment):\n", " \"\"\"Prints assignment on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " index = routing.Start(0)\n", - " plan_output = 'Route for vehicle 0:\\n'\n", + " plan_output = \"Route for vehicle 0:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", "\n", "\n", @@ -159,8 +162,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"locations\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -175,7 +179,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " assignment = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/tsp_circuit_board.ipynb b/examples/notebook/constraint_solver/tsp_circuit_board.ipynb index 94217052c1..0f82a14bf2 100644 --- a/examples/notebook/constraint_solver/tsp_circuit_board.ipynb +++ b/examples/notebook/constraint_solver/tsp_circuit_board.ipynb @@ -92,57 +92,59 @@ " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", " # Locations in block units\n", - " data['locations'] = [\n", - " (288, 149), (288, 129), (270, 133), (256, 141), (256, 157), (246, 157),\n", - " (236, 169), (228, 169), (228, 161), (220, 169), (212, 169), (204, 169),\n", - " (196, 169), (188, 169), (196, 161), (188, 145), (172, 145), (164, 145),\n", - " (156, 145), (148, 145), (140, 145), (148, 169), (164, 169), (172, 169),\n", - " (156, 169), (140, 169), (132, 169), (124, 169), (116, 161), (104, 153),\n", - " (104, 161), (104, 169), (90, 165), (80, 157), (64, 157), (64, 165),\n", - " (56, 169), (56, 161), (56, 153), (56, 145), (56, 137), (56, 129),\n", - " (56, 121), (40, 121), (40, 129), (40, 137), (40, 145), (40, 153),\n", - " (40, 161), (40, 169), (32, 169), (32, 161), (32, 153), (32, 145),\n", - " (32, 137), (32, 129), (32, 121), (32, 113), (40, 113), (56, 113),\n", - " (56, 105), (48, 99), (40, 99), (32, 97), (32, 89), (24, 89),\n", - " (16, 97), (16, 109), (8, 109), (8, 97), (8, 89), (8, 81),\n", - " (8, 73), (8, 65), (8, 57), (16, 57), (8, 49), (8, 41),\n", - " (24, 45), (32, 41), (32, 49), (32, 57), (32, 65), (32, 73),\n", - " (32, 81), (40, 83), (40, 73), (40, 63), (40, 51), (44, 43),\n", - " (44, 35), (44, 27), (32, 25), (24, 25), (16, 25), (16, 17),\n", - " (24, 17), (32, 17), (44, 11), (56, 9), (56, 17), (56, 25),\n", - " (56, 33), (56, 41), (64, 41), (72, 41), (72, 49), (56, 49),\n", - " (48, 51), (56, 57), (56, 65), (48, 63), (48, 73), (56, 73),\n", - " (56, 81), (48, 83), (56, 89), (56, 97), (104, 97), (104, 105),\n", - " (104, 113), (104, 121), (104, 129), (104, 137), (104, 145), (116, 145),\n", - " (124, 145), (132, 145), (132, 137), (140, 137), (148, 137), (156, 137),\n", - " (164, 137), (172, 125), (172, 117), (172, 109), (172, 101), (172, 93),\n", - " (172, 85), (180, 85), (180, 77), (180, 69), (180, 61), (180, 53),\n", - " (172, 53), (172, 61), (172, 69), (172, 77), (164, 81), (148, 85),\n", - " (124, 85), (124, 93), (124, 109), (124, 125), (124, 117), (124, 101),\n", - " (104, 89), (104, 81), (104, 73), (104, 65), (104, 49), (104, 41),\n", - " (104, 33), (104, 25), (104, 17), (92, 9), (80, 9), (72, 9),\n", - " (64, 21), (72, 25), (80, 25), (80, 25), (80, 41), (88, 49),\n", - " (104, 57), (124, 69), (124, 77), (132, 81), (140, 65), (132, 61),\n", - " (124, 61), (124, 53), (124, 45), (124, 37), (124, 29), (132, 21),\n", - " (124, 21), (120, 9), (128, 9), (136, 9), (148, 9), (162, 9),\n", - " (156, 25), (172, 21), (180, 21), (180, 29), (172, 29), (172, 37),\n", - " (172, 45), (180, 45), (180, 37), (188, 41), (196, 49), (204, 57),\n", - " (212, 65), (220, 73), (228, 69), (228, 77), (236, 77), (236, 69),\n", - " (236, 61), (228, 61), (228, 53), (236, 53), (236, 45), (228, 45),\n", - " (228, 37), (236, 37), (236, 29), (228, 29), (228, 21), (236, 21),\n", - " (252, 21), (260, 29), (260, 37), (260, 45), (260, 53), (260, 61),\n", - " (260, 69), (260, 77), (276, 77), (276, 69), (276, 61), (276, 53),\n", - " (284, 53), (284, 61), (284, 69), (284, 77), (284, 85), (284, 93),\n", - " (284, 101), (288, 109), (280, 109), (276, 101), (276, 93), (276, 85),\n", - " (268, 97), (260, 109), (252, 101), (260, 93), (260, 85), (236, 85),\n", - " (228, 85), (228, 93), (236, 93), (236, 101), (228, 101), (228, 109),\n", - " (228, 117), (228, 125), (220, 125), (212, 117), (204, 109), (196, 101),\n", - " (188, 93), (180, 93), (180, 101), (180, 109), (180, 117), (180, 125),\n", - " (196, 145), (204, 145), (212, 145), (220, 145), (228, 145), (236, 145),\n", - " (246, 141), (252, 125), (260, 129), (280, 133)\n", - " ] # yapf: disable\n", - " data['num_vehicles'] = 1\n", - " data['depot'] = 0\n", + " data[\"locations\"] = [\n", + " # fmt: off\n", + " (288, 149), (288, 129), (270, 133), (256, 141), (256, 157), (246, 157),\n", + " (236, 169), (228, 169), (228, 161), (220, 169), (212, 169), (204, 169),\n", + " (196, 169), (188, 169), (196, 161), (188, 145), (172, 145), (164, 145),\n", + " (156, 145), (148, 145), (140, 145), (148, 169), (164, 169), (172, 169),\n", + " (156, 169), (140, 169), (132, 169), (124, 169), (116, 161), (104, 153),\n", + " (104, 161), (104, 169), (90, 165), (80, 157), (64, 157), (64, 165),\n", + " (56, 169), (56, 161), (56, 153), (56, 145), (56, 137), (56, 129),\n", + " (56, 121), (40, 121), (40, 129), (40, 137), (40, 145), (40, 153),\n", + " (40, 161), (40, 169), (32, 169), (32, 161), (32, 153), (32, 145),\n", + " (32, 137), (32, 129), (32, 121), (32, 113), (40, 113), (56, 113),\n", + " (56, 105), (48, 99), (40, 99), (32, 97), (32, 89), (24, 89),\n", + " (16, 97), (16, 109), (8, 109), (8, 97), (8, 89), (8, 81),\n", + " (8, 73), (8, 65), (8, 57), (16, 57), (8, 49), (8, 41),\n", + " (24, 45), (32, 41), (32, 49), (32, 57), (32, 65), (32, 73),\n", + " (32, 81), (40, 83), (40, 73), (40, 63), (40, 51), (44, 43),\n", + " (44, 35), (44, 27), (32, 25), (24, 25), (16, 25), (16, 17),\n", + " (24, 17), (32, 17), (44, 11), (56, 9), (56, 17), (56, 25),\n", + " (56, 33), (56, 41), (64, 41), (72, 41), (72, 49), (56, 49),\n", + " (48, 51), (56, 57), (56, 65), (48, 63), (48, 73), (56, 73),\n", + " (56, 81), (48, 83), (56, 89), (56, 97), (104, 97), (104, 105),\n", + " (104, 113), (104, 121), (104, 129), (104, 137), (104, 145), (116, 145),\n", + " (124, 145), (132, 145), (132, 137), (140, 137), (148, 137), (156, 137),\n", + " (164, 137), (172, 125), (172, 117), (172, 109), (172, 101), (172, 93),\n", + " (172, 85), (180, 85), (180, 77), (180, 69), (180, 61), (180, 53),\n", + " (172, 53), (172, 61), (172, 69), (172, 77), (164, 81), (148, 85),\n", + " (124, 85), (124, 93), (124, 109), (124, 125), (124, 117), (124, 101),\n", + " (104, 89), (104, 81), (104, 73), (104, 65), (104, 49), (104, 41),\n", + " (104, 33), (104, 25), (104, 17), (92, 9), (80, 9), (72, 9),\n", + " (64, 21), (72, 25), (80, 25), (80, 25), (80, 41), (88, 49),\n", + " (104, 57), (124, 69), (124, 77), (132, 81), (140, 65), (132, 61),\n", + " (124, 61), (124, 53), (124, 45), (124, 37), (124, 29), (132, 21),\n", + " (124, 21), (120, 9), (128, 9), (136, 9), (148, 9), (162, 9),\n", + " (156, 25), (172, 21), (180, 21), (180, 29), (172, 29), (172, 37),\n", + " (172, 45), (180, 45), (180, 37), (188, 41), (196, 49), (204, 57),\n", + " (212, 65), (220, 73), (228, 69), (228, 77), (236, 77), (236, 69),\n", + " (236, 61), (228, 61), (228, 53), (236, 53), (236, 45), (228, 45),\n", + " (228, 37), (236, 37), (236, 29), (228, 29), (228, 21), (236, 21),\n", + " (252, 21), (260, 29), (260, 37), (260, 45), (260, 53), (260, 61),\n", + " (260, 69), (260, 77), (276, 77), (276, 69), (276, 61), (276, 53),\n", + " (284, 53), (284, 61), (284, 69), (284, 77), (284, 85), (284, 93),\n", + " (284, 101), (288, 109), (280, 109), (276, 101), (276, 93), (276, 85),\n", + " (268, 97), (260, 109), (252, 101), (260, 93), (260, 85), (236, 85),\n", + " (228, 85), (228, 93), (236, 93), (236, 101), (228, 101), (228, 109),\n", + " (228, 117), (228, 125), (220, 125), (212, 117), (204, 109), (196, 101),\n", + " (188, 93), (180, 93), (180, 101), (180, 109), (180, 117), (180, 125),\n", + " (196, 145), (204, 145), (212, 145), (220, 145), (228, 145), (236, 145),\n", + " (246, 141), (252, 125), (260, 129), (280, 133)\n", + " # fmt: on\n", + " ]\n", + " data[\"num_vehicles\"] = 1\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", @@ -156,26 +158,26 @@ " distances[from_counter][to_counter] = 0\n", " else:\n", " # Euclidean distance\n", - " distances[from_counter][to_counter] = (int(\n", - " math.hypot((from_node[0] - to_node[0]),\n", - " (from_node[1] - to_node[1]))))\n", + " distances[from_counter][to_counter] = int(\n", + " math.hypot((from_node[0] - to_node[0]), (from_node[1] - to_node[1]))\n", + " )\n", " return distances\n", "\n", "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " index = routing.Start(0)\n", - " plan_output = 'Route:\\n'\n", + " plan_output = \"Route:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", " print(plan_output)\n", - " plan_output += f'Objective: {route_distance}m\\n'\n", + " plan_output += f\"Objective: {route_distance}m\\n\"\n", "\n", "\n", "def main():\n", @@ -184,13 +186,14 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"locations\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - " distance_matrix = compute_euclidean_distance_matrix(data['locations'])\n", + " distance_matrix = compute_euclidean_distance_matrix(data[\"locations\"])\n", "\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", @@ -207,7 +210,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/tsp_cities.ipynb b/examples/notebook/constraint_solver/tsp_cities.ipynb index 1b683833af..84ba66d898 100644 --- a/examples/notebook/constraint_solver/tsp_cities.ipynb +++ b/examples/notebook/constraint_solver/tsp_cities.ipynb @@ -90,7 +90,7 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", + " data[\"distance_matrix\"] = [\n", " [0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],\n", " [2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],\n", " [713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],\n", @@ -104,26 +104,26 @@ " [1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],\n", " [2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],\n", " [1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],\n", - " ] # yapf: disable\n", - " data['num_vehicles'] = 1\n", - " data['depot'] = 0\n", + " ]\n", + " data[\"num_vehicles\"] = 1\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()} miles')\n", + " print(f\"Objective: {solution.ObjectiveValue()} miles\")\n", " index = routing.Start(0)\n", - " plan_output = 'Route for vehicle 0:\\n'\n", + " plan_output = \"Route for vehicle 0:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", " print(plan_output)\n", - " plan_output += f'Route distance: {route_distance}miles\\n'\n", + " plan_output += f\"Route distance: {route_distance}miles\\n\"\n", "\n", "\n", "def main():\n", @@ -132,8 +132,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -144,7 +145,7 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -154,7 +155,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb b/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb index dcda4855b0..623296389a 100644 --- a/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb +++ b/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb @@ -90,94 +90,45 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['num_vehicles'] = 1\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 1\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " index = routing.Start(0)\n", - " plan_output = 'Route for vehicle 0:\\n'\n", + " plan_output = \"Route for vehicle 0:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", "\n", "\n", @@ -187,20 +138,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -210,7 +161,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp.ipynb b/examples/notebook/constraint_solver/vrp.ipynb index 7fa7123810..c656f9c065 100644 --- a/examples/notebook/constraint_solver/vrp.ipynb +++ b/examples/notebook/constraint_solver/vrp.ipynb @@ -98,100 +98,52 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " total_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "\n", @@ -201,20 +153,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -224,7 +176,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", @@ -233,7 +186,7 @@ " if solution:\n", " print_solution(data, manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_breaks.ipynb b/examples/notebook/constraint_solver/vrp_breaks.ipynb index 91d2ae8fb0..258b24561f 100644 --- a/examples/notebook/constraint_solver/vrp_breaks.ipynb +++ b/examples/notebook/constraint_solver/vrp_breaks.ipynb @@ -98,9 +98,9 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", - " data['time_matrix'] = [\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", + " data[\"time_matrix\"] = [\n", " [0, 27, 38, 34, 29, 13, 25, 9, 15, 9, 26, 25, 19, 17, 23, 38, 33],\n", " [27, 0, 34, 15, 9, 25, 36, 17, 34, 37, 54, 29, 24, 33, 50, 43, 60],\n", " [38, 34, 0, 49, 43, 25, 13, 40, 23, 37, 20, 63, 58, 56, 39, 77, 37],\n", @@ -120,43 +120,45 @@ " [33, 60, 37, 67, 62, 35, 24, 42, 25, 23, 17, 42, 36, 26, 9, 39, 0],\n", " ]\n", " # 15 min of service time\n", - " data['service_time'] = [15] * len(data['time_matrix'])\n", - " data['service_time'][data['depot']] = 0\n", - " assert len(data['time_matrix']) == len(data['service_time'])\n", + " data[\"service_time\"] = [15] * len(data[\"time_matrix\"])\n", + " data[\"service_time\"][data[\"depot\"]] = 0\n", + " assert len(data[\"time_matrix\"]) == len(data[\"service_time\"])\n", " return data\n", "\n", "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", "\n", - " print('Breaks:')\n", + " print(\"Breaks:\")\n", " intervals = solution.IntervalVarContainer()\n", " for i in range(intervals.Size()):\n", " brk = intervals.Element(i)\n", " if brk.PerformedValue():\n", - " print(f'{brk.Var().Name()}: ' +\n", - " f'Start({brk.StartValue()}) Duration({brk.DurationValue()})')\n", + " print(\n", + " f\"{brk.Var().Name()}: \"\n", + " + f\"Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n", + " )\n", " else:\n", - " print(f'{brk.Var().Name()}: Unperformed')\n", + " print(f\"{brk.Var().Name()}: Unperformed\")\n", "\n", - " time_dimension = routing.GetDimensionOrDie('Time')\n", + " time_dimension = routing.GetDimensionOrDie(\"Time\")\n", " total_time = 0\n", " for vehicle_id in range(manager.GetNumberOfVehicles()):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " while not routing.IsEnd(index):\n", " time_var = time_dimension.CumulVar(index)\n", - " plan_output += f'{manager.IndexToNode(index)} '\n", - " plan_output += f'Time({solution.Value(time_var)}) -> '\n", + " plan_output += f\"{manager.IndexToNode(index)} \"\n", + " plan_output += f\"Time({solution.Value(time_var)}) -> \"\n", " index = solution.Value(routing.NextVar(index))\n", " time_var = time_dimension.CumulVar(index)\n", - " plan_output += f'{manager.IndexToNode(index)} '\n", - " plan_output += f'Time({solution.Value(time_var)})\\n'\n", - " plan_output += f'Time of the route: {solution.Value(time_var)}min\\n'\n", + " plan_output += f\"{manager.IndexToNode(index)} \"\n", + " plan_output += f\"Time({solution.Value(time_var)})\\n\"\n", + " plan_output += f\"Time of the route: {solution.Value(time_var)}min\\n\"\n", " print(plan_output)\n", " total_time += solution.Value(time_var)\n", - " print(f'Total time of all routes: {total_time}min')\n", + " print(f\"Total time of all routes: {total_time}min\")\n", "\n", "\n", "def main():\n", @@ -165,21 +167,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def time_callback(from_index, to_index):\n", " \"\"\"Returns the travel time + service time between the two nodes.\"\"\"\n", " # Convert from routing variable Index to time matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['time_matrix'][from_node][to_node] + data['service_time'][\n", - " from_node]\n", + " return data[\"time_matrix\"][from_node][to_node] + data[\"service_time\"][from_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", "\n", @@ -187,13 +188,14 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Time Windows constraint.\n", - " time = 'Time'\n", + " time = \"Time\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 10, # needed optional waiting time to place break\n", " 180, # maximum time per vehicle\n", " True, # Force start cumul to zero.\n", - " time)\n", + " time,\n", + " )\n", " time_dimension = routing.GetDimensionOrDie(time)\n", " time_dimension.SetGlobalSpanCostCoefficient(10)\n", "\n", @@ -202,7 +204,7 @@ " node_visit_transit = [0] * routing.Size()\n", " for index in range(routing.Size()):\n", " node = manager.IndexToNode(index)\n", - " node_visit_transit[index] = data['service_time'][node]\n", + " node_visit_transit[index] = data[\"service_time\"][node]\n", "\n", " break_intervals = {}\n", " for v in range(manager.GetNumberOfVehicles()):\n", @@ -212,19 +214,21 @@ " 60, # start max\n", " 10, # duration: 10 min\n", " False, # optional: no\n", - " f'Break for vehicle {v}')\n", + " f\"Break for vehicle {v}\",\n", + " )\n", " ]\n", " time_dimension.SetBreakIntervalsOfVehicle(\n", - " break_intervals[v], # breaks\n", - " v, # vehicle index\n", - " node_visit_transit)\n", + " break_intervals[v], v, node_visit_transit # breaks # vehicle index\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " # search_parameters.log_search = True\n", " search_parameters.time_limit.FromSeconds(2)\n", "\n", @@ -235,7 +239,7 @@ " if solution:\n", " print_solution(manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_capacity.ipynb b/examples/notebook/constraint_solver/vrp_capacity.ipynb index 9e947de26d..a74307e04e 100644 --- a/examples/notebook/constraint_solver/vrp_capacity.ipynb +++ b/examples/notebook/constraint_solver/vrp_capacity.ipynb @@ -90,109 +90,61 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['demands'] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]\n", - " data['vehicle_capacities'] = [15, 15, 15, 15]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"demands\"] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]\n", + " data[\"vehicle_capacities\"] = [15, 15, 15, 15]\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " total_distance = 0\n", " total_load = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " route_load = 0\n", " while not routing.IsEnd(index):\n", " node_index = manager.IndexToNode(index)\n", - " route_load += data['demands'][node_index]\n", - " plan_output += f' {node_index} Load({route_load}) -> '\n", + " route_load += data[\"demands\"][node_index]\n", + " plan_output += f\" {node_index} Load({route_load}) -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f' {manager.IndexToNode(index)} Load({route_load})\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", - " plan_output += f'Load of the route: {route_load}\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\" {manager.IndexToNode(index)} Load({route_load})\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", + " plan_output += f\"Load of the route: {route_load}\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", " total_load += route_load\n", - " print(f'Total distance of all routes: {total_distance}m')\n", - " print(f'Total load of all routes: {total_load}')\n", + " print(f\"Total distance of all routes: {total_distance}m\")\n", + " print(f\"Total load of all routes: {total_load}\")\n", "\n", "\n", "def main():\n", @@ -201,49 +153,50 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", " # Define cost of each arc.\n", " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", - "\n", " # Add Capacity constraint.\n", " def demand_callback(from_index):\n", " \"\"\"Returns the demand of the node.\"\"\"\n", " # Convert from routing variable Index to demands NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", - " return data['demands'][from_node]\n", + " return data[\"demands\"][from_node]\n", "\n", - " demand_callback_index = routing.RegisterUnaryTransitCallback(\n", - " demand_callback)\n", + " demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)\n", " routing.AddDimensionWithVehicleCapacity(\n", " demand_callback_index,\n", " 0, # null capacity slack\n", - " data['vehicle_capacities'], # vehicle maximum capacities\n", + " data[\"vehicle_capacities\"], # vehicle maximum capacities\n", " True, # start cumul to zero\n", - " 'Capacity')\n", + " \"Capacity\",\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " search_parameters.time_limit.FromSeconds(1)\n", "\n", " # Solve the problem.\n", diff --git a/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb b/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb index 9def852cc8..f07e42921b 100644 --- a/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb +++ b/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb @@ -90,118 +90,70 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['demands'] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]\n", - " data['vehicle_capacities'] = [15, 15, 15, 15]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"demands\"] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]\n", + " data[\"vehicle_capacities\"] = [15, 15, 15, 15]\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, assignment):\n", " \"\"\"Prints assignment on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " # Display dropped nodes.\n", - " dropped_nodes = 'Dropped nodes:'\n", + " dropped_nodes = \"Dropped nodes:\"\n", " for node in range(routing.Size()):\n", " if routing.IsStart(node) or routing.IsEnd(node):\n", " continue\n", " if assignment.Value(routing.NextVar(node)) == node:\n", - " dropped_nodes += f' {manager.IndexToNode(node)}'\n", + " dropped_nodes += f\" {manager.IndexToNode(node)}\"\n", " print(dropped_nodes)\n", " # Display routes\n", " total_distance = 0\n", " total_load = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " route_load = 0\n", " while not routing.IsEnd(index):\n", " node_index = manager.IndexToNode(index)\n", - " route_load += data['demands'][node_index]\n", - " plan_output += f' {node_index} Load({route_load}) -> '\n", + " route_load += data[\"demands\"][node_index]\n", + " plan_output += f\" {node_index} Load({route_load}) -> \"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f' {manager.IndexToNode(index)} Load({route_load})\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", - " plan_output += f'Load of the route: {route_load}\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\" {manager.IndexToNode(index)} Load({route_load})\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", + " plan_output += f\"Load of the route: {route_load}\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", " total_load += route_load\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", - " print(f'Total Load of all routes: {total_load}')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", + " print(f\"Total Load of all routes: {total_load}\")\n", "\n", "\n", "def main():\n", @@ -210,53 +162,54 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", " # Define cost of each arc.\n", " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", - "\n", " # Add Capacity constraint.\n", " def demand_callback(from_index):\n", " \"\"\"Returns the demand of the node.\"\"\"\n", " # Convert from routing variable Index to demands NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", - " return data['demands'][from_node]\n", + " return data[\"demands\"][from_node]\n", "\n", - " demand_callback_index = routing.RegisterUnaryTransitCallback(\n", - " demand_callback)\n", + " demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)\n", " routing.AddDimensionWithVehicleCapacity(\n", " demand_callback_index,\n", " 0, # null capacity slack\n", - " data['vehicle_capacities'], # vehicle maximum capacities\n", + " data[\"vehicle_capacities\"], # vehicle maximum capacities\n", " True, # start cumul to zero\n", - " 'Capacity')\n", + " \"Capacity\",\n", + " )\n", " # Allow to drop nodes.\n", " penalty = 1000\n", - " for node in range(1, len(data['distance_matrix'])):\n", + " for node in range(1, len(data[\"distance_matrix\"])):\n", " routing.AddDisjunction([manager.NodeToIndex(node)], penalty)\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " search_parameters.time_limit.FromSeconds(1)\n", "\n", " # Solve the problem.\n", diff --git a/examples/notebook/constraint_solver/vrp_global_span.ipynb b/examples/notebook/constraint_solver/vrp_global_span.ipynb index 0463d86974..62a5dad2af 100644 --- a/examples/notebook/constraint_solver/vrp_global_span.ipynb +++ b/examples/notebook/constraint_solver/vrp_global_span.ipynb @@ -98,100 +98,52 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " max_route_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " max_route_distance = max(route_distance, max_route_distance)\n", - " print(f'Maximum of the route distances: {max_route_distance}m')\n", + " print(f\"Maximum of the route distances: {max_route_distance}m\")\n", "\n", "\n", "\n", @@ -201,20 +153,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -222,20 +174,22 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", @@ -244,7 +198,7 @@ " if solution:\n", " print_solution(data, manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_initial_routes.ipynb b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb index 6e53c72952..d7c8bd944e 100644 --- a/examples/notebook/constraint_solver/vrp_initial_routes.ipynb +++ b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb @@ -90,106 +90,58 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['initial_routes'] = [\n", + " data[\"initial_routes\"] = [\n", " [8, 16, 14, 13, 12, 11],\n", " [3, 4, 9, 10],\n", " [15, 1],\n", " [7, 5, 2, 6],\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " max_route_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " max_route_distance = max(route_distance, max_route_distance)\n", - " print(f'Maximum of the route distances: {max_route_distance}m')\n", + " print(f\"Maximum of the route distances: {max_route_distance}m\")\n", "\n", "\n", "\n", @@ -199,20 +151,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -220,13 +172,14 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", @@ -242,18 +195,18 @@ " routing.CloseModelWithParameters(search_parameters)\n", "\n", " # Get initial solution from routes after closing the model.\n", - " initial_solution = routing.ReadAssignmentFromRoutes(data['initial_routes'],\n", - " True)\n", - " print('Initial solution:')\n", + " initial_solution = routing.ReadAssignmentFromRoutes(data[\"initial_routes\"], True)\n", + " print(\"Initial solution:\")\n", " print_solution(data, manager, routing, initial_solution)\n", "\n", " # Solve the problem.\n", " solution = routing.SolveFromAssignmentWithParameters(\n", - " initial_solution, search_parameters)\n", + " initial_solution, search_parameters\n", + " )\n", "\n", " # Print solution on console.\n", " if solution:\n", - " print('Solution after search:')\n", + " print(\"Solution after search:\")\n", " print_solution(data, manager, routing, solution)\n", "\n", "\n", diff --git a/examples/notebook/constraint_solver/vrp_node_max.ipynb b/examples/notebook/constraint_solver/vrp_node_max.ipynb index 6366eca36a..3b8ecc56f9 100644 --- a/examples/notebook/constraint_solver/vrp_node_max.ipynb +++ b/examples/notebook/constraint_solver/vrp_node_max.ipynb @@ -94,77 +94,28 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['value'] = [\n", + " data[\"value\"] = [\n", " 0, # depot\n", " 42, # 1\n", " 42, # 2\n", @@ -183,23 +134,23 @@ " 42, # 15\n", " 42, # 16\n", " ]\n", - " assert len(data['distance_matrix']) == len(data['value'])\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " assert len(data[\"distance_matrix\"]) == len(data[\"value\"])\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " max_route_distance = 0\n", - " dim_one = routing.GetDimensionOrDie('One')\n", - " dim_two = routing.GetDimensionOrDie('Two')\n", + " dim_one = routing.GetDimensionOrDie(\"One\")\n", + " dim_two = routing.GetDimensionOrDie(\"Two\")\n", "\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", " one_var = dim_one.CumulVar(index)\n", @@ -207,23 +158,27 @@ " two_var = dim_two.CumulVar(index)\n", " two_slack_var = dim_two.SlackVar(index)\n", " plan_output += (\n", - " f' N:{manager.IndexToNode(index)}'\n", - " f' one:({solution.Value(one_var)}, {solution.Value(one_slack_var)})'\n", - " f' two:({solution.Value(two_var)}, {solution.Value(two_slack_var)})'\n", - " ' -> ')\n", + " f\" N:{manager.IndexToNode(index)}\"\n", + " f\" one:({solution.Value(one_var)}, {solution.Value(one_slack_var)})\"\n", + " f\" two:({solution.Value(two_var)}, {solution.Value(two_slack_var)})\"\n", + " \" -> \"\n", + " )\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", + " previous_index, index, vehicle_id\n", + " )\n", " one_var = dim_one.CumulVar(index)\n", " two_var = dim_two.CumulVar(index)\n", - " plan_output += (f'N:{manager.IndexToNode(index)}'\n", - " f' one:{solution.Value(one_var)}'\n", - " f' two:{solution.Value(two_var)}\\n')\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " plan_output += (\n", + " f\"N:{manager.IndexToNode(index)}\"\n", + " f\" one:{solution.Value(one_var)}\"\n", + " f\" two:{solution.Value(two_var)}\\n\"\n", + " )\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " max_route_distance = max(route_distance, max_route_distance)\n", - " print(f'Maximum of the route distances: {max_route_distance}m')\n", + " print(f\"Maximum of the route distances: {max_route_distance}m\")\n", "\n", "\n", "\n", @@ -233,8 +188,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -246,7 +202,7 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -254,13 +210,14 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3_000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(10)\n", "\n", @@ -272,8 +229,9 @@ " 42 * 16, # capacity: be able to store PEAK*ROUTE_LENGTH in worst case\n", " 42, # slack_max: to be able to store peak in slack\n", " True, # Fix StartCumulToZero not really matter here\n", - " 'One')\n", - " dim_one = routing.GetDimensionOrDie('One')\n", + " \"One\",\n", + " )\n", + " dim_one = routing.GetDimensionOrDie(\"One\")\n", "\n", " # Dimension Two will be used to store the max node value in the route end node\n", " # CumulVar so we can use it as an objective cost.\n", @@ -282,16 +240,17 @@ " 42 * 16, # capacity: be able to have PEAK value in CumulVar(End)\n", " 42, # slack_max: to be able to store peak in slack\n", " True, # Fix StartCumulToZero YES here\n", - " 'Two')\n", - " dim_two = routing.GetDimensionOrDie('Two')\n", + " \"Two\",\n", + " )\n", + " dim_two = routing.GetDimensionOrDie(\"Two\")\n", "\n", " # force depot Slack to be value since we don't have any predecessor...\n", " for v in range(manager.GetNumberOfVehicles()):\n", " start = routing.Start(v)\n", - " dim_one.SlackVar(start).SetValue(data['value'][0])\n", + " dim_one.SlackVar(start).SetValue(data[\"value\"][0])\n", " routing.AddToAssignment(dim_one.SlackVar(start))\n", "\n", - " dim_two.SlackVar(start).SetValue(data['value'][0])\n", + " dim_two.SlackVar(start).SetValue(data[\"value\"][0])\n", " routing.AddToAssignment(dim_two.SlackVar(start))\n", "\n", " # Step by step relation\n", @@ -305,14 +264,12 @@ " for v in range(manager.GetNumberOfVehicles()):\n", " previous_index = routing.Start(v)\n", " cond = routing.NextVar(previous_index) == index\n", - " value = solver.Max(dim_one.SlackVar(previous_index),\n", - " data['value'][node])\n", + " value = solver.Max(dim_one.SlackVar(previous_index), data[\"value\"][node])\n", " test.append((cond * value).Var())\n", " for previous in range(1, 17):\n", " previous_index = manager.NodeToIndex(previous)\n", " cond = routing.NextVar(previous_index) == index\n", - " value = solver.Max(dim_one.SlackVar(previous_index),\n", - " data['value'][node])\n", + " value = solver.Max(dim_one.SlackVar(previous_index), data[\"value\"][node])\n", " test.append((cond * value).Var())\n", " solver.Add(solver.Sum(test) == dim_one.SlackVar(index))\n", "\n", @@ -335,9 +292,11 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " # search_parameters.log_search = True\n", " search_parameters.time_limit.FromSeconds(5)\n", "\n", @@ -348,7 +307,7 @@ " if solution:\n", " print_solution(data, manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb index ff07859ca3..7080724dc3 100644 --- a/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb @@ -90,77 +90,28 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['pickups_deliveries'] = [\n", + " data[\"pickups_deliveries\"] = [\n", " [1, 6],\n", " [2, 10],\n", " [4, 3],\n", @@ -170,30 +121,31 @@ " [13, 12],\n", " [16, 14],\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " total_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "def main():\n", @@ -202,8 +154,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -215,38 +168,41 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Define Transportation Requests.\n", - " for request in data['pickups_deliveries']:\n", + " for request in data[\"pickups_deliveries\"]:\n", " pickup_index = manager.NodeToIndex(request[0])\n", " delivery_index = manager.NodeToIndex(request[1])\n", " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", " routing.solver().Add(\n", - " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", - " delivery_index))\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n", + " )\n", " routing.solver().Add(\n", - " distance_dimension.CumulVar(pickup_index) <=\n", - " distance_dimension.CumulVar(delivery_index))\n", + " distance_dimension.CumulVar(pickup_index)\n", + " <= distance_dimension.CumulVar(delivery_index)\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb index 3d8e58af04..7d9adc7b4f 100644 --- a/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb @@ -90,77 +90,28 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['pickups_deliveries'] = [\n", + " data[\"pickups_deliveries\"] = [\n", " [1, 6],\n", " [2, 10],\n", " [4, 3],\n", @@ -170,30 +121,31 @@ " [13, 12],\n", " [16, 14],\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, assignment):\n", " \"\"\"Prints assignment on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " total_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "def main():\n", @@ -202,8 +154,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -215,40 +168,44 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Define Transportation Requests.\n", - " for request in data['pickups_deliveries']:\n", + " for request in data[\"pickups_deliveries\"]:\n", " pickup_index = manager.NodeToIndex(request[0])\n", " delivery_index = manager.NodeToIndex(request[1])\n", " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", " routing.solver().Add(\n", - " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", - " delivery_index))\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n", + " )\n", " routing.solver().Add(\n", - " distance_dimension.CumulVar(pickup_index) <=\n", - " distance_dimension.CumulVar(delivery_index))\n", + " distance_dimension.CumulVar(pickup_index)\n", + " <= distance_dimension.CumulVar(delivery_index)\n", + " )\n", " routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n", - " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_FIFO)\n", + " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_FIFO\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n", + " )\n", "\n", " # Solve the problem.\n", " assignment = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb index d22849ae68..e43ef4d199 100644 --- a/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb @@ -90,77 +90,28 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['pickups_deliveries'] = [\n", + " data[\"pickups_deliveries\"] = [\n", " [1, 6],\n", " [2, 10],\n", " [4, 3],\n", @@ -170,30 +121,31 @@ " [13, 12],\n", " [16, 14],\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, assignment):\n", " \"\"\"Prints assignment on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " total_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "def main():\n", @@ -202,8 +154,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -215,40 +168,44 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Define Transportation Requests.\n", - " for request in data['pickups_deliveries']:\n", + " for request in data[\"pickups_deliveries\"]:\n", " pickup_index = manager.NodeToIndex(request[0])\n", " delivery_index = manager.NodeToIndex(request[1])\n", " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", " routing.solver().Add(\n", - " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", - " delivery_index))\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n", + " )\n", " routing.solver().Add(\n", - " distance_dimension.CumulVar(pickup_index) <=\n", - " distance_dimension.CumulVar(delivery_index))\n", + " distance_dimension.CumulVar(pickup_index)\n", + " <= distance_dimension.CumulVar(delivery_index)\n", + " )\n", " routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n", - " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_LIFO)\n", + " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_LIFO\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n", + " )\n", "\n", " # Solve the problem.\n", " assignment = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp_resources.ipynb b/examples/notebook/constraint_solver/vrp_resources.ipynb index 036cdff0d1..b14d3fe12f 100644 --- a/examples/notebook/constraint_solver/vrp_resources.ipynb +++ b/examples/notebook/constraint_solver/vrp_resources.ipynb @@ -90,7 +90,7 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['time_matrix'] = [\n", + " data[\"time_matrix\"] = [\n", " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n", " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n", " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n", @@ -109,7 +109,7 @@ " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n", " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n", " ]\n", - " data['time_windows'] = [\n", + " data[\"time_windows\"] = [\n", " (0, 5), # depot\n", " (7, 12), # 1\n", " (10, 15), # 2\n", @@ -128,37 +128,39 @@ " (10, 15), # 15\n", " (5, 15), # 16\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['vehicle_load_time'] = 5\n", - " data['vehicle_unload_time'] = 5\n", - " data['depot_capacity'] = 2\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"vehicle_load_time\"] = 5\n", + " data[\"vehicle_unload_time\"] = 5\n", + " data[\"depot_capacity\"] = 2\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", - " time_dimension = routing.GetDimensionOrDie('Time')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", + " time_dimension = routing.GetDimensionOrDie(\"Time\")\n", " total_time = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " while not routing.IsEnd(index):\n", " time_var = time_dimension.CumulVar(index)\n", " plan_output += (\n", - " f'{manager.IndexToNode(index)}'\n", - " f' Time({solution.Min(time_var)}, {solution.Max(time_var)})'\n", - " ' -> ')\n", + " f\"{manager.IndexToNode(index)}\"\n", + " f\" Time({solution.Min(time_var)}, {solution.Max(time_var)})\"\n", + " \" -> \"\n", + " )\n", " index = solution.Value(routing.NextVar(index))\n", " time_var = time_dimension.CumulVar(index)\n", " plan_output += (\n", - " f'{manager.IndexToNode(index)}'\n", - " f' Time({solution.Min(time_var)},{solution.Max(time_var)})\\n')\n", - " plan_output += f'Time of the route: {solution.Min(time_var)}min\\n'\n", + " f\"{manager.IndexToNode(index)}\"\n", + " f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\\n\"\n", + " )\n", + " plan_output += f\"Time of the route: {solution.Min(time_var)}min\\n\"\n", " print(plan_output)\n", " total_time += solution.Min(time_var)\n", - " print(f'Total time of all routes: {total_time}min')\n", + " print(f\"Total time of all routes: {total_time}min\")\n", "\n", "\n", "def main():\n", @@ -167,20 +169,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def time_callback(from_index, to_index):\n", " \"\"\"Returns the travel time between the two nodes.\"\"\"\n", " # Convert from routing variable Index to time matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['time_matrix'][from_node][to_node]\n", + " return data[\"time_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", "\n", @@ -188,57 +190,66 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Time Windows constraint.\n", - " time = 'Time'\n", + " time = \"Time\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 60, # allow waiting time\n", " 60, # maximum time per vehicle\n", " False, # Don't force start cumul to zero.\n", - " time)\n", + " time,\n", + " )\n", " time_dimension = routing.GetDimensionOrDie(time)\n", " # Add time window constraints for each location except depot.\n", - " for location_idx, time_window in enumerate(data['time_windows']):\n", + " for location_idx, time_window in enumerate(data[\"time_windows\"]):\n", " if location_idx == 0:\n", " continue\n", " index = manager.NodeToIndex(location_idx)\n", " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", " # Add time window constraints for each vehicle start node.\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", - " data['time_windows'][0][1])\n", + " time_dimension.CumulVar(index).SetRange(\n", + " data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n", + " )\n", "\n", " # Add resource constraints at the depot.\n", " solver = routing.solver()\n", " intervals = []\n", - " for i in range(data['num_vehicles']):\n", + " for i in range(data[\"num_vehicles\"]):\n", " # Add time windows at start of routes\n", " intervals.append(\n", " solver.FixedDurationIntervalVar(\n", " time_dimension.CumulVar(routing.Start(i)),\n", - " data['vehicle_load_time'], 'depot_interval'))\n", + " data[\"vehicle_load_time\"],\n", + " \"depot_interval\",\n", + " )\n", + " )\n", " # Add time windows at end of routes.\n", " intervals.append(\n", " solver.FixedDurationIntervalVar(\n", " time_dimension.CumulVar(routing.End(i)),\n", - " data['vehicle_unload_time'], 'depot_interval'))\n", + " data[\"vehicle_unload_time\"],\n", + " \"depot_interval\",\n", + " )\n", + " )\n", "\n", " depot_usage = [1 for i in range(len(intervals))]\n", " solver.Add(\n", - " solver.Cumulative(intervals, depot_usage, data['depot_capacity'],\n", - " 'depot'))\n", + " solver.Cumulative(intervals, depot_usage, data[\"depot_capacity\"], \"depot\")\n", + " )\n", "\n", " # Instantiate route start and end times to produce feasible times.\n", - " for i in range(data['num_vehicles']):\n", + " for i in range(data[\"num_vehicles\"]):\n", " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.Start(i)))\n", - " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.End(i)))\n", + " time_dimension.CumulVar(routing.Start(i))\n", + " )\n", + " routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", @@ -247,7 +258,7 @@ " if solution:\n", " print_solution(data, manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_solution_callback.ipynb b/examples/notebook/constraint_solver/vrp_solution_callback.ipynb index 50b8b744ce..0295a28d4e 100644 --- a/examples/notebook/constraint_solver/vrp_solution_callback.ipynb +++ b/examples/notebook/constraint_solver/vrp_solution_callback.ipynb @@ -98,109 +98,66 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", - "def print_solution(routing_manager: pywrapcp.RoutingIndexManager,\n", - " routing_model: pywrapcp.RoutingModel):\n", + "def print_solution(\n", + " routing_manager: pywrapcp.RoutingIndexManager, routing_model: pywrapcp.RoutingModel\n", + "):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print('################')\n", - " print(f'Solution objective: {routing_model.CostVar().Value()}')\n", + " print(\"################\")\n", + " print(f\"Solution objective: {routing_model.CostVar().Value()}\")\n", " total_distance = 0\n", " for vehicle_id in range(routing_manager.GetNumberOfVehicles()):\n", " index = routing_model.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing_model.IsEnd(index):\n", - " plan_output += f' {routing_manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {routing_manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = routing_model.NextVar(index).Value()\n", " route_distance += routing_model.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f' {routing_manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\" {routing_manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "class SolutionCallback:\n", " \"\"\"Create a solution callback.\"\"\"\n", "\n", - " def __init__(self, manager: pywrapcp.RoutingIndexManager,\n", - " model: pywrapcp.RoutingModel, limit: int):\n", + " def __init__(\n", + " self,\n", + " manager: pywrapcp.RoutingIndexManager,\n", + " model: pywrapcp.RoutingModel,\n", + " limit: int,\n", + " ):\n", " self._routing_manager = manager\n", " self._routing_model = model\n", " self._counter = 0\n", @@ -223,9 +180,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " routing_manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'],\n", - " data['depot'])\n", + " routing_manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing_model = pywrapcp.RoutingModel(routing_manager)\n", @@ -237,22 +194,22 @@ " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = routing_manager.IndexToNode(from_index)\n", " to_node = routing_manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", - " transit_callback_index = routing_model.RegisterTransitCallback(\n", - " distance_callback)\n", + " transit_callback_index = routing_model.RegisterTransitCallback(distance_callback)\n", "\n", " # Define cost of each arc.\n", " routing_model.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing_model.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing_model.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", @@ -263,9 +220,11 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " search_parameters.time_limit.FromSeconds(5)\n", "\n", " # Solve the problem.\n", @@ -273,9 +232,9 @@ "\n", " # Print solution on console.\n", " if solution:\n", - " print(f'Best objective: {solution_callback.objectives[-1]}')\n", + " print(f\"Best objective: {solution_callback.objectives[-1]}\")\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_starts_ends.ipynb b/examples/notebook/constraint_solver/vrp_starts_ends.ipynb index 5a994a4875..77c566b60f 100644 --- a/examples/notebook/constraint_solver/vrp_starts_ends.ipynb +++ b/examples/notebook/constraint_solver/vrp_starts_ends.ipynb @@ -90,101 +90,53 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['distance_matrix'] = [\n", - " [\n", - " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", - " 468, 776, 662\n", - " ],\n", - " [\n", - " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", - " 1016, 868, 1210\n", - " ],\n", - " [\n", - " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", - " 1130, 788, 1552, 754\n", - " ],\n", - " [\n", - " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", - " 1164, 560, 1358\n", - " ],\n", - " [\n", - " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", - " 1050, 674, 1244\n", - " ],\n", - " [\n", - " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", - " 514, 1050, 708\n", - " ],\n", - " [\n", - " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", - " 514, 1278, 480\n", - " ],\n", - " [\n", - " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", - " 662, 742, 856\n", - " ],\n", - " [\n", - " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", - " 320, 1084, 514\n", - " ],\n", - " [\n", - " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", - " 274, 810, 468\n", - " ],\n", - " [\n", - " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", - " 730, 388, 1152, 354\n", - " ],\n", - " [\n", - " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", - " 308, 650, 274, 844\n", - " ],\n", - " [\n", - " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", - " 536, 388, 730\n", - " ],\n", - " [\n", - " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", - " 342, 422, 536\n", - " ],\n", - " [\n", - " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", - " 342, 0, 764, 194\n", - " ],\n", - " [\n", - " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", - " 388, 422, 764, 0, 798\n", - " ],\n", - " [\n", - " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", - " 536, 194, 798, 0\n", - " ],\n", + " data[\"distance_matrix\"] = [\n", + " # fmt: off\n", + " [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n", + " [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n", + " [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n", + " [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n", + " [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n", + " [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n", + " [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n", + " [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n", + " [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n", + " [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n", + " [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n", + " [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n", + " [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n", + " [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n", + " [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n", + " [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n", + " [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n", + " # fmt: on\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['starts'] = [1, 2, 15, 16]\n", - " data['ends'] = [0, 0, 0, 0]\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"starts\"] = [1, 2, 15, 16]\n", + " data[\"ends\"] = [0, 0, 0, 0]\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " max_route_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " max_route_distance = max(route_distance, max_route_distance)\n", - " print(f'Maximum of the route distances: {max_route_distance}m')\n", + " print(f\"Maximum of the route distances: {max_route_distance}m\")\n", "\n", "\n", "def main():\n", @@ -193,21 +145,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", - " data['num_vehicles'], data['starts'],\n", - " data['ends'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"starts\"], data[\"ends\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def distance_callback(from_index, to_index):\n", " \"\"\"Returns the distance between the two nodes.\"\"\"\n", " # Convert from routing variable Index to distance matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['distance_matrix'][from_node][to_node]\n", + " return data[\"distance_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", "\n", @@ -215,20 +166,22 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 2000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp_time_windows.ipynb b/examples/notebook/constraint_solver/vrp_time_windows.ipynb index 988a89164f..65ce87405a 100644 --- a/examples/notebook/constraint_solver/vrp_time_windows.ipynb +++ b/examples/notebook/constraint_solver/vrp_time_windows.ipynb @@ -90,7 +90,7 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['time_matrix'] = [\n", + " data[\"time_matrix\"] = [\n", " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n", " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n", " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n", @@ -109,7 +109,7 @@ " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n", " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n", " ]\n", - " data['time_windows'] = [\n", + " data[\"time_windows\"] = [\n", " (0, 5), # depot\n", " (7, 12), # 1\n", " (10, 15), # 2\n", @@ -128,34 +128,36 @@ " (10, 15), # 15\n", " (11, 15), # 16\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(data, manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", - " time_dimension = routing.GetDimensionOrDie('Time')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", + " time_dimension = routing.GetDimensionOrDie(\"Time\")\n", " total_time = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " while not routing.IsEnd(index):\n", " time_var = time_dimension.CumulVar(index)\n", " plan_output += (\n", - " f'{manager.IndexToNode(index)}'\n", - " f' Time({solution.Min(time_var)},{solution.Max(time_var)})'\n", - " ' -> ')\n", + " f\"{manager.IndexToNode(index)}\"\n", + " f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\"\n", + " \" -> \"\n", + " )\n", " index = solution.Value(routing.NextVar(index))\n", " time_var = time_dimension.CumulVar(index)\n", " plan_output += (\n", - " f'{manager.IndexToNode(index)}'\n", - " f' Time({solution.Min(time_var)},{solution.Max(time_var)})\\n')\n", - " plan_output += f'Time of the route: {solution.Min(time_var)}min\\n'\n", + " f\"{manager.IndexToNode(index)}\"\n", + " f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\\n\"\n", + " )\n", + " plan_output += f\"Time of the route: {solution.Min(time_var)}min\\n\"\n", " print(plan_output)\n", " total_time += solution.Min(time_var)\n", - " print(f'Total time of all routes: {total_time}min')\n", + " print(f\"Total time of all routes: {total_time}min\")\n", "\n", "\n", "def main():\n", @@ -164,20 +166,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def time_callback(from_index, to_index):\n", " \"\"\"Returns the travel time between the two nodes.\"\"\"\n", " # Convert from routing variable Index to time matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['time_matrix'][from_node][to_node]\n", + " return data[\"time_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", "\n", @@ -185,39 +187,41 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Time Windows constraint.\n", - " time = 'Time'\n", + " time = \"Time\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 30, # allow waiting time\n", " 30, # maximum time per vehicle\n", " False, # Don't force start cumul to zero.\n", - " time)\n", + " time,\n", + " )\n", " time_dimension = routing.GetDimensionOrDie(time)\n", " # Add time window constraints for each location except depot.\n", - " for location_idx, time_window in enumerate(data['time_windows']):\n", - " if location_idx == data['depot']:\n", + " for location_idx, time_window in enumerate(data[\"time_windows\"]):\n", + " if location_idx == data[\"depot\"]:\n", " continue\n", " index = manager.NodeToIndex(location_idx)\n", " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", " # Add time window constraints for each vehicle start node.\n", - " depot_idx = data['depot']\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " depot_idx = data[\"depot\"]\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", " time_dimension.CumulVar(index).SetRange(\n", - " data['time_windows'][depot_idx][0],\n", - " data['time_windows'][depot_idx][1])\n", + " data[\"time_windows\"][depot_idx][0], data[\"time_windows\"][depot_idx][1]\n", + " )\n", "\n", " # Instantiate route start and end times to produce feasible times.\n", - " for i in range(data['num_vehicles']):\n", + " for i in range(data[\"num_vehicles\"]):\n", " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.Start(i)))\n", - " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.End(i)))\n", + " time_dimension.CumulVar(routing.Start(i))\n", + " )\n", + " routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/constraint_solver/vrp_tokens.ipynb b/examples/notebook/constraint_solver/vrp_tokens.ipynb index 5bac71fd17..4097c1c96c 100644 --- a/examples/notebook/constraint_solver/vrp_tokens.ipynb +++ b/examples/notebook/constraint_solver/vrp_tokens.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Simple VRP with special locations which need to be visited at end of the route.\n" ] }, @@ -90,7 +91,7 @@ " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", " # Special location don't consume token, while regular one consume one\n", - " data['tokens'] = [\n", + " data[\"tokens\"] = [\n", " 0, # 0 depot\n", " 0, # 1 special node\n", " 0, # 2 special node\n", @@ -112,20 +113,20 @@ " -1, # 18\n", " ]\n", " # just need to be big enough, not a limiting factor\n", - " data['vehicle_tokens'] = [20, 20, 20, 20]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"vehicle_tokens\"] = [20, 20, 20, 20]\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", - " token_dimension = routing.GetDimensionOrDie('Token')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", + " token_dimension = routing.GetDimensionOrDie(\"Token\")\n", " total_distance = 0\n", " total_token = 0\n", " for vehicle_id in range(manager.GetNumberOfVehicles()):\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " index = routing.Start(vehicle_id)\n", " total_token += solution.Value(token_dimension.CumulVar(index))\n", " route_distance = 0\n", @@ -134,20 +135,21 @@ " node_index = manager.IndexToNode(index)\n", " token_var = token_dimension.CumulVar(index)\n", " route_token = solution.Value(token_var)\n", - " plan_output += f' {node_index} Token({route_token}) -> '\n", + " plan_output += f\" {node_index} Token({route_token}) -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", + " previous_index, index, vehicle_id\n", + " )\n", " node_index = manager.IndexToNode(index)\n", " token_var = token_dimension.CumulVar(index)\n", " route_token = solution.Value(token_var)\n", - " plan_output += f' {node_index} Token({route_token})\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " plan_output += f\" {node_index} Token({route_token})\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " total_distance += route_distance\n", " print(plan_output)\n", - " print(f'Total distance of all routes: {total_distance}m')\n", - " print(f'Total token of all routes: {total_token}')\n", + " print(f\"Total distance of all routes: {total_distance}m\")\n", + " print(f\"Total token of all routes: {total_token}\")\n", "\n", "\n", "def main():\n", @@ -156,8 +158,9 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['tokens']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"tokens\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", @@ -176,8 +179,9 @@ " 0, # null slack\n", " 3000, # maximum distance per vehicle\n", " True, # start cumul to zero\n", - " 'distance')\n", - " distance_dimension = routing.GetDimensionOrDie('distance')\n", + " \"distance\",\n", + " )\n", + " distance_dimension = routing.GetDimensionOrDie(\"distance\")\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Define cost of each arc.\n", @@ -188,17 +192,18 @@ " \"\"\"Returns the number of token consumed by the node.\"\"\"\n", " # Convert from routing variable Index to tokens NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", - " return data['tokens'][from_node]\n", + " return data[\"tokens\"][from_node]\n", "\n", " token_callback_index = routing.RegisterUnaryTransitCallback(token_callback)\n", " routing.AddDimensionWithVehicleCapacity(\n", " token_callback_index,\n", " 0, # null capacity slack\n", - " data['vehicle_tokens'], # vehicle maximum tokens\n", + " data[\"vehicle_tokens\"], # vehicle maximum tokens\n", " False, # start cumul to zero\n", - " 'Token')\n", + " \"Token\",\n", + " )\n", " # Add constraint: special node can only be visited if token remaining is zero\n", - " token_dimension = routing.GetDimensionOrDie('Token')\n", + " token_dimension = routing.GetDimensionOrDie(\"Token\")\n", " for node in range(1, 6):\n", " index = manager.NodeToIndex(node)\n", " routing.solver().Add(token_dimension.CumulVar(index) == 0)\n", @@ -206,16 +211,20 @@ " # Instantiate route start and end times to produce feasible times.\n", " for i in range(manager.GetNumberOfVehicles()):\n", " routing.AddVariableMinimizedByFinalizer(\n", - " token_dimension.CumulVar(routing.Start(i)))\n", + " token_dimension.CumulVar(routing.Start(i))\n", + " )\n", " routing.AddVariableMinimizedByFinalizer(\n", - " token_dimension.CumulVar(routing.End(i)))\n", + " token_dimension.CumulVar(routing.End(i))\n", + " )\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " search_parameters.time_limit.FromSeconds(1)\n", "\n", " # Solve the problem.\n", @@ -225,7 +234,7 @@ " if solution:\n", " print_solution(manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb b/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb index f6d30024f1..be2f359492 100644 --- a/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb +++ b/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb @@ -89,23 +89,24 @@ "\n", "def print_solution(manager, routing, solution):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {solution.ObjectiveValue()}')\n", + " print(f\"Objective: {solution.ObjectiveValue()}\")\n", " max_route_distance = 0\n", " for vehicle_id in range(manager.GetNumberOfVehicles()):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} -> '\n", + " plan_output += f\" {manager.IndexToNode(index)} -> \"\n", " previous_index = index\n", " index = solution.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f'{manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\"{manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " max_route_distance = max(route_distance, max_route_distance)\n", - " print(f'Maximum of the route distances: {max_route_distance}m')\n", + " print(f\"Maximum of the route distances: {max_route_distance}m\")\n", "\n", "\n", "def main():\n", @@ -134,22 +135,25 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Distance constraint.\n", - " dimension_name = 'Distance'\n", + " dimension_name = \"Distance\"\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 0, # no slack\n", " 3000, # vehicle maximum travel distance\n", " True, # start cumul to zero\n", - " dimension_name)\n", + " dimension_name,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", " search_parameters.local_search_metaheuristic = (\n", - " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n", + " )\n", " search_parameters.log_search = True\n", " search_parameters.time_limit.FromSeconds(5)\n", "\n", diff --git a/examples/notebook/constraint_solver/vrpgs.ipynb b/examples/notebook/constraint_solver/vrpgs.ipynb index 4fc88189a2..d6a76a0f17 100644 --- a/examples/notebook/constraint_solver/vrpgs.ipynb +++ b/examples/notebook/constraint_solver/vrpgs.ipynb @@ -101,55 +101,50 @@ " data = {}\n", " # Locations in block unit\n", " locations_ = [\n", - " (4, 4), # depot\n", - " (2, 0),\n", - " (8, 0), # locations to visit\n", - " (0, 1),\n", - " (1, 1),\n", - " (5, 2),\n", - " (7, 2),\n", - " (3, 3),\n", - " (6, 3),\n", - " (5, 5),\n", - " (8, 5),\n", - " (1, 6),\n", - " (2, 6),\n", - " (3, 7),\n", - " (6, 7),\n", - " (0, 8),\n", - " (7, 8),\n", + " # fmt: off\n", + " (4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8),\n", + " # fmt: on\n", " ]\n", " # Compute locations in meters using the block dimension defined as follow\n", " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", " # here we use: 114m x 80m city block\n", " # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n", - " data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations_]\n", - " data['num_locations'] = len(data['locations'])\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations_]\n", + " data[\"num_locations\"] = len(data[\"locations\"])\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", "\n", "def print_solution(data, manager, routing, assignment):\n", " \"\"\"Prints solution on console.\"\"\"\n", - " print(f'Objective: {assignment.ObjectiveValue()}')\n", + " print(f\"Objective: {assignment.ObjectiveValue()}\")\n", " total_distance = 0\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n", + " plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n", " route_distance = 0\n", " while not routing.IsEnd(index):\n", - " plan_output += f' {manager.IndexToNode(index)} ->'\n", + " plan_output += f\" {manager.IndexToNode(index)} ->\"\n", " previous_index = index\n", " index = assignment.Value(routing.NextVar(index))\n", " route_distance += routing.GetArcCostForVehicle(\n", - " previous_index, index, vehicle_id)\n", - " plan_output += f' {manager.IndexToNode(index)}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", + " previous_index, index, vehicle_id\n", + " )\n", + " plan_output += f\" {manager.IndexToNode(index)}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", - " print(f'Total Distance of all routes: {total_distance}m')\n", + " print(f\"Total Distance of all routes: {total_distance}m\")\n", "\n", "\n", "\n", @@ -158,22 +153,22 @@ "#######################\n", "def manhattan_distance(position_1, position_2):\n", " \"\"\"Computes the Manhattan distance between two points.\"\"\"\n", - " return (abs(position_1[0] - position_2[0]) +\n", - " abs(position_1[1] - position_2[1]))\n", + " return abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])\n", "\n", "\n", "def create_distance_evaluator(data):\n", " \"\"\"Creates callback to return distance between points.\"\"\"\n", " distances_ = {}\n", " # precompute distance between location to have distance callback in O(1)\n", - " for from_node in range(data['num_locations']):\n", + " for from_node in range(data[\"num_locations\"]):\n", " distances_[from_node] = {}\n", - " for to_node in range(data['num_locations']):\n", + " for to_node in range(data[\"num_locations\"]):\n", " if from_node == to_node:\n", " distances_[from_node][to_node] = 0\n", " else:\n", - " distances_[from_node][to_node] = (manhattan_distance(\n", - " data['locations'][from_node], data['locations'][to_node]))\n", + " distances_[from_node][to_node] = manhattan_distance(\n", + " data[\"locations\"][from_node], data[\"locations\"][to_node]\n", + " )\n", "\n", " def distance_evaluator(manager, from_index, to_index):\n", " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", @@ -187,13 +182,14 @@ "\n", "def add_distance_dimension(routing, distance_evaluator_index):\n", " \"\"\"Add Global Span constraint.\"\"\"\n", - " distance = 'Distance'\n", + " distance = \"Distance\"\n", " routing.AddDimension(\n", " distance_evaluator_index,\n", " 0, # null slack\n", " 3000, # maximum distance per vehicle\n", " True, # start cumul to zero\n", - " distance)\n", + " distance,\n", + " )\n", " distance_dimension = routing.GetDimensionOrDie(distance)\n", " # Try to minimize the max distance among vehicles.\n", " # /!\\ It doesn't mean the standard deviation is minimized\n", @@ -206,15 +202,17 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " data[\"num_locations\"], data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", " # Define weight of each edge\n", " distance_evaluator_index = routing.RegisterTransitCallback(\n", - " functools.partial(create_distance_evaluator(data), manager))\n", + " functools.partial(create_distance_evaluator(data), manager)\n", + " )\n", "\n", " # Define cost of each arc.\n", " routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", @@ -225,7 +223,8 @@ " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", @@ -234,7 +233,7 @@ " if solution:\n", " print_solution(data, manager, routing, solution)\n", " else:\n", - " print('No solution found !')\n", + " print(\"No solution found !\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb b/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb index 88ba7d846e..08dd65d82c 100644 --- a/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb +++ b/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb @@ -90,7 +90,7 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['time_matrix'] = [\n", + " data[\"time_matrix\"] = [\n", " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n", " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n", " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n", @@ -109,7 +109,7 @@ " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n", " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n", " ]\n", - " data['time_windows'] = [\n", + " data[\"time_windows\"] = [\n", " (0, 5), # depot\n", " (7, 12), # 1\n", " (10, 15), # 2\n", @@ -128,8 +128,8 @@ " (10, 15), # 15\n", " (11, 15), # 16\n", " ]\n", - " data['num_vehicles'] = 4\n", - " data['depot'] = 0\n", + " data[\"num_vehicles\"] = 4\n", + " data[\"depot\"] = 0\n", " return data\n", "\n", "\n", @@ -137,21 +137,35 @@ "def print_solution(routes, cumul_data):\n", " \"\"\"Print the solution.\"\"\"\n", " total_time = 0\n", - " route_str = ''\n", + " route_str = \"\"\n", " for i, route in enumerate(routes):\n", - " route_str += 'Route ' + str(i) + ':\\n'\n", + " route_str += \"Route \" + str(i) + \":\\n\"\n", " start_time = cumul_data[i][0][0]\n", " end_time = cumul_data[i][0][1]\n", - " route_str += ' ' + str(route[0]) + \\\n", - " ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n", + " route_str += (\n", + " \" \"\n", + " + str(route[0])\n", + " + \" Time(\"\n", + " + str(start_time)\n", + " + \", \"\n", + " + str(end_time)\n", + " + \")\"\n", + " )\n", " for j in range(1, len(route)):\n", " start_time = cumul_data[i][j][0]\n", " end_time = cumul_data[i][j][1]\n", - " route_str += ' -> ' + str(route[j]) + \\\n", - " ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n", - " route_str += f'\\n Route time: {start_time}min\\n\\n'\n", + " route_str += (\n", + " \" -> \"\n", + " + str(route[j])\n", + " + \" Time(\"\n", + " + str(start_time)\n", + " + \", \"\n", + " + str(end_time)\n", + " + \")\"\n", + " )\n", + " route_str += f\"\\n Route time: {start_time}min\\n\\n\"\n", " total_time += cumul_data[i][len(route) - 1][0]\n", - " route_str += f'Total time: {total_time}min'\n", + " route_str += f\"Total time: {total_time}min\"\n", " print(route_str)\n", "\n", "\n", @@ -200,20 +214,20 @@ " data = create_data_model()\n", "\n", " # Create the routing index manager.\n", - " manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", - " data['num_vehicles'], data['depot'])\n", + " manager = pywrapcp.RoutingIndexManager(\n", + " len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n", + " )\n", "\n", " # Create Routing Model.\n", " routing = pywrapcp.RoutingModel(manager)\n", "\n", - "\n", " # Create and register a transit callback.\n", " def time_callback(from_index, to_index):\n", " \"\"\"Returns the travel time between the two nodes.\"\"\"\n", " # Convert from routing variable Index to time matrix NodeIndex.\n", " from_node = manager.IndexToNode(from_index)\n", " to_node = manager.IndexToNode(to_index)\n", - " return data['time_matrix'][from_node][to_node]\n", + " return data[\"time_matrix\"][from_node][to_node]\n", "\n", " transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", "\n", @@ -221,38 +235,41 @@ " routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", "\n", " # Add Time Windows constraint.\n", - " time = 'Time'\n", + " time = \"Time\"\n", "\n", " routing.AddDimension(\n", " transit_callback_index,\n", " 30, # allow waiting time\n", " 30, # maximum time per vehicle\n", " False, # Don't force cumulative time to be 0 at start of routes.\n", - " time)\n", + " time,\n", + " )\n", " time_dimension = routing.GetDimensionOrDie(time)\n", " # Add time window constraints for each location except depot.\n", - " for location_idx, time_window in enumerate(data['time_windows']):\n", + " for location_idx, time_window in enumerate(data[\"time_windows\"]):\n", " if location_idx == 0:\n", " continue\n", " index = manager.NodeToIndex(location_idx)\n", " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", " # Add time window constraints for each vehicle start node.\n", - " for vehicle_id in range(data['num_vehicles']):\n", + " for vehicle_id in range(data[\"num_vehicles\"]):\n", " index = routing.Start(vehicle_id)\n", - " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", - " data['time_windows'][0][1])\n", + " time_dimension.CumulVar(index).SetRange(\n", + " data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n", + " )\n", "\n", " # Instantiate route start and end times to produce feasible times.\n", - " for i in range(data['num_vehicles']):\n", + " for i in range(data[\"num_vehicles\"]):\n", " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.Start(i)))\n", - " routing.AddVariableMinimizedByFinalizer(\n", - " time_dimension.CumulVar(routing.End(i)))\n", + " time_dimension.CumulVar(routing.Start(i))\n", + " )\n", + " routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n", "\n", " # Setting first solution heuristic.\n", " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", " search_parameters.first_solution_strategy = (\n", - " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n", + " )\n", "\n", " # Solve the problem.\n", " solution = routing.SolveWithParameters(search_parameters)\n", diff --git a/examples/notebook/examples/appointments.ipynb b/examples/notebook/examples/appointments.ipynb index 20cb6a090c..aa5bda9a11 100644 --- a/examples/notebook/examples/appointments.ipynb +++ b/examples/notebook/examples/appointments.ipynb @@ -92,12 +92,11 @@ "from ortools.linear_solver import pywraplp\n", "from ortools.sat.python import cp_model\n", "\n", - "_LOAD_MIN = flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.')\n", - "_LOAD_MAX = flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.')\n", - "_COMMUTE_TIME = flags.DEFINE_integer('commute_time', 30,\n", - " 'Commute time in minutes.')\n", - "_NUM_WORKERS = flags.DEFINE_integer('num_workers', 98,\n", - " 'Maximum number of workers.')\n", + "\n", + "_LOAD_MIN = flags.DEFINE_integer(\"load_min\", 480, \"Minimum load in minutes.\")\n", + "_LOAD_MAX = flags.DEFINE_integer(\"load_max\", 540, \"Maximum load in minutes.\")\n", + "_COMMUTE_TIME = flags.DEFINE_integer(\"commute_time\", 30, \"Commute time in minutes.\")\n", + "_NUM_WORKERS = flags.DEFINE_integer(\"num_workers\", 98, \"Maximum number of workers.\")\n", "\n", "\n", "class AllSolutionCollector(cp_model.CpSolverSolutionCallback):\n", @@ -118,24 +117,21 @@ " return self.__collect\n", "\n", "\n", - "def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,\n", - " total_size_max):\n", + "def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max):\n", " \"\"\"Enumerate all possible knapsacks with total size in the given range.\n", "\n", - " Args:\n", - " item_sizes: a list of integers. item_sizes[i] is the size of item #i.\n", - " total_size_min: an integer, the minimum total size.\n", - " total_size_max: an integer, the maximum total size.\n", + " Args:\n", + " item_sizes: a list of integers. item_sizes[i] is the size of item #i.\n", + " total_size_min: an integer, the minimum total size.\n", + " total_size_max: an integer, the maximum total size.\n", "\n", - " Returns:\n", - " The list of all the knapsacks whose total size is in the given inclusive\n", - " range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an\n", - " nonnegative integer: the number of times we put item #K in the knapsack.\n", - " \"\"\"\n", + " Returns:\n", + " The list of all the knapsacks whose total size is in the given inclusive\n", + " range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an\n", + " nonnegative integer: the number of times we put item #K in the knapsack.\n", + " \"\"\"\n", " model = cp_model.CpModel()\n", - " variables = [\n", - " model.NewIntVar(0, total_size_max // size, '') for size in item_sizes\n", - " ]\n", + " variables = [model.NewIntVar(0, total_size_max // size, \"\") for size in item_sizes]\n", " load = sum(variables[i] * size for i, size in enumerate(item_sizes))\n", " model.AddLinearConstraint(load, total_size_min, total_size_max)\n", "\n", @@ -148,52 +144,52 @@ " return solution_collector.combinations()\n", "\n", "\n", - "def AggregateItemCollectionsOptimally(item_collections, max_num_collections,\n", - " ideal_item_ratios):\n", + "def AggregateItemCollectionsOptimally(\n", + " item_collections, max_num_collections, ideal_item_ratios\n", + "):\n", " \"\"\"Selects a set (with repetition) of combination of items optimally.\n", "\n", - " Given a set of collections of N possible items (in each collection, an item\n", - " may appear multiple times), a given \"ideal breakdown of items\", and a\n", - " maximum number of collections, this method finds the optimal way to\n", - " aggregate the collections in order to:\n", - " - maximize the overall number of items\n", - " - while keeping the ratio of each item, among the overall selection, as close\n", - " as possible to a given input ratio (which depends on the item).\n", - " Each collection may be selected more than one time.\n", + " Given a set of collections of N possible items (in each collection, an item\n", + " may appear multiple times), a given \"ideal breakdown of items\", and a\n", + " maximum number of collections, this method finds the optimal way to\n", + " aggregate the collections in order to:\n", + " - maximize the overall number of items\n", + " - while keeping the ratio of each item, among the overall selection, as close\n", + " as possible to a given input ratio (which depends on the item).\n", + " Each collection may be selected more than one time.\n", "\n", - " Args:\n", - " item_collections: a list of item collections. Each item collection is a list\n", - " of integers [#item0, ..., #itemN-1], where #itemK is the number of times\n", - " item #K appears in the collection, and N is the number of distinct items.\n", - " max_num_collections: an integer, the maximum number of item collections that\n", - " may be selected (counting repetitions of the same collection).\n", - " ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is\n", - " the ideal ratio of item #K in the whole aggregated selection.\n", + " Args:\n", + " item_collections: a list of item collections. Each item collection is a list\n", + " of integers [#item0, ..., #itemN-1], where #itemK is the number of times\n", + " item #K appears in the collection, and N is the number of distinct items.\n", + " max_num_collections: an integer, the maximum number of item collections that\n", + " may be selected (counting repetitions of the same collection).\n", + " ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is\n", + " the ideal ratio of item #K in the whole aggregated selection.\n", "\n", - " Returns:\n", - " A pair (objective value, list of pairs (item collection, num_selections)),\n", - " where:\n", - " - \"objective value\" is the value of the internal objective function used\n", - " by the MIP Solver\n", - " - Each \"item collection\" is an element of the input item_collections\n", - " - and its associated \"num_selections\" is the number of times it was\n", - " selected.\n", - " \"\"\"\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " Returns:\n", + " A pair (objective value, list of pairs (item collection, num_selections)),\n", + " where:\n", + " - \"objective value\" is the value of the internal objective function used\n", + " by the MIP Solver\n", + " - Each \"item collection\" is an element of the input item_collections\n", + " - and its associated \"num_selections\" is the number of times it was\n", + " selected.\n", + " \"\"\"\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if not solver:\n", " return []\n", " n = len(ideal_item_ratios)\n", " num_distinct_collections = len(item_collections)\n", " max_num_items_per_collection = 0\n", " for template in item_collections:\n", - " max_num_items_per_collection = max(max_num_items_per_collection,\n", - " sum(template))\n", + " max_num_items_per_collection = max(max_num_items_per_collection, sum(template))\n", " upper_bound = max_num_items_per_collection * max_num_collections\n", "\n", " # num_selections_of_collection[i] is an IntVar that represents the number\n", " # of times that we will use collection #i in our global selection.\n", " num_selections_of_collection = [\n", - " solver.IntVar(0, max_num_collections, 's[%d]' % i)\n", + " solver.IntVar(0, max_num_collections, \"s[%d]\" % i)\n", " for i in range(num_distinct_collections)\n", " ]\n", "\n", @@ -201,19 +197,17 @@ " # aggregated over all selected collections. This is enforced with dedicated\n", " # constraints that bind them with the num_selections_of_collection vars.\n", " num_overall_item = [\n", - " solver.IntVar(0, upper_bound, 'num_overall_item[%d]' % i)\n", - " for i in range(n)\n", + " solver.IntVar(0, upper_bound, \"num_overall_item[%d]\" % i) for i in range(n)\n", " ]\n", " for i in range(n):\n", " ct = solver.Constraint(0.0, 0.0)\n", " ct.SetCoefficient(num_overall_item[i], -1)\n", " for j in range(num_distinct_collections):\n", - " ct.SetCoefficient(num_selections_of_collection[j],\n", - " item_collections[j][i])\n", + " ct.SetCoefficient(num_selections_of_collection[j], item_collections[j][i])\n", "\n", " # Maintain the num_all_item variable as the sum of all num_overall_item\n", " # variables.\n", - " num_all_items = solver.IntVar(0, upper_bound, 'num_all_items')\n", + " num_all_items = solver.IntVar(0, upper_bound, \"num_all_items\")\n", " solver.Add(solver.Sum(num_overall_item) == num_all_items)\n", "\n", " # Sets the total number of workers.\n", @@ -221,15 +215,16 @@ "\n", " # Objective variables.\n", " deviation_vars = [\n", - " solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i)\n", - " for i in range(n)\n", + " solver.NumVar(0, upper_bound, \"deviation_vars[%d]\" % i) for i in range(n)\n", " ]\n", " for i in range(n):\n", " deviation = deviation_vars[i]\n", - " solver.Add(deviation >= num_overall_item[i] -\n", - " ideal_item_ratios[i] * num_all_items)\n", - " solver.Add(deviation >= ideal_item_ratios[i] * num_all_items -\n", - " num_overall_item[i])\n", + " solver.Add(\n", + " deviation >= num_overall_item[i] - ideal_item_ratios[i] * num_all_items\n", + " )\n", + " solver.Add(\n", + " deviation >= ideal_item_ratios[i] * num_all_items - num_overall_item[i]\n", + " )\n", "\n", " solver.Maximize(num_all_items - solver.Sum(deviation_vars))\n", "\n", @@ -244,43 +239,57 @@ "def GetOptimalSchedule(demand):\n", " \"\"\"Computes the optimal schedule for the installation input.\n", "\n", - " Args:\n", - " demand: a list of \"appointment types\". Each \"appointment type\" is a triple\n", - " (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the\n", - " ideal percentage (in [0..100.0]) of that type of appointment among all\n", - " appointments scheduled.\n", + " Args:\n", + " demand: a list of \"appointment types\". Each \"appointment type\" is a triple\n", + " (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the\n", + " ideal percentage (in [0..100.0]) of that type of appointment among all\n", + " appointments scheduled.\n", "\n", - " Returns:\n", - " The same output type as EnumerateAllKnapsacksWithRepetition.\n", - " \"\"\"\n", + " Returns:\n", + " The same output type as EnumerateAllKnapsacksWithRepetition.\n", + " \"\"\"\n", " combinations = EnumerateAllKnapsacksWithRepetition(\n", - " [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value,\n", - " _LOAD_MAX.value)\n", - " print(('Found %d possible day schedules ' % len(combinations) +\n", - " '(i.e. combination of appointments filling up one worker\\'s day)'))\n", + " [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value\n", + " )\n", + " print(\n", + " (\n", + " \"Found %d possible day schedules \" % len(combinations)\n", + " + \"(i.e. combination of appointments filling up one worker's day)\"\n", + " )\n", + " )\n", "\n", " selection = AggregateItemCollectionsOptimally(\n", - " combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand])\n", + " combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand]\n", + " )\n", " output = []\n", " for i in range(len(selection)):\n", " if selection[i] != 0:\n", - " output.append((selection[i], [(combinations[i][t], demand[t][1])\n", - " for t in range(len(demand))\n", - " if combinations[i][t] != 0]))\n", + " output.append(\n", + " (\n", + " selection[i],\n", + " [\n", + " (combinations[i][t], demand[t][1])\n", + " for t in range(len(demand))\n", + " if combinations[i][t] != 0\n", + " ],\n", + " )\n", + " )\n", "\n", " return output\n", "\n", "\n", "def main(_):\n", - " demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)]\n", - " print('*** input problem ***')\n", - " print('Appointments: ')\n", + " demand = [(45.0, \"Type1\", 90), (30.0, \"Type2\", 120), (25.0, \"Type3\", 180)]\n", + " print(\"*** input problem ***\")\n", + " print(\"Appointments: \")\n", " for a in demand:\n", - " print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2]))\n", - " print('Commute time = %d' % _COMMUTE_TIME.value)\n", - " print('Acceptable duration of a work day = [%d..%d]' %\n", - " (_LOAD_MIN.value, _LOAD_MAX.value))\n", - " print('%d workers' % _NUM_WORKERS.value)\n", + " print(\" %.2f%% of %s : %d min\" % (a[0], a[1], a[2]))\n", + " print(\"Commute time = %d\" % _COMMUTE_TIME.value)\n", + " print(\n", + " \"Acceptable duration of a work day = [%d..%d]\"\n", + " % (_LOAD_MIN.value, _LOAD_MAX.value)\n", + " )\n", + " print(\"%d workers\" % _NUM_WORKERS.value)\n", " selection = GetOptimalSchedule(demand)\n", " print()\n", " installed = 0\n", @@ -288,27 +297,27 @@ " for a in demand:\n", " installed_per_type[a[1]] = 0\n", "\n", - " print('*** output solution ***')\n", + " print(\"*** output solution ***\")\n", " for template in selection:\n", " num_instances = template[0]\n", - " print('%d schedules with ' % num_instances)\n", + " print(\"%d schedules with \" % num_instances)\n", " for t in template[1]:\n", " mult = t[0]\n", - " print(' %d installation of type %s' % (mult, t[1]))\n", + " print(\" %d installation of type %s\" % (mult, t[1]))\n", " installed += num_instances * mult\n", " installed_per_type[t[1]] += num_instances * mult\n", "\n", " print()\n", - " print('%d installations planned' % installed)\n", + " print(\"%d installations planned\" % installed)\n", " for a in demand:\n", " name = a[1]\n", " per_type = installed_per_type[name]\n", " if installed != 0:\n", " print(\n", - " f' {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned'\n", + " f\" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned\"\n", " )\n", " else:\n", - " print(f' {per_type} installations of type {name} planned')\n", + " print(f\" {per_type} installations of type {name} planned\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/assignment_with_constraints_sat.ipynb b/examples/notebook/examples/assignment_with_constraints_sat.ipynb index 2490af8d1f..341ebe2086 100644 --- a/examples/notebook/examples/assignment_with_constraints_sat.ipynb +++ b/examples/notebook/examples/assignment_with_constraints_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solve an assignment problem with combination constraints on workers.\n" ] }, @@ -89,19 +90,27 @@ "def solve_assignment():\n", " \"\"\"Solve the assignment problem.\"\"\"\n", " # Data.\n", - " cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48, 101],\n", - " [125, 95, 90, 105, 59, 120], [45, 110, 95, 115, 104, 83],\n", - " [60, 105, 80, 75, 59, 62], [45, 65, 110, 95, 47, 31],\n", - " [38, 51, 107, 41, 69, 99], [47, 85, 57, 71, 92, 77],\n", - " [39, 63, 97, 49, 118, 56], [47, 101, 71, 60, 88, 109],\n", - " [17, 39, 103, 64, 61, 92], [101, 45, 83, 59, 92, 27]]\n", + " cost = [\n", + " [90, 76, 75, 70, 50, 74],\n", + " [35, 85, 55, 65, 48, 101],\n", + " [125, 95, 90, 105, 59, 120],\n", + " [45, 110, 95, 115, 104, 83],\n", + " [60, 105, 80, 75, 59, 62],\n", + " [45, 65, 110, 95, 47, 31],\n", + " [38, 51, 107, 41, 69, 99],\n", + " [47, 85, 57, 71, 92, 77],\n", + " [39, 63, 97, 49, 118, 56],\n", + " [47, 101, 71, 60, 88, 109],\n", + " [17, 39, 103, 64, 61, 92],\n", + " [101, 45, 83, 59, 92, 27],\n", + " ]\n", "\n", " group1 = [\n", " [0, 0, 1, 1], # Workers 2, 3\n", " [0, 1, 0, 1], # Workers 1, 3\n", " [0, 1, 1, 0], # Workers 1, 2\n", " [1, 1, 0, 0], # Workers 0, 1\n", - " [1, 0, 1, 0]\n", + " [1, 0, 1, 0],\n", " ] # Workers 0, 2\n", "\n", " group2 = [\n", @@ -109,7 +118,7 @@ " [0, 1, 0, 1], # Workers 5, 7\n", " [0, 1, 1, 0], # Workers 5, 6\n", " [1, 1, 0, 0], # Workers 4, 5\n", - " [1, 0, 0, 1]\n", + " [1, 0, 0, 1],\n", " ] # Workers 4, 7\n", "\n", " group3 = [\n", @@ -117,7 +126,7 @@ " [0, 1, 0, 1], # Workers 9, 11\n", " [0, 1, 1, 0], # Workers 9, 10\n", " [1, 0, 1, 0], # Workers 8, 10\n", - " [1, 0, 0, 1]\n", + " [1, 0, 0, 1],\n", " ] # Workers 8, 11\n", "\n", " sizes = [10, 7, 3, 12, 15, 4, 11, 5]\n", @@ -131,10 +140,10 @@ "\n", " model = cp_model.CpModel()\n", " # Variables\n", - " selected = [[model.NewBoolVar('x[%i,%i]' % (i, j))\n", - " for j in all_tasks]\n", - " for i in all_workers]\n", - " works = [model.NewBoolVar('works[%i]' % i) for i in all_workers]\n", + " selected = [\n", + " [model.NewBoolVar(\"x[%i,%i]\" % (i, j)) for j in all_tasks] for i in all_workers\n", + " ]\n", + " works = [model.NewBoolVar(\"works[%i]\" % i) for i in all_workers]\n", "\n", " # Constraints\n", "\n", @@ -148,47 +157,43 @@ "\n", " # Total task size for each worker is at most total_size_max\n", " for i in all_workers:\n", - " model.Add(\n", - " sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)\n", + " model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)\n", "\n", " # Group constraints.\n", - " model.AddAllowedAssignments([works[0], works[1], works[2], works[3]],\n", - " group1)\n", - " model.AddAllowedAssignments([works[4], works[5], works[6], works[7]],\n", - " group2)\n", - " model.AddAllowedAssignments([works[8], works[9], works[10], works[11]],\n", - " group3)\n", + " model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1)\n", + " model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2)\n", + " model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3)\n", "\n", " # Objective\n", " model.Minimize(\n", - " sum(selected[i][j] * cost[i][j]\n", - " for j in all_tasks\n", - " for i in all_workers))\n", + " sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers)\n", + " )\n", "\n", " # Solve and output solution.\n", " solver = cp_model.CpSolver()\n", " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('Total cost = %i' % solver.ObjectiveValue())\n", + " print(\"Total cost = %i\" % solver.ObjectiveValue())\n", " print()\n", " for i in all_workers:\n", " for j in all_tasks:\n", " if solver.BooleanValue(selected[i][j]):\n", - " print('Worker ', i, ' assigned to task ', j, ' Cost = ',\n", - " cost[i][j])\n", + " print(\n", + " \"Worker \", i, \" assigned to task \", j, \" Cost = \", cost[i][j]\n", + " )\n", "\n", " print()\n", "\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " solve_assignment()\n", "\n", "\n", diff --git a/examples/notebook/examples/balance_group_sat.ipynb b/examples/notebook/examples/balance_group_sat.ipynb index db8e1b4c49..3d2cda0335 100644 --- a/examples/notebook/examples/balance_group_sat.ipynb +++ b/examples/notebook/examples/balance_group_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "We are trying to group items in equal sized groups.\n", "\n", "Each item has a color and a value. We want the sum of values of each group to\n", @@ -105,10 +106,10 @@ " self.__item_in_group = item_in_group\n", "\n", " def on_solution_callback(self):\n", - " print('Solution %i' % self.__solution_count)\n", + " print(\"Solution %i\" % self.__solution_count)\n", " self.__solution_count += 1\n", "\n", - " print(' objective value = %i' % self.ObjectiveValue())\n", + " print(\" objective value = %i\" % self.ObjectiveValue())\n", " groups = {}\n", " sums = {}\n", " for g in self.__all_groups:\n", @@ -121,19 +122,19 @@ "\n", " for g in self.__all_groups:\n", " group = groups[g]\n", - " print('group %i: sum = %0.2f [' % (g, sums[g]), end='')\n", + " print(\"group %i: sum = %0.2f [\" % (g, sums[g]), end=\"\")\n", " for item in group:\n", " value = self.__values[item]\n", " color = self.__colors[item]\n", - " print(' (%i, %i, %i)' % (item, value, color), end='')\n", - " print(']')\n", + " print(\" (%i, %i, %i)\" % (item, value, color), end=\"\")\n", + " print(\"]\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " \"\"\"Solves a group balancing problem.\"\"\"\n", "\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " # Data.\n", " num_groups = 10\n", " num_items = 100\n", @@ -162,9 +163,11 @@ " if colors[i] == c:\n", " items_per_color[c].append(i)\n", "\n", - " print('Model has %i items, %i groups, and %i colors' %\n", - " (num_items, num_groups, num_colors))\n", - " print(' average sum per group = %i' % average_sum_per_group)\n", + " print(\n", + " \"Model has %i items, %i groups, and %i colors\"\n", + " % (num_items, num_groups, num_colors)\n", + " )\n", + " print(\" average sum per group = %i\" % average_sum_per_group)\n", "\n", " # Model.\n", "\n", @@ -173,43 +176,42 @@ " item_in_group = {}\n", " for i in all_items:\n", " for g in all_groups:\n", - " item_in_group[(i, g)] = model.NewBoolVar('item %d in group %d' %\n", - " (i, g))\n", + " item_in_group[(i, g)] = model.NewBoolVar(\"item %d in group %d\" % (i, g))\n", "\n", " # Each group must have the same size.\n", " for g in all_groups:\n", - " model.Add(\n", - " sum(item_in_group[(i, g)]\n", - " for i in all_items) == num_items_per_group)\n", + " model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)\n", "\n", " # One item must belong to exactly one group.\n", " for i in all_items:\n", " model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1)\n", "\n", " # The deviation of the sum of each items in a group against the average.\n", - " e = model.NewIntVar(0, 550, 'epsilon')\n", + " e = model.NewIntVar(0, 550, \"epsilon\")\n", "\n", " # Constrain the sum of values in one group around the average sum per group.\n", " for g in all_groups:\n", " model.Add(\n", - " sum(item_in_group[(i, g)] * values[i]\n", - " for i in all_items) <= average_sum_per_group + e)\n", + " sum(item_in_group[(i, g)] * values[i] for i in all_items)\n", + " <= average_sum_per_group + e\n", + " )\n", " model.Add(\n", - " sum(item_in_group[(i, g)] * values[i]\n", - " for i in all_items) >= average_sum_per_group - e)\n", + " sum(item_in_group[(i, g)] * values[i] for i in all_items)\n", + " >= average_sum_per_group - e\n", + " )\n", "\n", " # color_in_group variables.\n", " color_in_group = {}\n", " for g in all_groups:\n", " for c in all_colors:\n", " color_in_group[(c, g)] = model.NewBoolVar(\n", - " 'color %d is in group %d' % (c, g))\n", + " \"color %d is in group %d\" % (c, g)\n", + " )\n", "\n", " # Item is in a group implies its color is in that group.\n", " for i in all_items:\n", " for g in all_groups:\n", - " model.AddImplication(item_in_group[(i, g)],\n", - " color_in_group[(colors[i], g)])\n", + " model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)])\n", "\n", " # If a color is in a group, it must contains at least\n", " # min_items_of_same_color_per_group items from that color.\n", @@ -217,8 +219,9 @@ " for g in all_groups:\n", " literal = color_in_group[(c, g)]\n", " model.Add(\n", - " sum(item_in_group[(i, g)] for i in items_per_color[c]) >=\n", - " min_items_of_same_color_per_group).OnlyEnforceIf(literal)\n", + " sum(item_in_group[(i, g)] for i in items_per_color[c])\n", + " >= min_items_of_same_color_per_group\n", + " ).OnlyEnforceIf(literal)\n", "\n", " # Compute the maximum number of colors in a group.\n", " max_color = num_items_per_group // min_items_of_same_color_per_group\n", @@ -226,8 +229,7 @@ " # Redundant constraint, it helps with solving time.\n", " if max_color < num_colors:\n", " for g in all_groups:\n", - " model.Add(\n", - " sum(color_in_group[(c, g)] for c in all_colors) <= max_color)\n", + " model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)\n", "\n", " # Minimize epsilon\n", " model.Minimize(e)\n", @@ -235,18 +237,19 @@ " solver = cp_model.CpSolver()\n", " # solver.parameters.log_search_progress = True\n", " solver.parameters.num_workers = 16\n", - " solution_printer = SolutionPrinter(values, colors, all_groups, all_items,\n", - " item_in_group)\n", + " solution_printer = SolutionPrinter(\n", + " values, colors, all_groups, all_items, item_in_group\n", + " )\n", " status = solver.Solve(model, solution_printer)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('Optimal epsilon: %i' % solver.ObjectiveValue())\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Optimal epsilon: %i\" % solver.ObjectiveValue())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", " else:\n", - " print('No solution found')\n", + " print(\"No solution found\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/bus_driver_scheduling_sat.ipynb b/examples/notebook/examples/bus_driver_scheduling_sat.ipynb index 53e475dc11..5f05a9503e 100644 --- a/examples/notebook/examples/bus_driver_scheduling_sat.ipynb +++ b/examples/notebook/examples/bus_driver_scheduling_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This model implements a bus driver scheduling problem.\n", "\n", "Constraints:\n", @@ -99,13 +100,16 @@ "from ortools.sat.python import cp_model\n", "\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", "_PARAMS = flags.DEFINE_string(\n", - " 'params',\n", - " 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:30',\n", - " 'Sat solver parameters.')\n", - "_INSTANCE = flags.DEFINE_integer('instance', 0,\n", - " 'Instance to select (0, 1, 2, 3).', 0, 3)\n", + " \"params\",\n", + " \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:30\",\n", + " \"Sat solver parameters.\",\n", + ")\n", + "_INSTANCE = flags.DEFINE_integer(\n", + " \"instance\", 0, \"Instance to select (0, 1, 2, 3).\", 0, 3\n", + ")\n", "\n", "SAMPLE_SHIFTS_TINY = [\n", " #\n", @@ -117,33 +121,33 @@ " # - shift end minute\n", " # - shift duration in minutes\n", " #\n", - " [1, '08:00', '09:05', 480, 545, 65],\n", - " [2, '08:00', '08:35', 480, 515, 35],\n", - " [3, '08:11', '09:41', 491, 581, 90],\n", - " [4, '08:28', '08:50', 508, 530, 22],\n", - " [5, '08:35', '08:45', 515, 525, 10],\n", - " [6, '08:40', '08:50', 520, 530, 10],\n", - " [7, '09:03', '10:28', 543, 628, 85],\n", - " [8, '09:23', '09:49', 563, 589, 26],\n", - " [9, '09:30', '09:40', 570, 580, 10],\n", - " [10, '09:57', '10:20', 597, 620, 23],\n", - " [11, '10:09', '11:03', 609, 663, 54],\n", - " [12, '10:20', '10:30', 620, 630, 10],\n", - " [13, '11:00', '11:10', 660, 670, 10],\n", - " [14, '11:45', '12:24', 705, 744, 39],\n", - " [15, '12:18', '13:00', 738, 780, 42],\n", - " [16, '13:18', '14:44', 798, 884, 86],\n", - " [17, '13:53', '14:49', 833, 889, 56],\n", - " [18, '14:03', '14:50', 843, 890, 47],\n", - " [19, '14:28', '15:15', 868, 915, 47],\n", - " [20, '14:30', '15:41', 870, 941, 71],\n", - " [21, '14:48', '15:35', 888, 935, 47],\n", - " [22, '15:03', '15:50', 903, 950, 47],\n", - " [23, '15:28', '16:54', 928, 1014, 86],\n", - " [24, '15:38', '16:25', 938, 985, 47],\n", - " [25, '15:40', '15:56', 940, 956, 16],\n", - " [26, '15:58', '16:45', 958, 1005, 47],\n", - " [27, '16:04', '17:30', 964, 1050, 86],\n", + " [1, \"08:00\", \"09:05\", 480, 545, 65],\n", + " [2, \"08:00\", \"08:35\", 480, 515, 35],\n", + " [3, \"08:11\", \"09:41\", 491, 581, 90],\n", + " [4, \"08:28\", \"08:50\", 508, 530, 22],\n", + " [5, \"08:35\", \"08:45\", 515, 525, 10],\n", + " [6, \"08:40\", \"08:50\", 520, 530, 10],\n", + " [7, \"09:03\", \"10:28\", 543, 628, 85],\n", + " [8, \"09:23\", \"09:49\", 563, 589, 26],\n", + " [9, \"09:30\", \"09:40\", 570, 580, 10],\n", + " [10, \"09:57\", \"10:20\", 597, 620, 23],\n", + " [11, \"10:09\", \"11:03\", 609, 663, 54],\n", + " [12, \"10:20\", \"10:30\", 620, 630, 10],\n", + " [13, \"11:00\", \"11:10\", 660, 670, 10],\n", + " [14, \"11:45\", \"12:24\", 705, 744, 39],\n", + " [15, \"12:18\", \"13:00\", 738, 780, 42],\n", + " [16, \"13:18\", \"14:44\", 798, 884, 86],\n", + " [17, \"13:53\", \"14:49\", 833, 889, 56],\n", + " [18, \"14:03\", \"14:50\", 843, 890, 47],\n", + " [19, \"14:28\", \"15:15\", 868, 915, 47],\n", + " [20, \"14:30\", \"15:41\", 870, 941, 71],\n", + " [21, \"14:48\", \"15:35\", 888, 935, 47],\n", + " [22, \"15:03\", \"15:50\", 903, 950, 47],\n", + " [23, \"15:28\", \"16:54\", 928, 1014, 86],\n", + " [24, \"15:38\", \"16:25\", 938, 985, 47],\n", + " [25, \"15:40\", \"15:56\", 940, 956, 16],\n", + " [26, \"15:58\", \"16:45\", 958, 1005, 47],\n", + " [27, \"16:04\", \"17:30\", 964, 1050, 86],\n", "] # yapf:disable\n", "\n", "SAMPLE_SHIFTS_SMALL = [\n", @@ -156,1643 +160,1642 @@ " # - shift end minute\n", " # - shift duration in minutes\n", " #\n", - " [0, '05:18', '06:00', 318, 360, 42],\n", - " [1, '05:26', '06:08', 326, 368, 42],\n", - " [2, '05:40', '05:56', 340, 356, 16],\n", - " [3, '06:06', '06:51', 366, 411, 45],\n", - " [4, '06:40', '07:52', 400, 472, 72],\n", - " [5, '06:42', '07:13', 402, 433, 31],\n", - " [6, '06:48', '08:15', 408, 495, 87],\n", - " [7, '06:59', '08:07', 419, 487, 68],\n", - " [8, '07:20', '07:36', 440, 456, 16],\n", - " [9, '07:35', '08:22', 455, 502, 47],\n", - " [10, '07:50', '08:55', 470, 535, 65],\n", - " [11, '08:00', '09:05', 480, 545, 65],\n", - " [12, '08:00', '08:35', 480, 515, 35],\n", - " [13, '08:11', '09:41', 491, 581, 90],\n", - " [14, '08:28', '08:50', 508, 530, 22],\n", - " [15, '08:35', '08:45', 515, 525, 10],\n", - " [16, '08:40', '08:50', 520, 530, 10],\n", - " [17, '09:03', '10:28', 543, 628, 85],\n", - " [18, '09:23', '09:49', 563, 589, 26],\n", - " [19, '09:30', '09:40', 570, 580, 10],\n", - " [20, '09:57', '10:20', 597, 620, 23],\n", - " [21, '10:09', '11:03', 609, 663, 54],\n", - " [22, '10:20', '10:30', 620, 630, 10],\n", - " [23, '11:00', '11:10', 660, 670, 10],\n", - " [24, '11:45', '12:24', 705, 744, 39],\n", - " [25, '12:18', '13:00', 738, 780, 42],\n", - " [26, '13:18', '14:44', 798, 884, 86],\n", - " [27, '13:53', '14:49', 833, 889, 56],\n", - " [28, '14:03', '14:50', 843, 890, 47],\n", - " [29, '14:28', '15:15', 868, 915, 47],\n", - " [30, '14:30', '15:41', 870, 941, 71],\n", - " [31, '14:48', '15:35', 888, 935, 47],\n", - " [32, '15:03', '15:50', 903, 950, 47],\n", - " [33, '15:28', '16:54', 928, 1014, 86],\n", - " [34, '15:38', '16:25', 938, 985, 47],\n", - " [35, '15:40', '15:56', 940, 956, 16],\n", - " [36, '15:58', '16:45', 958, 1005, 47],\n", - " [37, '16:04', '17:30', 964, 1050, 86],\n", - " [38, '16:28', '17:15', 988, 1035, 47],\n", - " [39, '16:36', '17:21', 996, 1041, 45],\n", - " [40, '16:50', '17:00', 1010, 1020, 10],\n", - " [41, '16:54', '18:20', 1014, 1100, 86],\n", - " [42, '17:01', '17:13', 1021, 1033, 12],\n", - " [43, '17:19', '18:31', 1039, 1111, 72],\n", - " [44, '17:23', '18:10', 1043, 1090, 47],\n", - " [45, '17:34', '18:15', 1054, 1095, 41],\n", - " [46, '18:04', '19:29', 1084, 1169, 85],\n", - " [47, '18:34', '19:58', 1114, 1198, 84],\n", - " [48, '19:56', '20:34', 1196, 1234, 38],\n", - " [49, '20:05', '20:48', 1205, 1248, 43]\n", + " [0, \"05:18\", \"06:00\", 318, 360, 42],\n", + " [1, \"05:26\", \"06:08\", 326, 368, 42],\n", + " [2, \"05:40\", \"05:56\", 340, 356, 16],\n", + " [3, \"06:06\", \"06:51\", 366, 411, 45],\n", + " [4, \"06:40\", \"07:52\", 400, 472, 72],\n", + " [5, \"06:42\", \"07:13\", 402, 433, 31],\n", + " [6, \"06:48\", \"08:15\", 408, 495, 87],\n", + " [7, \"06:59\", \"08:07\", 419, 487, 68],\n", + " [8, \"07:20\", \"07:36\", 440, 456, 16],\n", + " [9, \"07:35\", \"08:22\", 455, 502, 47],\n", + " [10, \"07:50\", \"08:55\", 470, 535, 65],\n", + " [11, \"08:00\", \"09:05\", 480, 545, 65],\n", + " [12, \"08:00\", \"08:35\", 480, 515, 35],\n", + " [13, \"08:11\", \"09:41\", 491, 581, 90],\n", + " [14, \"08:28\", \"08:50\", 508, 530, 22],\n", + " [15, \"08:35\", \"08:45\", 515, 525, 10],\n", + " [16, \"08:40\", \"08:50\", 520, 530, 10],\n", + " [17, \"09:03\", \"10:28\", 543, 628, 85],\n", + " [18, \"09:23\", \"09:49\", 563, 589, 26],\n", + " [19, \"09:30\", \"09:40\", 570, 580, 10],\n", + " [20, \"09:57\", \"10:20\", 597, 620, 23],\n", + " [21, \"10:09\", \"11:03\", 609, 663, 54],\n", + " [22, \"10:20\", \"10:30\", 620, 630, 10],\n", + " [23, \"11:00\", \"11:10\", 660, 670, 10],\n", + " [24, \"11:45\", \"12:24\", 705, 744, 39],\n", + " [25, \"12:18\", \"13:00\", 738, 780, 42],\n", + " [26, \"13:18\", \"14:44\", 798, 884, 86],\n", + " [27, \"13:53\", \"14:49\", 833, 889, 56],\n", + " [28, \"14:03\", \"14:50\", 843, 890, 47],\n", + " [29, \"14:28\", \"15:15\", 868, 915, 47],\n", + " [30, \"14:30\", \"15:41\", 870, 941, 71],\n", + " [31, \"14:48\", \"15:35\", 888, 935, 47],\n", + " [32, \"15:03\", \"15:50\", 903, 950, 47],\n", + " [33, \"15:28\", \"16:54\", 928, 1014, 86],\n", + " [34, \"15:38\", \"16:25\", 938, 985, 47],\n", + " [35, \"15:40\", \"15:56\", 940, 956, 16],\n", + " [36, \"15:58\", \"16:45\", 958, 1005, 47],\n", + " [37, \"16:04\", \"17:30\", 964, 1050, 86],\n", + " [38, \"16:28\", \"17:15\", 988, 1035, 47],\n", + " [39, \"16:36\", \"17:21\", 996, 1041, 45],\n", + " [40, \"16:50\", \"17:00\", 1010, 1020, 10],\n", + " [41, \"16:54\", \"18:20\", 1014, 1100, 86],\n", + " [42, \"17:01\", \"17:13\", 1021, 1033, 12],\n", + " [43, \"17:19\", \"18:31\", 1039, 1111, 72],\n", + " [44, \"17:23\", \"18:10\", 1043, 1090, 47],\n", + " [45, \"17:34\", \"18:15\", 1054, 1095, 41],\n", + " [46, \"18:04\", \"19:29\", 1084, 1169, 85],\n", + " [47, \"18:34\", \"19:58\", 1114, 1198, 84],\n", + " [48, \"19:56\", \"20:34\", 1196, 1234, 38],\n", + " [49, \"20:05\", \"20:48\", 1205, 1248, 43],\n", "] # yapf:disable\n", "\n", "SAMPLE_SHIFTS_MEDIUM = [\n", - " [0, '04:30', '04:53', 270, 293, 23],\n", - " [1, '04:46', '04:56', 286, 296, 10],\n", - " [2, '04:52', '05:56', 292, 356, 64],\n", - " [3, '04:53', '05:23', 293, 323, 30],\n", - " [4, '05:07', '05:44', 307, 344, 37],\n", - " [5, '05:10', '06:06', 310, 366, 56],\n", - " [6, '05:18', '06:03', 318, 363, 45],\n", - " [7, '05:30', '05:40', 330, 340, 10],\n", - " [8, '05:30', '05:40', 330, 340, 10],\n", - " [9, '05:33', '06:15', 333, 375, 42],\n", - " [10, '05:40', '05:50', 340, 350, 10],\n", - " [11, '05:43', '06:08', 343, 368, 25],\n", - " [12, '05:54', '07:20', 354, 440, 86],\n", - " [13, '06:04', '06:37', 364, 397, 33],\n", - " [14, '06:13', '06:58', 373, 418, 45],\n", - " [15, '06:14', '07:40', 374, 460, 86],\n", - " [16, '06:15', '07:15', 375, 435, 60],\n", - " [17, '06:16', '06:26', 376, 386, 10],\n", - " [18, '06:17', '06:34', 377, 394, 17],\n", - " [19, '06:20', '06:36', 380, 396, 16],\n", - " [20, '06:22', '07:06', 382, 426, 44],\n", - " [21, '06:24', '07:50', 384, 470, 86],\n", - " [22, '06:27', '06:44', 387, 404, 17],\n", - " [23, '06:30', '06:40', 390, 400, 10],\n", - " [24, '06:31', '06:43', 391, 403, 12],\n", - " [25, '06:33', '07:53', 393, 473, 80],\n", - " [26, '06:34', '07:09', 394, 429, 35],\n", - " [27, '06:40', '06:56', 400, 416, 16],\n", - " [28, '06:44', '07:17', 404, 437, 33],\n", - " [29, '06:46', '06:58', 406, 418, 12],\n", - " [30, '06:49', '07:43', 409, 463, 54],\n", - " [31, '06:50', '07:05', 410, 425, 15],\n", - " [32, '06:52', '07:36', 412, 456, 44],\n", - " [33, '06:54', '07:27', 414, 447, 33],\n", - " [34, '06:56', '08:23', 416, 503, 87],\n", - " [35, '07:04', '07:44', 424, 464, 40],\n", - " [36, '07:11', '08:36', 431, 516, 85],\n", - " [37, '07:17', '07:35', 437, 455, 18],\n", - " [38, '07:22', '08:06', 442, 486, 44],\n", - " [39, '07:27', '08:15', 447, 495, 48],\n", - " [40, '07:35', '07:45', 455, 465, 10],\n", - " [41, '07:43', '08:08', 463, 488, 25],\n", - " [42, '07:50', '08:37', 470, 517, 47],\n", - " [43, '07:58', '08:45', 478, 525, 47],\n", - " [44, '08:00', '08:35', 480, 515, 35],\n", - " [45, '08:06', '08:51', 486, 531, 45],\n", - " [46, '08:10', '08:45', 490, 525, 35],\n", - " [47, '08:15', '08:30', 495, 510, 15],\n", - " [48, '08:16', '09:00', 496, 540, 44],\n", - " [49, '08:18', '09:16', 498, 556, 58],\n", - " [50, '08:20', '08:36', 500, 516, 16],\n", - " [51, '08:27', '09:07', 507, 547, 40],\n", - " [52, '08:30', '08:45', 510, 525, 15],\n", - " [53, '08:35', '09:15', 515, 555, 40],\n", - " [54, '08:46', '09:30', 526, 570, 44],\n", - " [55, '08:51', '09:17', 531, 557, 26],\n", - " [56, '08:55', '09:15', 535, 555, 20],\n", - " [57, '08:58', '09:38', 538, 578, 40],\n", - " [58, '09:00', '09:35', 540, 575, 35],\n", - " [59, '09:00', '09:16', 540, 556, 16],\n", - " [60, '09:20', '09:36', 560, 576, 16],\n", - " [61, '09:31', '09:43', 571, 583, 12],\n", - " [62, '09:33', '10:15', 573, 615, 42],\n", - " [63, '09:54', '10:05', 594, 605, 11],\n", - " [64, '10:11', '10:38', 611, 638, 27],\n", - " [65, '10:18', '11:00', 618, 660, 42],\n", - " [66, '10:21', '10:47', 621, 647, 26],\n", - " [67, '10:25', '11:04', 625, 664, 39],\n", - " [68, '10:26', '11:08', 626, 668, 42],\n", - " [69, '10:44', '12:11', 644, 731, 87],\n", - " [70, '11:00', '11:16', 660, 676, 16],\n", - " [71, '11:15', '11:54', 675, 714, 39],\n", - " [72, '11:16', '11:28', 676, 688, 12],\n", - " [73, '11:20', '11:30', 680, 690, 10],\n", - " [74, '11:21', '11:47', 681, 707, 26],\n", - " [75, '11:25', '12:04', 685, 724, 39],\n", - " [76, '11:34', '11:45', 694, 705, 11],\n", - " [77, '11:35', '12:14', 695, 734, 39],\n", - " [78, '11:41', '12:23', 701, 743, 42],\n", - " [79, '11:44', '12:35', 704, 755, 51],\n", - " [80, '11:46', '11:58', 706, 718, 12],\n", - " [81, '12:00', '12:10', 720, 730, 10],\n", - " [82, '12:04', '12:15', 724, 735, 11],\n", - " [83, '12:04', '13:04', 724, 784, 60],\n", - " [84, '12:11', '12:38', 731, 758, 27],\n", - " [85, '12:15', '12:54', 735, 774, 39],\n", - " [86, '12:25', '13:10', 745, 790, 45],\n", - " [87, '12:30', '12:40', 750, 760, 10],\n", - " [88, '12:34', '13:58', 754, 838, 84],\n", - " [89, '12:38', '13:25', 758, 805, 47],\n", - " [90, '12:48', '13:35', 768, 815, 47],\n", - " [91, '13:00', '13:16', 780, 796, 16],\n", - " [92, '13:05', '13:44', 785, 824, 39],\n", - " [93, '13:08', '13:55', 788, 835, 47],\n", - " [94, '13:14', '14:38', 794, 878, 84],\n", - " [95, '13:23', '13:49', 803, 829, 26],\n", - " [96, '13:25', '14:04', 805, 844, 39],\n", - " [97, '13:28', '14:54', 808, 894, 86],\n", - " [98, '13:31', '13:43', 811, 823, 12],\n", - " [99, '13:34', '14:58', 814, 898, 84],\n", - " [100, '13:38', '14:25', 818, 865, 47],\n", - " [101, '13:38', '15:04', 818, 904, 86],\n", - " [102, '13:39', '14:33', 819, 873, 54],\n", - " [103, '13:40', '13:50', 820, 830, 10],\n", - " [104, '13:43', '14:10', 823, 850, 27],\n", - " [105, '13:48', '14:35', 828, 875, 47],\n", - " [106, '13:48', '14:35', 828, 875, 47],\n", - " [107, '13:53', '14:40', 833, 880, 47],\n", - " [108, '13:58', '15:24', 838, 924, 86],\n", - " [109, '13:58', '14:25', 838, 865, 27],\n", - " [110, '14:00', '14:16', 840, 856, 16],\n", - " [111, '14:13', '15:00', 853, 900, 47],\n", - " [112, '14:20', '15:31', 860, 931, 71],\n", - " [113, '14:25', '15:02', 865, 902, 37],\n", - " [114, '14:34', '14:45', 874, 885, 11],\n", - " [115, '14:40', '15:51', 880, 951, 71],\n", - " [116, '14:40', '14:56', 880, 896, 16],\n", - " [117, '14:46', '14:58', 886, 898, 12],\n", - " [118, '14:49', '15:43', 889, 943, 54],\n", - " [119, '14:52', '15:21', 892, 921, 29],\n", - " [120, '14:58', '16:24', 898, 984, 86],\n", - " [121, '14:59', '15:53', 899, 953, 54],\n", - " [122, '15:00', '15:10', 900, 910, 10],\n", - " [123, '15:00', '15:35', 900, 935, 35],\n", - " [124, '15:08', '15:45', 908, 945, 37],\n", - " [125, '15:12', '15:36', 912, 936, 24],\n", - " [126, '15:18', '16:05', 918, 965, 47],\n", - " [127, '15:24', '16:05', 924, 965, 41],\n", - " [128, '15:31', '15:43', 931, 943, 12],\n", - " [129, '15:35', '15:54', 935, 954, 19],\n", - " [130, '15:36', '16:21', 936, 981, 45],\n", - " [131, '15:39', '16:33', 939, 993, 54],\n", - " [132, '15:48', '16:35', 948, 995, 47],\n", - " [133, '15:50', '17:01', 950, 1021, 71],\n", - " [134, '16:03', '16:50', 963, 1010, 47],\n", - " [135, '16:18', '17:44', 978, 1064, 86],\n", - " [136, '16:24', '17:05', 984, 1025, 41],\n", - " [137, '16:28', '17:15', 988, 1035, 47],\n", - " [138, '16:34', '17:15', 994, 1035, 41],\n", - " [139, '16:38', '17:25', 998, 1045, 47],\n", - " [140, '16:40', '16:56', 1000, 1016, 16],\n", - " [141, '16:45', '17:04', 1005, 1024, 19],\n", - " [142, '16:52', '17:36', 1012, 1056, 44],\n", - " [143, '16:58', '17:45', 1018, 1065, 47],\n", - " [144, '17:04', '18:30', 1024, 1110, 86],\n", - " [145, '17:04', '17:45', 1024, 1065, 41],\n", - " [146, '17:09', '18:03', 1029, 1083, 54],\n", - " [147, '17:18', '18:44', 1038, 1124, 86],\n", - " [148, '17:28', '18:15', 1048, 1095, 47],\n", - " [149, '17:29', '18:41', 1049, 1121, 72],\n", - " [150, '17:36', '18:21', 1056, 1101, 45],\n", - " [151, '17:38', '18:25', 1058, 1105, 47],\n", - " [152, '17:40', '17:56', 1060, 1076, 16],\n", - " [153, '17:45', '18:04', 1065, 1084, 19],\n", - " [154, '17:46', '17:58', 1066, 1078, 12],\n", - " [155, '17:48', '18:35', 1068, 1115, 47],\n", - " [156, '17:49', '18:43', 1069, 1123, 54],\n", - " [157, '17:55', '18:14', 1075, 1094, 19],\n", - " [158, '17:58', '18:45', 1078, 1125, 47],\n", - " [159, '18:00', '19:11', 1080, 1151, 71],\n", - " [160, '18:04', '18:45', 1084, 1125, 41],\n", - " [161, '18:09', '19:03', 1089, 1143, 54],\n", - " [162, '18:13', '19:00', 1093, 1140, 47],\n", - " [163, '18:13', '18:40', 1093, 1120, 27],\n", - " [164, '18:19', '19:13', 1099, 1153, 54],\n", - " [165, '18:28', '19:25', 1108, 1165, 57],\n", - " [166, '18:48', '19:28', 1128, 1168, 40],\n", - " [167, '19:03', '19:45', 1143, 1185, 42],\n", - " [168, '19:20', '19:36', 1160, 1176, 16],\n", - " [169, '19:21', '19:31', 1161, 1171, 10],\n", - " [170, '19:25', '20:04', 1165, 1204, 39],\n", - " [171, '19:26', '20:08', 1166, 1208, 42],\n", - " [172, '19:30', '19:40', 1170, 1180, 10],\n", - " [173, '19:44', '20:33', 1184, 1233, 49],\n", - " [174, '19:48', '21:09', 1188, 1269, 81],\n", - " [175, '19:53', '21:02', 1193, 1262, 69],\n", - " [176, '20:04', '20:29', 1204, 1229, 25],\n", - " [177, '20:17', '21:03', 1217, 1263, 46],\n", - " [178, '20:20', '20:57', 1220, 1257, 37],\n", - " [179, '20:29', '21:18', 1229, 1278, 49],\n", - " [180, '20:35', '21:54', 1235, 1314, 79],\n", - " [181, '20:40', '20:50', 1240, 1250, 10],\n", - " [182, '20:47', '21:42', 1247, 1302, 55],\n", - " [183, '21:00', '21:10', 1260, 1270, 10],\n", - " [184, '21:07', '21:44', 1267, 1304, 37],\n", - " [185, '21:14', '22:03', 1274, 1323, 49],\n", - " [186, '21:39', '21:55', 1299, 1315, 16],\n", - " [187, '21:40', '22:17', 1300, 1337, 37],\n", - " [188, '21:40', '21:50', 1300, 1310, 10],\n", - " [189, '21:48', '22:03', 1308, 1323, 15],\n", - " [190, '22:17', '23:03', 1337, 1383, 46],\n", - " [191, '22:43', '23:08', 1363, 1388, 25],\n", - " [192, '23:35', '01:05', 1415, 1505, 90],\n", - " [193, '23:46', '00:01', 1426, 1441, 15],\n", - " [194, '23:47', '00:33', 1427, 1473, 46],\n", - " [195, '23:52', '00:24', 1432, 1464, 32],\n", - " [196, '23:58', '00:38', 1438, 1478, 40],\n", - " [197, '00:02', '00:12', 1442, 1452, 10],\n", - " [198, '00:07', '00:39', 1447, 1479, 32],\n", - " [199, '00:25', '01:12', 1465, 1512, 47]\n", + " [0, \"04:30\", \"04:53\", 270, 293, 23],\n", + " [1, \"04:46\", \"04:56\", 286, 296, 10],\n", + " [2, \"04:52\", \"05:56\", 292, 356, 64],\n", + " [3, \"04:53\", \"05:23\", 293, 323, 30],\n", + " [4, \"05:07\", \"05:44\", 307, 344, 37],\n", + " [5, \"05:10\", \"06:06\", 310, 366, 56],\n", + " [6, \"05:18\", \"06:03\", 318, 363, 45],\n", + " [7, \"05:30\", \"05:40\", 330, 340, 10],\n", + " [8, \"05:30\", \"05:40\", 330, 340, 10],\n", + " [9, \"05:33\", \"06:15\", 333, 375, 42],\n", + " [10, \"05:40\", \"05:50\", 340, 350, 10],\n", + " [11, \"05:43\", \"06:08\", 343, 368, 25],\n", + " [12, \"05:54\", \"07:20\", 354, 440, 86],\n", + " [13, \"06:04\", \"06:37\", 364, 397, 33],\n", + " [14, \"06:13\", \"06:58\", 373, 418, 45],\n", + " [15, \"06:14\", \"07:40\", 374, 460, 86],\n", + " [16, \"06:15\", \"07:15\", 375, 435, 60],\n", + " [17, \"06:16\", \"06:26\", 376, 386, 10],\n", + " [18, \"06:17\", \"06:34\", 377, 394, 17],\n", + " [19, \"06:20\", \"06:36\", 380, 396, 16],\n", + " [20, \"06:22\", \"07:06\", 382, 426, 44],\n", + " [21, \"06:24\", \"07:50\", 384, 470, 86],\n", + " [22, \"06:27\", \"06:44\", 387, 404, 17],\n", + " [23, \"06:30\", \"06:40\", 390, 400, 10],\n", + " [24, \"06:31\", \"06:43\", 391, 403, 12],\n", + " [25, \"06:33\", \"07:53\", 393, 473, 80],\n", + " [26, \"06:34\", \"07:09\", 394, 429, 35],\n", + " [27, \"06:40\", \"06:56\", 400, 416, 16],\n", + " [28, \"06:44\", \"07:17\", 404, 437, 33],\n", + " [29, \"06:46\", \"06:58\", 406, 418, 12],\n", + " [30, \"06:49\", \"07:43\", 409, 463, 54],\n", + " [31, \"06:50\", \"07:05\", 410, 425, 15],\n", + " [32, \"06:52\", \"07:36\", 412, 456, 44],\n", + " [33, \"06:54\", \"07:27\", 414, 447, 33],\n", + " [34, \"06:56\", \"08:23\", 416, 503, 87],\n", + " [35, \"07:04\", \"07:44\", 424, 464, 40],\n", + " [36, \"07:11\", \"08:36\", 431, 516, 85],\n", + " [37, \"07:17\", \"07:35\", 437, 455, 18],\n", + " [38, \"07:22\", \"08:06\", 442, 486, 44],\n", + " [39, \"07:27\", \"08:15\", 447, 495, 48],\n", + " [40, \"07:35\", \"07:45\", 455, 465, 10],\n", + " [41, \"07:43\", \"08:08\", 463, 488, 25],\n", + " [42, \"07:50\", \"08:37\", 470, 517, 47],\n", + " [43, \"07:58\", \"08:45\", 478, 525, 47],\n", + " [44, \"08:00\", \"08:35\", 480, 515, 35],\n", + " [45, \"08:06\", \"08:51\", 486, 531, 45],\n", + " [46, \"08:10\", \"08:45\", 490, 525, 35],\n", + " [47, \"08:15\", \"08:30\", 495, 510, 15],\n", + " [48, \"08:16\", \"09:00\", 496, 540, 44],\n", + " [49, \"08:18\", \"09:16\", 498, 556, 58],\n", + " [50, \"08:20\", \"08:36\", 500, 516, 16],\n", + " [51, \"08:27\", \"09:07\", 507, 547, 40],\n", + " [52, \"08:30\", \"08:45\", 510, 525, 15],\n", + " [53, \"08:35\", \"09:15\", 515, 555, 40],\n", + " [54, \"08:46\", \"09:30\", 526, 570, 44],\n", + " [55, \"08:51\", \"09:17\", 531, 557, 26],\n", + " [56, \"08:55\", \"09:15\", 535, 555, 20],\n", + " [57, \"08:58\", \"09:38\", 538, 578, 40],\n", + " [58, \"09:00\", \"09:35\", 540, 575, 35],\n", + " [59, \"09:00\", \"09:16\", 540, 556, 16],\n", + " [60, \"09:20\", \"09:36\", 560, 576, 16],\n", + " [61, \"09:31\", \"09:43\", 571, 583, 12],\n", + " [62, \"09:33\", \"10:15\", 573, 615, 42],\n", + " [63, \"09:54\", \"10:05\", 594, 605, 11],\n", + " [64, \"10:11\", \"10:38\", 611, 638, 27],\n", + " [65, \"10:18\", \"11:00\", 618, 660, 42],\n", + " [66, \"10:21\", \"10:47\", 621, 647, 26],\n", + " [67, \"10:25\", \"11:04\", 625, 664, 39],\n", + " [68, \"10:26\", \"11:08\", 626, 668, 42],\n", + " [69, \"10:44\", \"12:11\", 644, 731, 87],\n", + " [70, \"11:00\", \"11:16\", 660, 676, 16],\n", + " [71, \"11:15\", \"11:54\", 675, 714, 39],\n", + " [72, \"11:16\", \"11:28\", 676, 688, 12],\n", + " [73, \"11:20\", \"11:30\", 680, 690, 10],\n", + " [74, \"11:21\", \"11:47\", 681, 707, 26],\n", + " [75, \"11:25\", \"12:04\", 685, 724, 39],\n", + " [76, \"11:34\", \"11:45\", 694, 705, 11],\n", + " [77, \"11:35\", \"12:14\", 695, 734, 39],\n", + " [78, \"11:41\", \"12:23\", 701, 743, 42],\n", + " [79, \"11:44\", \"12:35\", 704, 755, 51],\n", + " [80, \"11:46\", \"11:58\", 706, 718, 12],\n", + " [81, \"12:00\", \"12:10\", 720, 730, 10],\n", + " [82, \"12:04\", \"12:15\", 724, 735, 11],\n", + " [83, \"12:04\", \"13:04\", 724, 784, 60],\n", + " [84, \"12:11\", \"12:38\", 731, 758, 27],\n", + " [85, \"12:15\", \"12:54\", 735, 774, 39],\n", + " [86, \"12:25\", \"13:10\", 745, 790, 45],\n", + " [87, \"12:30\", \"12:40\", 750, 760, 10],\n", + " [88, \"12:34\", \"13:58\", 754, 838, 84],\n", + " [89, \"12:38\", \"13:25\", 758, 805, 47],\n", + " [90, \"12:48\", \"13:35\", 768, 815, 47],\n", + " [91, \"13:00\", \"13:16\", 780, 796, 16],\n", + " [92, \"13:05\", \"13:44\", 785, 824, 39],\n", + " [93, \"13:08\", \"13:55\", 788, 835, 47],\n", + " [94, \"13:14\", \"14:38\", 794, 878, 84],\n", + " [95, \"13:23\", \"13:49\", 803, 829, 26],\n", + " [96, \"13:25\", \"14:04\", 805, 844, 39],\n", + " [97, \"13:28\", \"14:54\", 808, 894, 86],\n", + " [98, \"13:31\", \"13:43\", 811, 823, 12],\n", + " [99, \"13:34\", \"14:58\", 814, 898, 84],\n", + " [100, \"13:38\", \"14:25\", 818, 865, 47],\n", + " [101, \"13:38\", \"15:04\", 818, 904, 86],\n", + " [102, \"13:39\", \"14:33\", 819, 873, 54],\n", + " [103, \"13:40\", \"13:50\", 820, 830, 10],\n", + " [104, \"13:43\", \"14:10\", 823, 850, 27],\n", + " [105, \"13:48\", \"14:35\", 828, 875, 47],\n", + " [106, \"13:48\", \"14:35\", 828, 875, 47],\n", + " [107, \"13:53\", \"14:40\", 833, 880, 47],\n", + " [108, \"13:58\", \"15:24\", 838, 924, 86],\n", + " [109, \"13:58\", \"14:25\", 838, 865, 27],\n", + " [110, \"14:00\", \"14:16\", 840, 856, 16],\n", + " [111, \"14:13\", \"15:00\", 853, 900, 47],\n", + " [112, \"14:20\", \"15:31\", 860, 931, 71],\n", + " [113, \"14:25\", \"15:02\", 865, 902, 37],\n", + " [114, \"14:34\", \"14:45\", 874, 885, 11],\n", + " [115, \"14:40\", \"15:51\", 880, 951, 71],\n", + " [116, \"14:40\", \"14:56\", 880, 896, 16],\n", + " [117, \"14:46\", \"14:58\", 886, 898, 12],\n", + " [118, \"14:49\", \"15:43\", 889, 943, 54],\n", + " [119, \"14:52\", \"15:21\", 892, 921, 29],\n", + " [120, \"14:58\", \"16:24\", 898, 984, 86],\n", + " [121, \"14:59\", \"15:53\", 899, 953, 54],\n", + " [122, \"15:00\", \"15:10\", 900, 910, 10],\n", + " [123, \"15:00\", \"15:35\", 900, 935, 35],\n", + " [124, \"15:08\", \"15:45\", 908, 945, 37],\n", + " [125, \"15:12\", \"15:36\", 912, 936, 24],\n", + " [126, \"15:18\", \"16:05\", 918, 965, 47],\n", + " [127, \"15:24\", \"16:05\", 924, 965, 41],\n", + " [128, \"15:31\", \"15:43\", 931, 943, 12],\n", + " [129, \"15:35\", \"15:54\", 935, 954, 19],\n", + " [130, \"15:36\", \"16:21\", 936, 981, 45],\n", + " [131, \"15:39\", \"16:33\", 939, 993, 54],\n", + " [132, \"15:48\", \"16:35\", 948, 995, 47],\n", + " [133, \"15:50\", \"17:01\", 950, 1021, 71],\n", + " [134, \"16:03\", \"16:50\", 963, 1010, 47],\n", + " [135, \"16:18\", \"17:44\", 978, 1064, 86],\n", + " [136, \"16:24\", \"17:05\", 984, 1025, 41],\n", + " [137, \"16:28\", \"17:15\", 988, 1035, 47],\n", + " [138, \"16:34\", \"17:15\", 994, 1035, 41],\n", + " [139, \"16:38\", \"17:25\", 998, 1045, 47],\n", + " [140, \"16:40\", \"16:56\", 1000, 1016, 16],\n", + " [141, \"16:45\", \"17:04\", 1005, 1024, 19],\n", + " [142, \"16:52\", \"17:36\", 1012, 1056, 44],\n", + " [143, \"16:58\", \"17:45\", 1018, 1065, 47],\n", + " [144, \"17:04\", \"18:30\", 1024, 1110, 86],\n", + " [145, \"17:04\", \"17:45\", 1024, 1065, 41],\n", + " [146, \"17:09\", \"18:03\", 1029, 1083, 54],\n", + " [147, \"17:18\", \"18:44\", 1038, 1124, 86],\n", + " [148, \"17:28\", \"18:15\", 1048, 1095, 47],\n", + " [149, \"17:29\", \"18:41\", 1049, 1121, 72],\n", + " [150, \"17:36\", \"18:21\", 1056, 1101, 45],\n", + " [151, \"17:38\", \"18:25\", 1058, 1105, 47],\n", + " [152, \"17:40\", \"17:56\", 1060, 1076, 16],\n", + " [153, \"17:45\", \"18:04\", 1065, 1084, 19],\n", + " [154, \"17:46\", \"17:58\", 1066, 1078, 12],\n", + " [155, \"17:48\", \"18:35\", 1068, 1115, 47],\n", + " [156, \"17:49\", \"18:43\", 1069, 1123, 54],\n", + " [157, \"17:55\", \"18:14\", 1075, 1094, 19],\n", + " [158, \"17:58\", \"18:45\", 1078, 1125, 47],\n", + " [159, \"18:00\", \"19:11\", 1080, 1151, 71],\n", + " [160, \"18:04\", \"18:45\", 1084, 1125, 41],\n", + " [161, \"18:09\", \"19:03\", 1089, 1143, 54],\n", + " [162, \"18:13\", \"19:00\", 1093, 1140, 47],\n", + " [163, \"18:13\", \"18:40\", 1093, 1120, 27],\n", + " [164, \"18:19\", \"19:13\", 1099, 1153, 54],\n", + " [165, \"18:28\", \"19:25\", 1108, 1165, 57],\n", + " [166, \"18:48\", \"19:28\", 1128, 1168, 40],\n", + " [167, \"19:03\", \"19:45\", 1143, 1185, 42],\n", + " [168, \"19:20\", \"19:36\", 1160, 1176, 16],\n", + " [169, \"19:21\", \"19:31\", 1161, 1171, 10],\n", + " [170, \"19:25\", \"20:04\", 1165, 1204, 39],\n", + " [171, \"19:26\", \"20:08\", 1166, 1208, 42],\n", + " [172, \"19:30\", \"19:40\", 1170, 1180, 10],\n", + " [173, \"19:44\", \"20:33\", 1184, 1233, 49],\n", + " [174, \"19:48\", \"21:09\", 1188, 1269, 81],\n", + " [175, \"19:53\", \"21:02\", 1193, 1262, 69],\n", + " [176, \"20:04\", \"20:29\", 1204, 1229, 25],\n", + " [177, \"20:17\", \"21:03\", 1217, 1263, 46],\n", + " [178, \"20:20\", \"20:57\", 1220, 1257, 37],\n", + " [179, \"20:29\", \"21:18\", 1229, 1278, 49],\n", + " [180, \"20:35\", \"21:54\", 1235, 1314, 79],\n", + " [181, \"20:40\", \"20:50\", 1240, 1250, 10],\n", + " [182, \"20:47\", \"21:42\", 1247, 1302, 55],\n", + " [183, \"21:00\", \"21:10\", 1260, 1270, 10],\n", + " [184, \"21:07\", \"21:44\", 1267, 1304, 37],\n", + " [185, \"21:14\", \"22:03\", 1274, 1323, 49],\n", + " [186, \"21:39\", \"21:55\", 1299, 1315, 16],\n", + " [187, \"21:40\", \"22:17\", 1300, 1337, 37],\n", + " [188, \"21:40\", \"21:50\", 1300, 1310, 10],\n", + " [189, \"21:48\", \"22:03\", 1308, 1323, 15],\n", + " [190, \"22:17\", \"23:03\", 1337, 1383, 46],\n", + " [191, \"22:43\", \"23:08\", 1363, 1388, 25],\n", + " [192, \"23:35\", \"01:05\", 1415, 1505, 90],\n", + " [193, \"23:46\", \"00:01\", 1426, 1441, 15],\n", + " [194, \"23:47\", \"00:33\", 1427, 1473, 46],\n", + " [195, \"23:52\", \"00:24\", 1432, 1464, 32],\n", + " [196, \"23:58\", \"00:38\", 1438, 1478, 40],\n", + " [197, \"00:02\", \"00:12\", 1442, 1452, 10],\n", + " [198, \"00:07\", \"00:39\", 1447, 1479, 32],\n", + " [199, \"00:25\", \"01:12\", 1465, 1512, 47],\n", "] # yapf:disable\n", "\n", "SAMPLE_SHIFTS_LARGE = [\n", - " [0, '04:18', '05:00', 258, 300, 42],\n", - " [1, '04:27', '05:08', 267, 308, 41],\n", - " [2, '04:29', '05:26', 269, 326, 57],\n", - " [3, '04:29', '04:55', 269, 295, 26],\n", - " [4, '04:30', '04:53', 270, 293, 23],\n", - " [5, '04:30', '04:51', 270, 291, 21],\n", - " [6, '04:31', '04:53', 271, 293, 22],\n", - " [7, '04:33', '05:15', 273, 315, 42],\n", - " [8, '04:34', '04:44', 274, 284, 10],\n", - " [9, '04:34', '05:03', 274, 303, 29],\n", - " [10, '04:35', '04:50', 275, 290, 15],\n", - " [11, '04:36', '04:46', 276, 286, 10],\n", - " [12, '04:37', '05:18', 277, 318, 41],\n", - " [13, '04:41', '05:13', 281, 313, 32],\n", - " [14, '04:42', '05:23', 282, 323, 41],\n", - " [15, '04:43', '04:53', 283, 293, 10],\n", - " [16, '04:44', '05:45', 284, 345, 61],\n", - " [17, '04:45', '05:11', 285, 311, 26],\n", - " [18, '04:46', '05:01', 286, 301, 15],\n", - " [19, '04:46', '04:56', 286, 296, 10],\n", - " [20, '04:47', '05:14', 287, 314, 27],\n", - " [21, '04:48', '05:30', 288, 330, 42],\n", - " [22, '04:49', '05:41', 289, 341, 52],\n", - " [23, '04:49', '05:18', 289, 318, 29],\n", - " [24, '04:50', '05:33', 290, 333, 43],\n", - " [25, '04:52', '05:56', 292, 356, 64],\n", - " [26, '04:52', '05:07', 292, 307, 15],\n", - " [27, '04:53', '05:19', 293, 319, 26],\n", - " [28, '04:53', '05:23', 293, 323, 30],\n", - " [29, '04:55', '05:27', 295, 327, 32],\n", - " [30, '04:57', '05:38', 297, 338, 41],\n", - " [31, '05:00', '06:00', 300, 360, 60],\n", - " [32, '05:00', '05:54', 300, 354, 54],\n", - " [33, '05:01', '05:33', 301, 333, 32],\n", - " [34, '05:01', '05:26', 301, 326, 25],\n", - " [35, '05:02', '05:29', 302, 329, 27],\n", - " [36, '05:02', '05:12', 302, 312, 10],\n", - " [37, '05:03', '05:45', 303, 345, 42],\n", - " [38, '05:03', '05:18', 303, 318, 15],\n", - " [39, '05:03', '06:28', 303, 388, 85],\n", - " [40, '05:03', '05:13', 303, 313, 10],\n", - " [41, '05:04', '06:24', 304, 384, 80],\n", - " [42, '05:07', '05:44', 307, 344, 37],\n", - " [43, '05:08', '05:48', 308, 348, 40],\n", - " [44, '05:10', '06:06', 310, 366, 56],\n", - " [45, '05:11', '05:37', 311, 337, 26],\n", - " [46, '05:11', '05:53', 311, 353, 42],\n", - " [47, '05:13', '06:15', 313, 375, 62],\n", - " [48, '05:13', '05:38', 313, 338, 25],\n", - " [49, '05:16', '05:44', 316, 344, 28],\n", - " [50, '05:17', '05:27', 317, 327, 10],\n", - " [51, '05:18', '06:40', 318, 400, 82],\n", - " [52, '05:18', '06:03', 318, 363, 45],\n", - " [53, '05:18', '06:11', 318, 371, 53],\n", - " [54, '05:18', '06:00', 318, 360, 42],\n", - " [55, '05:19', '06:34', 319, 394, 75],\n", - " [56, '05:20', '06:17', 320, 377, 57],\n", - " [57, '05:22', '05:59', 322, 359, 37],\n", - " [58, '05:24', '05:48', 324, 348, 24],\n", - " [59, '05:25', '05:40', 325, 340, 15],\n", - " [60, '05:26', '06:08', 326, 368, 42],\n", - " [61, '05:27', '06:30', 327, 390, 63],\n", - " [62, '05:27', '05:54', 327, 354, 27],\n", - " [63, '05:28', '05:53', 328, 353, 25],\n", - " [64, '05:29', '05:44', 329, 344, 15],\n", - " [65, '05:30', '05:40', 330, 340, 10],\n", - " [66, '05:30', '05:40', 330, 340, 10],\n", - " [67, '05:30', '05:40', 330, 340, 10],\n", - " [68, '05:32', '06:53', 332, 413, 81],\n", - " [69, '05:33', '07:00', 333, 420, 87],\n", - " [70, '05:33', '06:15', 333, 375, 42],\n", - " [71, '05:33', '05:47', 333, 347, 14],\n", - " [72, '05:37', '06:13', 337, 373, 36],\n", - " [73, '05:37', '06:05', 337, 365, 28],\n", - " [74, '05:38', '06:33', 338, 393, 55],\n", - " [75, '05:38', '06:04', 338, 364, 26],\n", - " [76, '05:38', '06:18', 338, 378, 40],\n", - " [77, '05:39', '05:54', 339, 354, 15],\n", - " [78, '05:40', '05:56', 340, 356, 16],\n", - " [79, '05:40', '06:41', 340, 401, 61],\n", - " [80, '05:40', '05:50', 340, 350, 10],\n", - " [81, '05:41', '06:23', 341, 383, 42],\n", - " [82, '05:41', '06:01', 341, 361, 20],\n", - " [83, '05:43', '06:08', 343, 368, 25],\n", - " [84, '05:44', '07:10', 344, 430, 86],\n", - " [85, '05:44', '05:55', 344, 355, 11],\n", - " [86, '05:45', '06:44', 345, 404, 59],\n", - " [87, '05:47', '06:17', 347, 377, 30],\n", - " [88, '05:48', '07:08', 348, 428, 80],\n", - " [89, '05:48', '06:30', 348, 390, 42],\n", - " [90, '05:50', '06:50', 350, 410, 60],\n", - " [91, '05:50', '06:00', 350, 360, 10],\n", - " [92, '05:50', '06:00', 350, 360, 10],\n", - " [93, '05:50', '06:51', 350, 411, 61],\n", - " [94, '05:52', '06:33', 352, 393, 41],\n", - " [95, '05:52', '06:36', 352, 396, 44],\n", - " [96, '05:52', '06:23', 352, 383, 31],\n", - " [97, '05:54', '06:14', 354, 374, 20],\n", - " [98, '05:54', '07:20', 354, 440, 86],\n", - " [99, '05:55', '06:40', 355, 400, 45],\n", - " [100, '05:55', '06:27', 355, 387, 32],\n", - " [101, '05:56', '06:35', 356, 395, 39],\n", - " [102, '05:56', '06:06', 356, 366, 10],\n", - " [103, '05:57', '06:21', 357, 381, 24],\n", - " [104, '05:58', '07:23', 358, 443, 85],\n", - " [105, '05:58', '06:23', 358, 383, 25],\n", - " [106, '05:58', '06:08', 358, 368, 10],\n", - " [107, '05:58', '06:43', 358, 403, 45],\n", - " [108, '06:00', '06:10', 360, 370, 10],\n", - " [109, '06:00', '06:16', 360, 376, 16],\n", - " [110, '06:00', '07:01', 360, 421, 61],\n", - " [111, '06:01', '07:00', 361, 420, 59],\n", - " [112, '06:01', '06:13', 361, 373, 12],\n", - " [113, '06:01', '06:45', 361, 405, 44],\n", - " [114, '06:03', '06:50', 363, 410, 47],\n", - " [115, '06:04', '06:37', 364, 397, 33],\n", - " [116, '06:04', '07:30', 364, 450, 86],\n", - " [117, '06:05', '06:24', 365, 384, 19],\n", - " [118, '06:06', '06:51', 366, 411, 45],\n", - " [119, '06:07', '06:43', 367, 403, 36],\n", - " [120, '06:08', '07:30', 368, 450, 82],\n", - " [121, '06:10', '06:20', 370, 380, 10],\n", - " [122, '06:10', '07:17', 370, 437, 67],\n", - " [123, '06:11', '06:54', 371, 414, 43],\n", - " [124, '06:11', '06:21', 371, 381, 10],\n", - " [125, '06:13', '06:38', 373, 398, 25],\n", - " [126, '06:13', '06:58', 373, 418, 45],\n", - " [127, '06:13', '06:53', 373, 413, 40],\n", - " [128, '06:14', '07:03', 374, 423, 49],\n", - " [129, '06:14', '06:47', 374, 407, 33],\n", - " [130, '06:14', '07:40', 374, 460, 86],\n", - " [131, '06:15', '07:15', 375, 435, 60],\n", - " [132, '06:16', '06:28', 376, 388, 12],\n", - " [133, '06:16', '06:26', 376, 386, 10],\n", - " [134, '06:17', '06:34', 377, 394, 17],\n", - " [135, '06:18', '07:06', 378, 426, 48],\n", - " [136, '06:18', '07:38', 378, 458, 80],\n", - " [137, '06:18', '07:02', 378, 422, 44],\n", - " [138, '06:19', '06:53', 379, 413, 34],\n", - " [139, '06:20', '07:25', 380, 445, 65],\n", - " [140, '06:20', '06:36', 380, 396, 16],\n", - " [141, '06:20', '06:30', 380, 390, 10],\n", - " [142, '06:20', '06:30', 380, 390, 10],\n", - " [143, '06:21', '06:49', 381, 409, 28],\n", - " [144, '06:22', '07:06', 382, 426, 44],\n", - " [145, '06:24', '07:50', 384, 470, 86],\n", - " [146, '06:24', '06:57', 384, 417, 33],\n", - " [147, '06:26', '07:45', 386, 465, 79],\n", - " [148, '06:26', '07:10', 386, 430, 44],\n", - " [149, '06:27', '06:44', 387, 404, 17],\n", - " [150, '06:28', '06:53', 388, 413, 25],\n", - " [151, '06:28', '07:14', 388, 434, 46],\n", - " [152, '06:29', '07:03', 389, 423, 34],\n", - " [153, '06:30', '06:40', 390, 400, 10],\n", - " [154, '06:30', '07:37', 390, 457, 67],\n", - " [155, '06:31', '06:43', 391, 403, 12],\n", - " [156, '06:33', '07:14', 393, 434, 41],\n", - " [157, '06:33', '07:53', 393, 473, 80],\n", - " [158, '06:34', '08:16', 394, 496, 102],\n", - " [159, '06:34', '07:09', 394, 429, 35],\n", - " [160, '06:34', '07:07', 394, 427, 33],\n", - " [161, '06:36', '07:21', 396, 441, 45],\n", - " [162, '06:37', '07:22', 397, 442, 45],\n", - " [163, '06:37', '06:54', 397, 414, 17],\n", - " [164, '06:38', '07:30', 398, 450, 52],\n", - " [165, '06:38', '07:18', 398, 438, 40],\n", - " [166, '06:39', '07:33', 399, 453, 54],\n", - " [167, '06:40', '07:52', 400, 472, 72],\n", - " [168, '06:40', '06:50', 400, 410, 10],\n", - " [169, '06:40', '07:22', 400, 442, 42],\n", - " [170, '06:40', '06:56', 400, 416, 16],\n", - " [171, '06:41', '08:00', 401, 480, 79],\n", - " [172, '06:42', '07:26', 402, 446, 44],\n", - " [173, '06:42', '07:13', 402, 433, 31],\n", - " [174, '06:43', '07:08', 403, 428, 25],\n", - " [175, '06:43', '07:30', 403, 450, 47],\n", - " [176, '06:43', '07:23', 403, 443, 40],\n", - " [177, '06:44', '07:17', 404, 437, 33],\n", - " [178, '06:44', '08:13', 404, 493, 89],\n", - " [179, '06:46', '07:01', 406, 421, 15],\n", - " [180, '06:46', '06:58', 406, 418, 12],\n", - " [181, '06:47', '07:04', 407, 424, 17],\n", - " [182, '06:48', '08:15', 408, 495, 87],\n", - " [183, '06:48', '07:34', 408, 454, 46],\n", - " [184, '06:48', '07:37', 408, 457, 49],\n", - " [185, '06:49', '07:43', 409, 463, 54],\n", - " [186, '06:50', '08:00', 410, 480, 70],\n", - " [187, '06:50', '07:00', 410, 420, 10],\n", - " [188, '06:50', '07:05', 410, 425, 15],\n", - " [189, '06:51', '07:18', 411, 438, 27],\n", - " [190, '06:52', '07:36', 412, 456, 44],\n", - " [191, '06:53', '07:37', 413, 457, 44],\n", - " [192, '06:54', '08:20', 414, 500, 86],\n", - " [193, '06:54', '07:27', 414, 447, 33],\n", - " [194, '06:54', '07:20', 414, 440, 26],\n", - " [195, '06:56', '08:23', 416, 503, 87],\n", - " [196, '06:57', '07:12', 417, 432, 15],\n", - " [197, '06:57', '07:58', 417, 478, 61],\n", - " [198, '06:57', '07:45', 417, 465, 48],\n", - " [199, '06:57', '07:40', 417, 460, 43],\n", - " [200, '06:58', '07:23', 418, 443, 25],\n", - " [201, '06:59', '07:53', 419, 473, 54],\n", - " [202, '06:59', '08:07', 419, 487, 68],\n", - " [203, '07:00', '07:10', 420, 430, 10],\n", - " [204, '07:00', '07:16', 420, 436, 16],\n", - " [205, '07:01', '08:30', 421, 510, 89],\n", - " [206, '07:01', '07:13', 421, 433, 12],\n", - " [207, '07:01', '07:43', 421, 463, 42],\n", - " [208, '07:03', '08:30', 423, 510, 87],\n", - " [209, '07:04', '07:37', 424, 457, 33],\n", - " [210, '07:04', '07:44', 424, 464, 40],\n", - " [211, '07:05', '07:52', 425, 472, 47],\n", - " [212, '07:05', '08:05', 425, 485, 60],\n", - " [213, '07:05', '07:46', 425, 466, 41],\n", - " [214, '07:06', '07:51', 426, 471, 45],\n", - " [215, '07:07', '08:08', 427, 488, 61],\n", - " [216, '07:07', '07:52', 427, 472, 45],\n", - " [217, '07:07', '08:16', 427, 496, 69],\n", - " [218, '07:07', '07:27', 427, 447, 20],\n", - " [219, '07:09', '07:50', 429, 470, 41],\n", - " [220, '07:09', '08:40', 429, 520, 91],\n", - " [221, '07:09', '08:03', 429, 483, 54],\n", - " [222, '07:10', '07:20', 430, 440, 10],\n", - " [223, '07:11', '08:36', 431, 516, 85],\n", - " [224, '07:12', '08:00', 432, 480, 48],\n", - " [225, '07:12', '07:47', 432, 467, 35],\n", - " [226, '07:13', '07:54', 433, 474, 41],\n", - " [227, '07:13', '07:38', 433, 458, 25],\n", - " [228, '07:14', '07:59', 434, 479, 45],\n", - " [229, '07:16', '08:50', 436, 530, 94],\n", - " [230, '07:16', '07:28', 436, 448, 12],\n", - " [231, '07:17', '07:35', 437, 455, 18],\n", - " [232, '07:17', '07:58', 437, 478, 41],\n", - " [233, '07:18', '08:06', 438, 486, 48],\n", - " [234, '07:18', '08:44', 438, 524, 86],\n", - " [235, '07:19', '08:13', 439, 493, 54],\n", - " [236, '07:20', '08:02', 440, 482, 42],\n", - " [237, '07:20', '08:07', 440, 487, 47],\n", - " [238, '07:20', '07:30', 440, 450, 10],\n", - " [239, '07:20', '07:57', 440, 477, 37],\n", - " [240, '07:20', '07:36', 440, 456, 16],\n", - " [241, '07:21', '07:48', 441, 468, 27],\n", - " [242, '07:22', '08:06', 442, 486, 44],\n", - " [243, '07:22', '08:25', 442, 505, 63],\n", - " [244, '07:24', '08:27', 444, 507, 63],\n", - " [245, '07:24', '08:05', 444, 485, 41],\n", - " [246, '07:26', '08:23', 446, 503, 57],\n", - " [247, '07:26', '08:52', 446, 532, 86],\n", - " [248, '07:27', '08:07', 447, 487, 40],\n", - " [249, '07:27', '07:42', 447, 462, 15],\n", - " [250, '07:27', '08:15', 447, 495, 48],\n", - " [251, '07:28', '07:53', 448, 473, 25],\n", - " [252, '07:28', '08:09', 448, 489, 41],\n", - " [253, '07:28', '07:38', 448, 458, 10],\n", - " [254, '07:30', '08:35', 450, 515, 65],\n", - " [255, '07:31', '07:43', 451, 463, 12],\n", - " [256, '07:32', '08:13', 452, 493, 41],\n", - " [257, '07:34', '09:00', 454, 540, 86],\n", - " [258, '07:34', '08:33', 454, 513, 59],\n", - " [259, '07:34', '09:04', 454, 544, 90],\n", - " [260, '07:35', '08:22', 455, 502, 47],\n", - " [261, '07:35', '07:45', 455, 465, 10],\n", - " [262, '07:35', '08:16', 455, 496, 41],\n", - " [263, '07:36', '08:17', 456, 497, 41],\n", - " [264, '07:36', '08:36', 456, 516, 60],\n", - " [265, '07:37', '07:50', 457, 470, 13],\n", - " [266, '07:40', '07:56', 460, 476, 16],\n", - " [267, '07:40', '08:20', 460, 500, 40],\n", - " [268, '07:40', '08:45', 460, 525, 65],\n", - " [269, '07:41', '08:39', 461, 519, 58],\n", - " [270, '07:41', '07:51', 461, 471, 10],\n", - " [271, '07:42', '08:30', 462, 510, 48],\n", - " [272, '07:42', '08:21', 462, 501, 39],\n", - " [273, '07:43', '08:08', 463, 488, 25],\n", - " [274, '07:43', '08:24', 463, 504, 41],\n", - " [275, '07:44', '09:10', 464, 550, 86],\n", - " [276, '07:44', '08:43', 464, 523, 59],\n", - " [277, '07:46', '08:28', 466, 508, 42],\n", - " [278, '07:46', '07:58', 466, 478, 12],\n", - " [279, '07:47', '08:00', 467, 480, 13],\n", - " [280, '07:48', '09:14', 468, 554, 86],\n", - " [281, '07:49', '08:32', 469, 512, 43],\n", - " [282, '07:50', '08:55', 470, 535, 65],\n", - " [283, '07:50', '08:00', 470, 480, 10],\n", - " [284, '07:50', '08:37', 470, 517, 47],\n", - " [285, '07:50', '08:26', 470, 506, 36],\n", - " [286, '07:51', '08:18', 471, 498, 27],\n", - " [287, '07:52', '08:21', 472, 501, 29],\n", - " [288, '07:53', '08:35', 473, 515, 42],\n", - " [289, '07:54', '09:19', 474, 559, 85],\n", - " [290, '07:55', '08:53', 475, 533, 58],\n", - " [291, '07:56', '08:54', 476, 534, 58],\n", - " [292, '07:57', '08:39', 477, 519, 42],\n", - " [293, '07:57', '08:10', 477, 490, 13],\n", - " [294, '07:58', '08:45', 478, 525, 47],\n", - " [295, '07:58', '08:23', 478, 503, 25],\n", - " [296, '08:00', '08:10', 480, 490, 10],\n", - " [297, '08:00', '09:05', 480, 545, 65],\n", - " [298, '08:00', '08:16', 480, 496, 16],\n", - " [299, '08:00', '08:35', 480, 515, 35],\n", - " [300, '08:01', '08:13', 481, 493, 12],\n", - " [301, '08:01', '08:43', 481, 523, 42],\n", - " [302, '08:03', '09:26', 483, 566, 83],\n", - " [303, '08:04', '09:29', 484, 569, 85],\n", - " [304, '08:05', '08:21', 485, 501, 16],\n", - " [305, '08:05', '08:47', 485, 527, 42],\n", - " [306, '08:06', '08:51', 486, 531, 45],\n", - " [307, '08:06', '09:03', 486, 543, 57],\n", - " [308, '08:07', '08:20', 487, 500, 13],\n", - " [309, '08:08', '08:55', 488, 535, 47],\n", - " [310, '08:08', '08:50', 488, 530, 42],\n", - " [311, '08:10', '08:45', 490, 525, 35],\n", - " [312, '08:10', '09:15', 490, 555, 65],\n", - " [313, '08:10', '08:20', 490, 500, 10],\n", - " [314, '08:11', '09:41', 491, 581, 90],\n", - " [315, '08:12', '08:55', 492, 535, 43],\n", - " [316, '08:13', '08:38', 493, 518, 25],\n", - " [317, '08:14', '09:38', 494, 578, 84],\n", - " [318, '08:15', '08:30', 495, 510, 15],\n", - " [319, '08:16', '08:30', 496, 510, 14],\n", - " [320, '08:16', '08:28', 496, 508, 12],\n", - " [321, '08:16', '09:00', 496, 540, 44],\n", - " [322, '08:17', '09:13', 497, 553, 56],\n", - " [323, '08:18', '09:16', 498, 556, 58],\n", - " [324, '08:18', '09:05', 498, 545, 47],\n", - " [325, '08:20', '08:36', 500, 516, 16],\n", - " [326, '08:20', '08:55', 500, 535, 35],\n", - " [327, '08:20', '09:05', 500, 545, 45],\n", - " [328, '08:20', '08:30', 500, 510, 10],\n", - " [329, '08:20', '09:25', 500, 565, 65],\n", - " [330, '08:21', '08:38', 501, 518, 17],\n", - " [331, '08:21', '08:47', 501, 527, 26],\n", - " [332, '08:22', '08:45', 502, 525, 23],\n", - " [333, '08:23', '09:10', 503, 550, 47],\n", - " [334, '08:24', '09:48', 504, 588, 84],\n", - " [335, '08:26', '08:46', 506, 526, 20],\n", - " [336, '08:27', '09:07', 507, 547, 40],\n", - " [337, '08:28', '08:50', 508, 530, 22],\n", - " [338, '08:28', '09:56', 508, 596, 88],\n", - " [339, '08:28', '09:23', 508, 563, 55],\n", - " [340, '08:29', '09:20', 509, 560, 51],\n", - " [341, '08:30', '09:05', 510, 545, 35],\n", - " [342, '08:30', '08:45', 510, 525, 15],\n", - " [343, '08:30', '08:40', 510, 520, 10],\n", - " [344, '08:30', '09:35', 510, 575, 65],\n", - " [345, '08:31', '08:43', 511, 523, 12],\n", - " [346, '08:31', '09:13', 511, 553, 42],\n", - " [347, '08:34', '09:58', 514, 598, 84],\n", - " [348, '08:35', '08:55', 515, 535, 20],\n", - " [349, '08:35', '09:15', 515, 555, 40],\n", - " [350, '08:35', '08:45', 515, 525, 10],\n", - " [351, '08:36', '08:46', 516, 526, 10],\n", - " [352, '08:36', '09:00', 516, 540, 24],\n", - " [353, '08:38', '09:20', 518, 560, 42],\n", - " [354, '08:38', '09:35', 518, 575, 57],\n", - " [355, '08:38', '09:14', 518, 554, 36],\n", - " [356, '08:39', '09:33', 519, 573, 54],\n", - " [357, '08:40', '09:45', 520, 585, 65],\n", - " [358, '08:40', '08:50', 520, 530, 10],\n", - " [359, '08:40', '08:56', 520, 536, 16],\n", - " [360, '08:42', '09:25', 522, 565, 43],\n", - " [361, '08:43', '09:08', 523, 548, 25],\n", - " [362, '08:44', '09:35', 524, 575, 51],\n", - " [363, '08:45', '09:00', 525, 540, 15],\n", - " [364, '08:45', '09:05', 525, 545, 20],\n", - " [365, '08:46', '09:24', 526, 564, 38],\n", - " [366, '08:46', '08:58', 526, 538, 12],\n", - " [367, '08:46', '09:30', 526, 570, 44],\n", - " [368, '08:48', '10:11', 528, 611, 83],\n", - " [369, '08:48', '10:13', 528, 613, 85],\n", - " [370, '08:49', '09:43', 529, 583, 54],\n", - " [371, '08:50', '09:30', 530, 570, 40],\n", - " [372, '08:50', '10:00', 530, 600, 70],\n", - " [373, '08:50', '09:00', 530, 540, 10],\n", - " [374, '08:51', '09:17', 531, 557, 26],\n", - " [375, '08:53', '09:20', 533, 560, 27],\n", - " [376, '08:53', '09:35', 533, 575, 42],\n", - " [377, '08:55', '09:34', 535, 574, 39],\n", - " [378, '08:55', '09:15', 535, 555, 20],\n", - " [379, '08:58', '09:38', 538, 578, 40],\n", - " [380, '08:58', '10:26', 538, 626, 88],\n", - " [381, '08:59', '09:53', 539, 593, 54],\n", - " [382, '08:59', '09:50', 539, 590, 51],\n", - " [383, '09:00', '09:35', 540, 575, 35],\n", - " [384, '09:00', '09:16', 540, 556, 16],\n", - " [385, '09:00', '09:10', 540, 550, 10],\n", - " [386, '09:00', '09:16', 540, 556, 16],\n", - " [387, '09:01', '09:13', 541, 553, 12],\n", - " [388, '09:03', '09:45', 543, 585, 42],\n", - " [389, '09:03', '10:28', 543, 628, 85],\n", - " [390, '09:05', '09:44', 545, 584, 39],\n", - " [391, '09:05', '09:25', 545, 565, 20],\n", - " [392, '09:08', '09:53', 548, 593, 45],\n", - " [393, '09:08', '10:04', 548, 604, 56],\n", - " [394, '09:09', '10:03', 549, 603, 54],\n", - " [395, '09:10', '10:15', 550, 615, 65],\n", - " [396, '09:10', '09:20', 550, 560, 10],\n", - " [397, '09:11', '09:38', 551, 578, 27],\n", - " [398, '09:13', '10:00', 553, 600, 47],\n", - " [399, '09:14', '09:39', 554, 579, 25],\n", - " [400, '09:14', '10:05', 554, 605, 51],\n", - " [401, '09:15', '09:54', 555, 594, 39],\n", - " [402, '09:16', '09:28', 556, 568, 12],\n", - " [403, '09:18', '10:43', 558, 643, 85],\n", - " [404, '09:18', '10:41', 558, 641, 83],\n", - " [405, '09:18', '09:58', 558, 598, 40],\n", - " [406, '09:19', '10:13', 559, 613, 54],\n", - " [407, '09:20', '09:30', 560, 570, 10],\n", - " [408, '09:20', '09:36', 560, 576, 16],\n", - " [409, '09:21', '09:47', 561, 587, 26],\n", - " [410, '09:23', '10:30', 563, 630, 67],\n", - " [411, '09:23', '10:05', 563, 605, 42],\n", - " [412, '09:23', '09:49', 563, 589, 26],\n", - " [413, '09:24', '09:35', 564, 575, 11],\n", - " [414, '09:25', '09:35', 565, 575, 10],\n", - " [415, '09:25', '10:04', 565, 604, 39],\n", - " [416, '09:28', '10:08', 568, 608, 40],\n", - " [417, '09:29', '09:45', 569, 585, 16],\n", - " [418, '09:29', '10:20', 569, 620, 51],\n", - " [419, '09:29', '10:56', 569, 656, 87],\n", - " [420, '09:29', '10:23', 569, 623, 54],\n", - " [421, '09:30', '09:40', 570, 580, 10],\n", - " [422, '09:31', '09:43', 571, 583, 12],\n", - " [423, '09:33', '10:58', 573, 658, 85],\n", - " [424, '09:33', '10:15', 573, 615, 42],\n", - " [425, '09:34', '09:45', 574, 585, 11],\n", - " [426, '09:35', '10:14', 575, 614, 39],\n", - " [427, '09:38', '10:45', 578, 645, 67],\n", - " [428, '09:39', '10:33', 579, 633, 54],\n", - " [429, '09:40', '09:56', 580, 596, 16],\n", - " [430, '09:40', '09:50', 580, 590, 10],\n", - " [431, '09:41', '10:08', 581, 608, 27],\n", - " [432, '09:41', '10:23', 581, 623, 42],\n", - " [433, '09:44', '10:35', 584, 635, 51],\n", - " [434, '09:44', '11:11', 584, 671, 87],\n", - " [435, '09:44', '09:55', 584, 595, 11],\n", - " [436, '09:45', '10:24', 585, 624, 39],\n", - " [437, '09:46', '09:58', 586, 598, 12],\n", - " [438, '09:48', '10:30', 588, 630, 42],\n", - " [439, '09:48', '11:13', 588, 673, 85],\n", - " [440, '09:48', '10:04', 588, 604, 16],\n", - " [441, '09:49', '10:43', 589, 643, 54],\n", - " [442, '09:50', '10:00', 590, 600, 10],\n", - " [443, '09:51', '10:17', 591, 617, 26],\n", - " [444, '09:53', '10:49', 593, 649, 56],\n", - " [445, '09:53', '11:00', 593, 660, 67],\n", - " [446, '09:54', '10:05', 594, 605, 11],\n", - " [447, '09:55', '10:34', 595, 634, 39],\n", - " [448, '09:56', '10:38', 596, 638, 42],\n", - " [449, '09:57', '10:20', 597, 620, 23],\n", - " [450, '09:59', '11:26', 599, 686, 87],\n", - " [451, '09:59', '10:50', 599, 650, 51],\n", - " [452, '09:59', '10:53', 599, 653, 54],\n", - " [453, '10:00', '10:16', 600, 616, 16],\n", - " [454, '10:00', '10:10', 600, 610, 10],\n", - " [455, '10:01', '10:13', 601, 613, 12],\n", - " [456, '10:03', '11:28', 603, 688, 85],\n", - " [457, '10:03', '10:45', 603, 645, 42],\n", - " [458, '10:04', '10:15', 604, 615, 11],\n", - " [459, '10:05', '10:44', 605, 644, 39],\n", - " [460, '10:08', '11:15', 608, 675, 67],\n", - " [461, '10:09', '11:03', 609, 663, 54],\n", - " [462, '10:10', '10:20', 610, 620, 10],\n", - " [463, '10:11', '10:38', 611, 638, 27],\n", - " [464, '10:11', '10:53', 611, 653, 42],\n", - " [465, '10:14', '11:05', 614, 665, 51],\n", - " [466, '10:14', '11:41', 614, 701, 87],\n", - " [467, '10:14', '10:25', 614, 625, 11],\n", - " [468, '10:15', '10:54', 615, 654, 39],\n", - " [469, '10:16', '10:28', 616, 628, 12],\n", - " [470, '10:18', '11:43', 618, 703, 85],\n", - " [471, '10:18', '11:00', 618, 660, 42],\n", - " [472, '10:19', '11:13', 619, 673, 54],\n", - " [473, '10:20', '10:30', 620, 630, 10],\n", - " [474, '10:20', '10:36', 620, 636, 16],\n", - " [475, '10:21', '10:47', 621, 647, 26],\n", - " [476, '10:23', '11:30', 623, 690, 67],\n", - " [477, '10:23', '10:45', 623, 645, 22],\n", - " [478, '10:24', '10:35', 624, 635, 11],\n", - " [479, '10:25', '11:04', 625, 664, 39],\n", - " [480, '10:26', '11:08', 626, 668, 42],\n", - " [481, '10:29', '11:20', 629, 680, 51],\n", - " [482, '10:29', '11:23', 629, 683, 54],\n", - " [483, '10:29', '11:56', 629, 716, 87],\n", - " [484, '10:30', '10:40', 630, 640, 10],\n", - " [485, '10:31', '10:43', 631, 643, 12],\n", - " [486, '10:33', '11:15', 633, 675, 42],\n", - " [487, '10:33', '11:58', 633, 718, 85],\n", - " [488, '10:34', '10:45', 634, 645, 11],\n", - " [489, '10:35', '11:14', 635, 674, 39],\n", - " [490, '10:38', '11:45', 638, 705, 67],\n", - " [491, '10:39', '11:33', 639, 693, 54],\n", - " [492, '10:40', '10:50', 640, 650, 10],\n", - " [493, '10:40', '10:56', 640, 656, 16],\n", - " [494, '10:41', '11:23', 641, 683, 42],\n", - " [495, '10:41', '11:08', 641, 668, 27],\n", - " [496, '10:44', '12:11', 644, 731, 87],\n", - " [497, '10:44', '11:35', 644, 695, 51],\n", - " [498, '10:44', '10:55', 644, 655, 11],\n", - " [499, '10:45', '11:24', 645, 684, 39],\n", - " [500, '10:46', '10:58', 646, 658, 12],\n", - " [501, '10:48', '12:13', 648, 733, 85],\n", - " [502, '10:48', '11:30', 648, 690, 42],\n", - " [503, '10:49', '11:43', 649, 703, 54],\n", - " [504, '10:50', '11:00', 650, 660, 10],\n", - " [505, '10:51', '11:17', 651, 677, 26],\n", - " [506, '10:53', '12:00', 653, 720, 67],\n", - " [507, '10:53', '11:20', 653, 680, 27],\n", - " [508, '10:54', '11:05', 654, 665, 11],\n", - " [509, '10:55', '11:34', 655, 694, 39],\n", - " [510, '10:56', '11:38', 656, 698, 42],\n", - " [511, '10:59', '11:14', 659, 674, 15],\n", - " [512, '10:59', '12:26', 659, 746, 87],\n", - " [513, '10:59', '11:53', 659, 713, 54],\n", - " [514, '10:59', '11:50', 659, 710, 51],\n", - " [515, '11:00', '11:16', 660, 676, 16],\n", - " [516, '11:00', '11:10', 660, 670, 10],\n", - " [517, '11:01', '11:13', 661, 673, 12],\n", - " [518, '11:03', '11:45', 663, 705, 42],\n", - " [519, '11:03', '12:28', 663, 748, 85],\n", - " [520, '11:04', '11:15', 664, 675, 11],\n", - " [521, '11:05', '11:44', 665, 704, 39],\n", - " [522, '11:08', '12:15', 668, 735, 67],\n", - " [523, '11:09', '12:03', 669, 723, 54],\n", - " [524, '11:10', '11:20', 670, 680, 10],\n", - " [525, '11:11', '11:38', 671, 698, 27],\n", - " [526, '11:11', '11:53', 671, 713, 42],\n", - " [527, '11:14', '11:25', 674, 685, 11],\n", - " [528, '11:14', '12:05', 674, 725, 51],\n", - " [529, '11:14', '12:38', 674, 758, 84],\n", - " [530, '11:14', '12:41', 674, 761, 87],\n", - " [531, '11:15', '11:54', 675, 714, 39],\n", - " [532, '11:16', '11:28', 676, 688, 12],\n", - " [533, '11:18', '12:00', 678, 720, 42],\n", - " [534, '11:19', '12:13', 679, 733, 54],\n", - " [535, '11:20', '11:30', 680, 690, 10],\n", - " [536, '11:20', '11:36', 680, 696, 16],\n", - " [537, '11:21', '11:47', 681, 707, 26],\n", - " [538, '11:23', '12:30', 683, 750, 67],\n", - " [539, '11:23', '11:49', 683, 709, 26],\n", - " [540, '11:24', '12:48', 684, 768, 84],\n", - " [541, '11:24', '11:35', 684, 695, 11],\n", - " [542, '11:25', '12:04', 685, 724, 39],\n", - " [543, '11:26', '12:08', 686, 728, 42],\n", - " [544, '11:29', '11:44', 689, 704, 15],\n", - " [545, '11:29', '12:23', 689, 743, 54],\n", - " [546, '11:29', '12:20', 689, 740, 51],\n", - " [547, '11:29', '12:54', 689, 774, 85],\n", - " [548, '11:30', '11:40', 690, 700, 10],\n", - " [549, '11:31', '11:43', 691, 703, 12],\n", - " [550, '11:33', '12:15', 693, 735, 42],\n", - " [551, '11:34', '12:58', 694, 778, 84],\n", - " [552, '11:34', '11:45', 694, 705, 11],\n", - " [553, '11:35', '12:14', 695, 734, 39],\n", - " [554, '11:38', '12:45', 698, 765, 67],\n", - " [555, '11:39', '12:33', 699, 753, 54],\n", - " [556, '11:40', '11:56', 700, 716, 16],\n", - " [557, '11:40', '11:50', 700, 710, 10],\n", - " [558, '11:41', '12:08', 701, 728, 27],\n", - " [559, '11:41', '12:23', 701, 743, 42],\n", - " [560, '11:44', '11:55', 704, 715, 11],\n", - " [561, '11:44', '13:14', 704, 794, 90],\n", - " [562, '11:44', '13:08', 704, 788, 84],\n", - " [563, '11:44', '12:35', 704, 755, 51],\n", - " [564, '11:45', '12:24', 705, 744, 39],\n", - " [565, '11:46', '11:58', 706, 718, 12],\n", - " [566, '11:48', '12:30', 708, 750, 42],\n", - " [567, '11:49', '12:43', 709, 763, 54],\n", - " [568, '11:50', '12:00', 710, 720, 10],\n", - " [569, '11:51', '12:17', 711, 737, 26],\n", - " [570, '11:53', '12:49', 713, 769, 56],\n", - " [571, '11:53', '13:00', 713, 780, 67],\n", - " [572, '11:54', '13:18', 714, 798, 84],\n", - " [573, '11:54', '12:05', 714, 725, 11],\n", - " [574, '11:55', '12:40', 715, 760, 45],\n", - " [575, '11:55', '12:34', 715, 754, 39],\n", - " [576, '11:56', '12:35', 716, 755, 39],\n", - " [577, '11:57', '12:20', 717, 740, 23],\n", - " [578, '11:58', '12:29', 718, 749, 31],\n", - " [579, '11:59', '12:50', 719, 770, 51],\n", - " [580, '11:59', '12:53', 719, 773, 54],\n", - " [581, '11:59', '13:24', 719, 804, 85],\n", - " [582, '11:59', '12:14', 719, 734, 15],\n", - " [583, '12:00', '12:16', 720, 736, 16],\n", - " [584, '12:00', '12:10', 720, 730, 10],\n", - " [585, '12:01', '12:45', 721, 765, 44],\n", - " [586, '12:01', '12:13', 721, 733, 12],\n", - " [587, '12:03', '12:50', 723, 770, 47],\n", - " [588, '12:04', '12:15', 724, 735, 11],\n", - " [589, '12:04', '13:04', 724, 784, 60],\n", - " [590, '12:04', '13:28', 724, 808, 84],\n", - " [591, '12:05', '12:44', 725, 764, 39],\n", - " [592, '12:08', '13:11', 728, 791, 63],\n", - " [593, '12:08', '12:39', 728, 759, 31],\n", - " [594, '12:09', '13:03', 729, 783, 54],\n", - " [595, '12:10', '12:20', 730, 740, 10],\n", - " [596, '12:11', '12:55', 731, 775, 44],\n", - " [597, '12:11', '12:38', 731, 758, 27],\n", - " [598, '12:14', '13:05', 734, 785, 51],\n", - " [599, '12:14', '12:25', 734, 745, 11],\n", - " [600, '12:14', '13:44', 734, 824, 90],\n", - " [601, '12:14', '13:38', 734, 818, 84],\n", - " [602, '12:15', '12:54', 735, 774, 39],\n", - " [603, '12:16', '12:28', 736, 748, 12],\n", - " [604, '12:18', '13:00', 738, 780, 42],\n", - " [605, '12:19', '13:13', 739, 793, 54],\n", - " [606, '12:20', '12:30', 740, 750, 10],\n", - " [607, '12:20', '13:31', 740, 811, 71],\n", - " [608, '12:20', '12:30', 740, 750, 10],\n", - " [609, '12:20', '12:36', 740, 756, 16],\n", - " [610, '12:21', '12:47', 741, 767, 26],\n", - " [611, '12:23', '12:45', 743, 765, 22],\n", - " [612, '12:24', '12:35', 744, 755, 11],\n", - " [613, '12:24', '13:48', 744, 828, 84],\n", - " [614, '12:25', '13:10', 745, 790, 45],\n", - " [615, '12:25', '13:04', 745, 784, 39],\n", - " [616, '12:26', '13:05', 746, 785, 39],\n", - " [617, '12:28', '13:54', 748, 834, 86],\n", - " [618, '12:28', '12:38', 748, 758, 10],\n", - " [619, '12:28', '13:15', 748, 795, 47],\n", - " [620, '12:29', '13:23', 749, 803, 54],\n", - " [621, '12:30', '13:41', 750, 821, 71],\n", - " [622, '12:30', '12:40', 750, 760, 10],\n", - " [623, '12:31', '13:15', 751, 795, 44],\n", - " [624, '12:31', '12:43', 751, 763, 12],\n", - " [625, '12:33', '12:48', 753, 768, 15],\n", - " [626, '12:33', '13:20', 753, 800, 47],\n", - " [627, '12:34', '13:58', 754, 838, 84],\n", - " [628, '12:34', '13:34', 754, 814, 60],\n", - " [629, '12:34', '12:45', 754, 765, 11],\n", - " [630, '12:35', '13:14', 755, 794, 39],\n", - " [631, '12:38', '13:25', 758, 805, 47],\n", - " [632, '12:38', '13:25', 758, 805, 47],\n", - " [633, '12:38', '14:04', 758, 844, 86],\n", - " [634, '12:39', '13:33', 759, 813, 54],\n", - " [635, '12:40', '13:51', 760, 831, 71],\n", - " [636, '12:40', '12:50', 760, 770, 10],\n", - " [637, '12:40', '12:56', 760, 776, 16],\n", - " [638, '12:41', '13:08', 761, 788, 27],\n", - " [639, '12:43', '13:30', 763, 810, 47],\n", - " [640, '12:44', '12:55', 764, 775, 11],\n", - " [641, '12:44', '14:08', 764, 848, 84],\n", - " [642, '12:45', '13:24', 765, 804, 39],\n", - " [643, '12:46', '12:58', 766, 778, 12],\n", - " [644, '12:46', '13:21', 766, 801, 35],\n", - " [645, '12:48', '14:14', 768, 854, 86],\n", - " [646, '12:48', '13:35', 768, 815, 47],\n", - " [647, '12:48', '12:58', 768, 778, 10],\n", - " [648, '12:48', '13:35', 768, 815, 47],\n", - " [649, '12:49', '13:43', 769, 823, 54],\n", - " [650, '12:50', '14:01', 770, 841, 71],\n", - " [651, '12:50', '13:00', 770, 780, 10],\n", - " [652, '12:50', '13:00', 770, 780, 10],\n", - " [653, '12:51', '13:17', 771, 797, 26],\n", - " [654, '12:53', '13:20', 773, 800, 27],\n", - " [655, '12:53', '13:24', 773, 804, 31],\n", - " [656, '12:53', '13:40', 773, 820, 47],\n", - " [657, '12:54', '14:18', 774, 858, 84],\n", - " [658, '12:54', '13:05', 774, 785, 11],\n", - " [659, '12:55', '13:34', 775, 814, 39],\n", - " [660, '12:58', '14:24', 778, 864, 86],\n", - " [661, '12:58', '13:25', 778, 805, 27],\n", - " [662, '12:58', '13:45', 778, 825, 47],\n", - " [663, '12:58', '13:45', 778, 825, 47],\n", - " [664, '12:59', '13:53', 779, 833, 54],\n", - " [665, '13:00', '13:10', 780, 790, 10],\n", - " [666, '13:00', '13:16', 780, 796, 16],\n", - " [667, '13:00', '14:11', 780, 851, 71],\n", - " [668, '13:01', '13:13', 781, 793, 12],\n", - " [669, '13:03', '13:34', 783, 814, 31],\n", - " [670, '13:03', '13:50', 783, 830, 47],\n", - " [671, '13:04', '13:15', 784, 795, 11],\n", - " [672, '13:04', '14:28', 784, 868, 84],\n", - " [673, '13:05', '13:44', 785, 824, 39],\n", - " [674, '13:08', '13:55', 788, 835, 47],\n", - " [675, '13:08', '14:34', 788, 874, 86],\n", - " [676, '13:08', '13:55', 788, 835, 47],\n", - " [677, '13:09', '14:03', 789, 843, 54],\n", - " [678, '13:10', '13:20', 790, 800, 10],\n", - " [679, '13:10', '14:21', 790, 861, 71],\n", - " [680, '13:13', '14:00', 793, 840, 47],\n", - " [681, '13:13', '13:40', 793, 820, 27],\n", - " [682, '13:14', '14:38', 794, 878, 84],\n", - " [683, '13:14', '13:25', 794, 805, 11],\n", - " [684, '13:15', '13:54', 795, 834, 39],\n", - " [685, '13:16', '13:28', 796, 808, 12],\n", - " [686, '13:18', '14:05', 798, 845, 47],\n", - " [687, '13:18', '14:44', 798, 884, 86],\n", - " [688, '13:18', '14:05', 798, 845, 47],\n", - " [689, '13:19', '14:13', 799, 853, 54],\n", - " [690, '13:20', '13:36', 800, 816, 16],\n", - " [691, '13:20', '14:31', 800, 871, 71],\n", - " [692, '13:20', '13:30', 800, 810, 10],\n", - " [693, '13:21', '13:47', 801, 827, 26],\n", - " [694, '13:23', '14:10', 803, 850, 47],\n", - " [695, '13:23', '13:49', 803, 829, 26],\n", - " [696, '13:24', '14:48', 804, 888, 84],\n", - " [697, '13:24', '13:35', 804, 815, 11],\n", - " [698, '13:25', '14:04', 805, 844, 39],\n", - " [699, '13:28', '14:15', 808, 855, 47],\n", - " [700, '13:28', '14:54', 808, 894, 86],\n", - " [701, '13:28', '13:55', 808, 835, 27],\n", - " [702, '13:28', '14:15', 808, 855, 47],\n", - " [703, '13:29', '14:23', 809, 863, 54],\n", - " [704, '13:30', '13:40', 810, 820, 10],\n", - " [705, '13:30', '14:41', 810, 881, 71],\n", - " [706, '13:31', '13:43', 811, 823, 12],\n", - " [707, '13:33', '14:20', 813, 860, 47],\n", - " [708, '13:34', '14:58', 814, 898, 84],\n", - " [709, '13:34', '13:45', 814, 825, 11],\n", - " [710, '13:35', '14:14', 815, 854, 39],\n", - " [711, '13:38', '14:25', 818, 865, 47],\n", - " [712, '13:38', '14:25', 818, 865, 47],\n", - " [713, '13:38', '15:04', 818, 904, 86],\n", - " [714, '13:39', '14:33', 819, 873, 54],\n", - " [715, '13:40', '13:50', 820, 830, 10],\n", - " [716, '13:40', '13:56', 820, 836, 16],\n", - " [717, '13:40', '14:51', 820, 891, 71],\n", - " [718, '13:43', '14:30', 823, 870, 47],\n", - " [719, '13:43', '14:10', 823, 850, 27],\n", - " [720, '13:44', '15:09', 824, 909, 85],\n", - " [721, '13:44', '13:55', 824, 835, 11],\n", - " [722, '13:45', '14:24', 825, 864, 39],\n", - " [723, '13:46', '13:58', 826, 838, 12],\n", - " [724, '13:48', '14:35', 828, 875, 47],\n", - " [725, '13:48', '15:14', 828, 914, 86],\n", - " [726, '13:48', '14:35', 828, 875, 47],\n", - " [727, '13:49', '14:43', 829, 883, 54],\n", - " [728, '13:50', '14:00', 830, 840, 10],\n", - " [729, '13:50', '15:01', 830, 901, 71],\n", - " [730, '13:51', '14:17', 831, 857, 26],\n", - " [731, '13:53', '14:40', 833, 880, 47],\n", - " [732, '13:53', '14:49', 833, 889, 56],\n", - " [733, '13:54', '14:05', 834, 845, 11],\n", - " [734, '13:54', '15:19', 834, 919, 85],\n", - " [735, '13:55', '14:34', 835, 874, 39],\n", - " [736, '13:57', '14:20', 837, 860, 23],\n", - " [737, '13:58', '15:24', 838, 924, 86],\n", - " [738, '13:58', '14:45', 838, 885, 47],\n", - " [739, '13:58', '14:45', 838, 885, 47],\n", - " [740, '13:58', '14:25', 838, 865, 27],\n", - " [741, '13:59', '14:53', 839, 893, 54],\n", - " [742, '14:00', '14:16', 840, 856, 16],\n", - " [743, '14:00', '14:10', 840, 850, 10],\n", - " [744, '14:00', '15:11', 840, 911, 71],\n", - " [745, '14:01', '14:13', 841, 853, 12],\n", - " [746, '14:03', '14:50', 843, 890, 47],\n", - " [747, '14:04', '14:15', 844, 855, 11],\n", - " [748, '14:04', '15:29', 844, 929, 85],\n", - " [749, '14:05', '14:44', 845, 884, 39],\n", - " [750, '14:08', '14:55', 848, 895, 47],\n", - " [751, '14:08', '14:55', 848, 895, 47],\n", - " [752, '14:08', '15:34', 848, 934, 86],\n", - " [753, '14:09', '15:03', 849, 903, 54],\n", - " [754, '14:10', '15:21', 850, 921, 71],\n", - " [755, '14:10', '14:20', 850, 860, 10],\n", - " [756, '14:13', '15:00', 853, 900, 47],\n", - " [757, '14:13', '14:40', 853, 880, 27],\n", - " [758, '14:14', '15:40', 854, 940, 86],\n", - " [759, '14:14', '14:25', 854, 865, 11],\n", - " [760, '14:15', '14:54', 855, 894, 39],\n", - " [761, '14:16', '14:28', 856, 868, 12],\n", - " [762, '14:18', '15:05', 858, 905, 47],\n", - " [763, '14:18', '15:44', 858, 944, 86],\n", - " [764, '14:18', '15:05', 858, 905, 47],\n", - " [765, '14:19', '15:13', 859, 913, 54],\n", - " [766, '14:20', '15:31', 860, 931, 71],\n", - " [767, '14:20', '14:30', 860, 870, 10],\n", - " [768, '14:20', '14:36', 860, 876, 16],\n", - " [769, '14:21', '14:47', 861, 887, 26],\n", - " [770, '14:23', '15:10', 863, 910, 47],\n", - " [771, '14:23', '14:45', 863, 885, 22],\n", - " [772, '14:24', '15:50', 864, 950, 86],\n", - " [773, '14:24', '14:35', 864, 875, 11],\n", - " [774, '14:25', '15:02', 865, 902, 37],\n", - " [775, '14:26', '14:52', 866, 892, 26],\n", - " [776, '14:28', '15:15', 868, 915, 47],\n", - " [777, '14:28', '14:55', 868, 895, 27],\n", - " [778, '14:28', '15:54', 868, 954, 86],\n", - " [779, '14:28', '15:15', 868, 915, 47],\n", - " [780, '14:29', '15:23', 869, 923, 54],\n", - " [781, '14:30', '15:41', 870, 941, 71],\n", - " [782, '14:30', '14:40', 870, 880, 10],\n", - " [783, '14:31', '14:43', 871, 883, 12],\n", - " [784, '14:33', '15:20', 873, 920, 47],\n", - " [785, '14:34', '16:00', 874, 960, 86],\n", - " [786, '14:34', '14:45', 874, 885, 11],\n", - " [787, '14:35', '15:11', 875, 911, 36],\n", - " [788, '14:38', '15:25', 878, 925, 47],\n", - " [789, '14:38', '15:25', 878, 925, 47],\n", - " [790, '14:38', '16:04', 878, 964, 86],\n", - " [791, '14:39', '15:33', 879, 933, 54],\n", - " [792, '14:40', '14:50', 880, 890, 10],\n", - " [793, '14:40', '15:51', 880, 951, 71],\n", - " [794, '14:40', '14:56', 880, 896, 16],\n", - " [795, '14:43', '15:30', 883, 930, 47],\n", - " [796, '14:43', '15:10', 883, 910, 27],\n", - " [797, '14:44', '15:00', 884, 900, 16],\n", - " [798, '14:44', '16:10', 884, 970, 86],\n", - " [799, '14:45', '15:19', 885, 919, 34],\n", - " [800, '14:46', '14:58', 886, 898, 12],\n", - " [801, '14:48', '15:35', 888, 935, 47],\n", - " [802, '14:48', '15:35', 888, 935, 47],\n", - " [803, '14:48', '17:04', 888, 1024, 136],\n", - " [804, '14:49', '15:43', 889, 943, 54],\n", - " [805, '14:50', '16:01', 890, 961, 71],\n", - " [806, '14:50', '15:00', 890, 900, 10],\n", - " [807, '14:51', '15:17', 891, 917, 26],\n", - " [808, '14:52', '15:27', 892, 927, 35],\n", - " [809, '14:52', '15:21', 892, 921, 29],\n", - " [810, '14:53', '15:40', 893, 940, 47],\n", - " [811, '14:54', '15:08', 894, 908, 14],\n", - " [812, '14:54', '16:20', 894, 980, 86],\n", - " [813, '14:58', '16:24', 898, 984, 86],\n", - " [814, '14:58', '15:45', 898, 945, 47],\n", - " [815, '14:58', '15:25', 898, 925, 27],\n", - " [816, '14:58', '15:45', 898, 945, 47],\n", - " [817, '14:59', '15:53', 899, 953, 54],\n", - " [818, '15:00', '15:10', 900, 910, 10],\n", - " [819, '15:00', '15:35', 900, 935, 35],\n", - " [820, '15:00', '16:11', 900, 971, 71],\n", - " [821, '15:00', '15:16', 900, 916, 16],\n", - " [822, '15:01', '15:13', 901, 913, 12],\n", - " [823, '15:02', '15:16', 902, 916, 14],\n", - " [824, '15:03', '15:50', 903, 950, 47],\n", - " [825, '15:04', '16:30', 904, 990, 86],\n", - " [826, '15:08', '16:34', 908, 994, 86],\n", - " [827, '15:08', '15:55', 908, 955, 47],\n", - " [828, '15:08', '15:55', 908, 955, 47],\n", - " [829, '15:08', '15:45', 908, 945, 37],\n", - " [830, '15:09', '16:14', 909, 974, 65],\n", - " [831, '15:09', '16:03', 909, 963, 54],\n", - " [832, '15:10', '16:21', 910, 981, 71],\n", - " [833, '15:10', '15:20', 910, 920, 10],\n", - " [834, '15:11', '15:24', 911, 924, 13],\n", - " [835, '15:12', '15:36', 912, 936, 24],\n", - " [836, '15:13', '16:00', 913, 960, 47],\n", - " [837, '15:13', '15:40', 913, 940, 27],\n", - " [838, '15:14', '16:40', 914, 1000, 86],\n", - " [839, '15:16', '15:28', 916, 928, 12],\n", - " [840, '15:16', '15:55', 916, 955, 39],\n", - " [841, '15:18', '16:05', 918, 965, 47],\n", - " [842, '15:18', '16:44', 918, 1004, 86],\n", - " [843, '15:18', '16:05', 918, 965, 47],\n", - " [844, '15:19', '16:13', 919, 973, 54],\n", - " [845, '15:19', '15:34', 919, 934, 15],\n", - " [846, '15:20', '15:30', 920, 930, 10],\n", - " [847, '15:20', '16:31', 920, 991, 71],\n", - " [848, '15:20', '15:36', 920, 936, 16],\n", - " [849, '15:21', '15:47', 921, 947, 26],\n", - " [850, '15:21', '16:06', 921, 966, 45],\n", - " [851, '15:23', '16:10', 923, 970, 47],\n", - " [852, '15:24', '16:50', 924, 1010, 86],\n", - " [853, '15:24', '16:05', 924, 965, 41],\n", - " [854, '15:27', '15:51', 927, 951, 24],\n", - " [855, '15:27', '15:44', 927, 944, 17],\n", - " [856, '15:28', '16:15', 928, 975, 47],\n", - " [857, '15:28', '16:54', 928, 1014, 86],\n", - " [858, '15:28', '16:15', 928, 975, 47],\n", - " [859, '15:28', '15:55', 928, 955, 27],\n", - " [860, '15:29', '16:23', 929, 983, 54],\n", - " [861, '15:30', '16:41', 930, 1001, 71],\n", - " [862, '15:30', '15:40', 930, 940, 10],\n", - " [863, '15:31', '15:43', 931, 943, 12],\n", - " [864, '15:33', '16:20', 933, 980, 47],\n", - " [865, '15:34', '17:00', 934, 1020, 86],\n", - " [866, '15:34', '16:15', 934, 975, 41],\n", - " [867, '15:35', '15:54', 935, 954, 19],\n", - " [868, '15:36', '16:21', 936, 981, 45],\n", - " [869, '15:38', '16:25', 938, 985, 47],\n", - " [870, '15:38', '16:25', 938, 985, 47],\n", - " [871, '15:38', '16:39', 938, 999, 61],\n", - " [872, '15:39', '16:33', 939, 993, 54],\n", - " [873, '15:40', '15:50', 940, 950, 10],\n", - " [874, '15:40', '16:51', 940, 1011, 71],\n", - " [875, '15:40', '15:56', 940, 956, 16],\n", - " [876, '15:43', '16:10', 943, 970, 27],\n", - " [877, '15:43', '16:30', 943, 990, 47],\n", - " [878, '15:44', '17:10', 944, 1030, 86],\n", - " [879, '15:44', '16:25', 944, 985, 41],\n", - " [880, '15:45', '16:04', 945, 964, 19],\n", - " [881, '15:46', '15:58', 946, 958, 12],\n", - " [882, '15:48', '16:35', 948, 995, 47],\n", - " [883, '15:48', '16:35', 948, 995, 47],\n", - " [884, '15:48', '17:14', 948, 1034, 86],\n", - " [885, '15:49', '16:43', 949, 1003, 54],\n", - " [886, '15:50', '16:00', 950, 960, 10],\n", - " [887, '15:50', '17:01', 950, 1021, 71],\n", - " [888, '15:51', '16:18', 951, 978, 27],\n", - " [889, '15:52', '16:36', 952, 996, 44],\n", - " [890, '15:53', '16:40', 953, 1000, 47],\n", - " [891, '15:54', '17:20', 954, 1040, 86],\n", - " [892, '15:54', '16:35', 954, 995, 41],\n", - " [893, '15:55', '16:14', 955, 974, 19],\n", - " [894, '15:58', '16:25', 958, 985, 27],\n", - " [895, '15:58', '16:45', 958, 1005, 47],\n", - " [896, '15:58', '16:45', 958, 1005, 47],\n", - " [897, '15:58', '17:24', 958, 1044, 86],\n", - " [898, '15:59', '17:11', 959, 1031, 72],\n", - " [899, '15:59', '16:53', 959, 1013, 54],\n", - " [900, '16:00', '16:10', 960, 970, 10],\n", - " [901, '16:00', '16:16', 960, 976, 16],\n", - " [902, '16:01', '16:13', 961, 973, 12],\n", - " [903, '16:03', '16:50', 963, 1010, 47],\n", - " [904, '16:04', '17:30', 964, 1050, 86],\n", - " [905, '16:04', '16:45', 964, 1005, 41],\n", - " [906, '16:05', '16:24', 965, 984, 19],\n", - " [907, '16:06', '16:51', 966, 1011, 45],\n", - " [908, '16:08', '16:55', 968, 1015, 47],\n", - " [909, '16:08', '17:34', 968, 1054, 86],\n", - " [910, '16:08', '16:55', 968, 1015, 47],\n", - " [911, '16:09', '17:03', 969, 1023, 54],\n", - " [912, '16:09', '17:21', 969, 1041, 72],\n", - " [913, '16:10', '16:20', 970, 980, 10],\n", - " [914, '16:13', '16:40', 973, 1000, 27],\n", - " [915, '16:13', '17:00', 973, 1020, 47],\n", - " [916, '16:14', '16:55', 974, 1015, 41],\n", - " [917, '16:14', '17:40', 974, 1060, 86],\n", - " [918, '16:15', '16:34', 975, 994, 19],\n", - " [919, '16:16', '16:28', 976, 988, 12],\n", - " [920, '16:18', '17:05', 978, 1025, 47],\n", - " [921, '16:18', '17:05', 978, 1025, 47],\n", - " [922, '16:18', '17:44', 978, 1064, 86],\n", - " [923, '16:19', '17:31', 979, 1051, 72],\n", - " [924, '16:19', '17:13', 979, 1033, 54],\n", - " [925, '16:20', '16:30', 980, 990, 10],\n", - " [926, '16:20', '16:36', 980, 996, 16],\n", - " [927, '16:21', '16:48', 981, 1008, 27],\n", - " [928, '16:22', '17:06', 982, 1026, 44],\n", - " [929, '16:23', '17:10', 983, 1030, 47],\n", - " [930, '16:24', '17:05', 984, 1025, 41],\n", - " [931, '16:24', '17:50', 984, 1070, 86],\n", - " [932, '16:25', '16:44', 985, 1004, 19],\n", - " [933, '16:28', '17:15', 988, 1035, 47],\n", - " [934, '16:28', '17:15', 988, 1035, 47],\n", - " [935, '16:28', '16:55', 988, 1015, 27],\n", - " [936, '16:28', '17:54', 988, 1074, 86],\n", - " [937, '16:29', '17:23', 989, 1043, 54],\n", - " [938, '16:29', '17:41', 989, 1061, 72],\n", - " [939, '16:30', '16:40', 990, 1000, 10],\n", - " [940, '16:31', '16:43', 991, 1003, 12],\n", - " [941, '16:33', '17:20', 993, 1040, 47],\n", - " [942, '16:34', '17:15', 994, 1035, 41],\n", - " [943, '16:34', '18:00', 994, 1080, 86],\n", - " [944, '16:35', '16:54', 995, 1014, 19],\n", - " [945, '16:36', '17:21', 996, 1041, 45],\n", - " [946, '16:38', '17:25', 998, 1045, 47],\n", - " [947, '16:38', '17:25', 998, 1045, 47],\n", - " [948, '16:38', '18:04', 998, 1084, 86],\n", - " [949, '16:39', '17:33', 999, 1053, 54],\n", - " [950, '16:39', '17:51', 999, 1071, 72],\n", - " [951, '16:40', '16:56', 1000, 1016, 16],\n", - " [952, '16:40', '16:50', 1000, 1010, 10],\n", - " [953, '16:43', '17:10', 1003, 1030, 27],\n", - " [954, '16:43', '17:30', 1003, 1050, 47],\n", - " [955, '16:44', '17:25', 1004, 1045, 41],\n", - " [956, '16:44', '18:10', 1004, 1090, 86],\n", - " [957, '16:45', '17:04', 1005, 1024, 19],\n", - " [958, '16:46', '16:58', 1006, 1018, 12],\n", - " [959, '16:48', '18:14', 1008, 1094, 86],\n", - " [960, '16:48', '17:35', 1008, 1055, 47],\n", - " [961, '16:48', '17:35', 1008, 1055, 47],\n", - " [962, '16:49', '18:01', 1009, 1081, 72],\n", - " [963, '16:49', '17:43', 1009, 1063, 54],\n", - " [964, '16:50', '17:00', 1010, 1020, 10],\n", - " [965, '16:51', '17:18', 1011, 1038, 27],\n", - " [966, '16:52', '17:36', 1012, 1056, 44],\n", - " [967, '16:53', '17:40', 1013, 1060, 47],\n", - " [968, '16:54', '18:20', 1014, 1100, 86],\n", - " [969, '16:54', '17:35', 1014, 1055, 41],\n", - " [970, '16:55', '17:14', 1015, 1034, 19],\n", - " [971, '16:58', '17:25', 1018, 1045, 27],\n", - " [972, '16:58', '17:45', 1018, 1065, 47],\n", - " [973, '16:58', '17:45', 1018, 1065, 47],\n", - " [974, '16:58', '18:24', 1018, 1104, 86],\n", - " [975, '16:59', '18:11', 1019, 1091, 72],\n", - " [976, '16:59', '17:53', 1019, 1073, 54],\n", - " [977, '17:00', '17:16', 1020, 1036, 16],\n", - " [978, '17:00', '17:10', 1020, 1030, 10],\n", - " [979, '17:01', '17:13', 1021, 1033, 12],\n", - " [980, '17:03', '17:50', 1023, 1070, 47],\n", - " [981, '17:04', '18:30', 1024, 1110, 86],\n", - " [982, '17:04', '17:45', 1024, 1065, 41],\n", - " [983, '17:05', '17:24', 1025, 1044, 19],\n", - " [984, '17:06', '17:51', 1026, 1071, 45],\n", - " [985, '17:08', '17:55', 1028, 1075, 47],\n", - " [986, '17:08', '17:55', 1028, 1075, 47],\n", - " [987, '17:08', '18:34', 1028, 1114, 86],\n", - " [988, '17:09', '18:03', 1029, 1083, 54],\n", - " [989, '17:09', '18:21', 1029, 1101, 72],\n", - " [990, '17:10', '17:20', 1030, 1040, 10],\n", - " [991, '17:13', '17:40', 1033, 1060, 27],\n", - " [992, '17:13', '18:00', 1033, 1080, 47],\n", - " [993, '17:14', '17:55', 1034, 1075, 41],\n", - " [994, '17:14', '18:40', 1034, 1120, 86],\n", - " [995, '17:15', '17:34', 1035, 1054, 19],\n", - " [996, '17:16', '17:28', 1036, 1048, 12],\n", - " [997, '17:18', '18:05', 1038, 1085, 47],\n", - " [998, '17:18', '18:05', 1038, 1085, 47],\n", - " [999, '17:18', '18:44', 1038, 1124, 86],\n", - " [1000, '17:19', '18:31', 1039, 1111, 72],\n", - " [1001, '17:19', '18:13', 1039, 1093, 54],\n", - " [1002, '17:20', '17:36', 1040, 1056, 16],\n", - " [1003, '17:20', '17:30', 1040, 1050, 10],\n", - " [1004, '17:21', '17:47', 1041, 1067, 26],\n", - " [1005, '17:22', '18:06', 1042, 1086, 44],\n", - " [1006, '17:23', '18:10', 1043, 1090, 47],\n", - " [1007, '17:24', '18:50', 1044, 1130, 86],\n", - " [1008, '17:24', '18:05', 1044, 1085, 41],\n", - " [1009, '17:25', '17:44', 1045, 1064, 19],\n", - " [1010, '17:28', '17:55', 1048, 1075, 27],\n", - " [1011, '17:28', '18:15', 1048, 1095, 47],\n", - " [1012, '17:28', '18:15', 1048, 1095, 47],\n", - " [1013, '17:28', '18:54', 1048, 1134, 86],\n", - " [1014, '17:29', '18:41', 1049, 1121, 72],\n", - " [1015, '17:29', '18:23', 1049, 1103, 54],\n", - " [1016, '17:30', '17:40', 1050, 1060, 10],\n", - " [1017, '17:31', '17:43', 1051, 1063, 12],\n", - " [1018, '17:33', '18:20', 1053, 1100, 47],\n", - " [1019, '17:34', '18:15', 1054, 1095, 41],\n", - " [1020, '17:34', '19:00', 1054, 1140, 86],\n", - " [1021, '17:35', '17:54', 1055, 1074, 19],\n", - " [1022, '17:36', '18:21', 1056, 1101, 45],\n", - " [1023, '17:38', '18:25', 1058, 1105, 47],\n", - " [1024, '17:38', '19:04', 1058, 1144, 86],\n", - " [1025, '17:38', '18:25', 1058, 1105, 47],\n", - " [1026, '17:39', '18:51', 1059, 1131, 72],\n", - " [1027, '17:39', '18:33', 1059, 1113, 54],\n", - " [1028, '17:40', '17:56', 1060, 1076, 16],\n", - " [1029, '17:40', '17:50', 1060, 1070, 10],\n", - " [1030, '17:43', '18:10', 1063, 1090, 27],\n", - " [1031, '17:43', '18:30', 1063, 1110, 47],\n", - " [1032, '17:44', '18:25', 1064, 1105, 41],\n", - " [1033, '17:44', '19:14', 1064, 1154, 90],\n", - " [1034, '17:45', '18:04', 1065, 1084, 19],\n", - " [1035, '17:46', '17:58', 1066, 1078, 12],\n", - " [1036, '17:48', '18:35', 1068, 1115, 47],\n", - " [1037, '17:48', '18:35', 1068, 1115, 47],\n", - " [1038, '17:48', '19:14', 1068, 1154, 86],\n", - " [1039, '17:49', '19:01', 1069, 1141, 72],\n", - " [1040, '17:49', '18:43', 1069, 1123, 54],\n", - " [1041, '17:50', '18:00', 1070, 1080, 10],\n", - " [1042, '17:51', '18:17', 1071, 1097, 26],\n", - " [1043, '17:52', '18:36', 1072, 1116, 44],\n", - " [1044, '17:53', '18:40', 1073, 1120, 47],\n", - " [1045, '17:54', '18:35', 1074, 1115, 41],\n", - " [1046, '17:54', '18:57', 1074, 1137, 63],\n", - " [1047, '17:55', '18:14', 1075, 1094, 19],\n", - " [1048, '17:58', '18:45', 1078, 1125, 47],\n", - " [1049, '17:58', '18:45', 1078, 1125, 47],\n", - " [1050, '17:58', '18:25', 1078, 1105, 27],\n", - " [1051, '17:58', '19:26', 1078, 1166, 88],\n", - " [1052, '17:59', '18:53', 1079, 1133, 54],\n", - " [1053, '18:00', '19:11', 1080, 1151, 71],\n", - " [1054, '18:00', '18:10', 1080, 1090, 10],\n", - " [1055, '18:00', '18:16', 1080, 1096, 16],\n", - " [1056, '18:01', '18:13', 1081, 1093, 12],\n", - " [1057, '18:03', '18:50', 1083, 1130, 47],\n", - " [1058, '18:04', '18:45', 1084, 1125, 41],\n", - " [1059, '18:04', '19:29', 1084, 1169, 85],\n", - " [1060, '18:05', '18:24', 1085, 1104, 19],\n", - " [1061, '18:06', '18:51', 1086, 1131, 45],\n", - " [1062, '18:08', '18:55', 1088, 1135, 47],\n", - " [1063, '18:08', '19:06', 1088, 1146, 58],\n", - " [1064, '18:08', '18:55', 1088, 1135, 47],\n", - " [1065, '18:09', '19:03', 1089, 1143, 54],\n", - " [1066, '18:10', '18:20', 1090, 1100, 10],\n", - " [1067, '18:10', '19:21', 1090, 1161, 71],\n", - " [1068, '18:13', '19:00', 1093, 1140, 47],\n", - " [1069, '18:13', '18:40', 1093, 1120, 27],\n", - " [1070, '18:14', '19:43', 1094, 1183, 89],\n", - " [1071, '18:14', '18:55', 1094, 1135, 41],\n", - " [1072, '18:15', '18:34', 1095, 1114, 19],\n", - " [1073, '18:16', '18:28', 1096, 1108, 12],\n", - " [1074, '18:17', '18:27', 1097, 1107, 10],\n", - " [1075, '18:18', '19:41', 1098, 1181, 83],\n", - " [1076, '18:18', '18:58', 1098, 1138, 40],\n", - " [1077, '18:18', '19:05', 1098, 1145, 47],\n", - " [1078, '18:19', '19:13', 1099, 1153, 54],\n", - " [1079, '18:20', '19:31', 1100, 1171, 71],\n", - " [1080, '18:20', '18:36', 1100, 1116, 16],\n", - " [1081, '18:20', '18:30', 1100, 1110, 10],\n", - " [1082, '18:22', '19:05', 1102, 1145, 43],\n", - " [1083, '18:23', '19:05', 1103, 1145, 42],\n", - " [1084, '18:24', '19:27', 1104, 1167, 63],\n", - " [1085, '18:24', '19:05', 1104, 1145, 41],\n", - " [1086, '18:25', '18:44', 1105, 1124, 19],\n", - " [1087, '18:28', '19:25', 1108, 1165, 57],\n", - " [1088, '18:28', '18:55', 1108, 1135, 27],\n", - " [1089, '18:28', '19:08', 1108, 1148, 40],\n", - " [1090, '18:28', '19:15', 1108, 1155, 47],\n", - " [1091, '18:29', '19:23', 1109, 1163, 54],\n", - " [1092, '18:30', '19:05', 1110, 1145, 35],\n", - " [1093, '18:30', '18:40', 1110, 1120, 10],\n", - " [1094, '18:31', '18:43', 1111, 1123, 12],\n", - " [1095, '18:33', '19:15', 1113, 1155, 42],\n", - " [1096, '18:34', '19:58', 1114, 1198, 84],\n", - " [1097, '18:34', '19:14', 1114, 1154, 40],\n", - " [1098, '18:35', '18:55', 1115, 1135, 20],\n", - " [1099, '18:36', '19:20', 1116, 1160, 44],\n", - " [1100, '18:38', '19:25', 1118, 1165, 47],\n", - " [1101, '18:38', '19:23', 1118, 1163, 45],\n", - " [1102, '18:38', '19:56', 1118, 1196, 78],\n", - " [1103, '18:39', '19:33', 1119, 1173, 54],\n", - " [1104, '18:40', '18:50', 1120, 1130, 10],\n", - " [1105, '18:40', '19:45', 1120, 1185, 65],\n", - " [1106, '18:40', '18:56', 1120, 1136, 16],\n", - " [1107, '18:43', '19:10', 1123, 1150, 27],\n", - " [1108, '18:43', '19:30', 1123, 1170, 47],\n", - " [1109, '18:44', '19:24', 1124, 1164, 40],\n", - " [1110, '18:45', '19:05', 1125, 1145, 20],\n", - " [1111, '18:46', '18:58', 1126, 1138, 12],\n", - " [1112, '18:48', '19:35', 1128, 1175, 47],\n", - " [1113, '18:48', '20:12', 1128, 1212, 84],\n", - " [1114, '18:48', '20:11', 1128, 1211, 83],\n", - " [1115, '18:48', '19:28', 1128, 1168, 40],\n", - " [1116, '18:49', '19:43', 1129, 1183, 54],\n", - " [1117, '18:50', '19:00', 1130, 1140, 10],\n", - " [1118, '18:51', '19:01', 1131, 1141, 10],\n", - " [1119, '18:53', '19:35', 1133, 1175, 42],\n", - " [1120, '18:53', '19:15', 1133, 1155, 22],\n", - " [1121, '18:53', '20:00', 1133, 1200, 67],\n", - " [1122, '18:55', '19:15', 1135, 1155, 20],\n", - " [1123, '18:55', '19:34', 1135, 1174, 39],\n", - " [1124, '18:58', '19:38', 1138, 1178, 40],\n", - " [1125, '18:59', '19:53', 1139, 1193, 54],\n", - " [1126, '18:59', '19:50', 1139, 1190, 51],\n", - " [1127, '18:59', '19:53', 1139, 1193, 54],\n", - " [1128, '19:00', '19:16', 1140, 1156, 16],\n", - " [1129, '19:00', '19:10', 1140, 1150, 10],\n", - " [1130, '19:00', '19:16', 1140, 1156, 16],\n", - " [1131, '19:01', '19:13', 1141, 1153, 12],\n", - " [1132, '19:03', '20:26', 1143, 1226, 83],\n", - " [1133, '19:03', '19:45', 1143, 1185, 42],\n", - " [1134, '19:05', '19:44', 1145, 1184, 39],\n", - " [1135, '19:05', '19:25', 1145, 1165, 20],\n", - " [1136, '19:08', '20:15', 1148, 1215, 67],\n", - " [1137, '19:08', '19:35', 1148, 1175, 27],\n", - " [1138, '19:09', '19:49', 1149, 1189, 40],\n", - " [1139, '19:09', '20:03', 1149, 1203, 54],\n", - " [1140, '19:10', '19:20', 1150, 1160, 10],\n", - " [1141, '19:10', '19:20', 1150, 1160, 10],\n", - " [1142, '19:11', '19:53', 1151, 1193, 42],\n", - " [1143, '19:14', '20:26', 1154, 1226, 72],\n", - " [1144, '19:14', '19:35', 1154, 1175, 21],\n", - " [1145, '19:14', '19:24', 1154, 1164, 10],\n", - " [1146, '19:14', '20:05', 1154, 1205, 51],\n", - " [1147, '19:15', '19:30', 1155, 1170, 15],\n", - " [1148, '19:15', '19:54', 1155, 1194, 39],\n", - " [1149, '19:18', '20:39', 1158, 1239, 81],\n", - " [1150, '19:18', '20:00', 1158, 1200, 42],\n", - " [1151, '19:19', '20:14', 1159, 1214, 55],\n", - " [1152, '19:20', '19:30', 1160, 1170, 10],\n", - " [1153, '19:20', '19:36', 1160, 1176, 16],\n", - " [1154, '19:21', '19:31', 1161, 1171, 10],\n", - " [1155, '19:23', '20:30', 1163, 1230, 67],\n", - " [1156, '19:23', '19:35', 1163, 1175, 12],\n", - " [1157, '19:24', '19:45', 1164, 1185, 21],\n", - " [1158, '19:24', '19:45', 1164, 1185, 21],\n", - " [1159, '19:25', '20:04', 1165, 1204, 39],\n", - " [1160, '19:26', '20:08', 1166, 1208, 42],\n", - " [1161, '19:29', '20:02', 1169, 1202, 33],\n", - " [1162, '19:29', '20:18', 1169, 1218, 49],\n", - " [1163, '19:29', '20:41', 1169, 1241, 72],\n", - " [1164, '19:30', '19:40', 1170, 1180, 10],\n", - " [1165, '19:33', '20:54', 1173, 1254, 81],\n", - " [1166, '19:33', '20:17', 1173, 1217, 44],\n", - " [1167, '19:34', '19:55', 1174, 1195, 21],\n", - " [1168, '19:35', '20:14', 1175, 1214, 39],\n", - " [1169, '19:38', '20:05', 1178, 1205, 27],\n", - " [1170, '19:38', '20:45', 1178, 1245, 67],\n", - " [1171, '19:39', '20:12', 1179, 1212, 33],\n", - " [1172, '19:40', '19:50', 1180, 1190, 10],\n", - " [1173, '19:40', '19:56', 1180, 1196, 16],\n", - " [1174, '19:41', '20:27', 1181, 1227, 46],\n", - " [1175, '19:43', '19:55', 1183, 1195, 12],\n", - " [1176, '19:44', '20:05', 1184, 1205, 21],\n", - " [1177, '19:44', '20:33', 1184, 1233, 49],\n", - " [1178, '19:44', '21:00', 1184, 1260, 76],\n", - " [1179, '19:45', '20:24', 1185, 1224, 39],\n", - " [1180, '19:48', '20:37', 1188, 1237, 49],\n", - " [1181, '19:48', '21:09', 1188, 1269, 81],\n", - " [1182, '19:50', '20:00', 1190, 1200, 10],\n", - " [1183, '19:52', '20:29', 1192, 1229, 37],\n", - " [1184, '19:53', '20:08', 1193, 1208, 15],\n", - " [1185, '19:53', '21:02', 1193, 1262, 69],\n", - " [1186, '19:53', '20:20', 1193, 1220, 27],\n", - " [1187, '19:54', '20:19', 1194, 1219, 25],\n", - " [1188, '19:55', '20:34', 1195, 1234, 39],\n", - " [1189, '19:56', '20:34', 1196, 1234, 38],\n", - " [1190, '19:59', '20:48', 1199, 1248, 49],\n", - " [1191, '19:59', '21:20', 1199, 1280, 81],\n", - " [1192, '20:00', '20:16', 1200, 1216, 16],\n", - " [1193, '20:00', '20:10', 1200, 1210, 10],\n", - " [1194, '20:03', '20:42', 1203, 1242, 39],\n", - " [1195, '20:03', '21:24', 1203, 1284, 81],\n", - " [1196, '20:04', '20:29', 1204, 1229, 25],\n", - " [1197, '20:05', '20:48', 1205, 1248, 43],\n", - " [1198, '20:07', '20:44', 1207, 1244, 37],\n", - " [1199, '20:08', '20:40', 1208, 1240, 32],\n", - " [1200, '20:08', '20:35', 1208, 1235, 27],\n", - " [1201, '20:10', '20:20', 1210, 1220, 10],\n", - " [1202, '20:10', '20:22', 1210, 1222, 12],\n", - " [1203, '20:11', '20:47', 1211, 1247, 36],\n", - " [1204, '20:14', '21:04', 1214, 1264, 50],\n", - " [1205, '20:14', '21:03', 1214, 1263, 49],\n", - " [1206, '20:17', '21:03', 1217, 1263, 46],\n", - " [1207, '20:18', '21:39', 1218, 1299, 81],\n", - " [1208, '20:20', '20:30', 1220, 1230, 10],\n", - " [1209, '20:20', '20:57', 1220, 1257, 37],\n", - " [1210, '20:20', '20:36', 1220, 1236, 16],\n", - " [1211, '20:22', '20:59', 1222, 1259, 37],\n", - " [1212, '20:22', '20:42', 1222, 1242, 20],\n", - " [1213, '20:24', '20:49', 1224, 1249, 25],\n", - " [1214, '20:27', '21:22', 1227, 1282, 55],\n", - " [1215, '20:29', '21:18', 1229, 1278, 49],\n", - " [1216, '20:30', '21:07', 1230, 1267, 37],\n", - " [1217, '20:30', '20:40', 1230, 1240, 10],\n", - " [1218, '20:30', '20:40', 1230, 1240, 10],\n", - " [1219, '20:30', '21:40', 1230, 1300, 70],\n", - " [1220, '20:32', '21:18', 1232, 1278, 46],\n", - " [1221, '20:35', '21:54', 1235, 1314, 79],\n", - " [1222, '20:37', '21:14', 1237, 1274, 37],\n", - " [1223, '20:38', '21:08', 1238, 1268, 30],\n", - " [1224, '20:40', '20:50', 1240, 1250, 10],\n", - " [1225, '20:40', '21:17', 1240, 1277, 37],\n", - " [1226, '20:40', '20:56', 1240, 1256, 16],\n", - " [1227, '20:44', '21:33', 1244, 1293, 49],\n", - " [1228, '20:47', '21:33', 1247, 1293, 46],\n", - " [1229, '20:47', '21:42', 1247, 1302, 55],\n", - " [1230, '20:50', '21:00', 1250, 1260, 10],\n", - " [1231, '20:50', '22:00', 1250, 1320, 70],\n", - " [1232, '20:50', '22:09', 1250, 1329, 79],\n", - " [1233, '20:50', '21:27', 1250, 1287, 37],\n", - " [1234, '20:52', '21:29', 1252, 1289, 37],\n", - " [1235, '20:53', '21:20', 1253, 1280, 27],\n", - " [1236, '20:56', '21:11', 1256, 1271, 15],\n", - " [1237, '20:59', '21:48', 1259, 1308, 49],\n", - " [1238, '21:00', '21:10', 1260, 1270, 10],\n", - " [1239, '21:00', '21:37', 1260, 1297, 37],\n", - " [1240, '21:02', '21:48', 1262, 1308, 46],\n", - " [1241, '21:05', '22:24', 1265, 1344, 79],\n", - " [1242, '21:07', '21:44', 1267, 1304, 37],\n", - " [1243, '21:07', '22:02', 1267, 1322, 55],\n", - " [1244, '21:08', '21:38', 1268, 1298, 30],\n", - " [1245, '21:10', '22:25', 1270, 1345, 75],\n", - " [1246, '21:10', '21:20', 1270, 1280, 10],\n", - " [1247, '21:10', '21:47', 1270, 1307, 37],\n", - " [1248, '21:14', '22:03', 1274, 1323, 49],\n", - " [1249, '21:17', '22:03', 1277, 1323, 46],\n", - " [1250, '21:20', '22:18', 1280, 1338, 58],\n", - " [1251, '21:20', '21:57', 1280, 1317, 37],\n", - " [1252, '21:20', '21:30', 1280, 1290, 10],\n", - " [1253, '21:22', '21:59', 1282, 1319, 37],\n", - " [1254, '21:24', '21:49', 1284, 1309, 25],\n", - " [1255, '21:27', '22:21', 1287, 1341, 54],\n", - " [1256, '21:30', '22:07', 1290, 1327, 37],\n", - " [1257, '21:30', '22:20', 1290, 1340, 50],\n", - " [1258, '21:30', '21:40', 1290, 1300, 10],\n", - " [1259, '21:32', '22:18', 1292, 1338, 46],\n", - " [1260, '21:32', '22:01', 1292, 1321, 29],\n", - " [1261, '21:35', '22:54', 1295, 1374, 79],\n", - " [1262, '21:37', '22:14', 1297, 1334, 37],\n", - " [1263, '21:39', '21:55', 1299, 1315, 16],\n", - " [1264, '21:40', '22:17', 1300, 1337, 37],\n", - " [1265, '21:40', '21:50', 1300, 1310, 10],\n", - " [1266, '21:41', '22:08', 1301, 1328, 27],\n", - " [1267, '21:47', '22:16', 1307, 1336, 29],\n", - " [1268, '21:47', '22:51', 1307, 1371, 64],\n", - " [1269, '21:47', '22:33', 1307, 1353, 46],\n", - " [1270, '21:48', '22:03', 1308, 1323, 15],\n", - " [1271, '21:50', '22:55', 1310, 1375, 65],\n", - " [1272, '21:50', '22:27', 1310, 1347, 37],\n", - " [1273, '21:50', '22:00', 1310, 1320, 10],\n", - " [1274, '21:52', '22:29', 1312, 1349, 37],\n", - " [1275, '21:53', '22:19', 1313, 1339, 26],\n", - " [1276, '22:00', '22:38', 1320, 1358, 38],\n", - " [1277, '22:00', '22:10', 1320, 1330, 10],\n", - " [1278, '22:02', '22:12', 1322, 1332, 10],\n", - " [1279, '22:02', '22:48', 1322, 1368, 46],\n", - " [1280, '22:04', '22:31', 1324, 1351, 27],\n", - " [1281, '22:05', '23:24', 1325, 1404, 79],\n", - " [1282, '22:07', '22:44', 1327, 1364, 37],\n", - " [1283, '22:07', '22:39', 1327, 1359, 32],\n", - " [1284, '22:09', '22:25', 1329, 1345, 16],\n", - " [1285, '22:10', '23:25', 1330, 1405, 75],\n", - " [1286, '22:13', '22:38', 1333, 1358, 25],\n", - " [1287, '22:13', '22:53', 1333, 1373, 40],\n", - " [1288, '22:17', '22:27', 1337, 1347, 10],\n", - " [1289, '22:17', '23:03', 1337, 1383, 46],\n", - " [1290, '22:19', '22:46', 1339, 1366, 27],\n", - " [1291, '22:22', '22:59', 1342, 1379, 37],\n", - " [1292, '22:24', '22:48', 1344, 1368, 24],\n", - " [1293, '22:27', '22:52', 1347, 1372, 25],\n", - " [1294, '22:27', '23:21', 1347, 1401, 54],\n", - " [1295, '22:28', '23:08', 1348, 1388, 40],\n", - " [1296, '22:30', '23:17', 1350, 1397, 47],\n", - " [1297, '22:32', '22:42', 1352, 1362, 10],\n", - " [1298, '22:32', '23:11', 1352, 1391, 39],\n", - " [1299, '22:34', '23:01', 1354, 1381, 27],\n", - " [1300, '22:35', '23:54', 1355, 1434, 79],\n", - " [1301, '22:37', '23:14', 1357, 1394, 37],\n", - " [1302, '22:43', '23:23', 1363, 1403, 40],\n", - " [1303, '22:43', '23:08', 1363, 1388, 25],\n", - " [1304, '22:47', '23:33', 1367, 1413, 46],\n", - " [1305, '22:47', '22:57', 1367, 1377, 10],\n", - " [1306, '22:49', '23:16', 1369, 1396, 27],\n", - " [1307, '22:52', '23:29', 1372, 1409, 37],\n", - " [1308, '22:53', '23:15', 1373, 1395, 22],\n", - " [1309, '22:55', '23:55', 1375, 1435, 60],\n", - " [1310, '22:57', '23:51', 1377, 1431, 54],\n", - " [1311, '22:58', '23:38', 1378, 1418, 40],\n", - " [1312, '23:02', '23:41', 1382, 1421, 39],\n", - " [1313, '23:02', '23:12', 1382, 1392, 10],\n", - " [1314, '23:04', '23:31', 1384, 1411, 27],\n", - " [1315, '23:05', '00:24', 1385, 1464, 79],\n", - " [1316, '23:07', '23:44', 1387, 1424, 37],\n", - " [1317, '23:13', '23:53', 1393, 1433, 40],\n", - " [1318, '23:13', '23:38', 1393, 1418, 25],\n", - " [1319, '23:17', '00:03', 1397, 1443, 46],\n", - " [1320, '23:17', '23:27', 1397, 1407, 10],\n", - " [1321, '23:19', '23:46', 1399, 1426, 27],\n", - " [1322, '23:22', '23:59', 1402, 1439, 37],\n", - " [1323, '23:25', '00:25', 1405, 1465, 60],\n", - " [1324, '23:27', '00:21', 1407, 1461, 54],\n", - " [1325, '23:28', '00:08', 1408, 1448, 40],\n", - " [1326, '23:32', '23:42', 1412, 1422, 10],\n", - " [1327, '23:34', '00:01', 1414, 1441, 27],\n", - " [1328, '23:35', '01:05', 1415, 1505, 90],\n", - " [1329, '23:37', '00:09', 1417, 1449, 32],\n", - " [1330, '23:43', '00:23', 1423, 1463, 40],\n", - " [1331, '23:43', '00:08', 1423, 1448, 25],\n", - " [1332, '23:46', '00:01', 1426, 1441, 15],\n", - " [1333, '23:47', '23:57', 1427, 1437, 10],\n", - " [1334, '23:47', '00:33', 1427, 1473, 46],\n", - " [1335, '23:52', '00:24', 1432, 1464, 32],\n", - " [1336, '23:55', '00:49', 1435, 1489, 54],\n", - " [1337, '23:57', '00:57', 1437, 1497, 60],\n", - " [1338, '23:58', '00:38', 1438, 1478, 40],\n", - " [1339, '00:02', '00:12', 1442, 1452, 10],\n", - " [1340, '00:07', '00:39', 1447, 1479, 32],\n", - " [1341, '00:13', '00:38', 1453, 1478, 25],\n", - " [1342, '00:13', '00:51', 1453, 1491, 38],\n", - " [1343, '00:15', '01:14', 1455, 1514, 59],\n", - " [1344, '00:17', '01:23', 1457, 1523, 66],\n", - " [1345, '00:23', '00:33', 1463, 1473, 10],\n", - " [1346, '00:24', '00:40', 1464, 1480, 16],\n", - " [1347, '00:25', '01:12', 1465, 1512, 47],\n", - " [1348, '00:28', '01:07', 1468, 1507, 39],\n", - " [1349, '00:33', '01:05', 1473, 1505, 32],\n", - " [1350, '00:43', '01:21', 1483, 1521, 38],\n", - " [1351, '00:44', '00:54', 1484, 1494, 10],\n", - " [1352, '00:47', '01:09', 1487, 1509, 22],\n", - " [1353, '00:47', '01:26', 1487, 1526, 39],\n", - " [1354, '00:54', '01:04', 1494, 1504, 10],\n", - " [1355, '00:57', '01:07', 1497, 1507, 10]\n", + " [0, \"04:18\", \"05:00\", 258, 300, 42],\n", + " [1, \"04:27\", \"05:08\", 267, 308, 41],\n", + " [2, \"04:29\", \"05:26\", 269, 326, 57],\n", + " [3, \"04:29\", \"04:55\", 269, 295, 26],\n", + " [4, \"04:30\", \"04:53\", 270, 293, 23],\n", + " [5, \"04:30\", \"04:51\", 270, 291, 21],\n", + " [6, \"04:31\", \"04:53\", 271, 293, 22],\n", + " [7, \"04:33\", \"05:15\", 273, 315, 42],\n", + " [8, \"04:34\", \"04:44\", 274, 284, 10],\n", + " [9, \"04:34\", \"05:03\", 274, 303, 29],\n", + " [10, \"04:35\", \"04:50\", 275, 290, 15],\n", + " [11, \"04:36\", \"04:46\", 276, 286, 10],\n", + " [12, \"04:37\", \"05:18\", 277, 318, 41],\n", + " [13, \"04:41\", \"05:13\", 281, 313, 32],\n", + " [14, \"04:42\", \"05:23\", 282, 323, 41],\n", + " [15, \"04:43\", \"04:53\", 283, 293, 10],\n", + " [16, \"04:44\", \"05:45\", 284, 345, 61],\n", + " [17, \"04:45\", \"05:11\", 285, 311, 26],\n", + " [18, \"04:46\", \"05:01\", 286, 301, 15],\n", + " [19, \"04:46\", \"04:56\", 286, 296, 10],\n", + " [20, \"04:47\", \"05:14\", 287, 314, 27],\n", + " [21, \"04:48\", \"05:30\", 288, 330, 42],\n", + " [22, \"04:49\", \"05:41\", 289, 341, 52],\n", + " [23, \"04:49\", \"05:18\", 289, 318, 29],\n", + " [24, \"04:50\", \"05:33\", 290, 333, 43],\n", + " [25, \"04:52\", \"05:56\", 292, 356, 64],\n", + " [26, \"04:52\", \"05:07\", 292, 307, 15],\n", + " [27, \"04:53\", \"05:19\", 293, 319, 26],\n", + " [28, \"04:53\", \"05:23\", 293, 323, 30],\n", + " [29, \"04:55\", \"05:27\", 295, 327, 32],\n", + " [30, \"04:57\", \"05:38\", 297, 338, 41],\n", + " [31, \"05:00\", \"06:00\", 300, 360, 60],\n", + " [32, \"05:00\", \"05:54\", 300, 354, 54],\n", + " [33, \"05:01\", \"05:33\", 301, 333, 32],\n", + " [34, \"05:01\", \"05:26\", 301, 326, 25],\n", + " [35, \"05:02\", \"05:29\", 302, 329, 27],\n", + " [36, \"05:02\", \"05:12\", 302, 312, 10],\n", + " [37, \"05:03\", \"05:45\", 303, 345, 42],\n", + " [38, \"05:03\", \"05:18\", 303, 318, 15],\n", + " [39, \"05:03\", \"06:28\", 303, 388, 85],\n", + " [40, \"05:03\", \"05:13\", 303, 313, 10],\n", + " [41, \"05:04\", \"06:24\", 304, 384, 80],\n", + " [42, \"05:07\", \"05:44\", 307, 344, 37],\n", + " [43, \"05:08\", \"05:48\", 308, 348, 40],\n", + " [44, \"05:10\", \"06:06\", 310, 366, 56],\n", + " [45, \"05:11\", \"05:37\", 311, 337, 26],\n", + " [46, \"05:11\", \"05:53\", 311, 353, 42],\n", + " [47, \"05:13\", \"06:15\", 313, 375, 62],\n", + " [48, \"05:13\", \"05:38\", 313, 338, 25],\n", + " [49, \"05:16\", \"05:44\", 316, 344, 28],\n", + " [50, \"05:17\", \"05:27\", 317, 327, 10],\n", + " [51, \"05:18\", \"06:40\", 318, 400, 82],\n", + " [52, \"05:18\", \"06:03\", 318, 363, 45],\n", + " [53, \"05:18\", \"06:11\", 318, 371, 53],\n", + " [54, \"05:18\", \"06:00\", 318, 360, 42],\n", + " [55, \"05:19\", \"06:34\", 319, 394, 75],\n", + " [56, \"05:20\", \"06:17\", 320, 377, 57],\n", + " [57, \"05:22\", \"05:59\", 322, 359, 37],\n", + " [58, \"05:24\", \"05:48\", 324, 348, 24],\n", + " [59, \"05:25\", \"05:40\", 325, 340, 15],\n", + " [60, \"05:26\", \"06:08\", 326, 368, 42],\n", + " [61, \"05:27\", \"06:30\", 327, 390, 63],\n", + " [62, \"05:27\", \"05:54\", 327, 354, 27],\n", + " [63, \"05:28\", \"05:53\", 328, 353, 25],\n", + " [64, \"05:29\", \"05:44\", 329, 344, 15],\n", + " [65, \"05:30\", \"05:40\", 330, 340, 10],\n", + " [66, \"05:30\", \"05:40\", 330, 340, 10],\n", + " [67, \"05:30\", \"05:40\", 330, 340, 10],\n", + " [68, \"05:32\", \"06:53\", 332, 413, 81],\n", + " [69, \"05:33\", \"07:00\", 333, 420, 87],\n", + " [70, \"05:33\", \"06:15\", 333, 375, 42],\n", + " [71, \"05:33\", \"05:47\", 333, 347, 14],\n", + " [72, \"05:37\", \"06:13\", 337, 373, 36],\n", + " [73, \"05:37\", \"06:05\", 337, 365, 28],\n", + " [74, \"05:38\", \"06:33\", 338, 393, 55],\n", + " [75, \"05:38\", \"06:04\", 338, 364, 26],\n", + " [76, \"05:38\", \"06:18\", 338, 378, 40],\n", + " [77, \"05:39\", \"05:54\", 339, 354, 15],\n", + " [78, \"05:40\", \"05:56\", 340, 356, 16],\n", + " [79, \"05:40\", \"06:41\", 340, 401, 61],\n", + " [80, \"05:40\", \"05:50\", 340, 350, 10],\n", + " [81, \"05:41\", \"06:23\", 341, 383, 42],\n", + " [82, \"05:41\", \"06:01\", 341, 361, 20],\n", + " [83, \"05:43\", \"06:08\", 343, 368, 25],\n", + " [84, \"05:44\", \"07:10\", 344, 430, 86],\n", + " [85, \"05:44\", \"05:55\", 344, 355, 11],\n", + " [86, \"05:45\", \"06:44\", 345, 404, 59],\n", + " [87, \"05:47\", \"06:17\", 347, 377, 30],\n", + " [88, \"05:48\", \"07:08\", 348, 428, 80],\n", + " [89, \"05:48\", \"06:30\", 348, 390, 42],\n", + " [90, \"05:50\", \"06:50\", 350, 410, 60],\n", + " [91, \"05:50\", \"06:00\", 350, 360, 10],\n", + " [92, \"05:50\", \"06:00\", 350, 360, 10],\n", + " [93, \"05:50\", \"06:51\", 350, 411, 61],\n", + " [94, \"05:52\", \"06:33\", 352, 393, 41],\n", + " [95, \"05:52\", \"06:36\", 352, 396, 44],\n", + " [96, \"05:52\", \"06:23\", 352, 383, 31],\n", + " [97, \"05:54\", \"06:14\", 354, 374, 20],\n", + " [98, \"05:54\", \"07:20\", 354, 440, 86],\n", + " [99, \"05:55\", \"06:40\", 355, 400, 45],\n", + " [100, \"05:55\", \"06:27\", 355, 387, 32],\n", + " [101, \"05:56\", \"06:35\", 356, 395, 39],\n", + " [102, \"05:56\", \"06:06\", 356, 366, 10],\n", + " [103, \"05:57\", \"06:21\", 357, 381, 24],\n", + " [104, \"05:58\", \"07:23\", 358, 443, 85],\n", + " [105, \"05:58\", \"06:23\", 358, 383, 25],\n", + " [106, \"05:58\", \"06:08\", 358, 368, 10],\n", + " [107, \"05:58\", \"06:43\", 358, 403, 45],\n", + " [108, \"06:00\", \"06:10\", 360, 370, 10],\n", + " [109, \"06:00\", \"06:16\", 360, 376, 16],\n", + " [110, \"06:00\", \"07:01\", 360, 421, 61],\n", + " [111, \"06:01\", \"07:00\", 361, 420, 59],\n", + " [112, \"06:01\", \"06:13\", 361, 373, 12],\n", + " [113, \"06:01\", \"06:45\", 361, 405, 44],\n", + " [114, \"06:03\", \"06:50\", 363, 410, 47],\n", + " [115, \"06:04\", \"06:37\", 364, 397, 33],\n", + " [116, \"06:04\", \"07:30\", 364, 450, 86],\n", + " [117, \"06:05\", \"06:24\", 365, 384, 19],\n", + " [118, \"06:06\", \"06:51\", 366, 411, 45],\n", + " [119, \"06:07\", \"06:43\", 367, 403, 36],\n", + " [120, \"06:08\", \"07:30\", 368, 450, 82],\n", + " [121, \"06:10\", \"06:20\", 370, 380, 10],\n", + " [122, \"06:10\", \"07:17\", 370, 437, 67],\n", + " [123, \"06:11\", \"06:54\", 371, 414, 43],\n", + " [124, \"06:11\", \"06:21\", 371, 381, 10],\n", + " [125, \"06:13\", \"06:38\", 373, 398, 25],\n", + " [126, \"06:13\", \"06:58\", 373, 418, 45],\n", + " [127, \"06:13\", \"06:53\", 373, 413, 40],\n", + " [128, \"06:14\", \"07:03\", 374, 423, 49],\n", + " [129, \"06:14\", \"06:47\", 374, 407, 33],\n", + " [130, \"06:14\", \"07:40\", 374, 460, 86],\n", + " [131, \"06:15\", \"07:15\", 375, 435, 60],\n", + " [132, \"06:16\", \"06:28\", 376, 388, 12],\n", + " [133, \"06:16\", \"06:26\", 376, 386, 10],\n", + " [134, \"06:17\", \"06:34\", 377, 394, 17],\n", + " [135, \"06:18\", \"07:06\", 378, 426, 48],\n", + " [136, \"06:18\", \"07:38\", 378, 458, 80],\n", + " [137, \"06:18\", \"07:02\", 378, 422, 44],\n", + " [138, \"06:19\", \"06:53\", 379, 413, 34],\n", + " [139, \"06:20\", \"07:25\", 380, 445, 65],\n", + " [140, \"06:20\", \"06:36\", 380, 396, 16],\n", + " [141, \"06:20\", \"06:30\", 380, 390, 10],\n", + " [142, \"06:20\", \"06:30\", 380, 390, 10],\n", + " [143, \"06:21\", \"06:49\", 381, 409, 28],\n", + " [144, \"06:22\", \"07:06\", 382, 426, 44],\n", + " [145, \"06:24\", \"07:50\", 384, 470, 86],\n", + " [146, \"06:24\", \"06:57\", 384, 417, 33],\n", + " [147, \"06:26\", \"07:45\", 386, 465, 79],\n", + " [148, \"06:26\", \"07:10\", 386, 430, 44],\n", + " [149, \"06:27\", \"06:44\", 387, 404, 17],\n", + " [150, \"06:28\", \"06:53\", 388, 413, 25],\n", + " [151, \"06:28\", \"07:14\", 388, 434, 46],\n", + " [152, \"06:29\", \"07:03\", 389, 423, 34],\n", + " [153, \"06:30\", \"06:40\", 390, 400, 10],\n", + " [154, \"06:30\", \"07:37\", 390, 457, 67],\n", + " [155, \"06:31\", \"06:43\", 391, 403, 12],\n", + " [156, \"06:33\", \"07:14\", 393, 434, 41],\n", + " [157, \"06:33\", \"07:53\", 393, 473, 80],\n", + " [158, \"06:34\", \"08:16\", 394, 496, 102],\n", + " [159, \"06:34\", \"07:09\", 394, 429, 35],\n", + " [160, \"06:34\", \"07:07\", 394, 427, 33],\n", + " [161, \"06:36\", \"07:21\", 396, 441, 45],\n", + " [162, \"06:37\", \"07:22\", 397, 442, 45],\n", + " [163, \"06:37\", \"06:54\", 397, 414, 17],\n", + " [164, \"06:38\", \"07:30\", 398, 450, 52],\n", + " [165, \"06:38\", \"07:18\", 398, 438, 40],\n", + " [166, \"06:39\", \"07:33\", 399, 453, 54],\n", + " [167, \"06:40\", \"07:52\", 400, 472, 72],\n", + " [168, \"06:40\", \"06:50\", 400, 410, 10],\n", + " [169, \"06:40\", \"07:22\", 400, 442, 42],\n", + " [170, \"06:40\", \"06:56\", 400, 416, 16],\n", + " [171, \"06:41\", \"08:00\", 401, 480, 79],\n", + " [172, \"06:42\", \"07:26\", 402, 446, 44],\n", + " [173, \"06:42\", \"07:13\", 402, 433, 31],\n", + " [174, \"06:43\", \"07:08\", 403, 428, 25],\n", + " [175, \"06:43\", \"07:30\", 403, 450, 47],\n", + " [176, \"06:43\", \"07:23\", 403, 443, 40],\n", + " [177, \"06:44\", \"07:17\", 404, 437, 33],\n", + " [178, \"06:44\", \"08:13\", 404, 493, 89],\n", + " [179, \"06:46\", \"07:01\", 406, 421, 15],\n", + " [180, \"06:46\", \"06:58\", 406, 418, 12],\n", + " [181, \"06:47\", \"07:04\", 407, 424, 17],\n", + " [182, \"06:48\", \"08:15\", 408, 495, 87],\n", + " [183, \"06:48\", \"07:34\", 408, 454, 46],\n", + " [184, \"06:48\", \"07:37\", 408, 457, 49],\n", + " [185, \"06:49\", \"07:43\", 409, 463, 54],\n", + " [186, \"06:50\", \"08:00\", 410, 480, 70],\n", + " [187, \"06:50\", \"07:00\", 410, 420, 10],\n", + " [188, \"06:50\", \"07:05\", 410, 425, 15],\n", + " [189, \"06:51\", \"07:18\", 411, 438, 27],\n", + " [190, \"06:52\", \"07:36\", 412, 456, 44],\n", + " [191, \"06:53\", \"07:37\", 413, 457, 44],\n", + " [192, \"06:54\", \"08:20\", 414, 500, 86],\n", + " [193, \"06:54\", \"07:27\", 414, 447, 33],\n", + " [194, \"06:54\", \"07:20\", 414, 440, 26],\n", + " [195, \"06:56\", \"08:23\", 416, 503, 87],\n", + " [196, \"06:57\", \"07:12\", 417, 432, 15],\n", + " [197, \"06:57\", \"07:58\", 417, 478, 61],\n", + " [198, \"06:57\", \"07:45\", 417, 465, 48],\n", + " [199, \"06:57\", \"07:40\", 417, 460, 43],\n", + " [200, \"06:58\", \"07:23\", 418, 443, 25],\n", + " [201, \"06:59\", \"07:53\", 419, 473, 54],\n", + " [202, \"06:59\", \"08:07\", 419, 487, 68],\n", + " [203, \"07:00\", \"07:10\", 420, 430, 10],\n", + " [204, \"07:00\", \"07:16\", 420, 436, 16],\n", + " [205, \"07:01\", \"08:30\", 421, 510, 89],\n", + " [206, \"07:01\", \"07:13\", 421, 433, 12],\n", + " [207, \"07:01\", \"07:43\", 421, 463, 42],\n", + " [208, \"07:03\", \"08:30\", 423, 510, 87],\n", + " [209, \"07:04\", \"07:37\", 424, 457, 33],\n", + " [210, \"07:04\", \"07:44\", 424, 464, 40],\n", + " [211, \"07:05\", \"07:52\", 425, 472, 47],\n", + " [212, \"07:05\", \"08:05\", 425, 485, 60],\n", + " [213, \"07:05\", \"07:46\", 425, 466, 41],\n", + " [214, \"07:06\", \"07:51\", 426, 471, 45],\n", + " [215, \"07:07\", \"08:08\", 427, 488, 61],\n", + " [216, \"07:07\", \"07:52\", 427, 472, 45],\n", + " [217, \"07:07\", \"08:16\", 427, 496, 69],\n", + " [218, \"07:07\", \"07:27\", 427, 447, 20],\n", + " [219, \"07:09\", \"07:50\", 429, 470, 41],\n", + " [220, \"07:09\", \"08:40\", 429, 520, 91],\n", + " [221, \"07:09\", \"08:03\", 429, 483, 54],\n", + " [222, \"07:10\", \"07:20\", 430, 440, 10],\n", + " [223, \"07:11\", \"08:36\", 431, 516, 85],\n", + " [224, \"07:12\", \"08:00\", 432, 480, 48],\n", + " [225, \"07:12\", \"07:47\", 432, 467, 35],\n", + " [226, \"07:13\", \"07:54\", 433, 474, 41],\n", + " [227, \"07:13\", \"07:38\", 433, 458, 25],\n", + " [228, \"07:14\", \"07:59\", 434, 479, 45],\n", + " [229, \"07:16\", \"08:50\", 436, 530, 94],\n", + " [230, \"07:16\", \"07:28\", 436, 448, 12],\n", + " [231, \"07:17\", \"07:35\", 437, 455, 18],\n", + " [232, \"07:17\", \"07:58\", 437, 478, 41],\n", + " [233, \"07:18\", \"08:06\", 438, 486, 48],\n", + " [234, \"07:18\", \"08:44\", 438, 524, 86],\n", + " [235, \"07:19\", \"08:13\", 439, 493, 54],\n", + " [236, \"07:20\", \"08:02\", 440, 482, 42],\n", + " [237, \"07:20\", \"08:07\", 440, 487, 47],\n", + " [238, \"07:20\", \"07:30\", 440, 450, 10],\n", + " [239, \"07:20\", \"07:57\", 440, 477, 37],\n", + " [240, \"07:20\", \"07:36\", 440, 456, 16],\n", + " [241, \"07:21\", \"07:48\", 441, 468, 27],\n", + " [242, \"07:22\", \"08:06\", 442, 486, 44],\n", + " [243, \"07:22\", \"08:25\", 442, 505, 63],\n", + " [244, \"07:24\", \"08:27\", 444, 507, 63],\n", + " [245, \"07:24\", \"08:05\", 444, 485, 41],\n", + " [246, \"07:26\", \"08:23\", 446, 503, 57],\n", + " [247, \"07:26\", \"08:52\", 446, 532, 86],\n", + " [248, \"07:27\", \"08:07\", 447, 487, 40],\n", + " [249, \"07:27\", \"07:42\", 447, 462, 15],\n", + " [250, \"07:27\", \"08:15\", 447, 495, 48],\n", + " [251, \"07:28\", \"07:53\", 448, 473, 25],\n", + " [252, \"07:28\", \"08:09\", 448, 489, 41],\n", + " [253, \"07:28\", \"07:38\", 448, 458, 10],\n", + " [254, \"07:30\", \"08:35\", 450, 515, 65],\n", + " [255, \"07:31\", \"07:43\", 451, 463, 12],\n", + " [256, \"07:32\", \"08:13\", 452, 493, 41],\n", + " [257, \"07:34\", \"09:00\", 454, 540, 86],\n", + " [258, \"07:34\", \"08:33\", 454, 513, 59],\n", + " [259, \"07:34\", \"09:04\", 454, 544, 90],\n", + " [260, \"07:35\", \"08:22\", 455, 502, 47],\n", + " [261, \"07:35\", \"07:45\", 455, 465, 10],\n", + " [262, \"07:35\", \"08:16\", 455, 496, 41],\n", + " [263, \"07:36\", \"08:17\", 456, 497, 41],\n", + " [264, \"07:36\", \"08:36\", 456, 516, 60],\n", + " [265, \"07:37\", \"07:50\", 457, 470, 13],\n", + " [266, \"07:40\", \"07:56\", 460, 476, 16],\n", + " [267, \"07:40\", \"08:20\", 460, 500, 40],\n", + " [268, \"07:40\", \"08:45\", 460, 525, 65],\n", + " [269, \"07:41\", \"08:39\", 461, 519, 58],\n", + " [270, \"07:41\", \"07:51\", 461, 471, 10],\n", + " [271, \"07:42\", \"08:30\", 462, 510, 48],\n", + " [272, \"07:42\", \"08:21\", 462, 501, 39],\n", + " [273, \"07:43\", \"08:08\", 463, 488, 25],\n", + " [274, \"07:43\", \"08:24\", 463, 504, 41],\n", + " [275, \"07:44\", \"09:10\", 464, 550, 86],\n", + " [276, \"07:44\", \"08:43\", 464, 523, 59],\n", + " [277, \"07:46\", \"08:28\", 466, 508, 42],\n", + " [278, \"07:46\", \"07:58\", 466, 478, 12],\n", + " [279, \"07:47\", \"08:00\", 467, 480, 13],\n", + " [280, \"07:48\", \"09:14\", 468, 554, 86],\n", + " [281, \"07:49\", \"08:32\", 469, 512, 43],\n", + " [282, \"07:50\", \"08:55\", 470, 535, 65],\n", + " [283, \"07:50\", \"08:00\", 470, 480, 10],\n", + " [284, \"07:50\", \"08:37\", 470, 517, 47],\n", + " [285, \"07:50\", \"08:26\", 470, 506, 36],\n", + " [286, \"07:51\", \"08:18\", 471, 498, 27],\n", + " [287, \"07:52\", \"08:21\", 472, 501, 29],\n", + " [288, \"07:53\", \"08:35\", 473, 515, 42],\n", + " [289, \"07:54\", \"09:19\", 474, 559, 85],\n", + " [290, \"07:55\", \"08:53\", 475, 533, 58],\n", + " [291, \"07:56\", \"08:54\", 476, 534, 58],\n", + " [292, \"07:57\", \"08:39\", 477, 519, 42],\n", + " [293, \"07:57\", \"08:10\", 477, 490, 13],\n", + " [294, \"07:58\", \"08:45\", 478, 525, 47],\n", + " [295, \"07:58\", \"08:23\", 478, 503, 25],\n", + " [296, \"08:00\", \"08:10\", 480, 490, 10],\n", + " [297, \"08:00\", \"09:05\", 480, 545, 65],\n", + " [298, \"08:00\", \"08:16\", 480, 496, 16],\n", + " [299, \"08:00\", \"08:35\", 480, 515, 35],\n", + " [300, \"08:01\", \"08:13\", 481, 493, 12],\n", + " [301, \"08:01\", \"08:43\", 481, 523, 42],\n", + " [302, \"08:03\", \"09:26\", 483, 566, 83],\n", + " [303, \"08:04\", \"09:29\", 484, 569, 85],\n", + " [304, \"08:05\", \"08:21\", 485, 501, 16],\n", + " [305, \"08:05\", \"08:47\", 485, 527, 42],\n", + " [306, \"08:06\", \"08:51\", 486, 531, 45],\n", + " [307, \"08:06\", \"09:03\", 486, 543, 57],\n", + " [308, \"08:07\", \"08:20\", 487, 500, 13],\n", + " [309, \"08:08\", \"08:55\", 488, 535, 47],\n", + " [310, \"08:08\", \"08:50\", 488, 530, 42],\n", + " [311, \"08:10\", \"08:45\", 490, 525, 35],\n", + " [312, \"08:10\", \"09:15\", 490, 555, 65],\n", + " [313, \"08:10\", \"08:20\", 490, 500, 10],\n", + " [314, \"08:11\", \"09:41\", 491, 581, 90],\n", + " [315, \"08:12\", \"08:55\", 492, 535, 43],\n", + " [316, \"08:13\", \"08:38\", 493, 518, 25],\n", + " [317, \"08:14\", \"09:38\", 494, 578, 84],\n", + " [318, \"08:15\", \"08:30\", 495, 510, 15],\n", + " [319, \"08:16\", \"08:30\", 496, 510, 14],\n", + " [320, \"08:16\", \"08:28\", 496, 508, 12],\n", + " [321, \"08:16\", \"09:00\", 496, 540, 44],\n", + " [322, \"08:17\", \"09:13\", 497, 553, 56],\n", + " [323, \"08:18\", \"09:16\", 498, 556, 58],\n", + " [324, \"08:18\", \"09:05\", 498, 545, 47],\n", + " [325, \"08:20\", \"08:36\", 500, 516, 16],\n", + " [326, \"08:20\", \"08:55\", 500, 535, 35],\n", + " [327, \"08:20\", \"09:05\", 500, 545, 45],\n", + " [328, \"08:20\", \"08:30\", 500, 510, 10],\n", + " [329, \"08:20\", \"09:25\", 500, 565, 65],\n", + " [330, \"08:21\", \"08:38\", 501, 518, 17],\n", + " [331, \"08:21\", \"08:47\", 501, 527, 26],\n", + " [332, \"08:22\", \"08:45\", 502, 525, 23],\n", + " [333, \"08:23\", \"09:10\", 503, 550, 47],\n", + " [334, \"08:24\", \"09:48\", 504, 588, 84],\n", + " [335, \"08:26\", \"08:46\", 506, 526, 20],\n", + " [336, \"08:27\", \"09:07\", 507, 547, 40],\n", + " [337, \"08:28\", \"08:50\", 508, 530, 22],\n", + " [338, \"08:28\", \"09:56\", 508, 596, 88],\n", + " [339, \"08:28\", \"09:23\", 508, 563, 55],\n", + " [340, \"08:29\", \"09:20\", 509, 560, 51],\n", + " [341, \"08:30\", \"09:05\", 510, 545, 35],\n", + " [342, \"08:30\", \"08:45\", 510, 525, 15],\n", + " [343, \"08:30\", \"08:40\", 510, 520, 10],\n", + " [344, \"08:30\", \"09:35\", 510, 575, 65],\n", + " [345, \"08:31\", \"08:43\", 511, 523, 12],\n", + " [346, \"08:31\", \"09:13\", 511, 553, 42],\n", + " [347, \"08:34\", \"09:58\", 514, 598, 84],\n", + " [348, \"08:35\", \"08:55\", 515, 535, 20],\n", + " [349, \"08:35\", \"09:15\", 515, 555, 40],\n", + " [350, \"08:35\", \"08:45\", 515, 525, 10],\n", + " [351, \"08:36\", \"08:46\", 516, 526, 10],\n", + " [352, \"08:36\", \"09:00\", 516, 540, 24],\n", + " [353, \"08:38\", \"09:20\", 518, 560, 42],\n", + " [354, \"08:38\", \"09:35\", 518, 575, 57],\n", + " [355, \"08:38\", \"09:14\", 518, 554, 36],\n", + " [356, \"08:39\", \"09:33\", 519, 573, 54],\n", + " [357, \"08:40\", \"09:45\", 520, 585, 65],\n", + " [358, \"08:40\", \"08:50\", 520, 530, 10],\n", + " [359, \"08:40\", \"08:56\", 520, 536, 16],\n", + " [360, \"08:42\", \"09:25\", 522, 565, 43],\n", + " [361, \"08:43\", \"09:08\", 523, 548, 25],\n", + " [362, \"08:44\", \"09:35\", 524, 575, 51],\n", + " [363, \"08:45\", \"09:00\", 525, 540, 15],\n", + " [364, \"08:45\", \"09:05\", 525, 545, 20],\n", + " [365, \"08:46\", \"09:24\", 526, 564, 38],\n", + " [366, \"08:46\", \"08:58\", 526, 538, 12],\n", + " [367, \"08:46\", \"09:30\", 526, 570, 44],\n", + " [368, \"08:48\", \"10:11\", 528, 611, 83],\n", + " [369, \"08:48\", \"10:13\", 528, 613, 85],\n", + " [370, \"08:49\", \"09:43\", 529, 583, 54],\n", + " [371, \"08:50\", \"09:30\", 530, 570, 40],\n", + " [372, \"08:50\", \"10:00\", 530, 600, 70],\n", + " [373, \"08:50\", \"09:00\", 530, 540, 10],\n", + " [374, \"08:51\", \"09:17\", 531, 557, 26],\n", + " [375, \"08:53\", \"09:20\", 533, 560, 27],\n", + " [376, \"08:53\", \"09:35\", 533, 575, 42],\n", + " [377, \"08:55\", \"09:34\", 535, 574, 39],\n", + " [378, \"08:55\", \"09:15\", 535, 555, 20],\n", + " [379, \"08:58\", \"09:38\", 538, 578, 40],\n", + " [380, \"08:58\", \"10:26\", 538, 626, 88],\n", + " [381, \"08:59\", \"09:53\", 539, 593, 54],\n", + " [382, \"08:59\", \"09:50\", 539, 590, 51],\n", + " [383, \"09:00\", \"09:35\", 540, 575, 35],\n", + " [384, \"09:00\", \"09:16\", 540, 556, 16],\n", + " [385, \"09:00\", \"09:10\", 540, 550, 10],\n", + " [386, \"09:00\", \"09:16\", 540, 556, 16],\n", + " [387, \"09:01\", \"09:13\", 541, 553, 12],\n", + " [388, \"09:03\", \"09:45\", 543, 585, 42],\n", + " [389, \"09:03\", \"10:28\", 543, 628, 85],\n", + " [390, \"09:05\", \"09:44\", 545, 584, 39],\n", + " [391, \"09:05\", \"09:25\", 545, 565, 20],\n", + " [392, \"09:08\", \"09:53\", 548, 593, 45],\n", + " [393, \"09:08\", \"10:04\", 548, 604, 56],\n", + " [394, \"09:09\", \"10:03\", 549, 603, 54],\n", + " [395, \"09:10\", \"10:15\", 550, 615, 65],\n", + " [396, \"09:10\", \"09:20\", 550, 560, 10],\n", + " [397, \"09:11\", \"09:38\", 551, 578, 27],\n", + " [398, \"09:13\", \"10:00\", 553, 600, 47],\n", + " [399, \"09:14\", \"09:39\", 554, 579, 25],\n", + " [400, \"09:14\", \"10:05\", 554, 605, 51],\n", + " [401, \"09:15\", \"09:54\", 555, 594, 39],\n", + " [402, \"09:16\", \"09:28\", 556, 568, 12],\n", + " [403, \"09:18\", \"10:43\", 558, 643, 85],\n", + " [404, \"09:18\", \"10:41\", 558, 641, 83],\n", + " [405, \"09:18\", \"09:58\", 558, 598, 40],\n", + " [406, \"09:19\", \"10:13\", 559, 613, 54],\n", + " [407, \"09:20\", \"09:30\", 560, 570, 10],\n", + " [408, \"09:20\", \"09:36\", 560, 576, 16],\n", + " [409, \"09:21\", \"09:47\", 561, 587, 26],\n", + " [410, \"09:23\", \"10:30\", 563, 630, 67],\n", + " [411, \"09:23\", \"10:05\", 563, 605, 42],\n", + " [412, \"09:23\", \"09:49\", 563, 589, 26],\n", + " [413, \"09:24\", \"09:35\", 564, 575, 11],\n", + " [414, \"09:25\", \"09:35\", 565, 575, 10],\n", + " [415, \"09:25\", \"10:04\", 565, 604, 39],\n", + " [416, \"09:28\", \"10:08\", 568, 608, 40],\n", + " [417, \"09:29\", \"09:45\", 569, 585, 16],\n", + " [418, \"09:29\", \"10:20\", 569, 620, 51],\n", + " [419, \"09:29\", \"10:56\", 569, 656, 87],\n", + " [420, \"09:29\", \"10:23\", 569, 623, 54],\n", + " [421, \"09:30\", \"09:40\", 570, 580, 10],\n", + " [422, \"09:31\", \"09:43\", 571, 583, 12],\n", + " [423, \"09:33\", \"10:58\", 573, 658, 85],\n", + " [424, \"09:33\", \"10:15\", 573, 615, 42],\n", + " [425, \"09:34\", \"09:45\", 574, 585, 11],\n", + " [426, \"09:35\", \"10:14\", 575, 614, 39],\n", + " [427, \"09:38\", \"10:45\", 578, 645, 67],\n", + " [428, \"09:39\", \"10:33\", 579, 633, 54],\n", + " [429, \"09:40\", \"09:56\", 580, 596, 16],\n", + " [430, \"09:40\", \"09:50\", 580, 590, 10],\n", + " [431, \"09:41\", \"10:08\", 581, 608, 27],\n", + " [432, \"09:41\", \"10:23\", 581, 623, 42],\n", + " [433, \"09:44\", \"10:35\", 584, 635, 51],\n", + " [434, \"09:44\", \"11:11\", 584, 671, 87],\n", + " [435, \"09:44\", \"09:55\", 584, 595, 11],\n", + " [436, \"09:45\", \"10:24\", 585, 624, 39],\n", + " [437, \"09:46\", \"09:58\", 586, 598, 12],\n", + " [438, \"09:48\", \"10:30\", 588, 630, 42],\n", + " [439, \"09:48\", \"11:13\", 588, 673, 85],\n", + " [440, \"09:48\", \"10:04\", 588, 604, 16],\n", + " [441, \"09:49\", \"10:43\", 589, 643, 54],\n", + " [442, \"09:50\", \"10:00\", 590, 600, 10],\n", + " [443, \"09:51\", \"10:17\", 591, 617, 26],\n", + " [444, \"09:53\", \"10:49\", 593, 649, 56],\n", + " [445, \"09:53\", \"11:00\", 593, 660, 67],\n", + " [446, \"09:54\", \"10:05\", 594, 605, 11],\n", + " [447, \"09:55\", \"10:34\", 595, 634, 39],\n", + " [448, \"09:56\", \"10:38\", 596, 638, 42],\n", + " [449, \"09:57\", \"10:20\", 597, 620, 23],\n", + " [450, \"09:59\", \"11:26\", 599, 686, 87],\n", + " [451, \"09:59\", \"10:50\", 599, 650, 51],\n", + " [452, \"09:59\", \"10:53\", 599, 653, 54],\n", + " [453, \"10:00\", \"10:16\", 600, 616, 16],\n", + " [454, \"10:00\", \"10:10\", 600, 610, 10],\n", + " [455, \"10:01\", \"10:13\", 601, 613, 12],\n", + " [456, \"10:03\", \"11:28\", 603, 688, 85],\n", + " [457, \"10:03\", \"10:45\", 603, 645, 42],\n", + " [458, \"10:04\", \"10:15\", 604, 615, 11],\n", + " [459, \"10:05\", \"10:44\", 605, 644, 39],\n", + " [460, \"10:08\", \"11:15\", 608, 675, 67],\n", + " [461, \"10:09\", \"11:03\", 609, 663, 54],\n", + " [462, \"10:10\", \"10:20\", 610, 620, 10],\n", + " [463, \"10:11\", \"10:38\", 611, 638, 27],\n", + " [464, \"10:11\", \"10:53\", 611, 653, 42],\n", + " [465, \"10:14\", \"11:05\", 614, 665, 51],\n", + " [466, \"10:14\", \"11:41\", 614, 701, 87],\n", + " [467, \"10:14\", \"10:25\", 614, 625, 11],\n", + " [468, \"10:15\", \"10:54\", 615, 654, 39],\n", + " [469, \"10:16\", \"10:28\", 616, 628, 12],\n", + " [470, \"10:18\", \"11:43\", 618, 703, 85],\n", + " [471, \"10:18\", \"11:00\", 618, 660, 42],\n", + " [472, \"10:19\", \"11:13\", 619, 673, 54],\n", + " [473, \"10:20\", \"10:30\", 620, 630, 10],\n", + " [474, \"10:20\", \"10:36\", 620, 636, 16],\n", + " [475, \"10:21\", \"10:47\", 621, 647, 26],\n", + " [476, \"10:23\", \"11:30\", 623, 690, 67],\n", + " [477, \"10:23\", \"10:45\", 623, 645, 22],\n", + " [478, \"10:24\", \"10:35\", 624, 635, 11],\n", + " [479, \"10:25\", \"11:04\", 625, 664, 39],\n", + " [480, \"10:26\", \"11:08\", 626, 668, 42],\n", + " [481, \"10:29\", \"11:20\", 629, 680, 51],\n", + " [482, \"10:29\", \"11:23\", 629, 683, 54],\n", + " [483, \"10:29\", \"11:56\", 629, 716, 87],\n", + " [484, \"10:30\", \"10:40\", 630, 640, 10],\n", + " [485, \"10:31\", \"10:43\", 631, 643, 12],\n", + " [486, \"10:33\", \"11:15\", 633, 675, 42],\n", + " [487, \"10:33\", \"11:58\", 633, 718, 85],\n", + " [488, \"10:34\", \"10:45\", 634, 645, 11],\n", + " [489, \"10:35\", \"11:14\", 635, 674, 39],\n", + " [490, \"10:38\", \"11:45\", 638, 705, 67],\n", + " [491, \"10:39\", \"11:33\", 639, 693, 54],\n", + " [492, \"10:40\", \"10:50\", 640, 650, 10],\n", + " [493, \"10:40\", \"10:56\", 640, 656, 16],\n", + " [494, \"10:41\", \"11:23\", 641, 683, 42],\n", + " [495, \"10:41\", \"11:08\", 641, 668, 27],\n", + " [496, \"10:44\", \"12:11\", 644, 731, 87],\n", + " [497, \"10:44\", \"11:35\", 644, 695, 51],\n", + " [498, \"10:44\", \"10:55\", 644, 655, 11],\n", + " [499, \"10:45\", \"11:24\", 645, 684, 39],\n", + " [500, \"10:46\", \"10:58\", 646, 658, 12],\n", + " [501, \"10:48\", \"12:13\", 648, 733, 85],\n", + " [502, \"10:48\", \"11:30\", 648, 690, 42],\n", + " [503, \"10:49\", \"11:43\", 649, 703, 54],\n", + " [504, \"10:50\", \"11:00\", 650, 660, 10],\n", + " [505, \"10:51\", \"11:17\", 651, 677, 26],\n", + " [506, \"10:53\", \"12:00\", 653, 720, 67],\n", + " [507, \"10:53\", \"11:20\", 653, 680, 27],\n", + " [508, \"10:54\", \"11:05\", 654, 665, 11],\n", + " [509, \"10:55\", \"11:34\", 655, 694, 39],\n", + " [510, \"10:56\", \"11:38\", 656, 698, 42],\n", + " [511, \"10:59\", \"11:14\", 659, 674, 15],\n", + " [512, \"10:59\", \"12:26\", 659, 746, 87],\n", + " [513, \"10:59\", \"11:53\", 659, 713, 54],\n", + " [514, \"10:59\", \"11:50\", 659, 710, 51],\n", + " [515, \"11:00\", \"11:16\", 660, 676, 16],\n", + " [516, \"11:00\", \"11:10\", 660, 670, 10],\n", + " [517, \"11:01\", \"11:13\", 661, 673, 12],\n", + " [518, \"11:03\", \"11:45\", 663, 705, 42],\n", + " [519, \"11:03\", \"12:28\", 663, 748, 85],\n", + " [520, \"11:04\", \"11:15\", 664, 675, 11],\n", + " [521, \"11:05\", \"11:44\", 665, 704, 39],\n", + " [522, \"11:08\", \"12:15\", 668, 735, 67],\n", + " [523, \"11:09\", \"12:03\", 669, 723, 54],\n", + " [524, \"11:10\", \"11:20\", 670, 680, 10],\n", + " [525, \"11:11\", \"11:38\", 671, 698, 27],\n", + " [526, \"11:11\", \"11:53\", 671, 713, 42],\n", + " [527, \"11:14\", \"11:25\", 674, 685, 11],\n", + " [528, \"11:14\", \"12:05\", 674, 725, 51],\n", + " [529, \"11:14\", \"12:38\", 674, 758, 84],\n", + " [530, \"11:14\", \"12:41\", 674, 761, 87],\n", + " [531, \"11:15\", \"11:54\", 675, 714, 39],\n", + " [532, \"11:16\", \"11:28\", 676, 688, 12],\n", + " [533, \"11:18\", \"12:00\", 678, 720, 42],\n", + " [534, \"11:19\", \"12:13\", 679, 733, 54],\n", + " [535, \"11:20\", \"11:30\", 680, 690, 10],\n", + " [536, \"11:20\", \"11:36\", 680, 696, 16],\n", + " [537, \"11:21\", \"11:47\", 681, 707, 26],\n", + " [538, \"11:23\", \"12:30\", 683, 750, 67],\n", + " [539, \"11:23\", \"11:49\", 683, 709, 26],\n", + " [540, \"11:24\", \"12:48\", 684, 768, 84],\n", + " [541, \"11:24\", \"11:35\", 684, 695, 11],\n", + " [542, \"11:25\", \"12:04\", 685, 724, 39],\n", + " [543, \"11:26\", \"12:08\", 686, 728, 42],\n", + " [544, \"11:29\", \"11:44\", 689, 704, 15],\n", + " [545, \"11:29\", \"12:23\", 689, 743, 54],\n", + " [546, \"11:29\", \"12:20\", 689, 740, 51],\n", + " [547, \"11:29\", \"12:54\", 689, 774, 85],\n", + " [548, \"11:30\", \"11:40\", 690, 700, 10],\n", + " [549, \"11:31\", \"11:43\", 691, 703, 12],\n", + " [550, \"11:33\", \"12:15\", 693, 735, 42],\n", + " [551, \"11:34\", \"12:58\", 694, 778, 84],\n", + " [552, \"11:34\", \"11:45\", 694, 705, 11],\n", + " [553, \"11:35\", \"12:14\", 695, 734, 39],\n", + " [554, \"11:38\", \"12:45\", 698, 765, 67],\n", + " [555, \"11:39\", \"12:33\", 699, 753, 54],\n", + " [556, \"11:40\", \"11:56\", 700, 716, 16],\n", + " [557, \"11:40\", \"11:50\", 700, 710, 10],\n", + " [558, \"11:41\", \"12:08\", 701, 728, 27],\n", + " [559, \"11:41\", \"12:23\", 701, 743, 42],\n", + " [560, \"11:44\", \"11:55\", 704, 715, 11],\n", + " [561, \"11:44\", \"13:14\", 704, 794, 90],\n", + " [562, \"11:44\", \"13:08\", 704, 788, 84],\n", + " [563, \"11:44\", \"12:35\", 704, 755, 51],\n", + " [564, \"11:45\", \"12:24\", 705, 744, 39],\n", + " [565, \"11:46\", \"11:58\", 706, 718, 12],\n", + " [566, \"11:48\", \"12:30\", 708, 750, 42],\n", + " [567, \"11:49\", \"12:43\", 709, 763, 54],\n", + " [568, \"11:50\", \"12:00\", 710, 720, 10],\n", + " [569, \"11:51\", \"12:17\", 711, 737, 26],\n", + " [570, \"11:53\", \"12:49\", 713, 769, 56],\n", + " [571, \"11:53\", \"13:00\", 713, 780, 67],\n", + " [572, \"11:54\", \"13:18\", 714, 798, 84],\n", + " [573, \"11:54\", \"12:05\", 714, 725, 11],\n", + " [574, \"11:55\", \"12:40\", 715, 760, 45],\n", + " [575, \"11:55\", \"12:34\", 715, 754, 39],\n", + " [576, \"11:56\", \"12:35\", 716, 755, 39],\n", + " [577, \"11:57\", \"12:20\", 717, 740, 23],\n", + " [578, \"11:58\", \"12:29\", 718, 749, 31],\n", + " [579, \"11:59\", \"12:50\", 719, 770, 51],\n", + " [580, \"11:59\", \"12:53\", 719, 773, 54],\n", + " [581, \"11:59\", \"13:24\", 719, 804, 85],\n", + " [582, \"11:59\", \"12:14\", 719, 734, 15],\n", + " [583, \"12:00\", \"12:16\", 720, 736, 16],\n", + " [584, \"12:00\", \"12:10\", 720, 730, 10],\n", + " [585, \"12:01\", \"12:45\", 721, 765, 44],\n", + " [586, \"12:01\", \"12:13\", 721, 733, 12],\n", + " [587, \"12:03\", \"12:50\", 723, 770, 47],\n", + " [588, \"12:04\", \"12:15\", 724, 735, 11],\n", + " [589, \"12:04\", \"13:04\", 724, 784, 60],\n", + " [590, \"12:04\", \"13:28\", 724, 808, 84],\n", + " [591, \"12:05\", \"12:44\", 725, 764, 39],\n", + " [592, \"12:08\", \"13:11\", 728, 791, 63],\n", + " [593, \"12:08\", \"12:39\", 728, 759, 31],\n", + " [594, \"12:09\", \"13:03\", 729, 783, 54],\n", + " [595, \"12:10\", \"12:20\", 730, 740, 10],\n", + " [596, \"12:11\", \"12:55\", 731, 775, 44],\n", + " [597, \"12:11\", \"12:38\", 731, 758, 27],\n", + " [598, \"12:14\", \"13:05\", 734, 785, 51],\n", + " [599, \"12:14\", \"12:25\", 734, 745, 11],\n", + " [600, \"12:14\", \"13:44\", 734, 824, 90],\n", + " [601, \"12:14\", \"13:38\", 734, 818, 84],\n", + " [602, \"12:15\", \"12:54\", 735, 774, 39],\n", + " [603, \"12:16\", \"12:28\", 736, 748, 12],\n", + " [604, \"12:18\", \"13:00\", 738, 780, 42],\n", + " [605, \"12:19\", \"13:13\", 739, 793, 54],\n", + " [606, \"12:20\", \"12:30\", 740, 750, 10],\n", + " [607, \"12:20\", \"13:31\", 740, 811, 71],\n", + " [608, \"12:20\", \"12:30\", 740, 750, 10],\n", + " [609, \"12:20\", \"12:36\", 740, 756, 16],\n", + " [610, \"12:21\", \"12:47\", 741, 767, 26],\n", + " [611, \"12:23\", \"12:45\", 743, 765, 22],\n", + " [612, \"12:24\", \"12:35\", 744, 755, 11],\n", + " [613, \"12:24\", \"13:48\", 744, 828, 84],\n", + " [614, \"12:25\", \"13:10\", 745, 790, 45],\n", + " [615, \"12:25\", \"13:04\", 745, 784, 39],\n", + " [616, \"12:26\", \"13:05\", 746, 785, 39],\n", + " [617, \"12:28\", \"13:54\", 748, 834, 86],\n", + " [618, \"12:28\", \"12:38\", 748, 758, 10],\n", + " [619, \"12:28\", \"13:15\", 748, 795, 47],\n", + " [620, \"12:29\", \"13:23\", 749, 803, 54],\n", + " [621, \"12:30\", \"13:41\", 750, 821, 71],\n", + " [622, \"12:30\", \"12:40\", 750, 760, 10],\n", + " [623, \"12:31\", \"13:15\", 751, 795, 44],\n", + " [624, \"12:31\", \"12:43\", 751, 763, 12],\n", + " [625, \"12:33\", \"12:48\", 753, 768, 15],\n", + " [626, \"12:33\", \"13:20\", 753, 800, 47],\n", + " [627, \"12:34\", \"13:58\", 754, 838, 84],\n", + " [628, \"12:34\", \"13:34\", 754, 814, 60],\n", + " [629, \"12:34\", \"12:45\", 754, 765, 11],\n", + " [630, \"12:35\", \"13:14\", 755, 794, 39],\n", + " [631, \"12:38\", \"13:25\", 758, 805, 47],\n", + " [632, \"12:38\", \"13:25\", 758, 805, 47],\n", + " [633, \"12:38\", \"14:04\", 758, 844, 86],\n", + " [634, \"12:39\", \"13:33\", 759, 813, 54],\n", + " [635, \"12:40\", \"13:51\", 760, 831, 71],\n", + " [636, \"12:40\", \"12:50\", 760, 770, 10],\n", + " [637, \"12:40\", \"12:56\", 760, 776, 16],\n", + " [638, \"12:41\", \"13:08\", 761, 788, 27],\n", + " [639, \"12:43\", \"13:30\", 763, 810, 47],\n", + " [640, \"12:44\", \"12:55\", 764, 775, 11],\n", + " [641, \"12:44\", \"14:08\", 764, 848, 84],\n", + " [642, \"12:45\", \"13:24\", 765, 804, 39],\n", + " [643, \"12:46\", \"12:58\", 766, 778, 12],\n", + " [644, \"12:46\", \"13:21\", 766, 801, 35],\n", + " [645, \"12:48\", \"14:14\", 768, 854, 86],\n", + " [646, \"12:48\", \"13:35\", 768, 815, 47],\n", + " [647, \"12:48\", \"12:58\", 768, 778, 10],\n", + " [648, \"12:48\", \"13:35\", 768, 815, 47],\n", + " [649, \"12:49\", \"13:43\", 769, 823, 54],\n", + " [650, \"12:50\", \"14:01\", 770, 841, 71],\n", + " [651, \"12:50\", \"13:00\", 770, 780, 10],\n", + " [652, \"12:50\", \"13:00\", 770, 780, 10],\n", + " [653, \"12:51\", \"13:17\", 771, 797, 26],\n", + " [654, \"12:53\", \"13:20\", 773, 800, 27],\n", + " [655, \"12:53\", \"13:24\", 773, 804, 31],\n", + " [656, \"12:53\", \"13:40\", 773, 820, 47],\n", + " [657, \"12:54\", \"14:18\", 774, 858, 84],\n", + " [658, \"12:54\", \"13:05\", 774, 785, 11],\n", + " [659, \"12:55\", \"13:34\", 775, 814, 39],\n", + " [660, \"12:58\", \"14:24\", 778, 864, 86],\n", + " [661, \"12:58\", \"13:25\", 778, 805, 27],\n", + " [662, \"12:58\", \"13:45\", 778, 825, 47],\n", + " [663, \"12:58\", \"13:45\", 778, 825, 47],\n", + " [664, \"12:59\", \"13:53\", 779, 833, 54],\n", + " [665, \"13:00\", \"13:10\", 780, 790, 10],\n", + " [666, \"13:00\", \"13:16\", 780, 796, 16],\n", + " [667, \"13:00\", \"14:11\", 780, 851, 71],\n", + " [668, \"13:01\", \"13:13\", 781, 793, 12],\n", + " [669, \"13:03\", \"13:34\", 783, 814, 31],\n", + " [670, \"13:03\", \"13:50\", 783, 830, 47],\n", + " [671, \"13:04\", \"13:15\", 784, 795, 11],\n", + " [672, \"13:04\", \"14:28\", 784, 868, 84],\n", + " [673, \"13:05\", \"13:44\", 785, 824, 39],\n", + " [674, \"13:08\", \"13:55\", 788, 835, 47],\n", + " [675, \"13:08\", \"14:34\", 788, 874, 86],\n", + " [676, \"13:08\", \"13:55\", 788, 835, 47],\n", + " [677, \"13:09\", \"14:03\", 789, 843, 54],\n", + " [678, \"13:10\", \"13:20\", 790, 800, 10],\n", + " [679, \"13:10\", \"14:21\", 790, 861, 71],\n", + " [680, \"13:13\", \"14:00\", 793, 840, 47],\n", + " [681, \"13:13\", \"13:40\", 793, 820, 27],\n", + " [682, \"13:14\", \"14:38\", 794, 878, 84],\n", + " [683, \"13:14\", \"13:25\", 794, 805, 11],\n", + " [684, \"13:15\", \"13:54\", 795, 834, 39],\n", + " [685, \"13:16\", \"13:28\", 796, 808, 12],\n", + " [686, \"13:18\", \"14:05\", 798, 845, 47],\n", + " [687, \"13:18\", \"14:44\", 798, 884, 86],\n", + " [688, \"13:18\", \"14:05\", 798, 845, 47],\n", + " [689, \"13:19\", \"14:13\", 799, 853, 54],\n", + " [690, \"13:20\", \"13:36\", 800, 816, 16],\n", + " [691, \"13:20\", \"14:31\", 800, 871, 71],\n", + " [692, \"13:20\", \"13:30\", 800, 810, 10],\n", + " [693, \"13:21\", \"13:47\", 801, 827, 26],\n", + " [694, \"13:23\", \"14:10\", 803, 850, 47],\n", + " [695, \"13:23\", \"13:49\", 803, 829, 26],\n", + " [696, \"13:24\", \"14:48\", 804, 888, 84],\n", + " [697, \"13:24\", \"13:35\", 804, 815, 11],\n", + " [698, \"13:25\", \"14:04\", 805, 844, 39],\n", + " [699, \"13:28\", \"14:15\", 808, 855, 47],\n", + " [700, \"13:28\", \"14:54\", 808, 894, 86],\n", + " [701, \"13:28\", \"13:55\", 808, 835, 27],\n", + " [702, \"13:28\", \"14:15\", 808, 855, 47],\n", + " [703, \"13:29\", \"14:23\", 809, 863, 54],\n", + " [704, \"13:30\", \"13:40\", 810, 820, 10],\n", + " [705, \"13:30\", \"14:41\", 810, 881, 71],\n", + " [706, \"13:31\", \"13:43\", 811, 823, 12],\n", + " [707, \"13:33\", \"14:20\", 813, 860, 47],\n", + " [708, \"13:34\", \"14:58\", 814, 898, 84],\n", + " [709, \"13:34\", \"13:45\", 814, 825, 11],\n", + " [710, \"13:35\", \"14:14\", 815, 854, 39],\n", + " [711, \"13:38\", \"14:25\", 818, 865, 47],\n", + " [712, \"13:38\", \"14:25\", 818, 865, 47],\n", + " [713, \"13:38\", \"15:04\", 818, 904, 86],\n", + " [714, \"13:39\", \"14:33\", 819, 873, 54],\n", + " [715, \"13:40\", \"13:50\", 820, 830, 10],\n", + " [716, \"13:40\", \"13:56\", 820, 836, 16],\n", + " [717, \"13:40\", \"14:51\", 820, 891, 71],\n", + " [718, \"13:43\", \"14:30\", 823, 870, 47],\n", + " [719, \"13:43\", \"14:10\", 823, 850, 27],\n", + " [720, \"13:44\", \"15:09\", 824, 909, 85],\n", + " [721, \"13:44\", \"13:55\", 824, 835, 11],\n", + " [722, \"13:45\", \"14:24\", 825, 864, 39],\n", + " [723, \"13:46\", \"13:58\", 826, 838, 12],\n", + " [724, \"13:48\", \"14:35\", 828, 875, 47],\n", + " [725, \"13:48\", \"15:14\", 828, 914, 86],\n", + " [726, \"13:48\", \"14:35\", 828, 875, 47],\n", + " [727, \"13:49\", \"14:43\", 829, 883, 54],\n", + " [728, \"13:50\", \"14:00\", 830, 840, 10],\n", + " [729, \"13:50\", \"15:01\", 830, 901, 71],\n", + " [730, \"13:51\", \"14:17\", 831, 857, 26],\n", + " [731, \"13:53\", \"14:40\", 833, 880, 47],\n", + " [732, \"13:53\", \"14:49\", 833, 889, 56],\n", + " [733, \"13:54\", \"14:05\", 834, 845, 11],\n", + " [734, \"13:54\", \"15:19\", 834, 919, 85],\n", + " [735, \"13:55\", \"14:34\", 835, 874, 39],\n", + " [736, \"13:57\", \"14:20\", 837, 860, 23],\n", + " [737, \"13:58\", \"15:24\", 838, 924, 86],\n", + " [738, \"13:58\", \"14:45\", 838, 885, 47],\n", + " [739, \"13:58\", \"14:45\", 838, 885, 47],\n", + " [740, \"13:58\", \"14:25\", 838, 865, 27],\n", + " [741, \"13:59\", \"14:53\", 839, 893, 54],\n", + " [742, \"14:00\", \"14:16\", 840, 856, 16],\n", + " [743, \"14:00\", \"14:10\", 840, 850, 10],\n", + " [744, \"14:00\", \"15:11\", 840, 911, 71],\n", + " [745, \"14:01\", \"14:13\", 841, 853, 12],\n", + " [746, \"14:03\", \"14:50\", 843, 890, 47],\n", + " [747, \"14:04\", \"14:15\", 844, 855, 11],\n", + " [748, \"14:04\", \"15:29\", 844, 929, 85],\n", + " [749, \"14:05\", \"14:44\", 845, 884, 39],\n", + " [750, \"14:08\", \"14:55\", 848, 895, 47],\n", + " [751, \"14:08\", \"14:55\", 848, 895, 47],\n", + " [752, \"14:08\", \"15:34\", 848, 934, 86],\n", + " [753, \"14:09\", \"15:03\", 849, 903, 54],\n", + " [754, \"14:10\", \"15:21\", 850, 921, 71],\n", + " [755, \"14:10\", \"14:20\", 850, 860, 10],\n", + " [756, \"14:13\", \"15:00\", 853, 900, 47],\n", + " [757, \"14:13\", \"14:40\", 853, 880, 27],\n", + " [758, \"14:14\", \"15:40\", 854, 940, 86],\n", + " [759, \"14:14\", \"14:25\", 854, 865, 11],\n", + " [760, \"14:15\", \"14:54\", 855, 894, 39],\n", + " [761, \"14:16\", \"14:28\", 856, 868, 12],\n", + " [762, \"14:18\", \"15:05\", 858, 905, 47],\n", + " [763, \"14:18\", \"15:44\", 858, 944, 86],\n", + " [764, \"14:18\", \"15:05\", 858, 905, 47],\n", + " [765, \"14:19\", \"15:13\", 859, 913, 54],\n", + " [766, \"14:20\", \"15:31\", 860, 931, 71],\n", + " [767, \"14:20\", \"14:30\", 860, 870, 10],\n", + " [768, \"14:20\", \"14:36\", 860, 876, 16],\n", + " [769, \"14:21\", \"14:47\", 861, 887, 26],\n", + " [770, \"14:23\", \"15:10\", 863, 910, 47],\n", + " [771, \"14:23\", \"14:45\", 863, 885, 22],\n", + " [772, \"14:24\", \"15:50\", 864, 950, 86],\n", + " [773, \"14:24\", \"14:35\", 864, 875, 11],\n", + " [774, \"14:25\", \"15:02\", 865, 902, 37],\n", + " [775, \"14:26\", \"14:52\", 866, 892, 26],\n", + " [776, \"14:28\", \"15:15\", 868, 915, 47],\n", + " [777, \"14:28\", \"14:55\", 868, 895, 27],\n", + " [778, \"14:28\", \"15:54\", 868, 954, 86],\n", + " [779, \"14:28\", \"15:15\", 868, 915, 47],\n", + " [780, \"14:29\", \"15:23\", 869, 923, 54],\n", + " [781, \"14:30\", \"15:41\", 870, 941, 71],\n", + " [782, \"14:30\", \"14:40\", 870, 880, 10],\n", + " [783, \"14:31\", \"14:43\", 871, 883, 12],\n", + " [784, \"14:33\", \"15:20\", 873, 920, 47],\n", + " [785, \"14:34\", \"16:00\", 874, 960, 86],\n", + " [786, \"14:34\", \"14:45\", 874, 885, 11],\n", + " [787, \"14:35\", \"15:11\", 875, 911, 36],\n", + " [788, \"14:38\", \"15:25\", 878, 925, 47],\n", + " [789, \"14:38\", \"15:25\", 878, 925, 47],\n", + " [790, \"14:38\", \"16:04\", 878, 964, 86],\n", + " [791, \"14:39\", \"15:33\", 879, 933, 54],\n", + " [792, \"14:40\", \"14:50\", 880, 890, 10],\n", + " [793, \"14:40\", \"15:51\", 880, 951, 71],\n", + " [794, \"14:40\", \"14:56\", 880, 896, 16],\n", + " [795, \"14:43\", \"15:30\", 883, 930, 47],\n", + " [796, \"14:43\", \"15:10\", 883, 910, 27],\n", + " [797, \"14:44\", \"15:00\", 884, 900, 16],\n", + " [798, \"14:44\", \"16:10\", 884, 970, 86],\n", + " [799, \"14:45\", \"15:19\", 885, 919, 34],\n", + " [800, \"14:46\", \"14:58\", 886, 898, 12],\n", + " [801, \"14:48\", \"15:35\", 888, 935, 47],\n", + " [802, \"14:48\", \"15:35\", 888, 935, 47],\n", + " [803, \"14:48\", \"17:04\", 888, 1024, 136],\n", + " [804, \"14:49\", \"15:43\", 889, 943, 54],\n", + " [805, \"14:50\", \"16:01\", 890, 961, 71],\n", + " [806, \"14:50\", \"15:00\", 890, 900, 10],\n", + " [807, \"14:51\", \"15:17\", 891, 917, 26],\n", + " [808, \"14:52\", \"15:27\", 892, 927, 35],\n", + " [809, \"14:52\", \"15:21\", 892, 921, 29],\n", + " [810, \"14:53\", \"15:40\", 893, 940, 47],\n", + " [811, \"14:54\", \"15:08\", 894, 908, 14],\n", + " [812, \"14:54\", \"16:20\", 894, 980, 86],\n", + " [813, \"14:58\", \"16:24\", 898, 984, 86],\n", + " [814, \"14:58\", \"15:45\", 898, 945, 47],\n", + " [815, \"14:58\", \"15:25\", 898, 925, 27],\n", + " [816, \"14:58\", \"15:45\", 898, 945, 47],\n", + " [817, \"14:59\", \"15:53\", 899, 953, 54],\n", + " [818, \"15:00\", \"15:10\", 900, 910, 10],\n", + " [819, \"15:00\", \"15:35\", 900, 935, 35],\n", + " [820, \"15:00\", \"16:11\", 900, 971, 71],\n", + " [821, \"15:00\", \"15:16\", 900, 916, 16],\n", + " [822, \"15:01\", \"15:13\", 901, 913, 12],\n", + " [823, \"15:02\", \"15:16\", 902, 916, 14],\n", + " [824, \"15:03\", \"15:50\", 903, 950, 47],\n", + " [825, \"15:04\", \"16:30\", 904, 990, 86],\n", + " [826, \"15:08\", \"16:34\", 908, 994, 86],\n", + " [827, \"15:08\", \"15:55\", 908, 955, 47],\n", + " [828, \"15:08\", \"15:55\", 908, 955, 47],\n", + " [829, \"15:08\", \"15:45\", 908, 945, 37],\n", + " [830, \"15:09\", \"16:14\", 909, 974, 65],\n", + " [831, \"15:09\", \"16:03\", 909, 963, 54],\n", + " [832, \"15:10\", \"16:21\", 910, 981, 71],\n", + " [833, \"15:10\", \"15:20\", 910, 920, 10],\n", + " [834, \"15:11\", \"15:24\", 911, 924, 13],\n", + " [835, \"15:12\", \"15:36\", 912, 936, 24],\n", + " [836, \"15:13\", \"16:00\", 913, 960, 47],\n", + " [837, \"15:13\", \"15:40\", 913, 940, 27],\n", + " [838, \"15:14\", \"16:40\", 914, 1000, 86],\n", + " [839, \"15:16\", \"15:28\", 916, 928, 12],\n", + " [840, \"15:16\", \"15:55\", 916, 955, 39],\n", + " [841, \"15:18\", \"16:05\", 918, 965, 47],\n", + " [842, \"15:18\", \"16:44\", 918, 1004, 86],\n", + " [843, \"15:18\", \"16:05\", 918, 965, 47],\n", + " [844, \"15:19\", \"16:13\", 919, 973, 54],\n", + " [845, \"15:19\", \"15:34\", 919, 934, 15],\n", + " [846, \"15:20\", \"15:30\", 920, 930, 10],\n", + " [847, \"15:20\", \"16:31\", 920, 991, 71],\n", + " [848, \"15:20\", \"15:36\", 920, 936, 16],\n", + " [849, \"15:21\", \"15:47\", 921, 947, 26],\n", + " [850, \"15:21\", \"16:06\", 921, 966, 45],\n", + " [851, \"15:23\", \"16:10\", 923, 970, 47],\n", + " [852, \"15:24\", \"16:50\", 924, 1010, 86],\n", + " [853, \"15:24\", \"16:05\", 924, 965, 41],\n", + " [854, \"15:27\", \"15:51\", 927, 951, 24],\n", + " [855, \"15:27\", \"15:44\", 927, 944, 17],\n", + " [856, \"15:28\", \"16:15\", 928, 975, 47],\n", + " [857, \"15:28\", \"16:54\", 928, 1014, 86],\n", + " [858, \"15:28\", \"16:15\", 928, 975, 47],\n", + " [859, \"15:28\", \"15:55\", 928, 955, 27],\n", + " [860, \"15:29\", \"16:23\", 929, 983, 54],\n", + " [861, \"15:30\", \"16:41\", 930, 1001, 71],\n", + " [862, \"15:30\", \"15:40\", 930, 940, 10],\n", + " [863, \"15:31\", \"15:43\", 931, 943, 12],\n", + " [864, \"15:33\", \"16:20\", 933, 980, 47],\n", + " [865, \"15:34\", \"17:00\", 934, 1020, 86],\n", + " [866, \"15:34\", \"16:15\", 934, 975, 41],\n", + " [867, \"15:35\", \"15:54\", 935, 954, 19],\n", + " [868, \"15:36\", \"16:21\", 936, 981, 45],\n", + " [869, \"15:38\", \"16:25\", 938, 985, 47],\n", + " [870, \"15:38\", \"16:25\", 938, 985, 47],\n", + " [871, \"15:38\", \"16:39\", 938, 999, 61],\n", + " [872, \"15:39\", \"16:33\", 939, 993, 54],\n", + " [873, \"15:40\", \"15:50\", 940, 950, 10],\n", + " [874, \"15:40\", \"16:51\", 940, 1011, 71],\n", + " [875, \"15:40\", \"15:56\", 940, 956, 16],\n", + " [876, \"15:43\", \"16:10\", 943, 970, 27],\n", + " [877, \"15:43\", \"16:30\", 943, 990, 47],\n", + " [878, \"15:44\", \"17:10\", 944, 1030, 86],\n", + " [879, \"15:44\", \"16:25\", 944, 985, 41],\n", + " [880, \"15:45\", \"16:04\", 945, 964, 19],\n", + " [881, \"15:46\", \"15:58\", 946, 958, 12],\n", + " [882, \"15:48\", \"16:35\", 948, 995, 47],\n", + " [883, \"15:48\", \"16:35\", 948, 995, 47],\n", + " [884, \"15:48\", \"17:14\", 948, 1034, 86],\n", + " [885, \"15:49\", \"16:43\", 949, 1003, 54],\n", + " [886, \"15:50\", \"16:00\", 950, 960, 10],\n", + " [887, \"15:50\", \"17:01\", 950, 1021, 71],\n", + " [888, \"15:51\", \"16:18\", 951, 978, 27],\n", + " [889, \"15:52\", \"16:36\", 952, 996, 44],\n", + " [890, \"15:53\", \"16:40\", 953, 1000, 47],\n", + " [891, \"15:54\", \"17:20\", 954, 1040, 86],\n", + " [892, \"15:54\", \"16:35\", 954, 995, 41],\n", + " [893, \"15:55\", \"16:14\", 955, 974, 19],\n", + " [894, \"15:58\", \"16:25\", 958, 985, 27],\n", + " [895, \"15:58\", \"16:45\", 958, 1005, 47],\n", + " [896, \"15:58\", \"16:45\", 958, 1005, 47],\n", + " [897, \"15:58\", \"17:24\", 958, 1044, 86],\n", + " [898, \"15:59\", \"17:11\", 959, 1031, 72],\n", + " [899, \"15:59\", \"16:53\", 959, 1013, 54],\n", + " [900, \"16:00\", \"16:10\", 960, 970, 10],\n", + " [901, \"16:00\", \"16:16\", 960, 976, 16],\n", + " [902, \"16:01\", \"16:13\", 961, 973, 12],\n", + " [903, \"16:03\", \"16:50\", 963, 1010, 47],\n", + " [904, \"16:04\", \"17:30\", 964, 1050, 86],\n", + " [905, \"16:04\", \"16:45\", 964, 1005, 41],\n", + " [906, \"16:05\", \"16:24\", 965, 984, 19],\n", + " [907, \"16:06\", \"16:51\", 966, 1011, 45],\n", + " [908, \"16:08\", \"16:55\", 968, 1015, 47],\n", + " [909, \"16:08\", \"17:34\", 968, 1054, 86],\n", + " [910, \"16:08\", \"16:55\", 968, 1015, 47],\n", + " [911, \"16:09\", \"17:03\", 969, 1023, 54],\n", + " [912, \"16:09\", \"17:21\", 969, 1041, 72],\n", + " [913, \"16:10\", \"16:20\", 970, 980, 10],\n", + " [914, \"16:13\", \"16:40\", 973, 1000, 27],\n", + " [915, \"16:13\", \"17:00\", 973, 1020, 47],\n", + " [916, \"16:14\", \"16:55\", 974, 1015, 41],\n", + " [917, \"16:14\", \"17:40\", 974, 1060, 86],\n", + " [918, \"16:15\", \"16:34\", 975, 994, 19],\n", + " [919, \"16:16\", \"16:28\", 976, 988, 12],\n", + " [920, \"16:18\", \"17:05\", 978, 1025, 47],\n", + " [921, \"16:18\", \"17:05\", 978, 1025, 47],\n", + " [922, \"16:18\", \"17:44\", 978, 1064, 86],\n", + " [923, \"16:19\", \"17:31\", 979, 1051, 72],\n", + " [924, \"16:19\", \"17:13\", 979, 1033, 54],\n", + " [925, \"16:20\", \"16:30\", 980, 990, 10],\n", + " [926, \"16:20\", \"16:36\", 980, 996, 16],\n", + " [927, \"16:21\", \"16:48\", 981, 1008, 27],\n", + " [928, \"16:22\", \"17:06\", 982, 1026, 44],\n", + " [929, \"16:23\", \"17:10\", 983, 1030, 47],\n", + " [930, \"16:24\", \"17:05\", 984, 1025, 41],\n", + " [931, \"16:24\", \"17:50\", 984, 1070, 86],\n", + " [932, \"16:25\", \"16:44\", 985, 1004, 19],\n", + " [933, \"16:28\", \"17:15\", 988, 1035, 47],\n", + " [934, \"16:28\", \"17:15\", 988, 1035, 47],\n", + " [935, \"16:28\", \"16:55\", 988, 1015, 27],\n", + " [936, \"16:28\", \"17:54\", 988, 1074, 86],\n", + " [937, \"16:29\", \"17:23\", 989, 1043, 54],\n", + " [938, \"16:29\", \"17:41\", 989, 1061, 72],\n", + " [939, \"16:30\", \"16:40\", 990, 1000, 10],\n", + " [940, \"16:31\", \"16:43\", 991, 1003, 12],\n", + " [941, \"16:33\", \"17:20\", 993, 1040, 47],\n", + " [942, \"16:34\", \"17:15\", 994, 1035, 41],\n", + " [943, \"16:34\", \"18:00\", 994, 1080, 86],\n", + " [944, \"16:35\", \"16:54\", 995, 1014, 19],\n", + " [945, \"16:36\", \"17:21\", 996, 1041, 45],\n", + " [946, \"16:38\", \"17:25\", 998, 1045, 47],\n", + " [947, \"16:38\", \"17:25\", 998, 1045, 47],\n", + " [948, \"16:38\", \"18:04\", 998, 1084, 86],\n", + " [949, \"16:39\", \"17:33\", 999, 1053, 54],\n", + " [950, \"16:39\", \"17:51\", 999, 1071, 72],\n", + " [951, \"16:40\", \"16:56\", 1000, 1016, 16],\n", + " [952, \"16:40\", \"16:50\", 1000, 1010, 10],\n", + " [953, \"16:43\", \"17:10\", 1003, 1030, 27],\n", + " [954, \"16:43\", \"17:30\", 1003, 1050, 47],\n", + " [955, \"16:44\", \"17:25\", 1004, 1045, 41],\n", + " [956, \"16:44\", \"18:10\", 1004, 1090, 86],\n", + " [957, \"16:45\", \"17:04\", 1005, 1024, 19],\n", + " [958, \"16:46\", \"16:58\", 1006, 1018, 12],\n", + " [959, \"16:48\", \"18:14\", 1008, 1094, 86],\n", + " [960, \"16:48\", \"17:35\", 1008, 1055, 47],\n", + " [961, \"16:48\", \"17:35\", 1008, 1055, 47],\n", + " [962, \"16:49\", \"18:01\", 1009, 1081, 72],\n", + " [963, \"16:49\", \"17:43\", 1009, 1063, 54],\n", + " [964, \"16:50\", \"17:00\", 1010, 1020, 10],\n", + " [965, \"16:51\", \"17:18\", 1011, 1038, 27],\n", + " [966, \"16:52\", \"17:36\", 1012, 1056, 44],\n", + " [967, \"16:53\", \"17:40\", 1013, 1060, 47],\n", + " [968, \"16:54\", \"18:20\", 1014, 1100, 86],\n", + " [969, \"16:54\", \"17:35\", 1014, 1055, 41],\n", + " [970, \"16:55\", \"17:14\", 1015, 1034, 19],\n", + " [971, \"16:58\", \"17:25\", 1018, 1045, 27],\n", + " [972, \"16:58\", \"17:45\", 1018, 1065, 47],\n", + " [973, \"16:58\", \"17:45\", 1018, 1065, 47],\n", + " [974, \"16:58\", \"18:24\", 1018, 1104, 86],\n", + " [975, \"16:59\", \"18:11\", 1019, 1091, 72],\n", + " [976, \"16:59\", \"17:53\", 1019, 1073, 54],\n", + " [977, \"17:00\", \"17:16\", 1020, 1036, 16],\n", + " [978, \"17:00\", \"17:10\", 1020, 1030, 10],\n", + " [979, \"17:01\", \"17:13\", 1021, 1033, 12],\n", + " [980, \"17:03\", \"17:50\", 1023, 1070, 47],\n", + " [981, \"17:04\", \"18:30\", 1024, 1110, 86],\n", + " [982, \"17:04\", \"17:45\", 1024, 1065, 41],\n", + " [983, \"17:05\", \"17:24\", 1025, 1044, 19],\n", + " [984, \"17:06\", \"17:51\", 1026, 1071, 45],\n", + " [985, \"17:08\", \"17:55\", 1028, 1075, 47],\n", + " [986, \"17:08\", \"17:55\", 1028, 1075, 47],\n", + " [987, \"17:08\", \"18:34\", 1028, 1114, 86],\n", + " [988, \"17:09\", \"18:03\", 1029, 1083, 54],\n", + " [989, \"17:09\", \"18:21\", 1029, 1101, 72],\n", + " [990, \"17:10\", \"17:20\", 1030, 1040, 10],\n", + " [991, \"17:13\", \"17:40\", 1033, 1060, 27],\n", + " [992, \"17:13\", \"18:00\", 1033, 1080, 47],\n", + " [993, \"17:14\", \"17:55\", 1034, 1075, 41],\n", + " [994, \"17:14\", \"18:40\", 1034, 1120, 86],\n", + " [995, \"17:15\", \"17:34\", 1035, 1054, 19],\n", + " [996, \"17:16\", \"17:28\", 1036, 1048, 12],\n", + " [997, \"17:18\", \"18:05\", 1038, 1085, 47],\n", + " [998, \"17:18\", \"18:05\", 1038, 1085, 47],\n", + " [999, \"17:18\", \"18:44\", 1038, 1124, 86],\n", + " [1000, \"17:19\", \"18:31\", 1039, 1111, 72],\n", + " [1001, \"17:19\", \"18:13\", 1039, 1093, 54],\n", + " [1002, \"17:20\", \"17:36\", 1040, 1056, 16],\n", + " [1003, \"17:20\", \"17:30\", 1040, 1050, 10],\n", + " [1004, \"17:21\", \"17:47\", 1041, 1067, 26],\n", + " [1005, \"17:22\", \"18:06\", 1042, 1086, 44],\n", + " [1006, \"17:23\", \"18:10\", 1043, 1090, 47],\n", + " [1007, \"17:24\", \"18:50\", 1044, 1130, 86],\n", + " [1008, \"17:24\", \"18:05\", 1044, 1085, 41],\n", + " [1009, \"17:25\", \"17:44\", 1045, 1064, 19],\n", + " [1010, \"17:28\", \"17:55\", 1048, 1075, 27],\n", + " [1011, \"17:28\", \"18:15\", 1048, 1095, 47],\n", + " [1012, \"17:28\", \"18:15\", 1048, 1095, 47],\n", + " [1013, \"17:28\", \"18:54\", 1048, 1134, 86],\n", + " [1014, \"17:29\", \"18:41\", 1049, 1121, 72],\n", + " [1015, \"17:29\", \"18:23\", 1049, 1103, 54],\n", + " [1016, \"17:30\", \"17:40\", 1050, 1060, 10],\n", + " [1017, \"17:31\", \"17:43\", 1051, 1063, 12],\n", + " [1018, \"17:33\", \"18:20\", 1053, 1100, 47],\n", + " [1019, \"17:34\", \"18:15\", 1054, 1095, 41],\n", + " [1020, \"17:34\", \"19:00\", 1054, 1140, 86],\n", + " [1021, \"17:35\", \"17:54\", 1055, 1074, 19],\n", + " [1022, \"17:36\", \"18:21\", 1056, 1101, 45],\n", + " [1023, \"17:38\", \"18:25\", 1058, 1105, 47],\n", + " [1024, \"17:38\", \"19:04\", 1058, 1144, 86],\n", + " [1025, \"17:38\", \"18:25\", 1058, 1105, 47],\n", + " [1026, \"17:39\", \"18:51\", 1059, 1131, 72],\n", + " [1027, \"17:39\", \"18:33\", 1059, 1113, 54],\n", + " [1028, \"17:40\", \"17:56\", 1060, 1076, 16],\n", + " [1029, \"17:40\", \"17:50\", 1060, 1070, 10],\n", + " [1030, \"17:43\", \"18:10\", 1063, 1090, 27],\n", + " [1031, \"17:43\", \"18:30\", 1063, 1110, 47],\n", + " [1032, \"17:44\", \"18:25\", 1064, 1105, 41],\n", + " [1033, \"17:44\", \"19:14\", 1064, 1154, 90],\n", + " [1034, \"17:45\", \"18:04\", 1065, 1084, 19],\n", + " [1035, \"17:46\", \"17:58\", 1066, 1078, 12],\n", + " [1036, \"17:48\", \"18:35\", 1068, 1115, 47],\n", + " [1037, \"17:48\", \"18:35\", 1068, 1115, 47],\n", + " [1038, \"17:48\", \"19:14\", 1068, 1154, 86],\n", + " [1039, \"17:49\", \"19:01\", 1069, 1141, 72],\n", + " [1040, \"17:49\", \"18:43\", 1069, 1123, 54],\n", + " [1041, \"17:50\", \"18:00\", 1070, 1080, 10],\n", + " [1042, \"17:51\", \"18:17\", 1071, 1097, 26],\n", + " [1043, \"17:52\", \"18:36\", 1072, 1116, 44],\n", + " [1044, \"17:53\", \"18:40\", 1073, 1120, 47],\n", + " [1045, \"17:54\", \"18:35\", 1074, 1115, 41],\n", + " [1046, \"17:54\", \"18:57\", 1074, 1137, 63],\n", + " [1047, \"17:55\", \"18:14\", 1075, 1094, 19],\n", + " [1048, \"17:58\", \"18:45\", 1078, 1125, 47],\n", + " [1049, \"17:58\", \"18:45\", 1078, 1125, 47],\n", + " [1050, \"17:58\", \"18:25\", 1078, 1105, 27],\n", + " [1051, \"17:58\", \"19:26\", 1078, 1166, 88],\n", + " [1052, \"17:59\", \"18:53\", 1079, 1133, 54],\n", + " [1053, \"18:00\", \"19:11\", 1080, 1151, 71],\n", + " [1054, \"18:00\", \"18:10\", 1080, 1090, 10],\n", + " [1055, \"18:00\", \"18:16\", 1080, 1096, 16],\n", + " [1056, \"18:01\", \"18:13\", 1081, 1093, 12],\n", + " [1057, \"18:03\", \"18:50\", 1083, 1130, 47],\n", + " [1058, \"18:04\", \"18:45\", 1084, 1125, 41],\n", + " [1059, \"18:04\", \"19:29\", 1084, 1169, 85],\n", + " [1060, \"18:05\", \"18:24\", 1085, 1104, 19],\n", + " [1061, \"18:06\", \"18:51\", 1086, 1131, 45],\n", + " [1062, \"18:08\", \"18:55\", 1088, 1135, 47],\n", + " [1063, \"18:08\", \"19:06\", 1088, 1146, 58],\n", + " [1064, \"18:08\", \"18:55\", 1088, 1135, 47],\n", + " [1065, \"18:09\", \"19:03\", 1089, 1143, 54],\n", + " [1066, \"18:10\", \"18:20\", 1090, 1100, 10],\n", + " [1067, \"18:10\", \"19:21\", 1090, 1161, 71],\n", + " [1068, \"18:13\", \"19:00\", 1093, 1140, 47],\n", + " [1069, \"18:13\", \"18:40\", 1093, 1120, 27],\n", + " [1070, \"18:14\", \"19:43\", 1094, 1183, 89],\n", + " [1071, \"18:14\", \"18:55\", 1094, 1135, 41],\n", + " [1072, \"18:15\", \"18:34\", 1095, 1114, 19],\n", + " [1073, \"18:16\", \"18:28\", 1096, 1108, 12],\n", + " [1074, \"18:17\", \"18:27\", 1097, 1107, 10],\n", + " [1075, \"18:18\", \"19:41\", 1098, 1181, 83],\n", + " [1076, \"18:18\", \"18:58\", 1098, 1138, 40],\n", + " [1077, \"18:18\", \"19:05\", 1098, 1145, 47],\n", + " [1078, \"18:19\", \"19:13\", 1099, 1153, 54],\n", + " [1079, \"18:20\", \"19:31\", 1100, 1171, 71],\n", + " [1080, \"18:20\", \"18:36\", 1100, 1116, 16],\n", + " [1081, \"18:20\", \"18:30\", 1100, 1110, 10],\n", + " [1082, \"18:22\", \"19:05\", 1102, 1145, 43],\n", + " [1083, \"18:23\", \"19:05\", 1103, 1145, 42],\n", + " [1084, \"18:24\", \"19:27\", 1104, 1167, 63],\n", + " [1085, \"18:24\", \"19:05\", 1104, 1145, 41],\n", + " [1086, \"18:25\", \"18:44\", 1105, 1124, 19],\n", + " [1087, \"18:28\", \"19:25\", 1108, 1165, 57],\n", + " [1088, \"18:28\", \"18:55\", 1108, 1135, 27],\n", + " [1089, \"18:28\", \"19:08\", 1108, 1148, 40],\n", + " [1090, \"18:28\", \"19:15\", 1108, 1155, 47],\n", + " [1091, \"18:29\", \"19:23\", 1109, 1163, 54],\n", + " [1092, \"18:30\", \"19:05\", 1110, 1145, 35],\n", + " [1093, \"18:30\", \"18:40\", 1110, 1120, 10],\n", + " [1094, \"18:31\", \"18:43\", 1111, 1123, 12],\n", + " [1095, \"18:33\", \"19:15\", 1113, 1155, 42],\n", + " [1096, \"18:34\", \"19:58\", 1114, 1198, 84],\n", + " [1097, \"18:34\", \"19:14\", 1114, 1154, 40],\n", + " [1098, \"18:35\", \"18:55\", 1115, 1135, 20],\n", + " [1099, \"18:36\", \"19:20\", 1116, 1160, 44],\n", + " [1100, \"18:38\", \"19:25\", 1118, 1165, 47],\n", + " [1101, \"18:38\", \"19:23\", 1118, 1163, 45],\n", + " [1102, \"18:38\", \"19:56\", 1118, 1196, 78],\n", + " [1103, \"18:39\", \"19:33\", 1119, 1173, 54],\n", + " [1104, \"18:40\", \"18:50\", 1120, 1130, 10],\n", + " [1105, \"18:40\", \"19:45\", 1120, 1185, 65],\n", + " [1106, \"18:40\", \"18:56\", 1120, 1136, 16],\n", + " [1107, \"18:43\", \"19:10\", 1123, 1150, 27],\n", + " [1108, \"18:43\", \"19:30\", 1123, 1170, 47],\n", + " [1109, \"18:44\", \"19:24\", 1124, 1164, 40],\n", + " [1110, \"18:45\", \"19:05\", 1125, 1145, 20],\n", + " [1111, \"18:46\", \"18:58\", 1126, 1138, 12],\n", + " [1112, \"18:48\", \"19:35\", 1128, 1175, 47],\n", + " [1113, \"18:48\", \"20:12\", 1128, 1212, 84],\n", + " [1114, \"18:48\", \"20:11\", 1128, 1211, 83],\n", + " [1115, \"18:48\", \"19:28\", 1128, 1168, 40],\n", + " [1116, \"18:49\", \"19:43\", 1129, 1183, 54],\n", + " [1117, \"18:50\", \"19:00\", 1130, 1140, 10],\n", + " [1118, \"18:51\", \"19:01\", 1131, 1141, 10],\n", + " [1119, \"18:53\", \"19:35\", 1133, 1175, 42],\n", + " [1120, \"18:53\", \"19:15\", 1133, 1155, 22],\n", + " [1121, \"18:53\", \"20:00\", 1133, 1200, 67],\n", + " [1122, \"18:55\", \"19:15\", 1135, 1155, 20],\n", + " [1123, \"18:55\", \"19:34\", 1135, 1174, 39],\n", + " [1124, \"18:58\", \"19:38\", 1138, 1178, 40],\n", + " [1125, \"18:59\", \"19:53\", 1139, 1193, 54],\n", + " [1126, \"18:59\", \"19:50\", 1139, 1190, 51],\n", + " [1127, \"18:59\", \"19:53\", 1139, 1193, 54],\n", + " [1128, \"19:00\", \"19:16\", 1140, 1156, 16],\n", + " [1129, \"19:00\", \"19:10\", 1140, 1150, 10],\n", + " [1130, \"19:00\", \"19:16\", 1140, 1156, 16],\n", + " [1131, \"19:01\", \"19:13\", 1141, 1153, 12],\n", + " [1132, \"19:03\", \"20:26\", 1143, 1226, 83],\n", + " [1133, \"19:03\", \"19:45\", 1143, 1185, 42],\n", + " [1134, \"19:05\", \"19:44\", 1145, 1184, 39],\n", + " [1135, \"19:05\", \"19:25\", 1145, 1165, 20],\n", + " [1136, \"19:08\", \"20:15\", 1148, 1215, 67],\n", + " [1137, \"19:08\", \"19:35\", 1148, 1175, 27],\n", + " [1138, \"19:09\", \"19:49\", 1149, 1189, 40],\n", + " [1139, \"19:09\", \"20:03\", 1149, 1203, 54],\n", + " [1140, \"19:10\", \"19:20\", 1150, 1160, 10],\n", + " [1141, \"19:10\", \"19:20\", 1150, 1160, 10],\n", + " [1142, \"19:11\", \"19:53\", 1151, 1193, 42],\n", + " [1143, \"19:14\", \"20:26\", 1154, 1226, 72],\n", + " [1144, \"19:14\", \"19:35\", 1154, 1175, 21],\n", + " [1145, \"19:14\", \"19:24\", 1154, 1164, 10],\n", + " [1146, \"19:14\", \"20:05\", 1154, 1205, 51],\n", + " [1147, \"19:15\", \"19:30\", 1155, 1170, 15],\n", + " [1148, \"19:15\", \"19:54\", 1155, 1194, 39],\n", + " [1149, \"19:18\", \"20:39\", 1158, 1239, 81],\n", + " [1150, \"19:18\", \"20:00\", 1158, 1200, 42],\n", + " [1151, \"19:19\", \"20:14\", 1159, 1214, 55],\n", + " [1152, \"19:20\", \"19:30\", 1160, 1170, 10],\n", + " [1153, \"19:20\", \"19:36\", 1160, 1176, 16],\n", + " [1154, \"19:21\", \"19:31\", 1161, 1171, 10],\n", + " [1155, \"19:23\", \"20:30\", 1163, 1230, 67],\n", + " [1156, \"19:23\", \"19:35\", 1163, 1175, 12],\n", + " [1157, \"19:24\", \"19:45\", 1164, 1185, 21],\n", + " [1158, \"19:24\", \"19:45\", 1164, 1185, 21],\n", + " [1159, \"19:25\", \"20:04\", 1165, 1204, 39],\n", + " [1160, \"19:26\", \"20:08\", 1166, 1208, 42],\n", + " [1161, \"19:29\", \"20:02\", 1169, 1202, 33],\n", + " [1162, \"19:29\", \"20:18\", 1169, 1218, 49],\n", + " [1163, \"19:29\", \"20:41\", 1169, 1241, 72],\n", + " [1164, \"19:30\", \"19:40\", 1170, 1180, 10],\n", + " [1165, \"19:33\", \"20:54\", 1173, 1254, 81],\n", + " [1166, \"19:33\", \"20:17\", 1173, 1217, 44],\n", + " [1167, \"19:34\", \"19:55\", 1174, 1195, 21],\n", + " [1168, \"19:35\", \"20:14\", 1175, 1214, 39],\n", + " [1169, \"19:38\", \"20:05\", 1178, 1205, 27],\n", + " [1170, \"19:38\", \"20:45\", 1178, 1245, 67],\n", + " [1171, \"19:39\", \"20:12\", 1179, 1212, 33],\n", + " [1172, \"19:40\", \"19:50\", 1180, 1190, 10],\n", + " [1173, \"19:40\", \"19:56\", 1180, 1196, 16],\n", + " [1174, \"19:41\", \"20:27\", 1181, 1227, 46],\n", + " [1175, \"19:43\", \"19:55\", 1183, 1195, 12],\n", + " [1176, \"19:44\", \"20:05\", 1184, 1205, 21],\n", + " [1177, \"19:44\", \"20:33\", 1184, 1233, 49],\n", + " [1178, \"19:44\", \"21:00\", 1184, 1260, 76],\n", + " [1179, \"19:45\", \"20:24\", 1185, 1224, 39],\n", + " [1180, \"19:48\", \"20:37\", 1188, 1237, 49],\n", + " [1181, \"19:48\", \"21:09\", 1188, 1269, 81],\n", + " [1182, \"19:50\", \"20:00\", 1190, 1200, 10],\n", + " [1183, \"19:52\", \"20:29\", 1192, 1229, 37],\n", + " [1184, \"19:53\", \"20:08\", 1193, 1208, 15],\n", + " [1185, \"19:53\", \"21:02\", 1193, 1262, 69],\n", + " [1186, \"19:53\", \"20:20\", 1193, 1220, 27],\n", + " [1187, \"19:54\", \"20:19\", 1194, 1219, 25],\n", + " [1188, \"19:55\", \"20:34\", 1195, 1234, 39],\n", + " [1189, \"19:56\", \"20:34\", 1196, 1234, 38],\n", + " [1190, \"19:59\", \"20:48\", 1199, 1248, 49],\n", + " [1191, \"19:59\", \"21:20\", 1199, 1280, 81],\n", + " [1192, \"20:00\", \"20:16\", 1200, 1216, 16],\n", + " [1193, \"20:00\", \"20:10\", 1200, 1210, 10],\n", + " [1194, \"20:03\", \"20:42\", 1203, 1242, 39],\n", + " [1195, \"20:03\", \"21:24\", 1203, 1284, 81],\n", + " [1196, \"20:04\", \"20:29\", 1204, 1229, 25],\n", + " [1197, \"20:05\", \"20:48\", 1205, 1248, 43],\n", + " [1198, \"20:07\", \"20:44\", 1207, 1244, 37],\n", + " [1199, \"20:08\", \"20:40\", 1208, 1240, 32],\n", + " [1200, \"20:08\", \"20:35\", 1208, 1235, 27],\n", + " [1201, \"20:10\", \"20:20\", 1210, 1220, 10],\n", + " [1202, \"20:10\", \"20:22\", 1210, 1222, 12],\n", + " [1203, \"20:11\", \"20:47\", 1211, 1247, 36],\n", + " [1204, \"20:14\", \"21:04\", 1214, 1264, 50],\n", + " [1205, \"20:14\", \"21:03\", 1214, 1263, 49],\n", + " [1206, \"20:17\", \"21:03\", 1217, 1263, 46],\n", + " [1207, \"20:18\", \"21:39\", 1218, 1299, 81],\n", + " [1208, \"20:20\", \"20:30\", 1220, 1230, 10],\n", + " [1209, \"20:20\", \"20:57\", 1220, 1257, 37],\n", + " [1210, \"20:20\", \"20:36\", 1220, 1236, 16],\n", + " [1211, \"20:22\", \"20:59\", 1222, 1259, 37],\n", + " [1212, \"20:22\", \"20:42\", 1222, 1242, 20],\n", + " [1213, \"20:24\", \"20:49\", 1224, 1249, 25],\n", + " [1214, \"20:27\", \"21:22\", 1227, 1282, 55],\n", + " [1215, \"20:29\", \"21:18\", 1229, 1278, 49],\n", + " [1216, \"20:30\", \"21:07\", 1230, 1267, 37],\n", + " [1217, \"20:30\", \"20:40\", 1230, 1240, 10],\n", + " [1218, \"20:30\", \"20:40\", 1230, 1240, 10],\n", + " [1219, \"20:30\", \"21:40\", 1230, 1300, 70],\n", + " [1220, \"20:32\", \"21:18\", 1232, 1278, 46],\n", + " [1221, \"20:35\", \"21:54\", 1235, 1314, 79],\n", + " [1222, \"20:37\", \"21:14\", 1237, 1274, 37],\n", + " [1223, \"20:38\", \"21:08\", 1238, 1268, 30],\n", + " [1224, \"20:40\", \"20:50\", 1240, 1250, 10],\n", + " [1225, \"20:40\", \"21:17\", 1240, 1277, 37],\n", + " [1226, \"20:40\", \"20:56\", 1240, 1256, 16],\n", + " [1227, \"20:44\", \"21:33\", 1244, 1293, 49],\n", + " [1228, \"20:47\", \"21:33\", 1247, 1293, 46],\n", + " [1229, \"20:47\", \"21:42\", 1247, 1302, 55],\n", + " [1230, \"20:50\", \"21:00\", 1250, 1260, 10],\n", + " [1231, \"20:50\", \"22:00\", 1250, 1320, 70],\n", + " [1232, \"20:50\", \"22:09\", 1250, 1329, 79],\n", + " [1233, \"20:50\", \"21:27\", 1250, 1287, 37],\n", + " [1234, \"20:52\", \"21:29\", 1252, 1289, 37],\n", + " [1235, \"20:53\", \"21:20\", 1253, 1280, 27],\n", + " [1236, \"20:56\", \"21:11\", 1256, 1271, 15],\n", + " [1237, \"20:59\", \"21:48\", 1259, 1308, 49],\n", + " [1238, \"21:00\", \"21:10\", 1260, 1270, 10],\n", + " [1239, \"21:00\", \"21:37\", 1260, 1297, 37],\n", + " [1240, \"21:02\", \"21:48\", 1262, 1308, 46],\n", + " [1241, \"21:05\", \"22:24\", 1265, 1344, 79],\n", + " [1242, \"21:07\", \"21:44\", 1267, 1304, 37],\n", + " [1243, \"21:07\", \"22:02\", 1267, 1322, 55],\n", + " [1244, \"21:08\", \"21:38\", 1268, 1298, 30],\n", + " [1245, \"21:10\", \"22:25\", 1270, 1345, 75],\n", + " [1246, \"21:10\", \"21:20\", 1270, 1280, 10],\n", + " [1247, \"21:10\", \"21:47\", 1270, 1307, 37],\n", + " [1248, \"21:14\", \"22:03\", 1274, 1323, 49],\n", + " [1249, \"21:17\", \"22:03\", 1277, 1323, 46],\n", + " [1250, \"21:20\", \"22:18\", 1280, 1338, 58],\n", + " [1251, \"21:20\", \"21:57\", 1280, 1317, 37],\n", + " [1252, \"21:20\", \"21:30\", 1280, 1290, 10],\n", + " [1253, \"21:22\", \"21:59\", 1282, 1319, 37],\n", + " [1254, \"21:24\", \"21:49\", 1284, 1309, 25],\n", + " [1255, \"21:27\", \"22:21\", 1287, 1341, 54],\n", + " [1256, \"21:30\", \"22:07\", 1290, 1327, 37],\n", + " [1257, \"21:30\", \"22:20\", 1290, 1340, 50],\n", + " [1258, \"21:30\", \"21:40\", 1290, 1300, 10],\n", + " [1259, \"21:32\", \"22:18\", 1292, 1338, 46],\n", + " [1260, \"21:32\", \"22:01\", 1292, 1321, 29],\n", + " [1261, \"21:35\", \"22:54\", 1295, 1374, 79],\n", + " [1262, \"21:37\", \"22:14\", 1297, 1334, 37],\n", + " [1263, \"21:39\", \"21:55\", 1299, 1315, 16],\n", + " [1264, \"21:40\", \"22:17\", 1300, 1337, 37],\n", + " [1265, \"21:40\", \"21:50\", 1300, 1310, 10],\n", + " [1266, \"21:41\", \"22:08\", 1301, 1328, 27],\n", + " [1267, \"21:47\", \"22:16\", 1307, 1336, 29],\n", + " [1268, \"21:47\", \"22:51\", 1307, 1371, 64],\n", + " [1269, \"21:47\", \"22:33\", 1307, 1353, 46],\n", + " [1270, \"21:48\", \"22:03\", 1308, 1323, 15],\n", + " [1271, \"21:50\", \"22:55\", 1310, 1375, 65],\n", + " [1272, \"21:50\", \"22:27\", 1310, 1347, 37],\n", + " [1273, \"21:50\", \"22:00\", 1310, 1320, 10],\n", + " [1274, \"21:52\", \"22:29\", 1312, 1349, 37],\n", + " [1275, \"21:53\", \"22:19\", 1313, 1339, 26],\n", + " [1276, \"22:00\", \"22:38\", 1320, 1358, 38],\n", + " [1277, \"22:00\", \"22:10\", 1320, 1330, 10],\n", + " [1278, \"22:02\", \"22:12\", 1322, 1332, 10],\n", + " [1279, \"22:02\", \"22:48\", 1322, 1368, 46],\n", + " [1280, \"22:04\", \"22:31\", 1324, 1351, 27],\n", + " [1281, \"22:05\", \"23:24\", 1325, 1404, 79],\n", + " [1282, \"22:07\", \"22:44\", 1327, 1364, 37],\n", + " [1283, \"22:07\", \"22:39\", 1327, 1359, 32],\n", + " [1284, \"22:09\", \"22:25\", 1329, 1345, 16],\n", + " [1285, \"22:10\", \"23:25\", 1330, 1405, 75],\n", + " [1286, \"22:13\", \"22:38\", 1333, 1358, 25],\n", + " [1287, \"22:13\", \"22:53\", 1333, 1373, 40],\n", + " [1288, \"22:17\", \"22:27\", 1337, 1347, 10],\n", + " [1289, \"22:17\", \"23:03\", 1337, 1383, 46],\n", + " [1290, \"22:19\", \"22:46\", 1339, 1366, 27],\n", + " [1291, \"22:22\", \"22:59\", 1342, 1379, 37],\n", + " [1292, \"22:24\", \"22:48\", 1344, 1368, 24],\n", + " [1293, \"22:27\", \"22:52\", 1347, 1372, 25],\n", + " [1294, \"22:27\", \"23:21\", 1347, 1401, 54],\n", + " [1295, \"22:28\", \"23:08\", 1348, 1388, 40],\n", + " [1296, \"22:30\", \"23:17\", 1350, 1397, 47],\n", + " [1297, \"22:32\", \"22:42\", 1352, 1362, 10],\n", + " [1298, \"22:32\", \"23:11\", 1352, 1391, 39],\n", + " [1299, \"22:34\", \"23:01\", 1354, 1381, 27],\n", + " [1300, \"22:35\", \"23:54\", 1355, 1434, 79],\n", + " [1301, \"22:37\", \"23:14\", 1357, 1394, 37],\n", + " [1302, \"22:43\", \"23:23\", 1363, 1403, 40],\n", + " [1303, \"22:43\", \"23:08\", 1363, 1388, 25],\n", + " [1304, \"22:47\", \"23:33\", 1367, 1413, 46],\n", + " [1305, \"22:47\", \"22:57\", 1367, 1377, 10],\n", + " [1306, \"22:49\", \"23:16\", 1369, 1396, 27],\n", + " [1307, \"22:52\", \"23:29\", 1372, 1409, 37],\n", + " [1308, \"22:53\", \"23:15\", 1373, 1395, 22],\n", + " [1309, \"22:55\", \"23:55\", 1375, 1435, 60],\n", + " [1310, \"22:57\", \"23:51\", 1377, 1431, 54],\n", + " [1311, \"22:58\", \"23:38\", 1378, 1418, 40],\n", + " [1312, \"23:02\", \"23:41\", 1382, 1421, 39],\n", + " [1313, \"23:02\", \"23:12\", 1382, 1392, 10],\n", + " [1314, \"23:04\", \"23:31\", 1384, 1411, 27],\n", + " [1315, \"23:05\", \"00:24\", 1385, 1464, 79],\n", + " [1316, \"23:07\", \"23:44\", 1387, 1424, 37],\n", + " [1317, \"23:13\", \"23:53\", 1393, 1433, 40],\n", + " [1318, \"23:13\", \"23:38\", 1393, 1418, 25],\n", + " [1319, \"23:17\", \"00:03\", 1397, 1443, 46],\n", + " [1320, \"23:17\", \"23:27\", 1397, 1407, 10],\n", + " [1321, \"23:19\", \"23:46\", 1399, 1426, 27],\n", + " [1322, \"23:22\", \"23:59\", 1402, 1439, 37],\n", + " [1323, \"23:25\", \"00:25\", 1405, 1465, 60],\n", + " [1324, \"23:27\", \"00:21\", 1407, 1461, 54],\n", + " [1325, \"23:28\", \"00:08\", 1408, 1448, 40],\n", + " [1326, \"23:32\", \"23:42\", 1412, 1422, 10],\n", + " [1327, \"23:34\", \"00:01\", 1414, 1441, 27],\n", + " [1328, \"23:35\", \"01:05\", 1415, 1505, 90],\n", + " [1329, \"23:37\", \"00:09\", 1417, 1449, 32],\n", + " [1330, \"23:43\", \"00:23\", 1423, 1463, 40],\n", + " [1331, \"23:43\", \"00:08\", 1423, 1448, 25],\n", + " [1332, \"23:46\", \"00:01\", 1426, 1441, 15],\n", + " [1333, \"23:47\", \"23:57\", 1427, 1437, 10],\n", + " [1334, \"23:47\", \"00:33\", 1427, 1473, 46],\n", + " [1335, \"23:52\", \"00:24\", 1432, 1464, 32],\n", + " [1336, \"23:55\", \"00:49\", 1435, 1489, 54],\n", + " [1337, \"23:57\", \"00:57\", 1437, 1497, 60],\n", + " [1338, \"23:58\", \"00:38\", 1438, 1478, 40],\n", + " [1339, \"00:02\", \"00:12\", 1442, 1452, 10],\n", + " [1340, \"00:07\", \"00:39\", 1447, 1479, 32],\n", + " [1341, \"00:13\", \"00:38\", 1453, 1478, 25],\n", + " [1342, \"00:13\", \"00:51\", 1453, 1491, 38],\n", + " [1343, \"00:15\", \"01:14\", 1455, 1514, 59],\n", + " [1344, \"00:17\", \"01:23\", 1457, 1523, 66],\n", + " [1345, \"00:23\", \"00:33\", 1463, 1473, 10],\n", + " [1346, \"00:24\", \"00:40\", 1464, 1480, 16],\n", + " [1347, \"00:25\", \"01:12\", 1465, 1512, 47],\n", + " [1348, \"00:28\", \"01:07\", 1468, 1507, 39],\n", + " [1349, \"00:33\", \"01:05\", 1473, 1505, 32],\n", + " [1350, \"00:43\", \"01:21\", 1483, 1521, 38],\n", + " [1351, \"00:44\", \"00:54\", 1484, 1494, 10],\n", + " [1352, \"00:47\", \"01:09\", 1487, 1509, 22],\n", + " [1353, \"00:47\", \"01:26\", 1487, 1526, 39],\n", + " [1354, \"00:54\", \"01:04\", 1494, 1504, 10],\n", + " [1355, \"00:57\", \"01:07\", 1497, 1507, 10],\n", "] # yapf:disable\n", "\n", "\n", - "\n", "def bus_driver_scheduling(minimize_drivers, max_num_drivers):\n", " \"\"\"Optimize the bus driver scheduling problem.\n", "\n", - " This model has two modes.\n", + " This model has two modes.\n", "\n", - " If minimize_drivers == True, the objective will be to find the minimal\n", - " number of drivers, independently of the working times of each drivers.\n", + " If minimize_drivers == True, the objective will be to find the minimal\n", + " number of drivers, independently of the working times of each drivers.\n", "\n", - " Otherwise, will will create max_num_drivers non optional drivers, and\n", - " minimize the sum of working times of these drivers.\n", + " Otherwise, will will create max_num_drivers non optional drivers, and\n", + " minimize the sum of working times of these drivers.\n", "\n", - " Args:\n", - " minimize_drivers: A Boolean parameter specifying the objective of the\n", - " problem. If True, it tries to minimize the number of used drivers. If\n", - " false, it minimizes the sum of working times per workers.\n", - " max_num_drivers: This number specifies the exact number of non optional\n", - " drivers to use. This is only used if 'minimize_drivers' is False.\n", + " Args:\n", + " minimize_drivers: A Boolean parameter specifying the objective of the\n", + " problem. If True, it tries to minimize the number of used drivers. If\n", + " false, it minimizes the sum of working times per workers.\n", + " max_num_drivers: This number specifies the exact number of non optional\n", + " drivers to use. This is only used if 'minimize_drivers' is False.\n", "\n", - " Returns:\n", - " The objective value of the model.\n", - " \"\"\"\n", + " Returns:\n", + " The objective value of the model.\n", + " \"\"\"\n", " shifts = None\n", " if _INSTANCE.value == 0:\n", " shifts = SAMPLE_SHIFTS_TINY\n", @@ -1817,19 +1820,18 @@ "\n", " # Computed data.\n", " total_driving_time = sum(shift[5] for shift in shifts)\n", - " min_num_drivers = int(math.ceil(total_driving_time * 1.0 /\n", - " max_driving_time))\n", + " min_num_drivers = int(math.ceil(total_driving_time * 1.0 / max_driving_time))\n", " num_drivers = 2 * min_num_drivers if minimize_drivers else max_num_drivers\n", " min_start_time = min(shift[3] for shift in shifts)\n", " max_end_time = max(shift[4] for shift in shifts)\n", "\n", - " print('Bus driver scheduling')\n", - " print(' num shifts =', num_shifts)\n", - " print(' total driving time =', total_driving_time, 'minutes')\n", - " print(' min num drivers =', min_num_drivers)\n", - " print(' num drivers =', num_drivers)\n", - " print(' min start time =', min_start_time)\n", - " print(' max end time =', max_end_time)\n", + " print(\"Bus driver scheduling\")\n", + " print(\" num shifts =\", num_shifts)\n", + " print(\" total driving time =\", total_driving_time, \"minutes\")\n", + " print(\" min num drivers =\", min_num_drivers)\n", + " print(\" num drivers =\", num_drivers)\n", + " print(\" min start time =\", min_start_time)\n", + " print(\" max end time =\", max_end_time)\n", "\n", " model = cp_model.CpModel()\n", "\n", @@ -1871,15 +1873,15 @@ "\n", " for d in range(num_drivers):\n", " start_times.append(\n", - " model.NewIntVar(min_start_time - setup_time, max_end_time,\n", - " 'start_%i' % d))\n", + " model.NewIntVar(min_start_time - setup_time, max_end_time, \"start_%i\" % d)\n", + " )\n", " end_times.append(\n", - " model.NewIntVar(min_start_time, max_end_time + cleanup_time,\n", - " 'end_%i' % d))\n", - " driving_times.append(\n", - " model.NewIntVar(0, max_driving_time, 'driving_%i' % d))\n", + " model.NewIntVar(min_start_time, max_end_time + cleanup_time, \"end_%i\" % d)\n", + " )\n", + " driving_times.append(model.NewIntVar(0, max_driving_time, \"driving_%i\" % d))\n", " working_times.append(\n", - " model.NewIntVar(0, max_working_time, 'working_times_%i' % d))\n", + " model.NewIntVar(0, max_working_time, \"working_times_%i\" % d)\n", + " )\n", "\n", " incoming_literals = collections.defaultdict(list)\n", " outgoing_literals = collections.defaultdict(list)\n", @@ -1889,11 +1891,13 @@ " # Create all the shift variables before iterating on the transitions\n", " # between these shifts.\n", " for s in range(num_shifts):\n", - " total_driving[d, s] = model.NewIntVar(0, max_driving_time,\n", - " 'dr_%i_%i' % (d, s))\n", + " total_driving[d, s] = model.NewIntVar(\n", + " 0, max_driving_time, \"dr_%i_%i\" % (d, s)\n", + " )\n", " no_break_driving[d, s] = model.NewIntVar(\n", - " 0, max_driving_time_without_pauses, 'mdr_%i_%i' % (d, s))\n", - " performed[d, s] = model.NewBoolVar('performed_%i_%i' % (d, s))\n", + " 0, max_driving_time_without_pauses, \"mdr_%i_%i\" % (d, s)\n", + " )\n", + " performed[d, s] = model.NewBoolVar(\"performed_%i_%i\" % (d, s))\n", "\n", " for s in range(num_shifts):\n", " shift = shifts[s]\n", @@ -1902,37 +1906,30 @@ " # Arc from source to shift.\n", " # - set the start time of the driver\n", " # - increase driving time and driving time since break\n", - " source_lit = model.NewBoolVar('%i from source to %i' % (d, s))\n", + " source_lit = model.NewBoolVar(\"%i from source to %i\" % (d, s))\n", " outgoing_source_literals.append(source_lit)\n", " incoming_literals[s].append(source_lit)\n", " shared_incoming_literals[s].append(source_lit)\n", - " model.Add(start_times[d] == shift[3] -\n", - " setup_time).OnlyEnforceIf(source_lit)\n", + " model.Add(start_times[d] == shift[3] - setup_time).OnlyEnforceIf(source_lit)\n", " model.Add(total_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n", - " model.Add(no_break_driving[d,\n", - " s] == duration).OnlyEnforceIf(source_lit)\n", + " model.Add(no_break_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n", " starting_shifts[d, s] = source_lit\n", "\n", " # Arc from shift to sink\n", " # - set the end time of the driver\n", " # - set the driving times of the driver\n", - " sink_lit = model.NewBoolVar('%i from %i to sink' % (d, s))\n", + " sink_lit = model.NewBoolVar(\"%i from %i to sink\" % (d, s))\n", " outgoing_literals[s].append(sink_lit)\n", " shared_outgoing_literals[s].append(sink_lit)\n", " incoming_sink_literals.append(sink_lit)\n", - " model.Add(end_times[d] == shift[4] +\n", - " cleanup_time).OnlyEnforceIf(sink_lit)\n", - " model.Add(\n", - " driving_times[d] == total_driving[d, s]).OnlyEnforceIf(sink_lit)\n", + " model.Add(end_times[d] == shift[4] + cleanup_time).OnlyEnforceIf(sink_lit)\n", + " model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(sink_lit)\n", "\n", " # Node not performed\n", " # - set both driving times to 0\n", " # - add a looping arc on the node\n", - " model.Add(total_driving[d,\n", - " s] == 0).OnlyEnforceIf(performed[d,\n", - " s].Not())\n", - " model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(\n", - " performed[d, s].Not())\n", + " model.Add(total_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not())\n", + " model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not())\n", " incoming_literals[s].append(performed[d, s].Not())\n", " outgoing_literals[s].append(performed[d, s].Not())\n", " # Not adding to the shared lists, because, globally, each node will have\n", @@ -1942,28 +1939,31 @@ " # - add upper bound on start_time\n", " # - add lower bound on end_times\n", " model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf(\n", - " performed[d, s])\n", + " performed[d, s]\n", + " )\n", " model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf(\n", - " performed[d, s])\n", + " performed[d, s]\n", + " )\n", "\n", " for o in range(num_shifts):\n", " other = shifts[o]\n", " delay = other[3] - shift[4]\n", " if delay < min_delay_between_shifts:\n", " continue\n", - " lit = model.NewBoolVar('%i from %i to %i' % (d, s, o))\n", + " lit = model.NewBoolVar(\"%i from %i to %i\" % (d, s, o))\n", "\n", " # Increase driving time\n", - " model.Add(total_driving[d, o] == total_driving[d, s] +\n", - " other[5]).OnlyEnforceIf(lit)\n", + " model.Add(\n", + " total_driving[d, o] == total_driving[d, s] + other[5]\n", + " ).OnlyEnforceIf(lit)\n", "\n", " # Increase no_break_driving or reset it to 0 depending on the delay\n", " if delay >= min_pause_after_4h:\n", - " model.Add(\n", - " no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit)\n", + " model.Add(no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit)\n", " else:\n", - " model.Add(no_break_driving[d, o] == no_break_driving[d, s] +\n", - " other[5]).OnlyEnforceIf(lit)\n", + " model.Add(\n", + " no_break_driving[d, o] == no_break_driving[d, s] + other[5]\n", + " ).OnlyEnforceIf(lit)\n", "\n", " # Add arc\n", " outgoing_literals[s].append(lit)\n", @@ -1979,18 +1979,15 @@ "\n", " if minimize_drivers:\n", " # Driver is not working.\n", - " working = model.NewBoolVar('working_%i' % d)\n", - " model.Add(start_times[d] == min_start_time).OnlyEnforceIf(\n", - " working.Not())\n", - " model.Add(end_times[d] == min_start_time).OnlyEnforceIf(\n", - " working.Not())\n", + " working = model.NewBoolVar(\"working_%i\" % d)\n", + " model.Add(start_times[d] == min_start_time).OnlyEnforceIf(working.Not())\n", + " model.Add(end_times[d] == min_start_time).OnlyEnforceIf(working.Not())\n", " model.Add(driving_times[d] == 0).OnlyEnforceIf(working.Not())\n", " working_drivers.append(working)\n", " outgoing_source_literals.append(working.Not())\n", " incoming_sink_literals.append(working.Not())\n", " # Conditional working time constraints\n", - " model.Add(\n", - " working_times[d] >= min_working_time).OnlyEnforceIf(working)\n", + " model.Add(working_times[d] >= min_working_time).OnlyEnforceIf(working)\n", " model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not())\n", " else:\n", " # Working time constraints\n", @@ -2021,16 +2018,17 @@ " if minimize_drivers:\n", " # Push non working drivers to the end\n", " for d in range(num_drivers - 1):\n", - " model.AddImplication(working_drivers[d].Not(),\n", - " working_drivers[d + 1].Not())\n", + " model.AddImplication(working_drivers[d].Not(), working_drivers[d + 1].Not())\n", "\n", " # Redundant constraints: sum of driving times = sum of shift driving times\n", " model.Add(cp_model.LinearExpr.Sum(driving_times) == total_driving_time)\n", " if not minimize_drivers:\n", " model.Add(\n", - " cp_model.LinearExpr.Sum(working_times) == total_driving_time +\n", - " num_drivers * (setup_time + cleanup_time) +\n", - " cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights))\n", + " cp_model.LinearExpr.Sum(working_times)\n", + " == total_driving_time\n", + " + num_drivers * (setup_time + cleanup_time)\n", + " + cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights)\n", + " )\n", "\n", " if minimize_drivers:\n", " # Minimize the number of working drivers\n", @@ -2038,12 +2036,11 @@ " else:\n", " # Minimize the sum of delays between tasks, which in turns minimize the\n", " # sum of working times as the total driving time is fixed\n", - " model.Minimize(\n", - " cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights))\n", + " model.Minimize(cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights))\n", "\n", " if not minimize_drivers and _OUTPUT_PROTO.value:\n", - " print('Writing proto to %s' % _OUTPUT_PROTO.value)\n", - " with open(_OUTPUT_PROTO.value, 'w') as text_file:\n", + " print(\"Writing proto to %s\" % _OUTPUT_PROTO.value)\n", + " with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", " # Solve model.\n", @@ -2059,14 +2056,16 @@ " # Display solution\n", " if minimize_drivers:\n", " max_num_drivers = int(solver.ObjectiveValue())\n", - " print('minimal number of drivers =', max_num_drivers)\n", + " print(\"minimal number of drivers =\", max_num_drivers)\n", " return max_num_drivers\n", "\n", " for d in range(num_drivers):\n", - " print('Driver %i: ' % (d + 1))\n", - " print(' total driving time =', solver.Value(driving_times[d]))\n", - " print(' working time =',\n", - " solver.Value(working_times[d]) + setup_time + cleanup_time)\n", + " print(\"Driver %i: \" % (d + 1))\n", + " print(\" total driving time =\", solver.Value(driving_times[d]))\n", + " print(\n", + " \" working time =\",\n", + " solver.Value(working_times[d]) + setup_time + cleanup_time,\n", + " )\n", "\n", " first = True\n", " for s in range(num_shifts):\n", @@ -2079,8 +2078,8 @@ " # this one exceeds 30 minutes. For this, we look at the\n", " # no_break_driving which was reinitialized in that case.\n", " if solver.Value(no_break_driving[d, s]) == shift[5] and not first:\n", - " print(' **break**')\n", - " print(' shift ', shift[0], ':', shift[1], '-', shift[2])\n", + " print(\" **break**\")\n", + " print(\" shift \", shift[0], \":\", shift[1], \"-\", shift[2])\n", " first = False\n", "\n", " return int(solver.ObjectiveValue())\n", @@ -2088,12 +2087,12 @@ "\n", "def main(_):\n", " \"\"\"Optimize the bus driver allocation in two passes.\"\"\"\n", - " print('----------- first pass: minimize the number of drivers')\n", + " print(\"----------- first pass: minimize the number of drivers\")\n", " num_drivers = bus_driver_scheduling(True, -1)\n", " if num_drivers == -1:\n", - " print('no solution found, skipping the final step')\n", + " print(\"no solution found, skipping the final step\")\n", " else:\n", - " print('----------- second pass: minimize the sum of working times')\n", + " print(\"----------- second pass: minimize the sum of working times\")\n", " bus_driver_scheduling(False, num_drivers)\n", "\n", "\n", diff --git a/examples/notebook/examples/chemical_balance_sat.ipynb b/examples/notebook/examples/chemical_balance_sat.ipynb index a002657175..257da3ef03 100644 --- a/examples/notebook/examples/chemical_balance_sat.ipynb +++ b/examples/notebook/examples/chemical_balance_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "We are trying to group items in equal sized groups.\n", "\n", "Each item has a color and a value. We want the sum of values of each group to be\n", @@ -127,9 +128,13 @@ " max_set = [\n", " int(\n", " math.ceil(\n", - " min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n", + " min(\n", + " max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n", " for q in all_products\n", - " if chemical_set[s][q + 1] != 0)))\n", + " if chemical_set[s][q + 1] != 0\n", + " )\n", + " )\n", + " )\n", " for s in all_sets\n", " ]\n", "\n", @@ -139,14 +144,13 @@ "\n", " for p in all_products:\n", " model.Add(\n", - " sum(\n", - " int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", - " for s in all_sets) <= int(max_quantities[p][1] * 10000))\n", + " sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)\n", + " <= int(max_quantities[p][1] * 10000)\n", + " )\n", " model.Add(\n", - " sum(\n", - " int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", - " for s in all_sets) >= int(max_quantities[p][1] * 10000) -\n", - " epsilon)\n", + " sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)\n", + " >= int(max_quantities[p][1] * 10000) - epsilon\n", + " )\n", "\n", " model.Minimize(epsilon)\n", "\n", @@ -158,15 +162,15 @@ " print(f\"Optimal objective value = {solver.ObjectiveValue() / 10000.0}\")\n", "\n", " for s in all_sets:\n", - " print(f\" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}\",\n", - " end=\" \")\n", + " print(f\" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}\", end=\" \")\n", " print()\n", " for p in all_products:\n", " name = max_quantities[p][0]\n", " max_quantity = max_quantities[p][1]\n", " quantity = sum(\n", " solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]\n", - " for s in all_sets)\n", + " for s in all_sets\n", + " )\n", " print(f\"{name}: {quantity} out of {max_quantity}\")\n", "\n", "\n", diff --git a/examples/notebook/examples/clustering_sat.ipynb b/examples/notebook/examples/clustering_sat.ipynb index a3e67892df..f66351f856 100644 --- a/examples/notebook/examples/clustering_sat.ipynb +++ b/examples/notebook/examples/clustering_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Cluster 40 cities in 4 equal groups to minimize sum of crossed distances.\n" ] }, @@ -87,6 +88,7 @@ "\n", "\n", "distance_matrix = [\n", + " # fmt:off\n", " [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],\n", " [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],\n", " [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],\n", @@ -127,13 +129,14 @@ " [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],\n", " [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],\n", " [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],\n", - "] # yapf: disable\n", + " # fmt:on\n", + "]\n", "\n", "\n", "def clustering_sat():\n", " \"\"\"Entry point of the program.\"\"\"\n", " num_nodes = len(distance_matrix)\n", - " print('Num nodes =', num_nodes)\n", + " print(\"Num nodes =\", num_nodes)\n", "\n", " # Number of groups to split the nodes, must divide num_nodes.\n", " num_groups = 4\n", @@ -148,7 +151,7 @@ " obj_coeffs = []\n", " for n1 in range(num_nodes - 1):\n", " for n2 in range(n1 + 1, num_nodes):\n", - " same = model.NewBoolVar('neighbors_%i_%i' % (n1, n2))\n", + " same = model.NewBoolVar(\"neighbors_%i_%i\" % (n1, n2))\n", " neighbors[n1, n2] = same\n", " obj_vars.append(same)\n", " obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1])\n", @@ -156,23 +159,24 @@ " # Number of neighborss:\n", " for n in range(num_nodes):\n", " model.Add(\n", - " sum(neighbors[m, n] for m in range(n)) +\n", - " sum(neighbors[n, m]\n", - " for m in range(n + 1, num_nodes)) == group_size - 1)\n", + " sum(neighbors[m, n] for m in range(n))\n", + " + sum(neighbors[n, m] for m in range(n + 1, num_nodes))\n", + " == group_size - 1\n", + " )\n", "\n", " # Enforce transivity on all triplets.\n", " for n1 in range(num_nodes - 2):\n", " for n2 in range(n1 + 1, num_nodes - 1):\n", " for n3 in range(n2 + 1, num_nodes):\n", - " model.Add(neighbors[n1, n3] + neighbors[n2, n3] +\n", - " neighbors[n1, n2] != 2)\n", + " model.Add(\n", + " neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2\n", + " )\n", "\n", " # Redundant constraints on total sum of neighborss.\n", " model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2)\n", "\n", " # Minimize weighted sum of arcs.\n", - " model.Minimize(\n", - " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", "\n", " # Solve and print out the solution.\n", " solver = cp_model.CpSolver()\n", @@ -192,14 +196,14 @@ " for o in range(n + 1, num_nodes):\n", " if solver.BooleanValue(neighbors[n, o]):\n", " visited.add(o)\n", - " output += ' ' + str(o)\n", - " print('Group', g, ':', output)\n", + " output += \" \" + str(o)\n", + " print(\"Group\", g, \":\", output)\n", " break\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " clustering_sat()\n", "\n", "\n", diff --git a/examples/notebook/examples/cover_rectangle_sat.ipynb b/examples/notebook/examples/cover_rectangle_sat.ipynb index a84e0eebde..509e3717ca 100644 --- a/examples/notebook/examples/cover_rectangle_sat.ipynb +++ b/examples/notebook/examples/cover_rectangle_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Fill a 60x50 rectangle by a minimum number of non-overlapping squares.\n" ] }, @@ -102,16 +103,16 @@ "\n", " # Creates intervals for the NoOverlap2D and size variables.\n", " for i in range(num_squares):\n", - " size = model.NewIntVar(1, size_y, 'size_%i' % i)\n", - " start_x = model.NewIntVar(0, size_x, 'sx_%i' % i)\n", - " end_x = model.NewIntVar(0, size_x, 'ex_%i' % i)\n", - " start_y = model.NewIntVar(0, size_y, 'sy_%i' % i)\n", - " end_y = model.NewIntVar(0, size_y, 'ey_%i' % i)\n", + " size = model.NewIntVar(1, size_y, \"size_%i\" % i)\n", + " start_x = model.NewIntVar(0, size_x, \"sx_%i\" % i)\n", + " end_x = model.NewIntVar(0, size_x, \"ex_%i\" % i)\n", + " start_y = model.NewIntVar(0, size_y, \"sy_%i\" % i)\n", + " end_y = model.NewIntVar(0, size_y, \"ey_%i\" % i)\n", "\n", - " interval_x = model.NewIntervalVar(start_x, size, end_x, 'ix_%i' % i)\n", - " interval_y = model.NewIntervalVar(start_y, size, end_y, 'iy_%i' % i)\n", + " interval_x = model.NewIntervalVar(start_x, size, end_x, \"ix_%i\" % i)\n", + " interval_y = model.NewIntervalVar(start_y, size, end_y, \"iy_%i\" % i)\n", "\n", - " area = model.NewIntVar(1, size_y * size_y, 'area_%i' % i)\n", + " area = model.NewIntVar(1, size_y * size_y, \"area_%i\" % i)\n", " model.AddMultiplicationEquality(area, [size, size])\n", "\n", " areas.append(area)\n", @@ -136,7 +137,7 @@ " model.Add(sizes[i] <= sizes[i + 1])\n", "\n", " # Define same to be true iff sizes[i] == sizes[i + 1]\n", - " same = model.NewBoolVar('')\n", + " same = model.NewBoolVar(\"\")\n", " model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same)\n", " model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not())\n", "\n", @@ -149,35 +150,39 @@ "\n", " # Creates a solver and solves.\n", " solver = cp_model.CpSolver()\n", - " solver.parameters.num_workers = 8\n", + " solver.parameters.num_workers = 16\n", + " # solver.parameters.log_search_progress = True\n", + " solver.parameters.max_time_in_seconds = 10.0\n", " status = solver.Solve(model)\n", - " print('%s found in %0.2fs' % (solver.StatusName(status), solver.WallTime()))\n", + " print(\"%s found in %0.2fs\" % (solver.StatusName(status), solver.WallTime()))\n", "\n", " # Prints solution.\n", - " if status == cp_model.OPTIMAL:\n", - " display = [[' ' for _ in range(size_x)] for _ in range(size_y)]\n", + " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", + " display = [[\" \" for _ in range(size_x)] for _ in range(size_y)]\n", " for i in range(num_squares):\n", " sol_x = solver.Value(x_starts[i])\n", " sol_y = solver.Value(y_starts[i])\n", " sol_s = solver.Value(sizes[i])\n", - " char = format(i, '01x')\n", + " char = format(i, \"01x\")\n", " for j in range(sol_s):\n", " for k in range(sol_s):\n", - " if display[sol_y + j][sol_x + k] != ' ':\n", - " print('ERROR between %s and %s' %\n", - " (display[sol_y + j][sol_x + k], char))\n", + " if display[sol_y + j][sol_x + k] != \" \":\n", + " print(\n", + " \"ERROR between %s and %s\"\n", + " % (display[sol_y + j][sol_x + k], char)\n", + " )\n", " display[sol_y + j][sol_x + k] = char\n", "\n", " for line in range(size_y):\n", - " print(' '.join(display[line]))\n", + " print(\" \".join(display[line]))\n", " return status == cp_model.OPTIMAL\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " for num_squares in range(1, 15):\n", - " print('Trying with size =', num_squares)\n", + " print(\"Trying with size =\", num_squares)\n", " if cover_rectangle(num_squares):\n", " break\n", "\n", diff --git a/examples/notebook/examples/cryptarithm_sat.ipynb b/examples/notebook/examples/cryptarithm_sat.ipynb index 7b5cf93a59..ac79fe69ba 100644 --- a/examples/notebook/examples/cryptarithm_sat.ipynb +++ b/examples/notebook/examples/cryptarithm_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.\n", "\n" ] @@ -87,28 +88,27 @@ "\n", "\n", "def send_more_money():\n", - " \"\"\"Solve the cryptarithmic puzzle SEND+MORE=MONEY.\n", - " \"\"\"\n", + " \"\"\"Solve the cryptarithmic puzzle SEND+MORE=MONEY.\"\"\"\n", " model = cp_model.CpModel()\n", "\n", " # Create variables.\n", " # Since s is a leading digit, it can't be 0.\n", - " s = model.NewIntVar(1, 9, 's')\n", - " e = model.NewIntVar(0, 9, 'e')\n", - " n = model.NewIntVar(0, 9, 'n')\n", - " d = model.NewIntVar(0, 9, 'd')\n", + " s = model.NewIntVar(1, 9, \"s\")\n", + " e = model.NewIntVar(0, 9, \"e\")\n", + " n = model.NewIntVar(0, 9, \"n\")\n", + " d = model.NewIntVar(0, 9, \"d\")\n", " # Since m is a leading digit, it can't be 0.\n", - " m = model.NewIntVar(1, 9, 'm')\n", - " o = model.NewIntVar(0, 9, 'o')\n", - " r = model.NewIntVar(0, 9, 'r')\n", - " y = model.NewIntVar(0, 9, 'y')\n", + " m = model.NewIntVar(1, 9, \"m\")\n", + " o = model.NewIntVar(0, 9, \"o\")\n", + " r = model.NewIntVar(0, 9, \"r\")\n", + " y = model.NewIntVar(0, 9, \"y\")\n", "\n", " # Create carry variables. c0 is true if the first column of addends carries\n", " # a 1, c2 is true if the second column carries a 1, and so on.\n", - " c0 = model.NewBoolVar('c0')\n", - " c1 = model.NewBoolVar('c1')\n", - " c2 = model.NewBoolVar('c2')\n", - " c3 = model.NewBoolVar('c3')\n", + " c0 = model.NewBoolVar(\"c0\")\n", + " c1 = model.NewBoolVar(\"c1\")\n", + " c2 = model.NewBoolVar(\"c2\")\n", + " c3 = model.NewBoolVar(\"c3\")\n", "\n", " # Force all letters to take on different values.\n", " model.AddAllDifferent(s, e, n, d, m, o, r, y)\n", @@ -131,15 +131,15 @@ " # Solve model.\n", " solver = cp_model.CpSolver()\n", " if solver.Solve(model) == cp_model.OPTIMAL:\n", - " print('Optimal solution found!')\n", - " print('s:', solver.Value(s))\n", - " print('e:', solver.Value(e))\n", - " print('n:', solver.Value(n))\n", - " print('d:', solver.Value(d))\n", - " print('m:', solver.Value(m))\n", - " print('o:', solver.Value(o))\n", - " print('r:', solver.Value(r))\n", - " print('y:', solver.Value(y))\n", + " print(\"Optimal solution found!\")\n", + " print(\"s:\", solver.Value(s))\n", + " print(\"e:\", solver.Value(e))\n", + " print(\"n:\", solver.Value(n))\n", + " print(\"d:\", solver.Value(d))\n", + " print(\"m:\", solver.Value(m))\n", + " print(\"o:\", solver.Value(o))\n", + " print(\"r:\", solver.Value(r))\n", + " print(\"y:\", solver.Value(y))\n", "\n", "\n", "def main(_):\n", diff --git a/examples/notebook/examples/flexible_job_shop_sat.ipynb b/examples/notebook/examples/flexible_job_shop_sat.ipynb index 5d7d02d10c..db764f209a 100644 --- a/examples/notebook/examples/flexible_job_shop_sat.ipynb +++ b/examples/notebook/examples/flexible_job_shop_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves a flexible jobshop problems with the CP-SAT solver.\n", "\n", "A jobshop is a standard scheduling problem when you must sequence a\n", @@ -106,8 +107,10 @@ "\n", " def on_solution_callback(self):\n", " \"\"\"Called at each new solution.\"\"\"\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n", + " )\n", " self.__solution_count += 1\n", "\n", "\n", @@ -149,7 +152,7 @@ " max_task_duration = max(max_task_duration, alternative[0])\n", " horizon += max_task_duration\n", "\n", - " print('Horizon = %i' % horizon)\n", + " print(\"Horizon = %i\" % horizon)\n", "\n", " # Global storage of variables.\n", " intervals_per_resources = collections.defaultdict(list)\n", @@ -177,13 +180,15 @@ " max_duration = max(max_duration, alt_duration)\n", "\n", " # Create main interval for the task.\n", - " suffix_name = '_j%i_t%i' % (job_id, task_id)\n", - " start = model.NewIntVar(0, horizon, 'start' + suffix_name)\n", - " duration = model.NewIntVar(min_duration, max_duration,\n", - " 'duration' + suffix_name)\n", - " end = model.NewIntVar(0, horizon, 'end' + suffix_name)\n", - " interval = model.NewIntervalVar(start, duration, end,\n", - " 'interval' + suffix_name)\n", + " suffix_name = \"_j%i_t%i\" % (job_id, task_id)\n", + " start = model.NewIntVar(0, horizon, \"start\" + suffix_name)\n", + " duration = model.NewIntVar(\n", + " min_duration, max_duration, \"duration\" + suffix_name\n", + " )\n", + " end = model.NewIntVar(0, horizon, \"end\" + suffix_name)\n", + " interval = model.NewIntervalVar(\n", + " start, duration, end, \"interval\" + suffix_name\n", + " )\n", "\n", " # Store the start for the solution.\n", " starts[(job_id, task_id)] = start\n", @@ -197,14 +202,14 @@ " if num_alternatives > 1:\n", " l_presences = []\n", " for alt_id in all_alternatives:\n", - " alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id)\n", - " l_presence = model.NewBoolVar('presence' + alt_suffix)\n", - " l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix)\n", + " alt_suffix = \"_j%i_t%i_a%i\" % (job_id, task_id, alt_id)\n", + " l_presence = model.NewBoolVar(\"presence\" + alt_suffix)\n", + " l_start = model.NewIntVar(0, horizon, \"start\" + alt_suffix)\n", " l_duration = task[alt_id][0]\n", - " l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)\n", + " l_end = model.NewIntVar(0, horizon, \"end\" + alt_suffix)\n", " l_interval = model.NewOptionalIntervalVar(\n", - " l_start, l_duration, l_end, l_presence,\n", - " 'interval' + alt_suffix)\n", + " l_start, l_duration, l_end, l_presence, \"interval\" + alt_suffix\n", + " )\n", " l_presences.append(l_presence)\n", "\n", " # Link the primary/global variables with the local ones.\n", @@ -233,7 +238,7 @@ " model.AddNoOverlap(intervals)\n", "\n", " # Makespan objective\n", - " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " makespan = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(makespan, job_ends)\n", " model.Minimize(makespan)\n", "\n", @@ -244,7 +249,7 @@ "\n", " # Print final solution.\n", " for job_id in all_jobs:\n", - " print('Job %i:' % job_id)\n", + " print(\"Job %i:\" % job_id)\n", " for task_id in range(len(jobs[job_id])):\n", " start_value = solver.Value(starts[(job_id, task_id)])\n", " machine = -1\n", @@ -256,15 +261,16 @@ " machine = jobs[job_id][task_id][alt_id][1]\n", " selected = alt_id\n", " print(\n", - " ' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' %\n", - " (job_id, task_id, start_value, selected, machine, duration))\n", + " \" task_%i_%i starts at %i (alt %i, machine %i, duration %i)\"\n", + " % (job_id, task_id, start_value, selected, machine, duration)\n", + " )\n", "\n", - " print('Solve status: %s' % solver.StatusName(status))\n", - " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Solve status: %s\" % solver.StatusName(status))\n", + " print(\"Optimal objective value: %i\" % solver.ObjectiveValue())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "flexible_jobshop()\n", diff --git a/examples/notebook/examples/gate_scheduling_sat.ipynb b/examples/notebook/examples/gate_scheduling_sat.ipynb index 23fee0520d..f5ebbe7d3a 100644 --- a/examples/notebook/examples/gate_scheduling_sat.ipynb +++ b/examples/notebook/examples/gate_scheduling_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Gate Scheduling problem.\n", "\n", "We have a set of jobs to perform (duration, width).\n", @@ -114,7 +115,7 @@ " [1, 2],\n", " [6, 8],\n", " [4, 5],\n", - " [3, 7]\n", + " [3, 7],\n", " ]\n", "\n", " max_width = 10\n", @@ -133,31 +134,31 @@ "\n", " for i in all_jobs:\n", " # Create main interval.\n", - " start = model.NewIntVar(0, horizon, 'start_%i' % i)\n", + " start = model.NewIntVar(0, horizon, \"start_%i\" % i)\n", " duration = jobs[i][0]\n", - " end = model.NewIntVar(0, horizon, 'end_%i' % i)\n", - " interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)\n", + " end = model.NewIntVar(0, horizon, \"end_%i\" % i)\n", + " interval = model.NewIntervalVar(start, duration, end, \"interval_%i\" % i)\n", " starts.append(start)\n", " intervals.append(interval)\n", " ends.append(end)\n", " demands.append(jobs[i][1])\n", "\n", " # Create an optional copy of interval to be executed on machine 0.\n", - " performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)\n", + " performed_on_m0 = model.NewBoolVar(\"perform_%i_on_m0\" % i)\n", " performed.append(performed_on_m0)\n", - " start0 = model.NewIntVar(0, horizon, 'start_%i_on_m0' % i)\n", - " end0 = model.NewIntVar(0, horizon, 'end_%i_on_m0' % i)\n", - " interval0 = model.NewOptionalIntervalVar(start0, duration, end0,\n", - " performed_on_m0,\n", - " 'interval_%i_on_m0' % i)\n", + " start0 = model.NewIntVar(0, horizon, \"start_%i_on_m0\" % i)\n", + " end0 = model.NewIntVar(0, horizon, \"end_%i_on_m0\" % i)\n", + " interval0 = model.NewOptionalIntervalVar(\n", + " start0, duration, end0, performed_on_m0, \"interval_%i_on_m0\" % i\n", + " )\n", " intervals0.append(interval0)\n", "\n", " # Create an optional copy of interval to be executed on machine 1.\n", - " start1 = model.NewIntVar(0, horizon, 'start_%i_on_m1' % i)\n", - " end1 = model.NewIntVar(0, horizon, 'end_%i_on_m1' % i)\n", - " interval1 = model.NewOptionalIntervalVar(start1, duration, end1,\n", - " performed_on_m0.Not(),\n", - " 'interval_%i_on_m1' % i)\n", + " start1 = model.NewIntVar(0, horizon, \"start_%i_on_m1\" % i)\n", + " end1 = model.NewIntVar(0, horizon, \"end_%i_on_m1\" % i)\n", + " interval1 = model.NewOptionalIntervalVar(\n", + " start1, duration, end1, performed_on_m0.Not(), \"interval_%i_on_m1\" % i\n", + " )\n", " intervals1.append(interval1)\n", "\n", " # We only propagate the constraint if the tasks is performed on the machine.\n", @@ -172,7 +173,7 @@ " model.AddNoOverlap(intervals1)\n", "\n", " # Objective variable.\n", - " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " makespan = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(makespan, ends)\n", " model.Minimize(makespan)\n", "\n", @@ -185,9 +186,8 @@ "\n", " # Output solution.\n", " if visualization.RunFromIPython():\n", - " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width,\n", - " 40.0)\n", - " output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n", + " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)\n", + " output.AddTitle(\"Makespan = %i\" % solver.ObjectiveValue())\n", " color_manager = visualization.ColorManager()\n", " color_manager.SeedRandomColor(0)\n", "\n", @@ -197,24 +197,26 @@ " d_x = jobs[i][0]\n", " d_y = jobs[i][1]\n", " s_y = performed_machine * (max_width - d_y)\n", - " output.AddRectangle(start, s_y, d_x, d_y,\n", - " color_manager.RandomColor(), 'black', 'j%i' % i)\n", + " output.AddRectangle(\n", + " start, s_y, d_x, d_y, color_manager.RandomColor(), \"black\", \"j%i\" % i\n", + " )\n", "\n", " output.AddXScale()\n", " output.AddYScale()\n", " output.Display()\n", " else:\n", - " print('Solution')\n", - " print(' - makespan = %i' % solver.ObjectiveValue())\n", + " print(\"Solution\")\n", + " print(\" - makespan = %i\" % solver.ObjectiveValue())\n", " for i in all_jobs:\n", " performed_machine = 1 - solver.Value(performed[i])\n", " start = solver.Value(starts[i])\n", - " print(' - Job %i starts at %i on machine %i' %\n", - " (i, start, performed_machine))\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\n", + " \" - Job %i starts at %i on machine %i\" % (i, start, performed_machine)\n", + " )\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/golomb8.ipynb b/examples/notebook/examples/golomb8.ipynb index cd41ed6301..07aced541e 100644 --- a/examples/notebook/examples/golomb8.ipynb +++ b/examples/notebook/examples/golomb8.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This is the Golomb ruler problem.\n", "\n", "This model aims at maximizing radar interferences in a minimum space.\n", @@ -96,13 +97,13 @@ "\n", "def main(_):\n", " # Create the solver.\n", - " solver = pywrapcp.Solver('golomb ruler')\n", + " solver = pywrapcp.Solver(\"golomb ruler\")\n", "\n", " size = 8\n", " var_max = size * size\n", " all_vars = list(range(0, size))\n", "\n", - " marks = [solver.IntVar(0, var_max, 'marks_%d' % i) for i in all_vars]\n", + " marks = [solver.IntVar(0, var_max, \"marks_%d\" % i) for i in all_vars]\n", "\n", " objective = solver.Minimize(marks[size - 1], 1)\n", "\n", @@ -124,20 +125,27 @@ " collector = solver.AllSolutionCollector(solution)\n", "\n", " solver.Solve(\n", - " solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND,\n", - " solver.ASSIGN_MIN_VALUE), [objective, collector])\n", + " solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n", + " [objective, collector],\n", + " )\n", " for i in range(0, collector.SolutionCount()):\n", " obj_value = collector.Value(i, marks[size - 1])\n", " time = collector.WallTime(i)\n", " branches = collector.Branches(i)\n", " failures = collector.Failures(i)\n", - " print(('Solution #%i: value = %i, failures = %i, branches = %i,'\n", - " 'time = %i ms') % (i, obj_value, failures, branches, time))\n", + " print(\n", + " (\"Solution #%i: value = %i, failures = %i, branches = %i,\" \"time = %i ms\")\n", + " % (i, obj_value, failures, branches, time)\n", + " )\n", " time = solver.WallTime()\n", " branches = solver.Branches()\n", " failures = solver.Failures()\n", - " print(('Total run : failures = %i, branches = %i, time = %i ms' %\n", - " (failures, branches, time)))\n", + " print(\n", + " (\n", + " \"Total run : failures = %i, branches = %i, time = %i ms\"\n", + " % (failures, branches, time)\n", + " )\n", + " )\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/golomb_sat.ipynb b/examples/notebook/examples/golomb_sat.ipynb index 0902a6667d..b36fb3ab02 100644 --- a/examples/notebook/examples/golomb_sat.ipynb +++ b/examples/notebook/examples/golomb_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This is the Golomb ruler problem.\n", "\n", "This model aims at maximizing radar interferences in a minimum space.\n", @@ -95,11 +96,12 @@ "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "_ORDER = flags.DEFINE_integer('order', 8, 'Order of the ruler.')\n", + "_ORDER = flags.DEFINE_integer(\"order\", 8, \"Order of the ruler.\")\n", "_PARAMS = flags.DEFINE_string(\n", - " 'params',\n", - " 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n", - " 'Sat solver parameters.')\n", + " \"params\",\n", + " \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n", + " \"Sat solver parameters.\",\n", + ")\n", "\n", "\n", "def solve_golomb_ruler(order, params):\n", @@ -110,7 +112,7 @@ " var_max = order * order\n", " all_vars = list(range(0, order))\n", "\n", - " marks = [model.NewIntVar(0, var_max, f'marks_{i}') for i in all_vars]\n", + " marks = [model.NewIntVar(0, var_max, f\"marks_{i}\") for i in all_vars]\n", "\n", " model.Add(marks[0] == 0)\n", " for i in range(order - 2):\n", @@ -119,7 +121,7 @@ " diffs = []\n", " for i in range(order - 1):\n", " for j in range(i + 1, order):\n", - " diff = model.NewIntVar(0, var_max, f'diff [{j},{i}]')\n", + " diff = model.NewIntVar(0, var_max, f\"diff [{j},{i}]\")\n", " model.Add(diff == marks[j] - marks[i])\n", " diffs.append(diff)\n", " model.AddAllDifferent(diffs)\n", @@ -136,27 +138,27 @@ " if params:\n", " text_format.Parse(params, solver.parameters)\n", " solution_printer = cp_model.ObjectiveSolutionPrinter()\n", - " print(f'Golomb ruler(order={order})')\n", + " print(f\"Golomb ruler(order={order})\")\n", " status = solver.Solve(model, solution_printer)\n", "\n", " # Print solution.\n", - " print(f'status: {solver.StatusName(status)}')\n", + " print(f\"status: {solver.StatusName(status)}\")\n", " if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n", " for idx, var in enumerate(marks):\n", - " print(f'mark[{idx}]: {solver.Value(var)}')\n", + " print(f\"mark[{idx}]: {solver.Value(var)}\")\n", " intervals = [solver.Value(diff) for diff in diffs]\n", " intervals.sort()\n", - " print(f'intervals: {intervals}')\n", + " print(f\"intervals: {intervals}\")\n", "\n", - " print('Statistics:')\n", - " print(f'- conflicts: {solver.NumConflicts()}')\n", - " print(f'- branches : {solver.NumBranches()}')\n", - " print(f'- wall time: {solver.WallTime()}s\\n')\n", + " print(\"Statistics:\")\n", + " print(f\"- conflicts: {solver.NumConflicts()}\")\n", + " print(f\"- branches : {solver.NumBranches()}\")\n", + " print(f\"- wall time: {solver.WallTime()}s\\n\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " solve_golomb_ruler(_ORDER.value, _PARAMS.value)\n", "\n", "\n", diff --git a/examples/notebook/examples/hidato_sat.ipynb b/examples/notebook/examples/hidato_sat.ipynb index 5738d1ca06..96b83c0287 100644 --- a/examples/notebook/examples/hidato_sat.ipynb +++ b/examples/notebook/examples/hidato_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves the Hidato problem with the CP-SAT solver.\n" ] }, @@ -89,25 +90,29 @@ "def build_pairs(rows, cols):\n", " \"\"\"Build closeness pairs for consecutive numbers.\n", "\n", - " Build set of allowed pairs such that two consecutive numbers touch\n", - " each other in the grid.\n", + " Build set of allowed pairs such that two consecutive numbers touch\n", + " each other in the grid.\n", "\n", - " Returns:\n", - " A list of pairs for allowed consecutive position of numbers.\n", + " Returns:\n", + " A list of pairs for allowed consecutive position of numbers.\n", "\n", - " Args:\n", - " rows: the number of rows in the grid\n", - " cols: the number of columns in the grid\n", - " \"\"\"\n", + " Args:\n", + " rows: the number of rows in the grid\n", + " cols: the number of columns in the grid\n", + " \"\"\"\n", " result = []\n", " for x in range(rows):\n", " for y in range(cols):\n", " for dx in (-1, 0, 1):\n", " for dy in (-1, 0, 1):\n", - " if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and\n", - " y + dy < cols and (dx != 0 or dy != 0)):\n", - " result.append(\n", - " (x * cols + y, (x + dx) * cols + (y + dy)))\n", + " if (\n", + " x + dx >= 0\n", + " and x + dx < rows\n", + " and y + dy >= 0\n", + " and y + dy < cols\n", + " and (dx != 0 or dy != 0)\n", + " ):\n", + " result.append((x * cols + y, (x + dx) * cols + (y + dy)))\n", " return result\n", "\n", "\n", @@ -122,7 +127,7 @@ " position = positions[k]\n", " board[position // cols][position % cols] = k + 1\n", " # Print the board.\n", - " print('Solution')\n", + " print(\"Solution\")\n", " print_matrix(board)\n", "\n", "\n", @@ -131,12 +136,12 @@ " rows = len(game)\n", " cols = len(game[0])\n", " for i in range(rows):\n", - " line = ''\n", + " line = \"\"\n", " for j in range(cols):\n", " if game[i][j] == 0:\n", - " line += ' .'\n", + " line += \" .\"\n", " else:\n", - " line += '% 3s' % game[i][j]\n", + " line += \"% 3s\" % game[i][j]\n", " print(line)\n", "\n", "\n", @@ -152,34 +157,60 @@ " puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]\n", "\n", " elif problem == 2:\n", - " puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],\n", - " [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],\n", - " [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7, 0],\n", - " [0, 0, 0, 14, 0, 0, 0]]\n", + " puzzle = [\n", + " [0, 44, 41, 0, 0, 0, 0],\n", + " [0, 43, 0, 28, 29, 0, 0],\n", + " [0, 1, 0, 0, 0, 33, 0],\n", + " [0, 2, 25, 4, 34, 0, 36],\n", + " [49, 16, 0, 23, 0, 0, 0],\n", + " [0, 19, 0, 0, 12, 7, 0],\n", + " [0, 0, 0, 14, 0, 0, 0],\n", + " ]\n", "\n", " elif problem == 3:\n", " # Problems from the book:\n", " # Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n", " # Problem 1 (Practice)\n", - " puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0],\n", - " [23, 0, 1, 14, 11], [0, 25, 0, 0, 12]]\n", + " puzzle = [\n", + " [0, 0, 20, 0, 0],\n", + " [0, 0, 0, 16, 18],\n", + " [22, 0, 15, 0, 0],\n", + " [23, 0, 1, 14, 11],\n", + " [0, 25, 0, 0, 12],\n", + " ]\n", "\n", " elif problem == 4:\n", " # problem 2 (Practice)\n", - " puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5],\n", - " [0, 0, 7, 0, 0], [9, 8, 25, 1, 0]]\n", + " puzzle = [\n", + " [0, 0, 0, 0, 14],\n", + " [0, 18, 12, 0, 0],\n", + " [0, 0, 17, 4, 5],\n", + " [0, 0, 7, 0, 0],\n", + " [9, 8, 25, 1, 0],\n", + " ]\n", "\n", " elif problem == 5:\n", " # problem 3 (Beginner)\n", - " puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19],\n", - " [31, 23, 0, 0, 14, 0], [0, 33, 8, 0, 15, 1],\n", - " [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]\n", + " puzzle = [\n", + " [0, 26, 0, 0, 0, 18],\n", + " [0, 0, 27, 0, 0, 19],\n", + " [31, 23, 0, 0, 14, 0],\n", + " [0, 33, 8, 0, 15, 1],\n", + " [0, 0, 0, 5, 0, 0],\n", + " [35, 36, 0, 10, 0, 0],\n", + " ]\n", " elif problem == 6:\n", " # Problem 15 (Intermediate)\n", - " puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],\n", - " [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0, 50],\n", - " [0, 0, 0, 0, 22, 0, 48, 40], [9, 0, 0, 32, 23, 0, 0, 41],\n", - " [27, 0, 0, 0, 36, 0, 46, 0], [28, 30, 0, 35, 0, 0, 0, 0]]\n", + " puzzle = [\n", + " [64, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 63, 0, 59, 15, 57, 53, 0],\n", + " [0, 4, 0, 14, 0, 0, 0, 0],\n", + " [3, 0, 11, 0, 20, 19, 0, 50],\n", + " [0, 0, 0, 0, 22, 0, 48, 40],\n", + " [9, 0, 0, 32, 23, 0, 0, 41],\n", + " [27, 0, 0, 0, 36, 0, 46, 0],\n", + " [28, 30, 0, 35, 0, 0, 0, 0],\n", + " ]\n", " return puzzle\n", "\n", "\n", @@ -191,18 +222,16 @@ " r = len(puzzle)\n", " c = len(puzzle[0])\n", " if not visualization.RunFromIPython():\n", - " print('')\n", - " print('----- Solving problem %i -----' % index)\n", - " print('')\n", - " print(('Initial game (%i x %i)' % (r, c)))\n", + " print(\"\")\n", + " print(\"----- Solving problem %i -----\" % index)\n", + " print(\"\")\n", + " print((\"Initial game (%i x %i)\" % (r, c)))\n", " print_matrix(puzzle)\n", "\n", " #\n", " # declare variables\n", " #\n", - " positions = [\n", - " model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c)\n", - " ]\n", + " positions = [model.NewIntVar(0, r * c - 1, \"p[%i]\" % i) for i in range(r * c)]\n", "\n", " #\n", " # constraints\n", @@ -221,8 +250,7 @@ " # We use an allowed assignment constraint to model it.\n", " close_tuples = build_pairs(r, c)\n", " for k in range(0, r * c - 1):\n", - " model.AddAllowedAssignments([positions[k], positions[k + 1]],\n", - " close_tuples)\n", + " model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)\n", "\n", " #\n", " # solution and search\n", @@ -238,12 +266,10 @@ " val = solver.Value(var)\n", " x = val % c\n", " y = val // c\n", - " color = 'white' if puzzle[y][x] == 0 else 'lightgreen'\n", - " output.AddRectangle(x, r - y - 1, 1, 1, color, 'black',\n", - " str(i + 1))\n", + " color = \"white\" if puzzle[y][x] == 0 else \"lightgreen\"\n", + " output.AddRectangle(x, r - y - 1, 1, 1, color, \"black\", str(i + 1))\n", "\n", - " output.AddTitle('Puzzle %i solved in %f s' %\n", - " (index, solver.WallTime()))\n", + " output.AddTitle(\"Puzzle %i solved in %f s\" % (index, solver.WallTime()))\n", " output.Display()\n", " else:\n", " print_solution(\n", @@ -251,10 +277,10 @@ " r,\n", " c,\n", " )\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "def main(_):\n", diff --git a/examples/notebook/examples/integer_programming.ipynb b/examples/notebook/examples/integer_programming.ipynb index 4a641552e1..31a472e137 100644 --- a/examples/notebook/examples/integer_programming.ipynb +++ b/examples/notebook/examples/integer_programming.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Integer programming examples that show how to use the APIs.\n" ] }, @@ -83,12 +84,12 @@ "outputs": [], "source": [ "from ortools.linear_solver import pywraplp\n", - "from ortools.init import pywrapinit\n", "\n", "\n", "def Announce(solver, api_type):\n", - " print('---- Integer programming example with ' + solver + ' (' + api_type +\n", - " ') -----')\n", + " print(\n", + " \"---- Integer programming example with \" + solver + \" (\" + api_type + \") -----\"\n", + " )\n", "\n", "\n", "def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type):\n", @@ -98,12 +99,12 @@ " if not solver:\n", " return\n", "\n", - " Announce(optimization_problem_type, 'natural language API')\n", + " Announce(optimization_problem_type, \"natural language API\")\n", "\n", " infinity = solver.infinity()\n", " # x1 and x2 are integer non-negative variables.\n", - " x1 = solver.IntVar(0.0, infinity, 'x1')\n", - " x2 = solver.IntVar(0.0, infinity, 'x2')\n", + " x1 = solver.IntVar(0.0, infinity, \"x1\")\n", + " x2 = solver.IntVar(0.0, infinity, \"x2\")\n", "\n", " solver.Minimize(x1 + 2 * x2)\n", " solver.Add(3 * x1 + 2 * x2 >= 17)\n", @@ -117,12 +118,12 @@ " if not solver:\n", " return\n", "\n", - " Announce(optimization_problem_type, 'C++ style API')\n", + " Announce(optimization_problem_type, \"C++ style API\")\n", "\n", " infinity = solver.infinity()\n", " # x1 and x2 are integer non-negative variables.\n", - " x1 = solver.IntVar(0.0, infinity, 'x1')\n", - " x2 = solver.IntVar(0.0, infinity, 'x2')\n", + " x1 = solver.IntVar(0.0, infinity, \"x1\")\n", + " x2 = solver.IntVar(0.0, infinity, \"x2\")\n", "\n", " # Minimize x1 + 2 * x2.\n", " objective = solver.Objective()\n", @@ -139,8 +140,8 @@ "\n", "def SolveAndPrint(solver, variable_list):\n", " \"\"\"Solve the problem and print the solution.\"\"\"\n", - " print('Number of variables = %d' % solver.NumVariables())\n", - " print('Number of constraints = %d' % solver.NumConstraints())\n", + " print(\"Number of variables = %d\" % solver.NumVariables())\n", + " print(\"Number of constraints = %d\" % solver.NumConstraints())\n", "\n", " result_status = solver.Solve()\n", "\n", @@ -151,33 +152,33 @@ " # GLOP_LINEAR_PROGRAMMING, verifying the solution is highly recommended!).\n", " assert solver.VerifySolution(1e-7, True)\n", "\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", "\n", " # The objective value of the solution.\n", - " print('Optimal objective value = %f' % solver.Objective().Value())\n", + " print(\"Optimal objective value = %f\" % solver.Objective().Value())\n", "\n", " # The value of each variable in the solution.\n", " for variable in variable_list:\n", - " print('%s = %f' % (variable.name(), variable.solution_value()))\n", + " print(\"%s = %f\" % (variable.name(), variable.solution_value()))\n", "\n", - " print('Advanced usage:')\n", - " print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + " print(\"Advanced usage:\")\n", + " print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n", "\n", "\n", "def RunAllIntegerExampleNaturalLanguageAPI():\n", - " RunIntegerExampleNaturalLanguageAPI('GLPK')\n", - " RunIntegerExampleNaturalLanguageAPI('CBC')\n", - " RunIntegerExampleNaturalLanguageAPI('SCIP')\n", - " RunIntegerExampleNaturalLanguageAPI('SAT')\n", - " RunIntegerExampleNaturalLanguageAPI('Gurobi')\n", + " RunIntegerExampleNaturalLanguageAPI(\"GLPK\")\n", + " # Disabling due to ASAN errors with CBC.\n", + " # RunIntegerExampleNaturalLanguageAPI('CBC')\n", + " RunIntegerExampleNaturalLanguageAPI(\"SCIP\")\n", + " RunIntegerExampleNaturalLanguageAPI(\"SAT\")\n", "\n", "\n", "def RunAllIntegerExampleCppStyleAPI():\n", - " RunIntegerExampleCppStyleAPI('GLPK')\n", - " RunIntegerExampleCppStyleAPI('CBC')\n", - " RunIntegerExampleCppStyleAPI('SCIP')\n", - " RunIntegerExampleCppStyleAPI('SAT')\n", - " RunIntegerExampleCppStyleAPI('Gurobi')\n", + " RunIntegerExampleCppStyleAPI(\"GLPK\")\n", + " # Disabling due to ASAN errors with CBC.\n", + " # RunIntegerExampleCppStyleAPI('CBC')\n", + " RunIntegerExampleCppStyleAPI(\"SCIP\")\n", + " RunIntegerExampleCppStyleAPI(\"SAT\")\n", "\n", "\n", "def main():\n", @@ -185,11 +186,6 @@ " RunAllIntegerExampleCppStyleAPI()\n", "\n", "\n", - "pywrapinit.CppBridge.InitLogging('integer_programming.py')\n", - "cpp_flags = pywrapinit.CppFlags()\n", - "cpp_flags.stderrthreshold = 0\n", - "cpp_flags.log_prefix = False\n", - "pywrapinit.CppBridge.SetFlags(cpp_flags)\n", "main()\n", "\n" ] diff --git a/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb b/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb index 02398d10ce..950df8dedb 100644 --- a/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb +++ b/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This model implements a variation of the ft06 jobshop.\n", "\n", "A jobshop is a standard scheduling problem when you must sequence a\n", @@ -114,29 +115,42 @@ " all_machines = range(0, machines_count)\n", " all_jobs = range(0, jobs_count)\n", "\n", - " durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n", - " [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n", + " durations = [\n", + " [1, 3, 6, 7, 3, 6],\n", + " [8, 5, 10, 10, 10, 4],\n", + " [5, 4, 8, 9, 1, 7],\n", + " [5, 5, 5, 3, 8, 9],\n", + " [9, 3, 5, 4, 3, 1],\n", + " [3, 3, 9, 10, 4, 1],\n", + " ]\n", "\n", - " machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n", - " [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n", + " machines = [\n", + " [2, 0, 1, 3, 5, 4],\n", + " [1, 2, 4, 5, 0, 3],\n", + " [2, 3, 5, 0, 1, 4],\n", + " [1, 0, 2, 3, 4, 5],\n", + " [2, 1, 4, 5, 0, 3],\n", + " [1, 3, 5, 0, 4, 2],\n", + " ]\n", "\n", " # Computes horizon statically.\n", " horizon = 150\n", "\n", - " task_type = collections.namedtuple('task_type', 'start end interval')\n", + " task_type = collections.namedtuple(\"task_type\", \"start end interval\")\n", "\n", " # Creates jobs.\n", " all_tasks = {}\n", " for i in all_jobs:\n", " for j in all_machines:\n", - " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n", + " start_var = model.NewIntVar(0, horizon, \"start_%i_%i\" % (i, j))\n", " duration = durations[i][j]\n", - " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval_%i_%i' % (i, j))\n", - " all_tasks[(i, j)] = task_type(start=start_var,\n", - " end=end_var,\n", - " interval=interval_var)\n", + " end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n", + " interval_var = model.NewIntervalVar(\n", + " start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n", + " )\n", + " all_tasks[(i, j)] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var\n", + " )\n", "\n", " # Create disjuctive constraints.\n", " for i in all_machines:\n", @@ -156,23 +170,24 @@ " arcs = []\n", " for j1 in range(len(job_intervals)):\n", " # Initial arc from the dummy node (0) to a task.\n", - " start_lit = model.NewBoolVar('%i is first job' % j1)\n", - " arcs.append([0, j1 + 1, start_lit])\n", + " start_lit = model.NewBoolVar(\"%i is first job\" % j1)\n", + " arcs.append((0, j1 + 1, start_lit))\n", " # Final arc from an arc to the dummy node.\n", - " arcs.append([j1 + 1, 0, model.NewBoolVar('%i is last job' % j1)])\n", + " arcs.append((j1 + 1, 0, model.NewBoolVar(\"%i is last job\" % j1)))\n", "\n", " for j2 in range(len(job_intervals)):\n", " if j1 == j2:\n", " continue\n", "\n", - " lit = model.NewBoolVar('%i follows %i' % (j2, j1))\n", - " arcs.append([j1 + 1, j2 + 1, lit])\n", + " lit = model.NewBoolVar(\"%i follows %i\" % (j2, j1))\n", + " arcs.append((j1 + 1, j2 + 1, lit))\n", "\n", " # We add the reified precedence to link the literal with the\n", " # times of the two tasks.\n", " min_distance = distance_between_jobs(j1, j2)\n", - " model.Add(job_starts[j2] >= job_ends[j1] +\n", - " min_distance).OnlyEnforceIf(lit)\n", + " model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf(\n", + " lit\n", + " )\n", "\n", " model.AddCircuit(arcs)\n", "\n", @@ -182,9 +197,10 @@ " model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n", "\n", " # Makespan objective.\n", - " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " obj_var = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(\n", - " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n", + " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n", + " )\n", " model.Minimize(obj_var)\n", "\n", " # Solve model.\n", @@ -193,7 +209,7 @@ "\n", " # Output solution.\n", " if status == cp_model.OPTIMAL:\n", - " print('Optimal makespan: %i' % solver.ObjectiveValue())\n", + " print(\"Optimal makespan: %i\" % solver.ObjectiveValue())\n", "\n", "\n", "jobshop_ft06_distance()\n", diff --git a/examples/notebook/examples/jobshop_ft06_sat.ipynb b/examples/notebook/examples/jobshop_ft06_sat.ipynb index 8ac074f9ff..cc11ef1ea2 100644 --- a/examples/notebook/examples/jobshop_ft06_sat.ipynb +++ b/examples/notebook/examples/jobshop_ft06_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This model implements a simple jobshop named ft06.\n", "\n", "A jobshop is a standard scheduling problem when you must sequence a\n", @@ -107,29 +108,42 @@ " all_machines = range(0, machines_count)\n", " all_jobs = range(0, jobs_count)\n", "\n", - " durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n", - " [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n", + " durations = [\n", + " [1, 3, 6, 7, 3, 6],\n", + " [8, 5, 10, 10, 10, 4],\n", + " [5, 4, 8, 9, 1, 7],\n", + " [5, 5, 5, 3, 8, 9],\n", + " [9, 3, 5, 4, 3, 1],\n", + " [3, 3, 9, 10, 4, 1],\n", + " ]\n", "\n", - " machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n", - " [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n", + " machines = [\n", + " [2, 0, 1, 3, 5, 4],\n", + " [1, 2, 4, 5, 0, 3],\n", + " [2, 3, 5, 0, 1, 4],\n", + " [1, 0, 2, 3, 4, 5],\n", + " [2, 1, 4, 5, 0, 3],\n", + " [1, 3, 5, 0, 4, 2],\n", + " ]\n", "\n", " # Computes horizon dynamically.\n", " horizon = sum([sum(durations[i]) for i in all_jobs])\n", "\n", - " task_type = collections.namedtuple('task_type', 'start end interval')\n", + " task_type = collections.namedtuple(\"task_type\", \"start end interval\")\n", "\n", " # Creates jobs.\n", " all_tasks = {}\n", " for i in all_jobs:\n", " for j in all_machines:\n", - " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n", + " start_var = model.NewIntVar(0, horizon, \"start_%i_%i\" % (i, j))\n", " duration = durations[i][j]\n", - " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval_%i_%i' % (i, j))\n", - " all_tasks[(i, j)] = task_type(start=start_var,\n", - " end=end_var,\n", - " interval=interval_var)\n", + " end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n", + " interval_var = model.NewIntervalVar(\n", + " start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n", + " )\n", + " all_tasks[(i, j)] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var\n", + " )\n", "\n", " # Create disjuctive constraints.\n", " machine_to_jobs = {}\n", @@ -148,9 +162,10 @@ " model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n", "\n", " # Makespan objective.\n", - " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " obj_var = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(\n", - " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n", + " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n", + " )\n", " model.Minimize(obj_var)\n", "\n", " # Solve model.\n", @@ -161,12 +176,13 @@ " # Output solution.\n", " if status == cp_model.OPTIMAL:\n", " if visualization.RunFromIPython():\n", - " starts = [[\n", - " solver.Value(all_tasks[(i, j)][0]) for j in all_machines\n", - " ] for i in all_jobs]\n", - " visualization.DisplayJobshop(starts, durations, machines, 'FT06')\n", + " starts = [\n", + " [solver.Value(all_tasks[(i, j)][0]) for j in all_machines]\n", + " for i in all_jobs\n", + " ]\n", + " visualization.DisplayJobshop(starts, durations, machines, \"FT06\")\n", " else:\n", - " print('Optimal makespan: %i' % solver.ObjectiveValue())\n", + " print(\"Optimal makespan: %i\" % solver.ObjectiveValue())\n", "\n", "\n", "jobshop_ft06()\n", diff --git a/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb index 3663569250..3a858dd6ae 100644 --- a/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb +++ b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Jobshop with maintenance tasks using the CP-SAT solver.\n" ] }, @@ -96,8 +97,10 @@ "\n", " def on_solution_callback(self):\n", " \"\"\"Called at each new solution.\"\"\"\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n", + " )\n", " self.__solution_count += 1\n", "\n", "\n", @@ -119,10 +122,11 @@ " horizon = sum(task[1] for job in jobs_data for task in job)\n", "\n", " # Named tuple to store information about created variables.\n", - " task_type = collections.namedtuple('Task', 'start end interval')\n", + " task_type = collections.namedtuple(\"Task\", \"start end interval\")\n", " # Named tuple to manipulate solution information.\n", - " assigned_task_type = collections.namedtuple('assigned_task_type',\n", - " 'start job index duration')\n", + " assigned_task_type = collections.namedtuple(\n", + " \"assigned_task_type\", \"start job index duration\"\n", + " )\n", "\n", " # Creates job intervals and add to the corresponding machine lists.\n", " all_tasks = {}\n", @@ -132,18 +136,19 @@ " for task_id, task in enumerate(job):\n", " machine = task[0]\n", " duration = task[1]\n", - " suffix = '_%i_%i' % (job_id, task_id)\n", - " start_var = model.NewIntVar(0, horizon, 'start' + suffix)\n", - " end_var = model.NewIntVar(0, horizon, 'end' + suffix)\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval' + suffix)\n", - " all_tasks[job_id, task_id] = task_type(start=start_var,\n", - " end=end_var,\n", - " interval=interval_var)\n", + " suffix = \"_%i_%i\" % (job_id, task_id)\n", + " start_var = model.NewIntVar(0, horizon, \"start\" + suffix)\n", + " end_var = model.NewIntVar(0, horizon, \"end\" + suffix)\n", + " interval_var = model.NewIntervalVar(\n", + " start_var, duration, end_var, \"interval\" + suffix\n", + " )\n", + " all_tasks[job_id, task_id] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var\n", + " )\n", " machine_to_intervals[machine].append(interval_var)\n", "\n", " # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}).\n", - " machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, 'weekend_0'))\n", + " machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, \"weekend_0\"))\n", "\n", " # Create and add disjunctive constraints.\n", " for machine in all_machines:\n", @@ -152,15 +157,16 @@ " # Precedences inside a job.\n", " for job_id, job in enumerate(jobs_data):\n", " for task_id in range(len(job) - 1):\n", - " model.Add(all_tasks[job_id, task_id +\n", - " 1].start >= all_tasks[job_id, task_id].end)\n", + " model.Add(\n", + " all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end\n", + " )\n", "\n", " # Makespan objective.\n", - " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", - " model.AddMaxEquality(obj_var, [\n", - " all_tasks[job_id, len(job) - 1].end\n", - " for job_id, job in enumerate(jobs_data)\n", - " ])\n", + " obj_var = model.NewIntVar(0, horizon, \"makespan\")\n", + " model.AddMaxEquality(\n", + " obj_var,\n", + " [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)],\n", + " )\n", " model.Minimize(obj_var)\n", "\n", " # Solve model.\n", @@ -176,48 +182,50 @@ " for task_id, task in enumerate(job):\n", " machine = task[0]\n", " assigned_jobs[machine].append(\n", - " assigned_task_type(start=solver.Value(\n", - " all_tasks[job_id, task_id].start),\n", - " job=job_id,\n", - " index=task_id,\n", - " duration=task[1]))\n", + " assigned_task_type(\n", + " start=solver.Value(all_tasks[job_id, task_id].start),\n", + " job=job_id,\n", + " index=task_id,\n", + " duration=task[1],\n", + " )\n", + " )\n", "\n", " # Create per machine output lines.\n", - " output = ''\n", + " output = \"\"\n", " for machine in all_machines:\n", " # Sort by starting time.\n", " assigned_jobs[machine].sort()\n", - " sol_line_tasks = 'Machine ' + str(machine) + ': '\n", - " sol_line = ' '\n", + " sol_line_tasks = \"Machine \" + str(machine) + \": \"\n", + " sol_line = \" \"\n", "\n", " for assigned_task in assigned_jobs[machine]:\n", - " name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)\n", + " name = \"job_%i_%i\" % (assigned_task.job, assigned_task.index)\n", " # Add spaces to output to align columns.\n", - " sol_line_tasks += '%-10s' % name\n", + " sol_line_tasks += \"%-10s\" % name\n", " start = assigned_task.start\n", " duration = assigned_task.duration\n", "\n", - " sol_tmp = '[%i,%i]' % (start, start + duration)\n", + " sol_tmp = \"[%i,%i]\" % (start, start + duration)\n", " # Add spaces to output to align columns.\n", - " sol_line += '%-10s' % sol_tmp\n", + " sol_line += \"%-10s\" % sol_tmp\n", "\n", - " sol_line += '\\n'\n", - " sol_line_tasks += '\\n'\n", + " sol_line += \"\\n\"\n", + " sol_line_tasks += \"\\n\"\n", " output += sol_line_tasks\n", " output += sol_line\n", "\n", " # Finally print the solution found.\n", - " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", + " print(\"Optimal Schedule Length: %i\" % solver.ObjectiveValue())\n", " print(output)\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " jobshop_with_maintenance()\n", "\n", "\n", diff --git a/examples/notebook/examples/knapsack_2d_sat.ipynb b/examples/notebook/examples/knapsack_2d_sat.ipynb index 008d034df7..75dc9c3224 100644 --- a/examples/notebook/examples/knapsack_2d_sat.ipynb +++ b/examples/notebook/examples/knapsack_2d_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solver a 2D rectangle knapsack problem.\n", "\n", "This code is adapted from\n", @@ -96,14 +97,16 @@ "from ortools.sat.python import cp_model\n", "\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", - "_PARAMS = flags.DEFINE_string(\n", - " 'params',\n", - " 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n", - " 'Sat solver parameters.',\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\",\n", + " \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n", + " \"Sat solver parameters.\",\n", + ")\n", + "_MODEL = flags.DEFINE_string(\n", + " \"model\", \"rotation\", \"'duplicate' or 'rotation' or 'optional'\"\n", ")\n", - "_MODEL = flags.DEFINE_string('model', 'rotation',\n", - " '\\'duplicate\\' or \\'rotation\\' or \\'optional\\'')\n", "\n", "\n", "def build_data():\n", @@ -122,25 +125,25 @@ " k10 9 11 5 369.560 cyan\n", " \"\"\"\n", "\n", - " data = pd.read_table(io.StringIO(data), sep=r'\\s+')\n", - " print('Input data')\n", + " data = pd.read_table(io.StringIO(data), sep=r\"\\s+\")\n", + " print(\"Input data\")\n", " print(data)\n", "\n", " max_height = 20\n", " max_width = 30\n", "\n", - " print(f'Container max_width:{max_width} max_height:{max_height}')\n", - " print(f'#Items: {len(data.index)}')\n", + " print(f\"Container max_width:{max_width} max_height:{max_height}\")\n", + " print(f\"#Items: {len(data.index)}\")\n", " return (data, max_height, max_width)\n", "\n", "\n", "def solve_with_duplicate_items(data, max_height, max_width):\n", " \"\"\"Solve the problem by building 2 items (rotated or not) for each item.\"\"\"\n", " # Derived data (expanded to individual items).\n", - " data_widths = data['width'].to_numpy()\n", - " data_heights = data['height'].to_numpy()\n", - " data_availability = data['available'].to_numpy()\n", - " data_values = data['value'].to_numpy()\n", + " data_widths = data[\"width\"].to_numpy()\n", + " data_heights = data[\"height\"].to_numpy()\n", + " data_availability = data[\"available\"].to_numpy()\n", + " data_values = data[\"value\"].to_numpy()\n", "\n", " # Non duplicated items data.\n", " base_item_widths = np.repeat(data_widths, data_availability)\n", @@ -169,21 +172,29 @@ "\n", " for i in range(num_items):\n", " ## Is the item used?\n", - " is_used.append(model.NewBoolVar(f'is_used{i}'))\n", + " is_used.append(model.NewBoolVar(f\"is_used{i}\"))\n", "\n", " ## Item coordinates.\n", - " x_starts.append(model.NewIntVar(0, max_width, f'x_start{i}'))\n", - " x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}'))\n", - " y_starts.append(model.NewIntVar(0, max_height, f'y_start{i}'))\n", - " y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}'))\n", + " x_starts.append(model.NewIntVar(0, max_width, f\"x_start{i}\"))\n", + " x_ends.append(model.NewIntVar(0, max_width, f\"x_end{i}\"))\n", + " y_starts.append(model.NewIntVar(0, max_height, f\"y_start{i}\"))\n", + " y_ends.append(model.NewIntVar(0, max_height, f\"y_end{i}\"))\n", "\n", " ## Interval variables.\n", " x_intervals.append(\n", - " model.NewIntervalVar(x_starts[i], item_widths[i] * is_used[i],\n", - " x_ends[i], f'x_interval{i}'))\n", + " model.NewIntervalVar(\n", + " x_starts[i], item_widths[i] * is_used[i], x_ends[i], f\"x_interval{i}\"\n", + " )\n", + " )\n", " y_intervals.append(\n", - " model.NewIntervalVar(y_starts[i], item_heights[i] * is_used[i],\n", - " y_ends[i], f'y_interval{i}'))\n", + " model.NewIntervalVar(\n", + " y_starts[i], item_heights[i] * is_used[i], y_ends[i], f\"y_interval{i}\"\n", + " )\n", + " )\n", + "\n", + " # Unused boxes are fixed at (0.0).\n", + " model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n", + " model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n", "\n", " # Constraints.\n", "\n", @@ -199,8 +210,8 @@ "\n", " # Output proto to file.\n", " if _OUTPUT_PROTO.value:\n", - " print('Writing proto to %s' % _OUTPUT_PROTO.value)\n", - " with open(_OUTPUT_PROTO.value, 'w') as text_file:\n", + " print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n", + " with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", " # Solve model.\n", @@ -211,27 +222,29 @@ " status = solver.Solve(model)\n", "\n", " # Report solution.\n", - " if status == cp_model.OPTIMAL:\n", + " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n", - " data = pd.DataFrame({\n", - " 'x_start': [solver.Value(x_starts[i]) for i in used],\n", - " 'y_start': [solver.Value(y_starts[i]) for i in used],\n", - " 'item_width': [item_widths[i] for i in used],\n", - " 'item_height': [item_heights[i] for i in used],\n", - " 'x_end': [solver.Value(x_ends[i]) for i in used],\n", - " 'y_end': [solver.Value(y_ends[i]) for i in used],\n", - " 'item_value': [item_values[i] for i in used]\n", - " })\n", + " data = pd.DataFrame(\n", + " {\n", + " \"x_start\": [solver.Value(x_starts[i]) for i in used],\n", + " \"y_start\": [solver.Value(y_starts[i]) for i in used],\n", + " \"item_width\": [item_widths[i] for i in used],\n", + " \"item_height\": [item_heights[i] for i in used],\n", + " \"x_end\": [solver.Value(x_ends[i]) for i in used],\n", + " \"y_end\": [solver.Value(y_ends[i]) for i in used],\n", + " \"item_value\": [item_values[i] for i in used],\n", + " }\n", + " )\n", " print(data)\n", "\n", "\n", "def solve_with_duplicate_optional_items(data, max_height, max_width):\n", " \"\"\"Solve the problem by building 2 optional items (rotated or not) for each item.\"\"\"\n", " # Derived data (expanded to individual items).\n", - " data_widths = data['width'].to_numpy()\n", - " data_heights = data['height'].to_numpy()\n", - " data_availability = data['available'].to_numpy()\n", - " data_values = data['value'].to_numpy()\n", + " data_widths = data[\"width\"].to_numpy()\n", + " data_heights = data[\"height\"].to_numpy()\n", + " data_availability = data[\"available\"].to_numpy()\n", + " data_values = data[\"value\"].to_numpy()\n", "\n", " # Non duplicated items data.\n", " base_item_widths = np.repeat(data_widths, data_availability)\n", @@ -258,22 +271,30 @@ "\n", " for i in range(num_items):\n", " ## Is the item used?\n", - " is_used.append(model.NewBoolVar(f'is_used{i}'))\n", + " is_used.append(model.NewBoolVar(f\"is_used{i}\"))\n", "\n", " ## Item coordinates.\n", " x_starts.append(\n", - " model.NewIntVar(0, max_width - int(item_widths[i]), f'x_start{i}'))\n", + " model.NewIntVar(0, max_width - int(item_widths[i]), f\"x_start{i}\")\n", + " )\n", " y_starts.append(\n", - " model.NewIntVar(0, max_height - int(item_heights[i]),\n", - " f'y_start{i}'))\n", + " model.NewIntVar(0, max_height - int(item_heights[i]), f\"y_start{i}\")\n", + " )\n", "\n", " ## Interval variables.\n", " x_intervals.append(\n", - " model.NewOptionalFixedSizeIntervalVar(x_starts[i], item_widths[i],\n", - " is_used[i], f'x_interval{i}'))\n", + " model.NewOptionalFixedSizeIntervalVar(\n", + " x_starts[i], item_widths[i], is_used[i], f\"x_interval{i}\"\n", + " )\n", + " )\n", " y_intervals.append(\n", - " model.NewOptionalFixedSizeIntervalVar(y_starts[i], item_heights[i],\n", - " is_used[i], f'y_interval{i}'))\n", + " model.NewOptionalFixedSizeIntervalVar(\n", + " y_starts[i], item_heights[i], is_used[i], f\"y_interval{i}\"\n", + " )\n", + " )\n", + " # Unused boxes are fixed at (0.0).\n", + " model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n", + " model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n", "\n", " # Constraints.\n", "\n", @@ -289,8 +310,8 @@ "\n", " # Output proto to file.\n", " if _OUTPUT_PROTO.value:\n", - " print('Writing proto to %s' % _OUTPUT_PROTO.value)\n", - " with open(_OUTPUT_PROTO.value, 'w') as text_file:\n", + " print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n", + " with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", " # Solve model.\n", @@ -301,29 +322,29 @@ " status = solver.Solve(model)\n", "\n", " # Report solution.\n", - " if status == cp_model.OPTIMAL:\n", + " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n", - " data = pd.DataFrame({\n", - " 'x_start': [solver.Value(x_starts[i]) for i in used],\n", - " 'y_start': [solver.Value(y_starts[i]) for i in used],\n", - " 'item_width': [item_widths[i] for i in used],\n", - " 'item_height': [item_heights[i] for i in used],\n", - " 'x_end': [solver.Value(x_starts[i]) + item_widths[i] for i in used],\n", - " 'y_end': [\n", - " solver.Value(y_starts[i]) + item_heights[i] for i in used\n", - " ],\n", - " 'item_value': [item_values[i] for i in used]\n", - " })\n", + " data = pd.DataFrame(\n", + " {\n", + " \"x_start\": [solver.Value(x_starts[i]) for i in used],\n", + " \"y_start\": [solver.Value(y_starts[i]) for i in used],\n", + " \"item_width\": [item_widths[i] for i in used],\n", + " \"item_height\": [item_heights[i] for i in used],\n", + " \"x_end\": [solver.Value(x_starts[i]) + item_widths[i] for i in used],\n", + " \"y_end\": [solver.Value(y_starts[i]) + item_heights[i] for i in used],\n", + " \"item_value\": [item_values[i] for i in used],\n", + " }\n", + " )\n", " print(data)\n", "\n", "\n", "def solve_with_rotations(data, max_height, max_width):\n", " \"\"\"Solve the problem by rotating items.\"\"\"\n", " # Derived data (expanded to individual items).\n", - " data_widths = data['width'].to_numpy()\n", - " data_heights = data['height'].to_numpy()\n", - " data_availability = data['available'].to_numpy()\n", - " data_values = data['value'].to_numpy()\n", + " data_widths = data[\"width\"].to_numpy()\n", + " data_heights = data[\"height\"].to_numpy()\n", + " data_availability = data[\"available\"].to_numpy()\n", + " data_values = data[\"value\"].to_numpy()\n", "\n", " item_widths = np.repeat(data_widths, data_availability)\n", " item_heights = np.repeat(data_heights, data_availability)\n", @@ -347,26 +368,26 @@ " for i in range(num_items):\n", " sizes = [0, int(item_widths[i]), int(item_heights[i])]\n", " # X coordinates.\n", - " x_starts.append(model.NewIntVar(0, max_width, f'x_start{i}'))\n", + " x_starts.append(model.NewIntVar(0, max_width, f\"x_start{i}\"))\n", " x_sizes.append(\n", - " model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),\n", - " f'x_size{i}'))\n", - " x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}'))\n", + " model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"x_size{i}\")\n", + " )\n", + " x_ends.append(model.NewIntVar(0, max_width, f\"x_end{i}\"))\n", "\n", " # Y coordinates.\n", - " y_starts.append(model.NewIntVar(0, max_height, f'y_start{i}'))\n", + " y_starts.append(model.NewIntVar(0, max_height, f\"y_start{i}\"))\n", " y_sizes.append(\n", - " model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),\n", - " f'y_size{i}'))\n", - " y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}'))\n", + " model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"y_size{i}\")\n", + " )\n", + " y_ends.append(model.NewIntVar(0, max_height, f\"y_end{i}\"))\n", "\n", " ## Interval variables\n", " x_intervals.append(\n", - " model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i],\n", - " f'x_interval{i}'))\n", + " model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i], f\"x_interval{i}\")\n", + " )\n", " y_intervals.append(\n", - " model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i],\n", - " f'y_interval{i}'))\n", + " model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i], f\"y_interval{i}\")\n", + " )\n", "\n", " # is_used[i] == True if and only if item i is selected.\n", " is_used = []\n", @@ -375,9 +396,9 @@ "\n", " ## for each item, decide is unselected, no_rotation, rotated.\n", " for i in range(num_items):\n", - " not_selected = model.NewBoolVar(f'not_selected_{i}')\n", - " no_rotation = model.NewBoolVar(f'no_rotation_{i}')\n", - " rotated = model.NewBoolVar(f'rotated_{i}')\n", + " not_selected = model.NewBoolVar(f\"not_selected_{i}\")\n", + " no_rotation = model.NewBoolVar(f\"no_rotation_{i}\")\n", + " rotated = model.NewBoolVar(f\"rotated_{i}\")\n", "\n", " ### Exactly one state must be chosen.\n", " model.AddExactlyOne(not_selected, no_rotation, rotated)\n", @@ -385,8 +406,12 @@ " ### Define height and width according to the state.\n", " dim1 = item_widths[i]\n", " dim2 = item_heights[i]\n", + " # Unused boxes are fixed at (0.0).\n", " model.Add(x_sizes[i] == 0).OnlyEnforceIf(not_selected)\n", " model.Add(y_sizes[i] == 0).OnlyEnforceIf(not_selected)\n", + " model.Add(x_starts[i] == 0).OnlyEnforceIf(not_selected)\n", + " model.Add(y_starts[i] == 0).OnlyEnforceIf(not_selected)\n", + " # Sizes are fixed by the rotation.\n", " model.Add(x_sizes[i] == dim1).OnlyEnforceIf(no_rotation)\n", " model.Add(y_sizes[i] == dim2).OnlyEnforceIf(no_rotation)\n", " model.Add(x_sizes[i] == dim2).OnlyEnforceIf(rotated)\n", @@ -402,8 +427,8 @@ "\n", " # Output proto to file.\n", " if _OUTPUT_PROTO.value:\n", - " print('Writing proto to %s' % _OUTPUT_PROTO.value)\n", - " with open(_OUTPUT_PROTO.value, 'w') as text_file:\n", + " print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n", + " with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", " # Solve model.\n", @@ -414,26 +439,28 @@ " status = solver.Solve(model)\n", "\n", " # Report solution.\n", - " if status == cp_model.OPTIMAL:\n", + " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n", - " data = pd.DataFrame({\n", - " 'x_start': [solver.Value(x_starts[i]) for i in used],\n", - " 'y_start': [solver.Value(y_starts[i]) for i in used],\n", - " 'item_width': [solver.Value(x_sizes[i]) for i in used],\n", - " 'item_height': [solver.Value(y_sizes[i]) for i in used],\n", - " 'x_end': [solver.Value(x_ends[i]) for i in used],\n", - " 'y_end': [solver.Value(y_ends[i]) for i in used],\n", - " 'item_value': [item_values[i] for i in used]\n", - " })\n", + " data = pd.DataFrame(\n", + " {\n", + " \"x_start\": [solver.Value(x_starts[i]) for i in used],\n", + " \"y_start\": [solver.Value(y_starts[i]) for i in used],\n", + " \"item_width\": [solver.Value(x_sizes[i]) for i in used],\n", + " \"item_height\": [solver.Value(y_sizes[i]) for i in used],\n", + " \"x_end\": [solver.Value(x_ends[i]) for i in used],\n", + " \"y_end\": [solver.Value(y_ends[i]) for i in used],\n", + " \"item_value\": [item_values[i] for i in used],\n", + " }\n", + " )\n", " print(data)\n", "\n", "\n", "def main(_):\n", " \"\"\"Solve the problem with all models.\"\"\"\n", " data, max_height, max_width = build_data()\n", - " if _MODEL.value == 'duplicate':\n", + " if _MODEL.value == \"duplicate\":\n", " solve_with_duplicate_items(data, max_height, max_width)\n", - " elif _MODEL.value == 'optional':\n", + " elif _MODEL.value == \"optional\":\n", " solve_with_duplicate_optional_items(data, max_height, max_width)\n", " else:\n", " solve_with_rotations(data, max_height, max_width)\n", diff --git a/examples/notebook/examples/line_balancing_sat.ipynb b/examples/notebook/examples/line_balancing_sat.ipynb index b1ca8ec316..5473d88770 100644 --- a/examples/notebook/examples/line_balancing_sat.ipynb +++ b/examples/notebook/examples/line_balancing_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Reader and solver of the single assembly line balancing problem.\n", "\n", "from https://assembly-line-balancing.de/salbp/:\n", @@ -103,12 +104,14 @@ "\n", "from ortools.sat.python import cp_model\n", "\n", - "_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.')\n", - "_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.')\n", + "_INPUT = flags.DEFINE_string(\"input\", \"\", \"Input file to parse and solve.\")\n", + "_PARAMS = flags.DEFINE_string(\"params\", \"\", \"Sat solver parameters.\")\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", - "_MODEL = flags.DEFINE_string('model', 'boolean',\n", - " 'Model used: boolean, scheduling, greedy')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", + "_MODEL = flags.DEFINE_string(\n", + " \"model\", \"boolean\", \"Model used: boolean, scheduling, greedy\"\n", + ")\n", "\n", "\n", "class SectionInfo(object):\n", @@ -121,13 +124,13 @@ "\n", " def __str__(self):\n", " if self.index_map:\n", - " return f'SectionInfo(index_map={self.index_map})'\n", + " return f\"SectionInfo(index_map={self.index_map})\"\n", " elif self.set_of_pairs:\n", - " return f'SectionInfo(set_of_pairs={self.set_of_pairs})'\n", + " return f\"SectionInfo(set_of_pairs={self.set_of_pairs})\"\n", " elif self.value is not None:\n", - " return f'SectionInfo(value={self.value})'\n", + " return f\"SectionInfo(value={self.value})\"\n", " else:\n", - " return 'SectionInfo()'\n", + " return \"SectionInfo()\"\n", "\n", "\n", "def read_model(filename):\n", @@ -136,64 +139,64 @@ " current_info = SectionInfo()\n", "\n", " model = {}\n", - " with open(filename, 'r') as input_file:\n", - " print(f'Reading model from \\'{filename}\\'')\n", - " section_name = ''\n", + " with open(filename, \"r\") as input_file:\n", + " print(f\"Reading model from '{filename}'\")\n", + " section_name = \"\"\n", "\n", " for line in input_file:\n", " stripped_line = line.strip()\n", " if not stripped_line:\n", " continue\n", "\n", - " match_section_def = re.match(r'<([\\w\\s]+)>', stripped_line)\n", + " match_section_def = re.match(r\"<([\\w\\s]+)>\", stripped_line)\n", " if match_section_def:\n", " section_name = match_section_def.group(1)\n", - " if section_name == 'end':\n", + " if section_name == \"end\":\n", " continue\n", "\n", " current_info = SectionInfo()\n", " model[section_name] = current_info\n", " continue\n", "\n", - " match_single_number = re.match(r'^([0-9]+)$', stripped_line)\n", + " match_single_number = re.match(r\"^([0-9]+)$\", stripped_line)\n", " if match_single_number:\n", " current_info.value = int(match_single_number.group(1))\n", " continue\n", "\n", - " match_key_value = re.match(r'^([0-9]+)\\s+([0-9]+)$', stripped_line)\n", + " match_key_value = re.match(r\"^([0-9]+)\\s+([0-9]+)$\", stripped_line)\n", " if match_key_value:\n", " key = int(match_key_value.group(1))\n", " value = int(match_key_value.group(2))\n", " current_info.index_map[key] = value\n", " continue\n", "\n", - " match_pair = re.match(r'^([0-9]+),([0-9]+)$', stripped_line)\n", + " match_pair = re.match(r\"^([0-9]+),([0-9]+)$\", stripped_line)\n", " if match_pair:\n", " left = int(match_pair.group(1))\n", " right = int(match_pair.group(2))\n", " current_info.set_of_pairs.add((left, right))\n", " continue\n", "\n", - " print(f'Unrecognized line \\'{stripped_line}\\'')\n", + " print(f\"Unrecognized line '{stripped_line}'\")\n", "\n", " return model\n", "\n", "\n", "def print_stats(model):\n", - " print('Model Statistics')\n", + " print(\"Model Statistics\")\n", " for key, value in model.items():\n", - " print(f' - {key}: {value}')\n", + " print(f\" - {key}: {value}\")\n", "\n", "\n", "def solve_model_greedily(model):\n", " \"\"\"Compute a greedy solution.\"\"\"\n", - " print('Solving using a Greedy heuristics')\n", + " print(\"Solving using a Greedy heuristics\")\n", "\n", - " num_tasks = model['number of tasks'].value\n", + " num_tasks = model[\"number of tasks\"].value\n", " all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.\n", - " precedences = model['precedence relations'].set_of_pairs\n", - " durations = model['task times'].index_map\n", - " cycle_time = model['cycle time'].value\n", + " precedences = model[\"precedence relations\"].set_of_pairs\n", + " durations = model[\"task times\"].index_map\n", + " cycle_time = model[\"cycle time\"].value\n", "\n", " weights = collections.defaultdict(int)\n", " successors = collections.defaultdict(list)\n", @@ -212,7 +215,7 @@ "\n", " while len(assignment) < num_tasks:\n", " if not candidates:\n", - " print('error empty')\n", + " print(\"error empty\")\n", " break\n", "\n", " best = -1\n", @@ -242,7 +245,7 @@ " candidates.add(succ)\n", " del weights[succ]\n", "\n", - " print(f' greedy solution uses {current_pod + 1} pods.')\n", + " print(f\" greedy solution uses {current_pod + 1} pods.\")\n", "\n", " return assignment\n", "\n", @@ -250,13 +253,13 @@ "def solve_boolean_model(model, hint):\n", " \"\"\"Solve the given model.\"\"\"\n", "\n", - " print('Solving using the Boolean model')\n", + " print(\"Solving using the Boolean model\")\n", " # Model data\n", - " num_tasks = model['number of tasks'].value\n", + " num_tasks = model[\"number of tasks\"].value\n", " all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the model.\n", - " durations = model['task times'].index_map\n", - " precedences = model['precedence relations'].set_of_pairs\n", - " cycle_time = model['cycle time'].value\n", + " durations = model[\"task times\"].index_map\n", + " precedences = model[\"precedence relations\"].set_of_pairs\n", + " cycle_time = model[\"cycle time\"].value\n", "\n", " num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks - 1\n", " all_pods = range(num_pods)\n", @@ -271,11 +274,11 @@ " # Create the variables\n", " for t in all_tasks:\n", " for p in all_pods:\n", - " assign[t, p] = model.NewBoolVar(f'assign_{t}_{p}')\n", - " possible[t, p] = model.NewBoolVar(f'possible_{t}_{p}')\n", + " assign[t, p] = model.NewBoolVar(f\"assign_{t}_{p}\")\n", + " possible[t, p] = model.NewBoolVar(f\"possible_{t}_{p}\")\n", "\n", " # active[p] indicates if pod p is active.\n", - " active = [model.NewBoolVar(f'active_{p}') for p in all_pods]\n", + " active = [model.NewBoolVar(f\"active_{p}\") for p in all_pods]\n", "\n", " # Each task is done on exactly one pod.\n", " for t in all_tasks:\n", @@ -283,8 +286,7 @@ "\n", " # Total tasks assigned to one pod cannot exceed cycle time.\n", " for p in all_pods:\n", - " model.Add(\n", - " sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)\n", + " model.Add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)\n", "\n", " # Maintain the possible variables:\n", " # possible at pod p -> possible at any pod after p\n", @@ -302,8 +304,7 @@ " # Precedences.\n", " for before, after in precedences:\n", " for p in range(1, num_pods):\n", - " model.AddImplication(assign[before, p], possible[after,\n", - " p - 1].Not())\n", + " model.AddImplication(assign[before, p], possible[after, p - 1].Not())\n", "\n", " # Link active variables with the assign one.\n", " for p in all_pods:\n", @@ -327,7 +328,7 @@ " model.AddHint(assign[t, hint[t]], 1)\n", "\n", " if _OUTPUT_PROTO.value:\n", - " print(f'Writing proto to {_OUTPUT_PROTO.value}')\n", + " print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n", " model.ExportToFile(_OUTPUT_PROTO.value)\n", "\n", " # Solve model.\n", @@ -341,13 +342,13 @@ "def solve_scheduling_model(model, hint):\n", " \"\"\"Solve the given model using a cumutive model.\"\"\"\n", "\n", - " print('Solving using the scheduling model')\n", + " print(\"Solving using the scheduling model\")\n", " # Model data\n", - " num_tasks = model['number of tasks'].value\n", + " num_tasks = model[\"number of tasks\"].value\n", " all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.\n", - " durations = model['task times'].index_map\n", - " precedences = model['precedence relations'].set_of_pairs\n", - " cycle_time = model['cycle time'].value\n", + " durations = model[\"task times\"].index_map\n", + " precedences = model[\"precedence relations\"].set_of_pairs\n", + " cycle_time = model[\"cycle time\"].value\n", "\n", " num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks\n", "\n", @@ -356,21 +357,20 @@ " # pod[t] indicates on which pod the task is performed.\n", " pods = {}\n", " for t in all_tasks:\n", - " pods[t] = model.NewIntVar(0, num_pods - 1, f'pod_{t}')\n", + " pods[t] = model.NewIntVar(0, num_pods - 1, f\"pod_{t}\")\n", "\n", " # Create the variables\n", " intervals = []\n", " demands = []\n", " for t in all_tasks:\n", - " interval = model.NewFixedSizeIntervalVar(pods[t], 1, '')\n", + " interval = model.NewFixedSizeIntervalVar(pods[t], 1, \"\")\n", " intervals.append(interval)\n", " demands.append(durations[t])\n", "\n", " # Add terminating interval as the objective.\n", - " obj_var = model.NewIntVar(1, num_pods, 'obj_var')\n", - " obj_size = model.NewIntVar(1, num_pods, 'obj_duration')\n", - " obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1,\n", - " 'obj_interval')\n", + " obj_var = model.NewIntVar(1, num_pods, \"obj_var\")\n", + " obj_size = model.NewIntVar(1, num_pods, \"obj_duration\")\n", + " obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1, \"obj_interval\")\n", " intervals.append(obj_interval)\n", " demands.append(cycle_time)\n", "\n", @@ -389,7 +389,7 @@ " model.AddHint(pods[t], hint[t])\n", "\n", " if _OUTPUT_PROTO.value:\n", - " print(f'Writing proto to{_OUTPUT_PROTO.value}')\n", + " print(f\"Writing proto to{_OUTPUT_PROTO.value}\")\n", " model.ExportToFile(_OUTPUT_PROTO.value)\n", "\n", " # Solve model.\n", @@ -402,15 +402,15 @@ "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", "\n", " model = read_model(_INPUT.value)\n", " print_stats(model)\n", " greedy_solution = solve_model_greedily(model)\n", "\n", - " if _MODEL.value == 'boolean':\n", + " if _MODEL.value == \"boolean\":\n", " solve_boolean_model(model, greedy_solution)\n", - " elif _MODEL.value == 'scheduling':\n", + " elif _MODEL.value == \"scheduling\":\n", " solve_scheduling_model(model, greedy_solution)\n", "\n", "\n", diff --git a/examples/notebook/examples/linear_assignment_api.ipynb b/examples/notebook/examples/linear_assignment_api.ipynb index 67db00c482..ec8daef579 100644 --- a/examples/notebook/examples/linear_assignment_api.ipynb +++ b/examples/notebook/examples/linear_assignment_api.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Test linear sum assignment on a 4x4 matrix.\n", "\n", " Example taken from:\n", @@ -95,8 +96,7 @@ " \"\"\"Test linear sum assignment on a 4x4 matrix.\"\"\"\n", " num_sources = 4\n", " num_targets = 4\n", - " cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n", - " [45, 110, 95, 115]]\n", + " cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n", " expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]\n", "\n", " assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n", @@ -106,21 +106,22 @@ "\n", " solve_status = assignment.solve()\n", " if solve_status == assignment.OPTIMAL:\n", - " print('Successful solve.')\n", - " print('Total cost', assignment.optimal_cost(), '/', expected_cost)\n", + " print(\"Successful solve.\")\n", + " print(\"Total cost\", assignment.optimal_cost(), \"/\", expected_cost)\n", " for i in range(0, assignment.num_nodes()):\n", - " print('Left node %d assigned to right node %d with cost %d.' %\n", - " (i, assignment.right_mate(i), assignment.assignment_cost(i)))\n", + " print(\n", + " \"Left node %d assigned to right node %d with cost %d.\"\n", + " % (i, assignment.right_mate(i), assignment.assignment_cost(i))\n", + " )\n", " elif solve_status == assignment.INFEASIBLE:\n", - " print('No perfect matching exists.')\n", + " print(\"No perfect matching exists.\")\n", " elif solve_status == assignment.POSSIBLE_OVERFLOW:\n", - " print(\n", - " 'Some input costs are too large and may cause an integer overflow.')\n", + " print(\"Some input costs are too large and may cause an integer overflow.\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " run_assignment_on_4x4_matrix()\n", "\n", "\n", diff --git a/examples/notebook/examples/linear_programming.ipynb b/examples/notebook/examples/linear_programming.ipynb index 10c69dc593..a81c041956 100644 --- a/examples/notebook/examples/linear_programming.ipynb +++ b/examples/notebook/examples/linear_programming.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Linear programming examples that show how to use the APIs.\n" ] }, @@ -86,8 +87,9 @@ "\n", "\n", "def Announce(solver, api_type):\n", - " print('---- Linear programming example with ' + solver + ' (' + api_type +\n", - " ') -----')\n", + " print(\n", + " \"---- Linear programming example with \" + solver + \" (\" + api_type + \") -----\"\n", + " )\n", "\n", "\n", "def RunLinearExampleNaturalLanguageAPI(optimization_problem_type):\n", @@ -97,23 +99,25 @@ " if not solver:\n", " return\n", "\n", - " Announce(optimization_problem_type, 'natural language API')\n", + " Announce(optimization_problem_type, \"natural language API\")\n", "\n", " infinity = solver.infinity()\n", " # x1, x2 and x3 are continuous non-negative variables.\n", - " x1 = solver.NumVar(0.0, infinity, 'x1')\n", - " x2 = solver.NumVar(0.0, infinity, 'x2')\n", - " x3 = solver.NumVar(0.0, infinity, 'x3')\n", + " x1 = solver.NumVar(0.0, infinity, \"x1\")\n", + " x2 = solver.NumVar(0.0, infinity, \"x2\")\n", + " x3 = solver.NumVar(0.0, infinity, \"x3\")\n", "\n", " solver.Maximize(10 * x1 + 6 * x2 + 4 * x3)\n", - " c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, 'ConstraintName0')\n", + " c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, \"ConstraintName0\")\n", " c1 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300)\n", " sum_of_vars = sum([x1, x2, x3])\n", - " c2 = solver.Add(sum_of_vars <= 100.0, 'OtherConstraintName')\n", + " c2 = solver.Add(sum_of_vars <= 100.0, \"OtherConstraintName\")\n", "\n", - " SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != 'PDLP')\n", + " SolveAndPrint(\n", + " solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != \"PDLP\"\n", + " )\n", " # Print a linear expression's solution value.\n", - " print('Sum of vars: %s = %s' % (sum_of_vars, sum_of_vars.solution_value()))\n", + " print(\"Sum of vars: %s = %s\" % (sum_of_vars, sum_of_vars.solution_value()))\n", "\n", "\n", "def RunLinearExampleCppStyleAPI(optimization_problem_type):\n", @@ -122,13 +126,13 @@ " if not solver:\n", " return\n", "\n", - " Announce(optimization_problem_type, 'C++ style API')\n", + " Announce(optimization_problem_type, \"C++ style API\")\n", "\n", " infinity = solver.infinity()\n", " # x1, x2 and x3 are continuous non-negative variables.\n", - " x1 = solver.NumVar(0.0, infinity, 'x1')\n", - " x2 = solver.NumVar(0.0, infinity, 'x2')\n", - " x3 = solver.NumVar(0.0, infinity, 'x3')\n", + " x1 = solver.NumVar(0.0, infinity, \"x1\")\n", + " x2 = solver.NumVar(0.0, infinity, \"x2\")\n", + " x3 = solver.NumVar(0.0, infinity, \"x3\")\n", "\n", " # Maximize 10 * x1 + 6 * x2 + 4 * x3.\n", " objective = solver.Objective()\n", @@ -138,31 +142,32 @@ " objective.SetMaximization()\n", "\n", " # x1 + x2 + x3 <= 100.\n", - " c0 = solver.Constraint(-infinity, 100.0, 'c0')\n", + " c0 = solver.Constraint(-infinity, 100.0, \"c0\")\n", " c0.SetCoefficient(x1, 1)\n", " c0.SetCoefficient(x2, 1)\n", " c0.SetCoefficient(x3, 1)\n", "\n", " # 10 * x1 + 4 * x2 + 5 * x3 <= 600.\n", - " c1 = solver.Constraint(-infinity, 600.0, 'c1')\n", + " c1 = solver.Constraint(-infinity, 600.0, \"c1\")\n", " c1.SetCoefficient(x1, 10)\n", " c1.SetCoefficient(x2, 4)\n", " c1.SetCoefficient(x3, 5)\n", "\n", " # 2 * x1 + 2 * x2 + 6 * x3 <= 300.\n", - " c2 = solver.Constraint(-infinity, 300.0, 'c2')\n", + " c2 = solver.Constraint(-infinity, 300.0, \"c2\")\n", " c2.SetCoefficient(x1, 2)\n", " c2.SetCoefficient(x2, 2)\n", " c2.SetCoefficient(x3, 6)\n", "\n", - " SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2],\n", - " optimization_problem_type != 'PDLP')\n", + " SolveAndPrint(\n", + " solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != \"PDLP\"\n", + " )\n", "\n", "\n", "def SolveAndPrint(solver, variable_list, constraint_list, is_precise):\n", " \"\"\"Solve the problem and print the solution.\"\"\"\n", - " print('Number of variables = %d' % solver.NumVariables())\n", - " print('Number of constraints = %d' % solver.NumConstraints())\n", + " print(\"Number of variables = %d\" % solver.NumVariables())\n", + " print(\"Number of constraints = %d\" % solver.NumConstraints())\n", "\n", " result_status = solver.Solve()\n", "\n", @@ -174,37 +179,39 @@ " if is_precise:\n", " assert solver.VerifySolution(1e-7, True)\n", "\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", "\n", " # The objective value of the solution.\n", - " print('Optimal objective value = %f' % solver.Objective().Value())\n", + " print(\"Optimal objective value = %f\" % solver.Objective().Value())\n", "\n", " # The value of each variable in the solution.\n", " for variable in variable_list:\n", - " print('%s = %f' % (variable.name(), variable.solution_value()))\n", + " print(\"%s = %f\" % (variable.name(), variable.solution_value()))\n", "\n", - " print('Advanced usage:')\n", - " print('Problem solved in %d iterations' % solver.iterations())\n", + " print(\"Advanced usage:\")\n", + " print(\"Problem solved in %d iterations\" % solver.iterations())\n", " for variable in variable_list:\n", - " print('%s: reduced cost = %f' %\n", - " (variable.name(), variable.reduced_cost()))\n", + " print(\"%s: reduced cost = %f\" % (variable.name(), variable.reduced_cost()))\n", " activities = solver.ComputeConstraintActivities()\n", " for i, constraint in enumerate(constraint_list):\n", - " print(('constraint %d: dual value = %f\\n'\n", - " ' activity = %f' %\n", - " (i, constraint.dual_value(), activities[constraint.index()])))\n", + " print(\n", + " (\n", + " \"constraint %d: dual value = %f\\n activity = %f\"\n", + " % (i, constraint.dual_value(), activities[constraint.index()])\n", + " )\n", + " )\n", "\n", "\n", "def main():\n", - " RunLinearExampleNaturalLanguageAPI('GLOP')\n", - " RunLinearExampleNaturalLanguageAPI('GLPK_LP')\n", - " RunLinearExampleNaturalLanguageAPI('CLP')\n", - " RunLinearExampleNaturalLanguageAPI('PDLP')\n", + " RunLinearExampleNaturalLanguageAPI(\"GLOP\")\n", + " RunLinearExampleNaturalLanguageAPI(\"GLPK_LP\")\n", + " RunLinearExampleNaturalLanguageAPI(\"CLP\")\n", + " RunLinearExampleNaturalLanguageAPI(\"PDLP\")\n", "\n", - " RunLinearExampleCppStyleAPI('GLOP')\n", - " RunLinearExampleCppStyleAPI('GLPK_LP')\n", - " RunLinearExampleCppStyleAPI('CLP')\n", - " RunLinearExampleCppStyleAPI('PDLP')\n", + " RunLinearExampleCppStyleAPI(\"GLOP\")\n", + " RunLinearExampleCppStyleAPI(\"GLPK_LP\")\n", + " RunLinearExampleCppStyleAPI(\"CLP\")\n", + " RunLinearExampleCppStyleAPI(\"PDLP\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/magic_sequence_distribute.ipynb b/examples/notebook/examples/magic_sequence_distribute.ipynb index f39a685029..838366b1af 100644 --- a/examples/notebook/examples/magic_sequence_distribute.ipynb +++ b/examples/notebook/examples/magic_sequence_distribute.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Magic sequence problem.\n", "\n", "This models aims at building a sequence of numbers such that the number of\n", @@ -96,12 +97,12 @@ "\n", "def main(argv):\n", " # Create the solver.\n", - " solver = pywrapcp.Solver('magic sequence')\n", + " solver = pywrapcp.Solver(\"magic sequence\")\n", "\n", " # Create an array of IntVars to hold the answers.\n", " size = int(argv[1]) if len(argv) > 1 else 100\n", " all_values = list(range(0, size))\n", - " all_vars = [solver.IntVar(0, size, 'vars_%d' % i) for i in all_values]\n", + " all_vars = [solver.IntVar(0, size, \"vars_%d\" % i) for i in all_values]\n", "\n", " # The number of variables equal to j shall be the value of all_vars[j].\n", " solver.Add(solver.Distribute(all_vars, all_values, all_vars))\n", @@ -111,8 +112,8 @@ " solver.Add(solver.Sum(all_vars) == size)\n", "\n", " solver.NewSearch(\n", - " solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND,\n", - " solver.ASSIGN_MIN_VALUE))\n", + " solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + " )\n", " solver.NextSolution()\n", " print(all_vars)\n", " solver.EndSearch()\n", diff --git a/examples/notebook/examples/maze_escape_sat.ipynb b/examples/notebook/examples/maze_escape_sat.ipynb index 4e50020510..ce11029b4c 100644 --- a/examples/notebook/examples/maze_escape_sat.ipynb +++ b/examples/notebook/examples/maze_escape_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Excape the maze while collecting treasures in order.\n", "\n", "The path must begin at the 'start' position, finish at the 'end' position,\n", @@ -94,23 +95,29 @@ "from ortools.sat.python import cp_model\n", "\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", - "_PARAMS = flags.DEFINE_string('params',\n", - " 'num_search_workers:8,log_search_progress:true',\n", - " 'Sat solver parameters.')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\", \"num_search_workers:8,log_search_progress:true\", \"Sat solver parameters.\"\n", + ")\n", "\n", "\n", - "def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank,\n", - " arcs):\n", + "def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, arcs):\n", " \"\"\"Checks if the neighbor is valid, and adds it to the model.\"\"\"\n", - " if (x + dx < 0 or x + dx >= size or y + dy < 0 or y + dy >= size or\n", - " z + dz < 0 or z + dz >= size):\n", + " if (\n", + " x + dx < 0\n", + " or x + dx >= size\n", + " or y + dy < 0\n", + " or y + dy >= size\n", + " or z + dz < 0\n", + " or z + dz >= size\n", + " ):\n", " return\n", " before_index = index_map[(x, y, z)]\n", " before_rank = position_to_rank[(x, y, z)]\n", " after_index = index_map[(x + dx, y + dy, z + dz)]\n", " after_rank = position_to_rank[(x + dx, y + dy, z + dz)]\n", - " move_literal = model.NewBoolVar('')\n", + " move_literal = model.NewBoolVar(\"\")\n", " model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal)\n", " arcs.append((before_index, after_index, move_literal))\n", "\n", @@ -139,8 +146,7 @@ " position_to_rank = {}\n", "\n", " for coord in reverse_map:\n", - " position_to_rank[coord] = model.NewIntVar(0, counter - 1,\n", - " f'rank_{coord}')\n", + " position_to_rank[coord] = model.NewIntVar(0, counter - 1, f\"rank_{coord}\")\n", "\n", " # Path constraints.\n", " model.Add(position_to_rank[start] == 0)\n", @@ -154,18 +160,24 @@ " for x in range(size):\n", " for y in range(size):\n", " for z in range(size):\n", - " add_neighbor(size, x, y, z, -1, 0, 0, model, index_map,\n", - " position_to_rank, arcs)\n", - " add_neighbor(size, x, y, z, 1, 0, 0, model, index_map,\n", - " position_to_rank, arcs)\n", - " add_neighbor(size, x, y, z, 0, -1, 0, model, index_map,\n", - " position_to_rank, arcs)\n", - " add_neighbor(size, x, y, z, 0, 1, 0, model, index_map,\n", - " position_to_rank, arcs)\n", - " add_neighbor(size, x, y, z, 0, 0, -1, model, index_map,\n", - " position_to_rank, arcs)\n", - " add_neighbor(size, x, y, z, 0, 0, 1, model, index_map,\n", - " position_to_rank, arcs)\n", + " add_neighbor(\n", + " size, x, y, z, -1, 0, 0, model, index_map, position_to_rank, arcs\n", + " )\n", + " add_neighbor(\n", + " size, x, y, z, 1, 0, 0, model, index_map, position_to_rank, arcs\n", + " )\n", + " add_neighbor(\n", + " size, x, y, z, 0, -1, 0, model, index_map, position_to_rank, arcs\n", + " )\n", + " add_neighbor(\n", + " size, x, y, z, 0, 1, 0, model, index_map, position_to_rank, arcs\n", + " )\n", + " add_neighbor(\n", + " size, x, y, z, 0, 0, -1, model, index_map, position_to_rank, arcs\n", + " )\n", + " add_neighbor(\n", + " size, x, y, z, 0, 0, 1, model, index_map, position_to_rank, arcs\n", + " )\n", "\n", " # Closes the loop as the constraint expects a circuit, not a path.\n", " arcs.append((index_map[end], index_map[start], True))\n", @@ -186,28 +198,28 @@ "\n", " # Prints solution.\n", " if result == cp_model.OPTIMAL:\n", - " path = [''] * counter\n", + " path = [\"\"] * counter\n", " for x in range(size):\n", " for y in range(size):\n", " for z in range(size):\n", " position = (x, y, z)\n", " rank = solver.Value(position_to_rank[position])\n", - " msg = f'({x}, {y}, {z})'\n", + " msg = f\"({x}, {y}, {z})\"\n", " if position == start:\n", - " msg += ' [start]'\n", + " msg += \" [start]\"\n", " elif position == end:\n", - " msg += ' [end]'\n", + " msg += \" [end]\"\n", " else:\n", " for b in range(len(boxes)):\n", " if position == boxes[b]:\n", - " msg += f' [boxes {b}]'\n", + " msg += f\" [boxes {b}]\"\n", " path[rank] = msg\n", " print(path)\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value)\n", "\n", "\n", diff --git a/examples/notebook/examples/no_wait_baking_scheduling_sat.ipynb b/examples/notebook/examples/no_wait_baking_scheduling_sat.ipynb index ba553a817c..890a49ce74 100644 --- a/examples/notebook/examples/no_wait_baking_scheduling_sat.ipynb +++ b/examples/notebook/examples/no_wait_baking_scheduling_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Scheduling cooking tasks in a bakery using no-wait jobshop scheduling.\n", "\n", "We are scheduling a full day of baking:\n", @@ -93,34 +94,35 @@ "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "_PARAMS = flags.DEFINE_string('params',\n", - " 'num_search_workers:16, max_time_in_seconds:30',\n", - " 'Sat solver parameters.')\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\", \"num_search_workers:16, max_time_in_seconds:30\", \"Sat solver parameters.\"\n", + ")\n", "_PROTO_FILE = flags.DEFINE_string(\n", - " 'proto_file', '', 'If not empty, output the proto to this file.')\n", + " \"proto_file\", \"\", \"If not empty, output the proto to this file.\"\n", + ")\n", "\n", "# Recipes\n", - "CROISSANT = 'croissant'\n", - "APPLE_PIE = 'apple pie'\n", - "BRIOCHE = 'brioche'\n", - "CHOCOLATE_CAKE = 'chocolate cake'\n", + "CROISSANT = \"croissant\"\n", + "APPLE_PIE = \"apple pie\"\n", + "BRIOCHE = \"brioche\"\n", + "CHOCOLATE_CAKE = \"chocolate cake\"\n", "\n", "# Skills\n", - "BAKING = 'baking'\n", - "PROOFING = 'proofing'\n", - "COOKING = 'cooking'\n", - "COOLING = 'cooling'\n", - "DECORATING = 'decorating'\n", - "DISPLAY = 'display'\n", + "BAKING = \"baking\"\n", + "PROOFING = \"proofing\"\n", + "COOKING = \"cooking\"\n", + "COOLING = \"cooling\"\n", + "DECORATING = \"decorating\"\n", + "DISPLAY = \"display\"\n", "\n", "\n", "class Task(object):\n", " \"\"\"A unit baking task.\n", "\n", - " - Simple baking tasks have a fixed duration. They are performed by workers.\n", - " - Waiting/cooling/proofing tasks have a min and a max duration.\n", - " They are performed by machine or they use space resources.\n", - " \"\"\"\n", + " - Simple baking tasks have a fixed duration. They are performed by workers.\n", + " - Waiting/cooling/proofing tasks have a min and a max duration.\n", + " They are performed by machine or they use space resources.\n", + " \"\"\"\n", "\n", " def __init__(self, name, min_duration, max_duration):\n", " self.name = name\n", @@ -152,13 +154,13 @@ "class Resource(object):\n", " \"\"\"A resource is a worker, a machine, or just some space for cakes to rest.\n", "\n", - " - Workers have a capacity of 1 and can have variable efficiency.\n", - " - Machines and spaces have a capacity greater or equal to one, but the\n", - " efficiency is fixed to 100.\n", + " - Workers have a capacity of 1 and can have variable efficiency.\n", + " - Machines and spaces have a capacity greater or equal to one, but the\n", + " efficiency is fixed to 100.\n", "\n", - " For a worker with efficiency k and a task of duration t, the resulting\n", - " work will have a duration `ceil(t * k)`.\n", - " \"\"\"\n", + " For a worker with efficiency k and a task of duration t, the resulting\n", + " work will have a duration `ceil(t * k)`.\n", + " \"\"\"\n", "\n", " def __init__(self, name, capacity):\n", " self.name = name\n", @@ -176,12 +178,12 @@ " def __init__(self, unique_id, recipe_name, due_date, quantity):\n", " \"\"\"Builds an order.\n", "\n", - " Args:\n", - " unique_id: A unique identifier for the order. Used to display the result.\n", - " recipe_name: The name of the recipe. It must match one of the recipes.\n", - " due_date: The due date in minutes since midnight.\n", - " quantity: How many cakes to prepare.\n", - " \"\"\"\n", + " Args:\n", + " unique_id: A unique identifier for the order. Used to display the result.\n", + " recipe_name: The name of the recipe. It must match one of the recipes.\n", + " due_date: The due date in minutes since midnight.\n", + " quantity: How many cakes to prepare.\n", + " \"\"\"\n", " self.unique_id = unique_id\n", " self.recipe_name = recipe_name\n", " self.due_date = due_date\n", @@ -217,32 +219,41 @@ " chocolate_cake_recipe.add_task(DECORATING, 15, 15)\n", " chocolate_cake_recipe.add_task(DISPLAY, 5, 5 * 60)\n", " recipes = [\n", - " croissant_recipe, apple_pie_recipe, brioche_recipe,\n", - " chocolate_cake_recipe\n", + " croissant_recipe,\n", + " apple_pie_recipe,\n", + " brioche_recipe,\n", + " chocolate_cake_recipe,\n", " ]\n", "\n", " # Resources.\n", - " baker1 = Resource('baker1', 1).add_skill(BAKING, 1.0)\n", - " baker2 = Resource('baker2', 1).add_skill(BAKING, 1.0)\n", - " decorator1 = Resource('decorator1', 1).add_skill(DECORATING, 1.0)\n", - " waiting_space = Resource('waiting_space', 4).add_skill(PROOFING, 1.0)\n", - " oven = Resource('oven', 4).add_skill(COOKING, 1.0)\n", - " display_space = Resource('display_space', 12).add_skill(DISPLAY, 1.0)\n", + " baker1 = Resource(\"baker1\", 1).add_skill(BAKING, 1.0)\n", + " baker2 = Resource(\"baker2\", 1).add_skill(BAKING, 1.0)\n", + " decorator1 = Resource(\"decorator1\", 1).add_skill(DECORATING, 1.0)\n", + " waiting_space = Resource(\"waiting_space\", 4).add_skill(PROOFING, 1.0)\n", + " oven = Resource(\"oven\", 4).add_skill(COOKING, 1.0)\n", + " display_space = Resource(\"display_space\", 12).add_skill(DISPLAY, 1.0)\n", " resources = [baker1, baker2, decorator1, waiting_space, oven, display_space]\n", "\n", " # Orders\n", - " croissant_7am = Order('croissant_7am', CROISSANT, 7 * 60, 3)\n", - " croissant_8am = Order('croissant_8am', CROISSANT, 8 * 60, 3)\n", - " croissant_9am = Order('croissant_9am', CROISSANT, 9 * 60, 2)\n", - " croissant_10am = Order('croissant_10am', CROISSANT, 10 * 60, 1)\n", - " croissant_11am = Order('croissant_11am', CROISSANT, 11 * 60, 1)\n", - " brioche_10am = Order('brioche_10am', BRIOCHE, 10 * 60, 8)\n", - " brioche_12pm = Order('brioche_12pm', BRIOCHE, 12 * 60, 8)\n", - " apple_pie_1pm = Order('apple_pie_1pm', APPLE_PIE, 13 * 60, 10)\n", - " chocolate_4pm = Order('chocolate_4pm', CHOCOLATE_CAKE, 16 * 60, 10)\n", + " croissant_7am = Order(\"croissant_7am\", CROISSANT, 7 * 60, 3)\n", + " croissant_8am = Order(\"croissant_8am\", CROISSANT, 8 * 60, 3)\n", + " croissant_9am = Order(\"croissant_9am\", CROISSANT, 9 * 60, 2)\n", + " croissant_10am = Order(\"croissant_10am\", CROISSANT, 10 * 60, 1)\n", + " croissant_11am = Order(\"croissant_11am\", CROISSANT, 11 * 60, 1)\n", + " brioche_10am = Order(\"brioche_10am\", BRIOCHE, 10 * 60, 8)\n", + " brioche_12pm = Order(\"brioche_12pm\", BRIOCHE, 12 * 60, 8)\n", + " apple_pie_1pm = Order(\"apple_pie_1pm\", APPLE_PIE, 13 * 60, 10)\n", + " chocolate_4pm = Order(\"chocolate_4pm\", CHOCOLATE_CAKE, 16 * 60, 10)\n", " orders = [\n", - " croissant_7am, croissant_8am, croissant_9am, croissant_10am,\n", - " croissant_11am, brioche_10am, brioche_12pm, apple_pie_1pm, chocolate_4pm\n", + " croissant_7am,\n", + " croissant_8am,\n", + " croissant_9am,\n", + " croissant_10am,\n", + " croissant_11am,\n", + " brioche_10am,\n", + " brioche_12pm,\n", + " apple_pie_1pm,\n", + " chocolate_4pm,\n", " ]\n", "\n", " return recipes, resources, orders\n", @@ -276,50 +287,48 @@ " tardiness_vars = []\n", " for order in orders:\n", " for batch in range(order.quantity):\n", - " order_id = f'{order.unique_id}_{batch}'\n", + " order_id = f\"{order.unique_id}_{batch}\"\n", " sorted_orders.append(order_id)\n", " previous_end = None\n", " due_date = order.due_date\n", " recipe = recipe_by_name[order.recipe_name]\n", " for task in recipe.tasks:\n", " skill_name = task.name\n", - " suffix = f'_{order.unique_id}_batch{batch}_{skill_name}'\n", + " suffix = f\"_{order.unique_id}_batch{batch}_{skill_name}\"\n", "\n", " start = None\n", " if previous_end is None:\n", - " start = model.NewIntVar(start_work, horizon,\n", - " f'start{suffix}')\n", + " start = model.NewIntVar(start_work, horizon, f\"start{suffix}\")\n", " orders_sequence_of_events[order_id].append(\n", - " (start, f'start{suffix}'))\n", + " (start, f\"start{suffix}\")\n", + " )\n", " else:\n", " start = previous_end\n", "\n", - " size = model.NewIntVar(task.min_duration, task.max_duration,\n", - " f'size{suffix}')\n", + " size = model.NewIntVar(\n", + " task.min_duration, task.max_duration, f\"size{suffix}\"\n", + " )\n", " end = None\n", " if task == recipe.tasks[-1]:\n", " # The order must end after the due_date. Ideally, exactly at the\n", " # due_date.\n", - " tardiness = model.NewIntVar(0, horizon - due_date,\n", - " f'end{suffix}')\n", + " tardiness = model.NewIntVar(0, horizon - due_date, f\"end{suffix}\")\n", " end = tardiness + due_date\n", "\n", " # Store the end_var for the objective.\n", " tardiness_vars.append(tardiness)\n", " else:\n", - " end = model.NewIntVar(start_work, horizon, f'end{suffix}')\n", - " orders_sequence_of_events[order_id].append(\n", - " (end, f'end{suffix}'))\n", + " end = model.NewIntVar(start_work, horizon, f\"end{suffix}\")\n", + " orders_sequence_of_events[order_id].append((end, f\"end{suffix}\"))\n", " previous_end = end\n", "\n", " # Per resource copy.\n", " presence_literals = []\n", " for resource in resource_list_by_skill_name[skill_name]:\n", - " presence = model.NewBoolVar(\n", - " f'presence{suffix}_{resource.name}')\n", + " presence = model.NewBoolVar(f\"presence{suffix}_{resource.name}\")\n", " copy = model.NewOptionalIntervalVar(\n", - " start, size, end, presence,\n", - " f'interval{suffix}_{resource.name}')\n", + " start, size, end, presence, f\"interval{suffix}_{resource.name}\"\n", + " )\n", " interval_list_by_resource_name[resource.name].append(copy)\n", " presence_literals.append(presence)\n", "\n", @@ -332,8 +341,7 @@ " if resource.capacity == 1:\n", " model.AddNoOverlap(intervals)\n", " else:\n", - " model.AddCumulative(intervals, [1] * len(intervals),\n", - " resource.capacity)\n", + " model.AddCumulative(intervals, [1] * len(intervals), resource.capacity)\n", "\n", " # The objective is to minimize the sum of the tardiness values of each jobs.\n", " # The tardiness is difference between the end time of an order and its\n", @@ -349,15 +357,15 @@ "\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " for order_id in sorted_orders:\n", - " print(f'{order_id}:')\n", + " print(f\"{order_id}:\")\n", " for time_expr, event_id in orders_sequence_of_events[order_id]:\n", " time = solver.Value(time_expr)\n", - " print(f' {event_id} at {time // 60}:{time % 60:02}')\n", + " print(f\" {event_id} at {time // 60}:{time % 60:02}\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", "\n", " recipes, resources, orders = set_up_data()\n", " solve_with_cp_sat(recipes, resources, orders)\n", diff --git a/examples/notebook/examples/nqueens_sat.ipynb b/examples/notebook/examples/nqueens_sat.ipynb index 0a115099b4..7eb7710a51 100644 --- a/examples/notebook/examples/nqueens_sat.ipynb +++ b/examples/notebook/examples/nqueens_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "CP/SAT model for the N-queens problem.\n" ] }, @@ -86,7 +87,7 @@ "\n", "from ortools.sat.python import cp_model\n", "\n", - "_SIZE = flags.DEFINE_integer('size', 8, 'Number of queens.')\n", + "_SIZE = flags.DEFINE_integer(\"size\", 8, \"Number of queens.\")\n", "\n", "\n", "class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", @@ -103,8 +104,10 @@ "\n", " def on_solution_callback(self):\n", " current_time = time.time()\n", - " print('Solution %i, time = %f s' %\n", - " (self.__solution_count, current_time - self.__start_time))\n", + " print(\n", + " \"Solution %i, time = %f s\"\n", + " % (self.__solution_count, current_time - self.__start_time)\n", + " )\n", " self.__solution_count += 1\n", "\n", " all_queens = range(len(self.__queens))\n", @@ -112,9 +115,9 @@ " for j in all_queens:\n", " if self.Value(self.__queens[j]) == i:\n", " # There is a queen in column j, row i.\n", - " print('Q', end=' ')\n", + " print(\"Q\", end=\" \")\n", " else:\n", - " print('_', end=' ')\n", + " print(\"_\", end=\" \")\n", " print()\n", " print()\n", "\n", @@ -127,9 +130,7 @@ "\n", " ### Creates the variables.\n", " # The array index is the column, and the value is the row.\n", - " queens = [\n", - " model.NewIntVar(0, board_size - 1, 'x%i' % i) for i in range(board_size)\n", - " ]\n", + " queens = [model.NewIntVar(0, board_size - 1, \"x%i\" % i) for i in range(board_size)]\n", "\n", " ### Creates the constraints.\n", "\n", @@ -141,8 +142,8 @@ " diag1 = []\n", " diag2 = []\n", " for i in range(board_size):\n", - " q1 = model.NewIntVar(0, 2 * board_size, 'diag1_%i' % i)\n", - " q2 = model.NewIntVar(-board_size, board_size, 'diag2_%i' % i)\n", + " q1 = model.NewIntVar(0, 2 * board_size, \"diag1_%i\" % i)\n", + " q2 = model.NewIntVar(-board_size, board_size, \"diag2_%i\" % i)\n", " diag1.append(q1)\n", " diag2.append(q2)\n", " model.Add(q1 == queens[i] + i)\n", @@ -159,11 +160,11 @@ " solver.Solve(model, solution_printer)\n", "\n", " print()\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", - " print(' - solutions found : %i' % solution_printer.SolutionCount())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", + " print(\" - solutions found : %i\" % solution_printer.SolutionCount())\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/prize_collecting_tsp_sat.ipynb b/examples/notebook/examples/prize_collecting_tsp_sat.ipynb index 8df28cecf3..fb90b4c8ec 100644 --- a/examples/notebook/examples/prize_collecting_tsp_sat.ipynb +++ b/examples/notebook/examples/prize_collecting_tsp_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Simple prize collecting TSP problem with a max distance.\n" ] }, @@ -87,6 +88,7 @@ "\n", "\n", "DISTANCE_MATRIX = [\n", + " # fmt:off\n", " [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],\n", " [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],\n", " [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],\n", @@ -127,7 +129,8 @@ " [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],\n", " [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],\n", " [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],\n", - "] # yapf: disable\n", + " # fmt:on\n", + "]\n", "\n", "MAX_DISTANCE = 80_000\n", "\n", @@ -139,22 +142,22 @@ "def print_solution(solver, visited_nodes, used_arcs, num_nodes):\n", " \"\"\"Prints solution on console.\"\"\"\n", " # Display dropped nodes.\n", - " dropped_nodes = 'Dropped nodes:'\n", + " dropped_nodes = \"Dropped nodes:\"\n", " for i in range(num_nodes):\n", " if i == 0:\n", " continue\n", " if not solver.BooleanValue(visited_nodes[i]):\n", - " dropped_nodes += f' {i}({VISIT_VALUES[i]})'\n", + " dropped_nodes += f\" {i}({VISIT_VALUES[i]})\"\n", " print(dropped_nodes)\n", " # Display routes\n", " current_node = 0\n", - " plan_output = 'Route for vehicle 0:\\n'\n", + " plan_output = \"Route for vehicle 0:\\n\"\n", " route_distance = 0\n", " value_collected = 0\n", " route_is_finished = False\n", " while not route_is_finished:\n", " value_collected += VISIT_VALUES[current_node]\n", - " plan_output += f' {current_node} ->'\n", + " plan_output += f\" {current_node} ->\"\n", " # find next node\n", " for node in range(num_nodes):\n", " if node == current_node:\n", @@ -165,9 +168,9 @@ " if current_node == 0:\n", " route_is_finished = True\n", " break\n", - " plan_output += f' {current_node}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", - " plan_output += f'Value collected: {value_collected}/{sum(VISIT_VALUES)}\\n'\n", + " plan_output += f\" {current_node}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", + " plan_output += f\"Value collected: {value_collected}/{sum(VISIT_VALUES)}\\n\"\n", " print(plan_output)\n", "\n", "\n", @@ -175,7 +178,7 @@ " \"\"\"Entry point of the program.\"\"\"\n", " num_nodes = len(DISTANCE_MATRIX)\n", " all_nodes = range(num_nodes)\n", - " print(f'Num nodes = {num_nodes}')\n", + " print(f\"Num nodes = {num_nodes}\")\n", "\n", " # Model.\n", " model = cp_model.CpModel()\n", @@ -188,8 +191,8 @@ " # Create the circuit constraint.\n", " arcs = []\n", " for i in all_nodes:\n", - " is_visited = model.NewBoolVar(f'{i} is visited')\n", - " arcs.append([i, i, is_visited.Not()])\n", + " is_visited = model.NewBoolVar(f\"{i} is visited\")\n", + " arcs.append((i, i, is_visited.Not()))\n", "\n", " obj_vars.append(is_visited)\n", " obj_coeffs.append(VISIT_VALUES[i])\n", @@ -199,8 +202,8 @@ " if i == j:\n", " used_arcs[i, j] = is_visited.Not()\n", " continue\n", - " arc_is_used = model.NewBoolVar(f'{j} follows {i}')\n", - " arcs.append([i, j, arc_is_used])\n", + " arc_is_used = model.NewBoolVar(f\"{j} follows {i}\")\n", + " arcs.append((i, j, arc_is_used))\n", "\n", " obj_vars.append(arc_is_used)\n", " obj_coeffs.append(-DISTANCE_MATRIX[i][j])\n", @@ -213,13 +216,16 @@ "\n", " # limit the route distance\n", " model.Add(\n", - " sum(used_arcs[i, j] * DISTANCE_MATRIX[i][j]\n", + " sum(\n", + " used_arcs[i, j] * DISTANCE_MATRIX[i][j]\n", " for i in all_nodes\n", - " for j in all_nodes) <= MAX_DISTANCE)\n", + " for j in all_nodes\n", + " )\n", + " <= MAX_DISTANCE\n", + " )\n", "\n", " # Maximize visited node values minus the travelled distance.\n", - " model.Maximize(\n", - " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", "\n", " # Solve and print out the solution.\n", " solver = cp_model.CpSolver()\n", @@ -235,7 +241,7 @@ "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " prize_collecting_tsp()\n", "\n", "\n", diff --git a/examples/notebook/examples/prize_collecting_vrp_sat.ipynb b/examples/notebook/examples/prize_collecting_vrp_sat.ipynb index 8dfb6d7e18..47a8ac78df 100644 --- a/examples/notebook/examples/prize_collecting_vrp_sat.ipynb +++ b/examples/notebook/examples/prize_collecting_vrp_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Simple prize collecting VRP problem with a max distance.\n" ] }, @@ -87,6 +88,7 @@ "\n", "\n", "DISTANCE_MATRIX = [\n", + " # fmt:off\n", " [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],\n", " [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],\n", " [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],\n", @@ -127,7 +129,8 @@ " [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],\n", " [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],\n", " [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],\n", - "] # yapf: disable\n", + " # fmt:on\n", + "]\n", "\n", "MAX_DISTANCE = 80_000\n", "\n", @@ -139,29 +142,28 @@ "def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):\n", " \"\"\"Prints solution on console.\"\"\"\n", " # Display dropped nodes.\n", - " dropped_nodes = 'Dropped nodes:'\n", + " dropped_nodes = \"Dropped nodes:\"\n", " for node in range(num_nodes):\n", " if node == 0:\n", " continue\n", - " is_visited = sum([\n", - " solver.BooleanValue(visited_nodes[v][node])\n", - " for v in range(num_vehicles)\n", - " ])\n", + " is_visited = sum(\n", + " [solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)]\n", + " )\n", " if not is_visited:\n", - " dropped_nodes += f' {node}({VISIT_VALUES[node]})'\n", + " dropped_nodes += f\" {node}({VISIT_VALUES[node]})\"\n", " print(dropped_nodes)\n", " # Display routes\n", " total_distance = 0\n", " total_value_collected = 0\n", " for v in range(num_vehicles):\n", " current_node = 0\n", - " plan_output = f'Route for vehicle {v}:\\n'\n", + " plan_output = f\"Route for vehicle {v}:\\n\"\n", " route_distance = 0\n", " value_collected = 0\n", " route_is_finished = False\n", " while not route_is_finished:\n", " value_collected += VISIT_VALUES[current_node]\n", - " plan_output += f' {current_node} ->'\n", + " plan_output += f\" {current_node} ->\"\n", " # find next node\n", " for node in range(num_nodes):\n", " if node == current_node:\n", @@ -172,21 +174,21 @@ " if current_node == 0:\n", " route_is_finished = True\n", " break\n", - " plan_output += f' {current_node}\\n'\n", - " plan_output += f'Distance of the route: {route_distance}m\\n'\n", - " plan_output += f'Value collected: {value_collected}\\n'\n", + " plan_output += f\" {current_node}\\n\"\n", + " plan_output += f\"Distance of the route: {route_distance}m\\n\"\n", + " plan_output += f\"Value collected: {value_collected}\\n\"\n", " print(plan_output)\n", " total_distance += route_distance\n", " total_value_collected += value_collected\n", - " print(f'Total Distance: {total_distance}m')\n", - " print(f'Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}')\n", + " print(f\"Total Distance: {total_distance}m\")\n", + " print(f\"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}\")\n", "\n", "\n", "def prize_collecting_vrp():\n", " \"\"\"Entry point of the program.\"\"\"\n", " num_nodes = len(DISTANCE_MATRIX)\n", " num_vehicles = 4\n", - " print(f'Num nodes = {num_nodes}')\n", + " print(f\"Num nodes = {num_nodes}\")\n", "\n", " # Model.\n", " model = cp_model.CpModel()\n", @@ -203,8 +205,8 @@ " used_arcs[v] = {}\n", " arcs = []\n", " for i in all_nodes:\n", - " is_visited = model.NewBoolVar(f'{i} is visited')\n", - " arcs.append([i, i, is_visited.Not()])\n", + " is_visited = model.NewBoolVar(f\"{i} is visited\")\n", + " arcs.append((i, i, is_visited.Not()))\n", "\n", " obj_vars.append(is_visited)\n", " obj_coeffs.append(VISIT_VALUES[i])\n", @@ -214,8 +216,8 @@ " if i == j:\n", " used_arcs[v][i, j] = is_visited.Not()\n", " continue\n", - " arc_is_used = model.NewBoolVar(f'{j} follows {i}')\n", - " arcs.append([i, j, arc_is_used])\n", + " arc_is_used = model.NewBoolVar(f\"{j} follows {i}\")\n", + " arcs.append((i, j, arc_is_used))\n", "\n", " obj_vars.append(arc_is_used)\n", " obj_coeffs.append(-DISTANCE_MATRIX[i][j])\n", @@ -228,18 +230,20 @@ "\n", " # limit the route distance\n", " model.Add(\n", - " sum(used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]\n", + " sum(\n", + " used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]\n", " for i in all_nodes\n", - " for j in all_nodes) <= MAX_DISTANCE)\n", + " for j in all_nodes\n", + " )\n", + " <= MAX_DISTANCE\n", + " )\n", "\n", " # Each node is visited at most once\n", " for node in range(1, num_nodes):\n", - " model.AddAtMostOne(\n", - " [visited_nodes[v][node] for v in range(num_vehicles)])\n", + " model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)])\n", "\n", " # Maximize visited node values minus the travelled distance.\n", - " model.Maximize(\n", - " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", "\n", " # Solve and print out the solution.\n", " solver = cp_model.CpSolver()\n", @@ -249,13 +253,12 @@ "\n", " status = solver.Solve(model)\n", " if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n", - " print_solution(solver, visited_nodes, used_arcs, num_nodes,\n", - " num_vehicles)\n", + " print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles)\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " prize_collecting_vrp()\n", "\n", "\n", diff --git a/examples/notebook/examples/pyflow_example.ipynb b/examples/notebook/examples/pyflow_example.ipynb index 7a86a8f870..f2ac46bca0 100644 --- a/examples/notebook/examples/pyflow_example.ipynb +++ b/examples/notebook/examples/pyflow_example.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "MaxFlow and MinCostFlow examples.\n" ] }, @@ -89,7 +90,7 @@ "\n", "def max_flow_api():\n", " \"\"\"MaxFlow simple interface example.\"\"\"\n", - " print('MaxFlow on a simple network.')\n", + " print(\"MaxFlow on a simple network.\")\n", " tails = [0, 0, 0, 0, 1, 2, 3, 3, 4]\n", " heads = [1, 2, 3, 4, 3, 4, 4, 5, 5]\n", " capacities = [5, 8, 5, 3, 4, 5, 6, 6, 4]\n", @@ -98,52 +99,54 @@ " for i in range(0, len(tails)):\n", " smf.add_arc_with_capacity(tails[i], heads[i], capacities[i])\n", " if smf.solve(0, 5) == smf.OPTIMAL:\n", - " print('Total flow', smf.optimal_flow(), '/', expected_total_flow)\n", + " print(\"Total flow\", smf.optimal_flow(), \"/\", expected_total_flow)\n", " for i in range(smf.num_arcs()):\n", - " print('From source %d to target %d: %d / %d' %\n", - " (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i)))\n", - " print('Source side min-cut:', smf.get_source_side_min_cut())\n", - " print('Sink side min-cut:', smf.get_sink_side_min_cut())\n", + " print(\n", + " \"From source %d to target %d: %d / %d\"\n", + " % (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i))\n", + " )\n", + " print(\"Source side min-cut:\", smf.get_source_side_min_cut())\n", + " print(\"Sink side min-cut:\", smf.get_sink_side_min_cut())\n", " else:\n", - " print('There was an issue with the max flow input.')\n", + " print(\"There was an issue with the max flow input.\")\n", "\n", "\n", "def min_cost_flow_api():\n", " \"\"\"MinCostFlow simple interface example.\n", "\n", - " Note that this example is actually a linear sum assignment example and will\n", - " be more efficiently solved with the pywrapgraph.LinearSumAssignment class.\n", - " \"\"\"\n", - " print('MinCostFlow on 4x4 matrix.')\n", + " Note that this example is actually a linear sum assignment example and will\n", + " be more efficiently solved with the pywrapgraph.LinearSumAssignment class.\n", + " \"\"\"\n", + " print(\"MinCostFlow on 4x4 matrix.\")\n", " num_sources = 4\n", " num_targets = 4\n", - " costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n", - " [45, 110, 95, 115]]\n", + " costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n", " expected_cost = 275\n", " smcf = min_cost_flow.SimpleMinCostFlow()\n", " for source in range(0, num_sources):\n", " for target in range(0, num_targets):\n", - " smcf.add_arc_with_capacity_and_unit_cost(source,\n", - " num_sources + target, 1,\n", - " costs[source][target])\n", + " smcf.add_arc_with_capacity_and_unit_cost(\n", + " source, num_sources + target, 1, costs[source][target]\n", + " )\n", " for node in range(0, num_sources):\n", " smcf.set_node_supply(node, 1)\n", " smcf.set_node_supply(num_sources + node, -1)\n", " status = smcf.solve()\n", " if status == smcf.OPTIMAL:\n", - " print('Total flow', smcf.optimal_cost(), '/', expected_cost)\n", + " print(\"Total flow\", smcf.optimal_cost(), \"/\", expected_cost)\n", " for i in range(0, smcf.num_arcs()):\n", " if smcf.flow(i) > 0:\n", - " print('From source %d to target %d: cost %d' %\n", - " (smcf.tail(i), smcf.head(i) - num_sources,\n", - " smcf.unit_cost(i)))\n", + " print(\n", + " \"From source %d to target %d: cost %d\"\n", + " % (smcf.tail(i), smcf.head(i) - num_sources, smcf.unit_cost(i))\n", + " )\n", " else:\n", - " print('There was an issue with the min cost flow input.')\n", + " print(\"There was an issue with the min cost flow input.\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " max_flow_api()\n", " min_cost_flow_api()\n", "\n", diff --git a/examples/notebook/examples/qubo_sat.ipynb b/examples/notebook/examples/qubo_sat.ipynb index 551f2a39f2..b3401652ff 100644 --- a/examples/notebook/examples/qubo_sat.ipynb +++ b/examples/notebook/examples/qubo_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves a Qubo program using the CP-SAT solver.\n" ] }, @@ -86,6 +87,7 @@ "from ortools.sat.python import cp_model\n", "\n", "RAW_DATA = [\n", + " # fmt:off\n", " [\n", " 0, 0, 49.774821, -59.5968886, -46.0773896, 0, -65.166109, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 47.0957778, 15.259961, -98.7983264, 0, 0, 0, -20.7757184,\n", @@ -714,6 +716,7 @@ " 0, 84.8495464, 0, 0, 0, 0, 0, 0, 33.0825986, 46.995148, 0, 0, 0,\n", " -4.243203, 0, 0, -24.0124188\n", " ]\n", + " # fmt:on\n", "]\n", "\n", "\n", @@ -725,7 +728,7 @@ "\n", " num_vars = len(RAW_DATA)\n", " all_vars = range(num_vars)\n", - " variables = [model.NewBoolVar('x_%i' % i) for i in all_vars]\n", + " variables = [model.NewBoolVar(\"x_%i\" % i) for i in all_vars]\n", "\n", " obj_vars = []\n", " obj_coeffs = []\n", @@ -737,7 +740,7 @@ " if coeff == 0.0:\n", " continue\n", " x_j = variables[j]\n", - " var = model.NewBoolVar('')\n", + " var = model.NewBoolVar(\"\")\n", " model.AddBoolOr([x_i.Not(), x_j.Not(), var])\n", " model.AddImplication(var, x_i)\n", " model.AddImplication(var, x_j)\n", @@ -750,8 +753,7 @@ " obj_vars.append(variables[i])\n", " obj_coeffs.append(self_coeff)\n", "\n", - " model.Minimize(\n", - " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", "\n", " ### Solve model.\n", " solver = cp_model.CpSolver()\n", @@ -763,7 +765,7 @@ "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " solve_qubo()\n", "\n", "\n", diff --git a/examples/notebook/examples/rcpsp_sat.ipynb b/examples/notebook/examples/rcpsp_sat.ipynb index 308613978c..0df5a89f65 100644 --- a/examples/notebook/examples/rcpsp_sat.ipynb +++ b/examples/notebook/examples/rcpsp_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Sat based solver for the RCPSP problems (see rcpsp.proto).\n", "\n", "Introduction to the problem:\n", @@ -94,39 +95,47 @@ "\n", "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", - "from ortools.scheduling import pywraprcpsp\n", "from ortools.scheduling import rcpsp_pb2\n", + "from ortools.scheduling.python import rcpsp\n", "\n", - "class FLAGS: pass\n", - "\n", - "_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.')\n", + "_INPUT = flags.DEFINE_string(\"input\", \"\", \"Input file to parse and solve.\")\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", - "_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", + "_PARAMS = flags.DEFINE_string(\"params\", \"\", \"Sat solver parameters.\")\n", "_USE_INTERVAL_MAKESPAN = flags.DEFINE_bool(\n", - " 'use_interval_makespan', True,\n", - " 'Whether we encode the makespan using an interval or not.')\n", - "_HORIZON = flags.DEFINE_integer('horizon', -1, 'Force horizon.')\n", + " \"use_interval_makespan\",\n", + " False,\n", + " \"Whether we encode the makespan using an interval or not.\",\n", + ")\n", + "_HORIZON = flags.DEFINE_integer(\"horizon\", -1, \"Force horizon.\")\n", "_ADD_REDUNDANT_ENERGETIC_CONSTRAINTS = flags.DEFINE_bool(\n", - " 'add_redundant_energetic_constraints', False,\n", - " 'Add redundant energetic constraints on the pairs of tasks extracted from' +\n", - " ' precedence graph.')\n", + " \"add_redundant_energetic_constraints\",\n", + " False,\n", + " \"Add redundant energetic constraints on the pairs of tasks extracted from\"\n", + " + \" precedence graph.\",\n", + ")\n", "_DELAY_TIME_LIMIT = flags.DEFINE_float(\n", - " 'delay_time_limit', 0.0,\n", - " 'Time limit when computing min delay between tasks.' +\n", - " ' A non-positive time limit disable min delays computation.')\n", + " \"delay_time_limit\",\n", + " 0.0,\n", + " \"Time limit when computing min delay between tasks.\"\n", + " + \" A non-positive time limit disable min delays computation.\",\n", + ")\n", "_PREEMPTIVE_LB_TIME_LIMIT = flags.DEFINE_float(\n", - " 'preemptive_lb_time_limit', 0.0,\n", - " 'Time limit when computing a preemptive schedule lower bound.' +\n", - " ' A non-positive time limit disable this computation.')\n", + " \"preemptive_lb_time_limit\",\n", + " 0.0,\n", + " \"Time limit when computing a preemptive schedule lower bound.\"\n", + " + \" A non-positive time limit disable this computation.\",\n", + ")\n", "\n", "\n", "def PrintProblemStatistics(problem):\n", " \"\"\"Display various statistics on the problem.\"\"\"\n", "\n", " # Determine problem type.\n", - " problem_type = ('Resource Investment Problem'\n", - " if problem.is_resource_investment else 'RCPSP')\n", + " problem_type = (\n", + " \"Resource Investment Problem\" if problem.is_resource_investment else \"RCPSP\"\n", + " )\n", "\n", " num_resources = len(problem.resources)\n", " num_tasks = len(problem.tasks) - 2 # 2 sentinels.\n", @@ -146,43 +155,39 @@ " tasks_with_delay += 1\n", "\n", " if problem.is_rcpsp_max:\n", - " problem_type += '/Max delay'\n", + " problem_type += \"/Max delay\"\n", " # We print 2 less tasks as these are sentinel tasks that are not counted in\n", " # the description of the rcpsp models.\n", " if problem.is_consumer_producer:\n", - " print(f'Solving {problem_type} with:')\n", - " print(f' - {num_resources} reservoir resources')\n", - " print(f' - {num_tasks} tasks')\n", + " print(f\"Solving {problem_type} with:\")\n", + " print(f\" - {num_resources} reservoir resources\")\n", + " print(f\" - {num_tasks} tasks\")\n", " else:\n", - " print(f'Solving {problem_type} with:')\n", - " print(f' - {num_resources} renewable resources')\n", - " print(f' - {num_tasks} tasks')\n", + " print(f\"Solving {problem_type} with:\")\n", + " print(f\" - {num_resources} renewable resources\")\n", + " print(f\" - {num_tasks} tasks\")\n", " if tasks_with_alternatives:\n", - " print(\n", - " f' - {tasks_with_alternatives} tasks with alternative resources'\n", - " )\n", + " print(f\" - {tasks_with_alternatives} tasks with alternative resources\")\n", " if variable_duration_tasks:\n", - " print(\n", - " f' - {variable_duration_tasks} tasks with variable durations'\n", - " )\n", + " print(f\" - {variable_duration_tasks} tasks with variable durations\")\n", " if tasks_with_delay:\n", - " print(f' - {tasks_with_delay} tasks with successor delays')\n", + " print(f\" - {tasks_with_delay} tasks with successor delays\")\n", "\n", "\n", "def AnalyseDependencyGraph(problem):\n", " \"\"\"Analyses the dependency graph to improve the model.\n", "\n", - " Args:\n", - " problem: the protobuf of the problem to solve.\n", + " Args:\n", + " problem: the protobuf of the problem to solve.\n", "\n", - " Returns:\n", - " a list of (task1, task2, in_between_tasks) with task2 and indirect successor\n", - " of task1, and in_between_tasks being the list of all tasks after task1 and\n", - " before task2.\n", - " \"\"\"\n", + " Returns:\n", + " a list of (task1, task2, in_between_tasks) with task2 and indirect successor\n", + " of task1, and in_between_tasks being the list of all tasks after task1 and\n", + " before task2.\n", + " \"\"\"\n", "\n", " num_nodes = len(problem.tasks)\n", - " print(f'Analysing the dependency graph over {num_nodes} nodes')\n", + " print(f\"Analysing the dependency graph over {num_nodes} nodes\")\n", "\n", " ins = collections.defaultdict(list)\n", " outs = collections.defaultdict(list)\n", @@ -239,49 +244,50 @@ "\n", " # Sort entries lexicographically by (len(common), source, sink)\n", " def Price(entry):\n", - " return (num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] +\n", - " entry[1])\n", + " return num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] + entry[1]\n", "\n", " result.sort(key=Price)\n", - " print(f' - created {len(result)} pairs of nodes to examine', flush=True)\n", + " print(f\" - created {len(result)} pairs of nodes to examine\", flush=True)\n", " return result, after\n", "\n", "\n", - "def SolveRcpsp(problem,\n", - " proto_file,\n", - " params,\n", - " active_tasks,\n", - " source,\n", - " sink,\n", - " intervals_of_tasks,\n", - " delays,\n", - " in_main_solve=False,\n", - " initial_solution=None,\n", - " lower_bound=0):\n", + "def SolveRcpsp(\n", + " problem,\n", + " proto_file,\n", + " params,\n", + " active_tasks,\n", + " source,\n", + " sink,\n", + " intervals_of_tasks,\n", + " delays,\n", + " in_main_solve=False,\n", + " initial_solution=None,\n", + " lower_bound=0,\n", + "):\n", " \"\"\"Parse and solve a given RCPSP problem in proto format.\n", "\n", - " The model will only look at the tasks {source} + {sink} + active_tasks, and\n", - " ignore all others.\n", + " The model will only look at the tasks {source} + {sink} + active_tasks, and\n", + " ignore all others.\n", "\n", - " Args:\n", - " problem: the description of the model to solve in protobuf format\n", - " proto_file: the name of the file to export the CpModel proto to.\n", - " params: the string representation of the parameters to pass to the sat\n", - " solver.\n", - " active_tasks: the set of active tasks to consider.\n", - " source: the source task in the graph. Its end will be forced to 0.\n", - " sink: the sink task of the graph. Its start is the makespan of the problem.\n", - " intervals_of_tasks: a heuristic lists of (task1, task2, tasks) used to add\n", - " redundant energetic equations to the model.\n", - " delays: a list of (task1, task2, min_delays) used to add extended precedence\n", - " constraints (start(task2) >= end(task1) + min_delay).\n", - " in_main_solve: indicates if this is the main solve procedure.\n", - " initial_solution: A valid assignment used to hint the search.\n", - " lower_bound: A valid lower bound of the makespan objective.\n", + " Args:\n", + " problem: the description of the model to solve in protobuf format\n", + " proto_file: the name of the file to export the CpModel proto to.\n", + " params: the string representation of the parameters to pass to the sat\n", + " solver.\n", + " active_tasks: the set of active tasks to consider.\n", + " source: the source task in the graph. Its end will be forced to 0.\n", + " sink: the sink task of the graph. Its start is the makespan of the problem.\n", + " intervals_of_tasks: a heuristic lists of (task1, task2, tasks) used to add\n", + " redundant energetic equations to the model.\n", + " delays: a list of (task1, task2, min_delays) used to add extended precedence\n", + " constraints (start(task2) >= end(task1) + min_delay).\n", + " in_main_solve: indicates if this is the main solve procedure.\n", + " initial_solution: A valid assignment used to hint the search.\n", + " lower_bound: A valid lower bound of the makespan objective.\n", "\n", - " Returns:\n", - " (lower_bound of the objective, best solution found, asssignment)\n", - " \"\"\"\n", + " Returns:\n", + " (lower_bound of the objective, best solution found, assignment)\n", + " \"\"\"\n", " # Create the model.\n", " model = cp_model.CpModel()\n", " model.SetName(problem.name)\n", @@ -306,7 +312,7 @@ " for d in rd.min_delays:\n", " horizon += abs(d)\n", " if in_main_solve:\n", - " print(f'Horizon = {horizon}', flush=True)\n", + " print(f\"Horizon = {horizon}\", flush=True)\n", "\n", " # Containers.\n", " task_starts = {}\n", @@ -329,15 +335,13 @@ " num_recipes = len(task.recipes)\n", " all_recipes = range(num_recipes)\n", "\n", - " start_var = model.NewIntVar(0, horizon, f'start_of_task_{t}')\n", - " end_var = model.NewIntVar(0, horizon, f'end_of_task_{t}')\n", + " start_var = model.NewIntVar(0, horizon, f\"start_of_task_{t}\")\n", + " end_var = model.NewIntVar(0, horizon, f\"end_of_task_{t}\")\n", "\n", " literals = []\n", " if num_recipes > 1:\n", " # Create one literal per recipe.\n", - " literals = [\n", - " model.NewBoolVar(f'is_present_{t}_{r}') for r in all_recipes\n", - " ]\n", + " literals = [model.NewBoolVar(f\"is_present_{t}_{r}\") for r in all_recipes]\n", "\n", " # Exactly one recipe must be performed.\n", " model.AddExactlyOne(literals)\n", @@ -357,17 +361,19 @@ " # Create the duration variable from the accumulated durations.\n", " duration_var = model.NewIntVarFromDomain(\n", " cp_model.Domain.FromValues(task_to_recipe_durations[t]),\n", - " f'duration_of_task_{t}')\n", + " f\"duration_of_task_{t}\",\n", + " )\n", "\n", " # Link the recipe literals and the duration_var.\n", " for r in range(num_recipes):\n", - " model.Add(\n", - " duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf(\n", - " literals[r])\n", + " model.Add(duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf(\n", + " literals[r]\n", + " )\n", "\n", " # Create the interval of the task.\n", - " task_interval = model.NewIntervalVar(start_var, duration_var, end_var,\n", - " f'task_interval_{t}')\n", + " task_interval = model.NewIntervalVar(\n", + " start_var, duration_var, end_var, f\"task_interval_{t}\"\n", + " )\n", "\n", " # Store task variables.\n", " task_starts[t] = start_var\n", @@ -381,33 +387,38 @@ " demands = [demand_matrix[(res, recipe)] for recipe in all_recipes]\n", " task_resource_to_fixed_demands[(t, res)] = demands\n", " demand_var = model.NewIntVarFromDomain(\n", - " cp_model.Domain.FromValues(demands), f'demand_{t}_{res}')\n", + " cp_model.Domain.FromValues(demands), f\"demand_{t}_{res}\"\n", + " )\n", " task_to_resource_demands[t].append(demand_var)\n", "\n", " # Link the recipe literals and the demand_var.\n", " for r in all_recipes:\n", " model.Add(demand_var == demand_matrix[(res, r)]).OnlyEnforceIf(\n", - " literals[r])\n", + " literals[r]\n", + " )\n", "\n", " resource_to_sum_of_demand_max[res] += max(demands)\n", "\n", " # Create the energy expression for (task, resource):\n", " for res in all_resources:\n", " task_resource_to_energy[(t, res)] = sum(\n", - " literals[r] * task_to_recipe_durations[t][r] *\n", - " task_resource_to_fixed_demands[(t, res)][r]\n", - " for r in all_recipes)\n", + " literals[r]\n", + " * task_to_recipe_durations[t][r]\n", + " * task_resource_to_fixed_demands[(t, res)][r]\n", + " for r in all_recipes\n", + " )\n", " task_resource_to_max_energy[(t, res)] = max(\n", - " task_to_recipe_durations[t][r] *\n", - " task_resource_to_fixed_demands[(t, res)][r]\n", - " for r in all_recipes)\n", + " task_to_recipe_durations[t][r]\n", + " * task_resource_to_fixed_demands[(t, res)][r]\n", + " for r in all_recipes\n", + " )\n", "\n", " # Create makespan variable\n", - " makespan = model.NewIntVar(lower_bound, horizon, 'makespan')\n", - " makespan_size = model.NewIntVar(1, horizon, 'interval_makespan_size')\n", - " interval_makespan = model.NewIntervalVar(makespan, makespan_size,\n", - " model.NewConstant(horizon + 1),\n", - " 'interval_makespan')\n", + " makespan = model.NewIntVar(lower_bound, horizon, \"makespan\")\n", + " makespan_size = model.NewIntVar(1, horizon, \"interval_makespan_size\")\n", + " interval_makespan = model.NewIntervalVar(\n", + " makespan, makespan_size, model.NewConstant(horizon + 1), \"interval_makespan\"\n", + " )\n", "\n", " # Add precedences.\n", " if problem.is_rcpsp_max:\n", @@ -417,8 +428,7 @@ " task = problem.tasks[task_id]\n", " num_modes = len(task.recipes)\n", "\n", - " for successor_index in range(len(task.successors)):\n", - " next_id = task.successors[successor_index]\n", + " for successor_index, next_id in enumerate(task.successors):\n", " delay_matrix = task.successor_delays[successor_index]\n", " num_next_modes = len(problem.tasks[next_id].recipes)\n", " for m1 in range(num_modes):\n", @@ -429,8 +439,7 @@ " model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1)\n", " else:\n", " for m2 in range(num_next_modes):\n", - " delay = delay_matrix.recipe_delays[m1].min_delays[\n", - " m2]\n", + " delay = delay_matrix.recipe_delays[m1].min_delays[m2]\n", " s2 = task_starts[next_id]\n", " p2 = task_to_presence_literals[next_id][m2]\n", " model.Add(s1 + delay <= s2).OnlyEnforceIf([p1, p2])\n", @@ -452,18 +461,16 @@ " resource = problem.resources[res]\n", " c = resource.max_capacity\n", " if c == -1:\n", - " print(f'No capacity: {resource}')\n", + " print(f\"No capacity: {resource}\")\n", " c = resource_to_sum_of_demand_max[res]\n", "\n", " # RIP problems have only renewable resources, and no makespan.\n", " if problem.is_resource_investment or resource.renewable:\n", " intervals = [task_intervals[t] for t in all_active_tasks]\n", - " demands = [\n", - " task_to_resource_demands[t][res] for t in all_active_tasks\n", - " ]\n", + " demands = [task_to_resource_demands[t][res] for t in all_active_tasks]\n", "\n", " if problem.is_resource_investment:\n", - " capacity = model.NewIntVar(0, c, f'capacity_of_{res}')\n", + " capacity = model.NewIntVar(0, c, f\"capacity_of_{res}\")\n", " model.AddCumulative(intervals, demands, capacity)\n", " capacities.append(capacity)\n", " max_cost += c * resource.unit_cost\n", @@ -481,24 +488,32 @@ " if task_resource_to_fixed_demands[(t, res)][0]:\n", " reservoir_starts.append(task_starts[t])\n", " reservoir_demands.append(\n", - " task_resource_to_fixed_demands[(t, res)][0])\n", - " model.AddReservoirConstraint(reservoir_starts,\n", - " reservoir_demands,\n", - " resource.min_capacity,\n", - " resource.max_capacity)\n", + " task_resource_to_fixed_demands[(t, res)][0]\n", + " )\n", + " model.AddReservoirConstraint(\n", + " reservoir_starts,\n", + " reservoir_demands,\n", + " resource.min_capacity,\n", + " resource.max_capacity,\n", + " )\n", " else: # No producer-consumer. We just sum the demands.\n", " model.Add(\n", - " cp_model.LinearExpr.Sum([\n", - " task_to_resource_demands[t][res]\n", - " for t in all_active_tasks\n", - " ]) <= c)\n", + " cp_model.LinearExpr.Sum(\n", + " [task_to_resource_demands[t][res] for t in all_active_tasks]\n", + " )\n", + " <= c\n", + " )\n", "\n", " # Objective.\n", " if problem.is_resource_investment:\n", - " objective = model.NewIntVar(0, max_cost, 'capacity_costs')\n", - " model.Add(objective == sum(problem.resources[i].unit_cost *\n", - " capacities[i]\n", - " for i in range(len(capacities))))\n", + " objective = model.NewIntVar(0, max_cost, \"capacity_costs\")\n", + " model.Add(\n", + " objective\n", + " == sum(\n", + " problem.resources[i].unit_cost * capacities[i]\n", + " for i in range(len(capacities))\n", + " )\n", + " )\n", " else:\n", " objective = makespan\n", "\n", @@ -512,8 +527,7 @@ " elif local_start in active_tasks and local_end == sink:\n", " model.Add(makespan >= task_ends[local_start] + min_delay)\n", " elif local_start in active_tasks and local_end in active_tasks:\n", - " model.Add(task_starts[local_end] >= task_ends[local_start] +\n", - " min_delay)\n", + " model.Add(task_starts[local_end] >= task_ends[local_start] + min_delay)\n", "\n", " problem_is_single_mode = True\n", " for t in all_active_tasks:\n", @@ -533,10 +547,13 @@ " # graph, it add the energetic relaxation:\n", " # (start_var('end') - end_var('start')) * capacity_max >=\n", " # sum of linearized energies of all tasks from 'in_between_tasks'\n", - " if (not problem.is_resource_investment and\n", - " not problem.is_consumer_producer and\n", - " _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS.value and in_main_solve and\n", - " not problem_is_single_mode):\n", + " if (\n", + " not problem.is_resource_investment\n", + " and not problem.is_consumer_producer\n", + " and _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS.value\n", + " and in_main_solve\n", + " and not problem_is_single_mode\n", + " ):\n", " added_constraints = 0\n", " ignored_constraits = 0\n", " for local_start, local_end, common in intervals_of_tasks:\n", @@ -548,19 +565,21 @@ " if delays and (local_start, local_end) in delays:\n", " min_delay, _ = delays[local_start, local_end]\n", " sum_of_max_energies = sum(\n", - " task_resource_to_max_energy[(t, res)] for t in common)\n", + " task_resource_to_max_energy[(t, res)] for t in common\n", + " )\n", " if sum_of_max_energies <= c * min_delay:\n", " ignored_constraits += 1\n", " continue\n", " model.Add(\n", - " c *\n", - " (task_starts[local_end] - task_ends[local_start]) >= sum(\n", - " task_resource_to_energy[(t, res)] for t in common))\n", + " c * (task_starts[local_end] - task_ends[local_start])\n", + " >= sum(task_resource_to_energy[(t, res)] for t in common)\n", + " )\n", " added_constraints += 1\n", " print(\n", - " f'Added {added_constraints} redundant energetic constraints, and ' +\n", - " f'ignored {ignored_constraits} constraints.',\n", - " flush=True)\n", + " f\"Added {added_constraints} redundant energetic constraints, and \"\n", + " + f\"ignored {ignored_constraits} constraints.\",\n", + " flush=True,\n", + " )\n", "\n", " # Add solution hint.\n", " if initial_solution:\n", @@ -572,19 +591,25 @@ "\n", " # Write model to file.\n", " if proto_file:\n", - " print(f'Writing proto to{proto_file}')\n", + " print(f\"Writing proto to{proto_file}\")\n", " model.ExportToFile(proto_file)\n", "\n", " # Solve model.\n", " solver = cp_model.CpSolver()\n", + " if not _USE_INTERVAL_MAKESPAN.value:\n", + " solver.parameters.exploit_all_precedences = True\n", + " solver.parameters.use_hard_precedences_in_cumulative = True\n", " if params:\n", " text_format.Parse(params, solver.parameters)\n", + " if solver.parameters.num_workers >= 16 and solver.parameters.num_workers < 24:\n", + " solver.parameters.ignore_subsolvers.append(\"objective_lb_search\")\n", + " solver.parameters.extra_subsolvers.append(\"objective_shaving_search\")\n", " if in_main_solve:\n", " solver.parameters.log_search_progress = True\n", " status = solver.Solve(model)\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " assignment = rcpsp_pb2.RcpspAssignment()\n", - " for t in range(len(problem.tasks)):\n", + " for t, _ in enumerate(problem.tasks):\n", " if t in task_starts:\n", " assignment.start_of_task.append(solver.Value(task_starts[t]))\n", " for r in range(len(task_to_presence_literals[t])):\n", @@ -594,25 +619,32 @@ " else: # t is not an active task.\n", " assignment.start_of_task.append(0)\n", " assignment.selected_recipe_of_task.append(0)\n", - " return (int(solver.BestObjectiveBound()), int(solver.ObjectiveValue()),\n", - " assignment)\n", + " return (\n", + " int(solver.BestObjectiveBound()),\n", + " int(solver.ObjectiveValue()),\n", + " assignment,\n", + " )\n", " return -1, -1, None\n", "\n", "\n", "def ComputeDelaysBetweenNodes(problem, task_intervals):\n", " \"\"\"Computes the min delays between all pairs of tasks in 'task_intervals'.\n", "\n", - " Args:\n", - " problem: The protobuf of the model.\n", - " task_intervals: The output of the AnalysePrecedenceGraph().\n", + " Args:\n", + " problem: The protobuf of the model.\n", + " task_intervals: The output of the AnalysePrecedenceGraph().\n", "\n", - " Returns:\n", - " a list of (task1, task2, min_delay_between_task1_and_task2)\n", - " \"\"\"\n", - " print('Computing the minimum delay between pairs of intervals')\n", + " Returns:\n", + " a list of (task1, task2, min_delay_between_task1_and_task2)\n", + " \"\"\"\n", + " print(\"Computing the minimum delay between pairs of intervals\")\n", " delays = {}\n", - " if (problem.is_resource_investment or problem.is_consumer_producer or\n", - " problem.is_rcpsp_max or _DELAY_TIME_LIMIT.value <= 0.0):\n", + " if (\n", + " problem.is_resource_investment\n", + " or problem.is_consumer_producer\n", + " or problem.is_rcpsp_max\n", + " or _DELAY_TIME_LIMIT.value <= 0.0\n", + " ):\n", " return delays, None, False\n", "\n", " complete_problem_assignment = None\n", @@ -621,9 +653,15 @@ " optimal_found = True\n", " for start_task, end_task, active_tasks in task_intervals:\n", " min_delay, feasible_delay, assignment = SolveRcpsp(\n", - " problem, '',\n", - " f'num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}',\n", - " active_tasks, start_task, end_task, [], delays)\n", + " problem,\n", + " \"\",\n", + " f\"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}\",\n", + " active_tasks,\n", + " start_task,\n", + " end_task,\n", + " [],\n", + " delays,\n", + " )\n", " if min_delay != -1:\n", " delays[(start_task, end_task)] = min_delay, feasible_delay\n", " if start_task == 0 and end_task == len(problem.tasks) - 1:\n", @@ -636,9 +674,9 @@ " num_delays_not_found += 1\n", " optimal_found = False\n", "\n", - " print(f' - #optimal delays = {num_optimal_delays}', flush=True)\n", + " print(f\" - #optimal delays = {num_optimal_delays}\", flush=True)\n", " if num_delays_not_found:\n", - " print(f' - #not computed delays = {num_delays_not_found}', flush=True)\n", + " print(f\" - #not computed delays = {num_delays_not_found}\", flush=True)\n", "\n", " return delays, complete_problem_assignment, optimal_found\n", "\n", @@ -654,8 +692,10 @@ " resource = problem.resources[res]\n", " if not resource.renewable:\n", " continue\n", - " if (sum(demand_map[(t, res)] for t in current) +\n", - " demand_map[(candidate, res)] > resource.max_capacity):\n", + " if (\n", + " sum(demand_map[(t, res)] for t in current) + demand_map[(candidate, res)]\n", + " > resource.max_capacity\n", + " ):\n", " return False\n", "\n", " return True\n", @@ -664,23 +704,26 @@ "def ComputePreemptiveLowerBound(problem, after, lower_bound):\n", " \"\"\"Computes a preemtive lower bound for the makespan statically.\n", "\n", - " For this, it breaks all intervals into a set of intervals of size one.\n", - " Then it will try to assign all of them in a minimum number of configurations.\n", - " This is a standard complete set covering using column generation approach\n", - " where each column is a possible combination of itervals of size one.\n", + " For this, it breaks all intervals into a set of intervals of size one.\n", + " Then it will try to assign all of them in a minimum number of configurations.\n", + " This is a standard complete set covering using column generation approach\n", + " where each column is a possible combination of itervals of size one.\n", "\n", - " Args:\n", - " problem: The probuf of the model.\n", - " after: a task to list of task dict that contains all tasks after a given\n", - " task.\n", - " lower_bound: A valid lower bound of the problem. It can be 0.\n", + " Args:\n", + " problem: The probuf of the model.\n", + " after: a task to list of task dict that contains all tasks after a given\n", + " task.\n", + " lower_bound: A valid lower bound of the problem. It can be 0.\n", "\n", - " Returns:\n", - " a valid lower bound of the problem.\n", - " \"\"\"\n", + " Returns:\n", + " a valid lower bound of the problem.\n", + " \"\"\"\n", " # Check this is a single mode problem.\n", - " if (problem.is_rcpsp_max or problem.is_resource_investment or\n", - " problem.is_consumer_producer):\n", + " if (\n", + " problem.is_rcpsp_max\n", + " or problem.is_resource_investment\n", + " or problem.is_consumer_producer\n", + " ):\n", " return lower_bound\n", "\n", " demand_map = collections.defaultdict(int)\n", @@ -700,9 +743,11 @@ " max_duration = max(max_duration, recipe.duration)\n", " sum_of_demands += demand\n", "\n", - " print(f'Compute a bin-packing lower bound with {len(all_active_tasks)}' +\n", - " ' active tasks',\n", - " flush=True)\n", + " print(\n", + " f\"Compute a bin-packing lower bound with {len(all_active_tasks)}\"\n", + " + \" active tasks\",\n", + " flush=True,\n", + " )\n", " all_combinations = []\n", "\n", " for t in all_active_tasks:\n", @@ -714,7 +759,7 @@ "\n", " all_combinations.extend(new_combinations)\n", "\n", - " print(f' - created {len(all_combinations)} combinations')\n", + " print(f\" - created {len(all_combinations)} combinations\")\n", " if len(all_combinations) > 5000000:\n", " return lower_bound # Abort if too large.\n", "\n", @@ -725,7 +770,7 @@ " # do not use that column.\n", " # 2/ Merge all task with exactly same demands into one.\n", " model = cp_model.CpModel()\n", - " model.SetName(f'lower_bound_{problem.name}')\n", + " model.SetName(f\"lower_bound_{problem.name}\")\n", "\n", " vars_per_task = collections.defaultdict(list)\n", " all_vars = []\n", @@ -733,7 +778,7 @@ " min_duration = max_duration\n", " for t in c:\n", " min_duration = min(min_duration, duration_map[t])\n", - " count = model.NewIntVar(0, min_duration, f'count_{t}')\n", + " count = model.NewIntVar(0, min_duration, f\"count_{c}\")\n", " all_vars.append(count)\n", " for t in c:\n", " vars_per_task[t].append(count)\n", @@ -743,8 +788,7 @@ " model.Add(sum(vars_per_task[t]) >= duration_map[t])\n", "\n", " # Objective\n", - " objective_var = model.NewIntVar(lower_bound, sum_of_demands,\n", - " 'objective_var')\n", + " objective_var = model.NewIntVar(lower_bound, sum_of_demands, \"objective_var\")\n", " model.Add(objective_var == sum(all_vars))\n", "\n", " model.Minimize(objective_var)\n", @@ -755,24 +799,24 @@ " solver.parameters.max_time_in_seconds = _PREEMPTIVE_LB_TIME_LIMIT.value\n", " status = solver.Solve(model)\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " status_str = 'optimal' if status == cp_model.OPTIMAL else ''\n", + " status_str = \"optimal\" if status == cp_model.OPTIMAL else \"\"\n", " lower_bound = max(lower_bound, int(solver.BestObjectiveBound()))\n", - " print(f' - {status_str} static lower bound = {lower_bound}',\n", - " flush=True)\n", + " print(f\" - {status_str} static lower bound = {lower_bound}\", flush=True)\n", "\n", " return lower_bound\n", "\n", "\n", "def main(_):\n", - " rcpsp_parser = pywraprcpsp.RcpspParser()\n", - " rcpsp_parser.ParseFile(_INPUT.value)\n", + " rcpsp_parser = rcpsp.RcpspParser()\n", + " rcpsp_parser.parse_file(_INPUT.value)\n", "\n", - " problem = rcpsp_parser.Problem()\n", + " problem = rcpsp_parser.problem()\n", " PrintProblemStatistics(problem)\n", "\n", " intervals_of_tasks, after = AnalyseDependencyGraph(problem)\n", " delays, initial_solution, optimal_found = ComputeDelaysBetweenNodes(\n", - " problem, intervals_of_tasks)\n", + " problem, intervals_of_tasks\n", + " )\n", "\n", " last_task = len(problem.tasks) - 1\n", " key = (0, last_task)\n", @@ -780,17 +824,19 @@ " if not optimal_found and _PREEMPTIVE_LB_TIME_LIMIT.value > 0.0:\n", " lower_bound = ComputePreemptiveLowerBound(problem, after, lower_bound)\n", "\n", - " SolveRcpsp(problem=problem,\n", - " proto_file=_OUTPUT_PROTO.value,\n", - " params=_PARAMS.value,\n", - " active_tasks=set(range(1, last_task)),\n", - " source=0,\n", - " sink=last_task,\n", - " intervals_of_tasks=intervals_of_tasks,\n", - " delays=delays,\n", - " in_main_solve=True,\n", - " initial_solution=initial_solution,\n", - " lower_bound=lower_bound)\n", + " SolveRcpsp(\n", + " problem=problem,\n", + " proto_file=_OUTPUT_PROTO.value,\n", + " params=_PARAMS.value,\n", + " active_tasks=set(range(1, last_task)),\n", + " source=0,\n", + " sink=last_task,\n", + " intervals_of_tasks=intervals_of_tasks,\n", + " delays=delays,\n", + " in_main_solve=True,\n", + " initial_solution=initial_solution,\n", + " lower_bound=lower_bound,\n", + " )\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/shift_scheduling_sat.ipynb b/examples/notebook/examples/shift_scheduling_sat.ipynb index 95ae9d0968..0d92d87663 100644 --- a/examples/notebook/examples/shift_scheduling_sat.ipynb +++ b/examples/notebook/examples/shift_scheduling_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Creates a shift scheduling problem and solves it.\n" ] }, @@ -86,28 +87,30 @@ "from google.protobuf import text_format\n", "\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", - "_PARAMS = flags.DEFINE_string('params', 'max_time_in_seconds:10.0',\n", - " 'Sat solver parameters.')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\", \"max_time_in_seconds:10.0\", \"Sat solver parameters.\"\n", + ")\n", "\n", "\n", "def negated_bounded_span(works, start, length):\n", " \"\"\"Filters an isolated sub-sequence of variables assined to True.\n", "\n", - " Extract the span of Boolean variables [start, start + length), negate them,\n", - " and if there is variables to the left/right of this span, surround the span by\n", - " them in non negated form.\n", + " Extract the span of Boolean variables [start, start + length), negate them,\n", + " and if there is variables to the left/right of this span, surround the span by\n", + " them in non negated form.\n", "\n", - " Args:\n", - " works: a list of variables to extract the span from.\n", - " start: the start to the span.\n", - " length: the length of the span.\n", + " Args:\n", + " works: a list of variables to extract the span from.\n", + " start: the start to the span.\n", + " length: the length of the span.\n", "\n", - " Returns:\n", - " a list of variables which conjunction will be false if the sub-list is\n", - " assigned to True, and correctly bounded by variables assigned to False,\n", - " or by the start or end of works.\n", - " \"\"\"\n", + " Returns:\n", + " a list of variables which conjunction will be false if the sub-list is\n", + " assigned to True, and correctly bounded by variables assigned to False,\n", + " or by the start or end of works.\n", + " \"\"\"\n", " sequence = []\n", " # Left border (start of works, or works[start - 1])\n", " if start > 0:\n", @@ -120,35 +123,36 @@ " return sequence\n", "\n", "\n", - "def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,\n", - " soft_max, hard_max, max_cost, prefix):\n", + "def add_soft_sequence_constraint(\n", + " model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix\n", + "):\n", " \"\"\"Sequence constraint on true variables with soft and hard bounds.\n", "\n", - " This constraint look at every maximal contiguous sequence of variables\n", - " assigned to true. If forbids sequence of length < hard_min or > hard_max.\n", - " Then it creates penalty terms if the length is < soft_min or > soft_max.\n", + " This constraint look at every maximal contiguous sequence of variables\n", + " assigned to true. If forbids sequence of length < hard_min or > hard_max.\n", + " Then it creates penalty terms if the length is < soft_min or > soft_max.\n", "\n", - " Args:\n", - " model: the sequence constraint is built on this model.\n", - " works: a list of Boolean variables.\n", - " hard_min: any sequence of true variables must have a length of at least\n", - " hard_min.\n", - " soft_min: any sequence should have a length of at least soft_min, or a\n", - " linear penalty on the delta will be added to the objective.\n", - " min_cost: the coefficient of the linear penalty if the length is less than\n", - " soft_min.\n", - " soft_max: any sequence should have a length of at most soft_max, or a linear\n", - " penalty on the delta will be added to the objective.\n", - " hard_max: any sequence of true variables must have a length of at most\n", - " hard_max.\n", - " max_cost: the coefficient of the linear penalty if the length is more than\n", - " soft_max.\n", - " prefix: a base name for penalty literals.\n", + " Args:\n", + " model: the sequence constraint is built on this model.\n", + " works: a list of Boolean variables.\n", + " hard_min: any sequence of true variables must have a length of at least\n", + " hard_min.\n", + " soft_min: any sequence should have a length of at least soft_min, or a\n", + " linear penalty on the delta will be added to the objective.\n", + " min_cost: the coefficient of the linear penalty if the length is less than\n", + " soft_min.\n", + " soft_max: any sequence should have a length of at most soft_max, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " hard_max: any sequence of true variables must have a length of at most\n", + " hard_max.\n", + " max_cost: the coefficient of the linear penalty if the length is more than\n", + " soft_max.\n", + " prefix: a base name for penalty literals.\n", "\n", - " Returns:\n", - " a tuple (variables_list, coefficient_list) containing the different\n", - " penalties created by the sequence constraint.\n", - " \"\"\"\n", + " Returns:\n", + " a tuple (variables_list, coefficient_list) containing the different\n", + " penalties created by the sequence constraint.\n", + " \"\"\"\n", " cost_literals = []\n", " cost_coefficients = []\n", "\n", @@ -162,7 +166,7 @@ " for length in range(hard_min, soft_min):\n", " for start in range(len(works) - length + 1):\n", " span = negated_bounded_span(works, start, length)\n", - " name = ': under_span(start=%i, length=%i)' % (start, length)\n", + " name = \": under_span(start=%i, length=%i)\" % (start, length)\n", " lit = model.NewBoolVar(prefix + name)\n", " span.append(lit)\n", " model.AddBoolOr(span)\n", @@ -176,7 +180,7 @@ " for length in range(soft_max + 1, hard_max + 1):\n", " for start in range(len(works) - length + 1):\n", " span = negated_bounded_span(works, start, length)\n", - " name = ': over_span(start=%i, length=%i)' % (start, length)\n", + " name = \": over_span(start=%i, length=%i)\" % (start, length)\n", " lit = model.NewBoolVar(prefix + name)\n", " span.append(lit)\n", " model.AddBoolOr(span)\n", @@ -186,61 +190,61 @@ "\n", " # Just forbid any sequence of true variables with length hard_max + 1\n", " for start in range(len(works) - hard_max):\n", - " model.AddBoolOr(\n", - " [works[i].Not() for i in range(start, start + hard_max + 1)])\n", + " model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)])\n", " return cost_literals, cost_coefficients\n", "\n", "\n", - "def add_soft_sum_constraint(model, works, hard_min, soft_min, min_cost,\n", - " soft_max, hard_max, max_cost, prefix):\n", + "def add_soft_sum_constraint(\n", + " model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix\n", + "):\n", " \"\"\"Sum constraint with soft and hard bounds.\n", "\n", - " This constraint counts the variables assigned to true from works.\n", - " If forbids sum < hard_min or > hard_max.\n", - " Then it creates penalty terms if the sum is < soft_min or > soft_max.\n", + " This constraint counts the variables assigned to true from works.\n", + " If forbids sum < hard_min or > hard_max.\n", + " Then it creates penalty terms if the sum is < soft_min or > soft_max.\n", "\n", - " Args:\n", - " model: the sequence constraint is built on this model.\n", - " works: a list of Boolean variables.\n", - " hard_min: any sequence of true variables must have a sum of at least\n", - " hard_min.\n", - " soft_min: any sequence should have a sum of at least soft_min, or a linear\n", - " penalty on the delta will be added to the objective.\n", - " min_cost: the coefficient of the linear penalty if the sum is less than\n", - " soft_min.\n", - " soft_max: any sequence should have a sum of at most soft_max, or a linear\n", - " penalty on the delta will be added to the objective.\n", - " hard_max: any sequence of true variables must have a sum of at most\n", - " hard_max.\n", - " max_cost: the coefficient of the linear penalty if the sum is more than\n", - " soft_max.\n", - " prefix: a base name for penalty variables.\n", + " Args:\n", + " model: the sequence constraint is built on this model.\n", + " works: a list of Boolean variables.\n", + " hard_min: any sequence of true variables must have a sum of at least\n", + " hard_min.\n", + " soft_min: any sequence should have a sum of at least soft_min, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " min_cost: the coefficient of the linear penalty if the sum is less than\n", + " soft_min.\n", + " soft_max: any sequence should have a sum of at most soft_max, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " hard_max: any sequence of true variables must have a sum of at most\n", + " hard_max.\n", + " max_cost: the coefficient of the linear penalty if the sum is more than\n", + " soft_max.\n", + " prefix: a base name for penalty variables.\n", "\n", - " Returns:\n", - " a tuple (variables_list, coefficient_list) containing the different\n", - " penalties created by the sequence constraint.\n", - " \"\"\"\n", + " Returns:\n", + " a tuple (variables_list, coefficient_list) containing the different\n", + " penalties created by the sequence constraint.\n", + " \"\"\"\n", " cost_variables = []\n", " cost_coefficients = []\n", - " sum_var = model.NewIntVar(hard_min, hard_max, '')\n", + " sum_var = model.NewIntVar(hard_min, hard_max, \"\")\n", " # This adds the hard constraints on the sum.\n", " model.Add(sum_var == sum(works))\n", "\n", " # Penalize sums below the soft_min target.\n", " if soft_min > hard_min and min_cost > 0:\n", - " delta = model.NewIntVar(-len(works), len(works), '')\n", + " delta = model.NewIntVar(-len(works), len(works), \"\")\n", " model.Add(delta == soft_min - sum_var)\n", " # TODO(user): Compare efficiency with only excess >= soft_min - sum_var.\n", - " excess = model.NewIntVar(0, 7, prefix + ': under_sum')\n", + " excess = model.NewIntVar(0, 7, prefix + \": under_sum\")\n", " model.AddMaxEquality(excess, [delta, 0])\n", " cost_variables.append(excess)\n", " cost_coefficients.append(min_cost)\n", "\n", " # Penalize sums above the soft_max target.\n", " if soft_max < hard_max and max_cost > 0:\n", - " delta = model.NewIntVar(-7, 7, '')\n", + " delta = model.NewIntVar(-7, 7, \"\")\n", " model.Add(delta == sum_var - soft_max)\n", - " excess = model.NewIntVar(0, 7, prefix + ': over_sum')\n", + " excess = model.NewIntVar(0, 7, prefix + \": over_sum\")\n", " model.AddMaxEquality(excess, [delta, 0])\n", " cost_variables.append(excess)\n", " cost_coefficients.append(max_cost)\n", @@ -253,7 +257,7 @@ " # Data\n", " num_employees = 8\n", " num_weeks = 3\n", - " shifts = ['O', 'M', 'A', 'N']\n", + " shifts = [\"O\", \"M\", \"A\", \"N\"]\n", "\n", " # Fixed assignment: (employee, shift, day).\n", " # This fixes the first 2 days of the schedule.\n", @@ -286,7 +290,7 @@ " (5, 3, 10, -2),\n", " # Employee 2 does not want a night shift on the first Friday (positive\n", " # weight).\n", - " (2, 3, 4, 4)\n", + " (2, 3, 4, 4),\n", " ]\n", "\n", " # Shift constraints on continuous sequence :\n", @@ -343,7 +347,7 @@ " for e in range(num_employees):\n", " for s in range(num_shifts):\n", " for d in range(num_days):\n", - " work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))\n", + " work[e, s, d] = model.NewBoolVar(\"work%i_%i_%i\" % (e, s, d))\n", "\n", " # Linear terms of the objective in a minimization context.\n", " obj_int_vars = []\n", @@ -371,9 +375,16 @@ " for e in range(num_employees):\n", " works = [work[e, shift, d] for d in range(num_days)]\n", " variables, coeffs = add_soft_sequence_constraint(\n", - " model, works, hard_min, soft_min, min_cost, soft_max, hard_max,\n", + " model,\n", + " works,\n", + " hard_min,\n", + " soft_min,\n", + " min_cost,\n", + " soft_max,\n", + " hard_max,\n", " max_cost,\n", - " 'shift_constraint(employee %i, shift %i)' % (e, shift))\n", + " \"shift_constraint(employee %i, shift %i)\" % (e, shift),\n", + " )\n", " obj_bool_vars.extend(variables)\n", " obj_bool_coeffs.extend(coeffs)\n", "\n", @@ -384,10 +395,17 @@ " for w in range(num_weeks):\n", " works = [work[e, shift, d + w * 7] for d in range(7)]\n", " variables, coeffs = add_soft_sum_constraint(\n", - " model, works, hard_min, soft_min, min_cost, soft_max,\n", - " hard_max, max_cost,\n", - " 'weekly_sum_constraint(employee %i, shift %i, week %i)' %\n", - " (e, shift, w))\n", + " model,\n", + " works,\n", + " hard_min,\n", + " soft_min,\n", + " min_cost,\n", + " soft_max,\n", + " hard_max,\n", + " max_cost,\n", + " \"weekly_sum_constraint(employee %i, shift %i, week %i)\"\n", + " % (e, shift, w),\n", + " )\n", " obj_int_vars.extend(variables)\n", " obj_int_coeffs.extend(coeffs)\n", "\n", @@ -396,14 +414,15 @@ " for e in range(num_employees):\n", " for d in range(num_days - 1):\n", " transition = [\n", - " work[e, previous_shift, d].Not(), work[e, next_shift,\n", - " d + 1].Not()\n", + " work[e, previous_shift, d].Not(),\n", + " work[e, next_shift, d + 1].Not(),\n", " ]\n", " if cost == 0:\n", " model.AddBoolOr(transition)\n", " else:\n", " trans_var = model.NewBoolVar(\n", - " 'transition (employee=%i, day=%i)' % (e, d))\n", + " \"transition (employee=%i, day=%i)\" % (e, d)\n", + " )\n", " transition.append(trans_var)\n", " model.AddBoolOr(transition)\n", " obj_bool_vars.append(trans_var)\n", @@ -416,28 +435,25 @@ " works = [work[e, s, w * 7 + d] for e in range(num_employees)]\n", " # Ignore Off shift.\n", " min_demand = weekly_cover_demands[d][s - 1]\n", - " worked = model.NewIntVar(min_demand, num_employees, '')\n", + " worked = model.NewIntVar(min_demand, num_employees, \"\")\n", " model.Add(worked == sum(works))\n", " over_penalty = excess_cover_penalties[s - 1]\n", " if over_penalty > 0:\n", - " name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,\n", - " d)\n", - " excess = model.NewIntVar(0, num_employees - min_demand,\n", - " name)\n", + " name = \"excess_demand(shift=%i, week=%i, day=%i)\" % (s, w, d)\n", + " excess = model.NewIntVar(0, num_employees - min_demand, name)\n", " model.Add(excess == worked - min_demand)\n", " obj_int_vars.append(excess)\n", " obj_int_coeffs.append(over_penalty)\n", "\n", " # Objective\n", " model.Minimize(\n", - " sum(obj_bool_vars[i] * obj_bool_coeffs[i]\n", - " for i in range(len(obj_bool_vars))) +\n", - " sum(obj_int_vars[i] * obj_int_coeffs[i]\n", - " for i in range(len(obj_int_vars))))\n", + " sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars)))\n", + " + sum(obj_int_vars[i] * obj_int_coeffs[i] for i in range(len(obj_int_vars)))\n", + " )\n", "\n", " if output_proto:\n", - " print('Writing proto to %s' % output_proto)\n", - " with open(output_proto, 'w') as text_file:\n", + " print(\"Writing proto to %s\" % output_proto)\n", + " with open(output_proto, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", " # Solve the model.\n", @@ -450,38 +466,40 @@ " # Print solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", " print()\n", - " header = ' '\n", + " header = \" \"\n", " for w in range(num_weeks):\n", - " header += 'M T W T F S S '\n", + " header += \"M T W T F S S \"\n", " print(header)\n", " for e in range(num_employees):\n", - " schedule = ''\n", + " schedule = \"\"\n", " for d in range(num_days):\n", " for s in range(num_shifts):\n", " if solver.BooleanValue(work[e, s, d]):\n", - " schedule += shifts[s] + ' '\n", - " print('worker %i: %s' % (e, schedule))\n", + " schedule += shifts[s] + \" \"\n", + " print(\"worker %i: %s\" % (e, schedule))\n", " print()\n", - " print('Penalties:')\n", + " print(\"Penalties:\")\n", " for i, var in enumerate(obj_bool_vars):\n", " if solver.BooleanValue(var):\n", " penalty = obj_bool_coeffs[i]\n", " if penalty > 0:\n", - " print(' %s violated, penalty=%i' % (var.Name(), penalty))\n", + " print(\" %s violated, penalty=%i\" % (var.Name(), penalty))\n", " else:\n", - " print(' %s fulfilled, gain=%i' % (var.Name(), -penalty))\n", + " print(\" %s fulfilled, gain=%i\" % (var.Name(), -penalty))\n", "\n", " for i, var in enumerate(obj_int_vars):\n", " if solver.Value(var) > 0:\n", - " print(' %s violated by %i, linear penalty=%i' %\n", - " (var.Name(), solver.Value(var), obj_int_coeffs[i]))\n", + " print(\n", + " \" %s violated by %i, linear penalty=%i\"\n", + " % (var.Name(), solver.Value(var), obj_int_coeffs[i])\n", + " )\n", "\n", " print()\n", - " print('Statistics')\n", - " print(' - status : %s' % solver.StatusName(status))\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Statistics\")\n", + " print(\" - status : %s\" % solver.StatusName(status))\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "def main(_):\n", diff --git a/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb b/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb index ffb383ab65..0111f15bb8 100644 --- a/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb +++ b/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Single machine jobshop with setup times, release dates and due dates.\n" ] }, @@ -86,19 +87,22 @@ "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "#----------------------------------------------------------------------------\n", + "# ----------------------------------------------------------------------------\n", "# Command line arguments.\n", "_OUTPUT_PROTO = flags.DEFINE_string(\n", - " 'output_proto', '', 'Output file to write the cp_model proto to.')\n", + " \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n", + ")\n", "_PARAMS = flags.DEFINE_string(\n", - " 'params',\n", - " 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n", - " 'Sat solver parameters.')\n", - "_PREPROCESS = flags.DEFINE_bool('--preprocess_times', True,\n", - " 'Preprocess setup times and durations')\n", + " \"params\",\n", + " \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n", + " \"Sat solver parameters.\",\n", + ")\n", + "_PREPROCESS = flags.DEFINE_bool(\n", + " \"--preprocess_times\", True, \"Preprocess setup times and durations\"\n", + ")\n", "\n", "\n", - "#----------------------------------------------------------------------------\n", + "# ----------------------------------------------------------------------------\n", "# Intermediate solution printer\n", "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", " \"\"\"Print intermediate solutions.\"\"\"\n", @@ -109,8 +113,10 @@ "\n", " def on_solution_callback(self):\n", " \"\"\"Called after each new solution found.\"\"\"\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n", + " )\n", " self.__solution_count += 1\n", "\n", "\n", @@ -120,110 +126,343 @@ " parameters = _PARAMS.value\n", " output_proto_file = _OUTPUT_PROTO.value\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Data.\n", "\n", " job_durations = [\n", - " 2546, 8589, 5953, 3710, 3630, 3016, 4148, 8706, 1604, 5502, 9983, 6209,\n", - " 9920, 7860, 2176\n", + " 2546,\n", + " 8589,\n", + " 5953,\n", + " 3710,\n", + " 3630,\n", + " 3016,\n", + " 4148,\n", + " 8706,\n", + " 1604,\n", + " 5502,\n", + " 9983,\n", + " 6209,\n", + " 9920,\n", + " 7860,\n", + " 2176,\n", " ]\n", "\n", " setup_times = [\n", " [\n", - " 3559, 1638, 2000, 3676, 2741, 2439, 2406, 1526, 1600, 3356, 4324,\n", - " 1923, 3663, 4103, 2215\n", + " 3559,\n", + " 1638,\n", + " 2000,\n", + " 3676,\n", + " 2741,\n", + " 2439,\n", + " 2406,\n", + " 1526,\n", + " 1600,\n", + " 3356,\n", + " 4324,\n", + " 1923,\n", + " 3663,\n", + " 4103,\n", + " 2215,\n", " ],\n", " [\n", - " 1442, 3010, 1641, 4490, 2060, 2143, 3376, 3891, 3513, 2855, 2653,\n", - " 1471, 2257, 1186, 2354\n", + " 1442,\n", + " 3010,\n", + " 1641,\n", + " 4490,\n", + " 2060,\n", + " 2143,\n", + " 3376,\n", + " 3891,\n", + " 3513,\n", + " 2855,\n", + " 2653,\n", + " 1471,\n", + " 2257,\n", + " 1186,\n", + " 2354,\n", " ],\n", " [\n", - " 1728, 3583, 3243, 4080, 2191, 3644, 4023, 3510, 2135, 1346, 1410,\n", - " 3565, 3181, 1126, 4169\n", + " 1728,\n", + " 3583,\n", + " 3243,\n", + " 4080,\n", + " 2191,\n", + " 3644,\n", + " 4023,\n", + " 3510,\n", + " 2135,\n", + " 1346,\n", + " 1410,\n", + " 3565,\n", + " 3181,\n", + " 1126,\n", + " 4169,\n", " ],\n", " [\n", - " 1291, 1703, 3103, 4001, 1712, 1137, 3341, 3485, 2557, 2435, 1972,\n", - " 1986, 1522, 4734, 2520\n", + " 1291,\n", + " 1703,\n", + " 3103,\n", + " 4001,\n", + " 1712,\n", + " 1137,\n", + " 3341,\n", + " 3485,\n", + " 2557,\n", + " 2435,\n", + " 1972,\n", + " 1986,\n", + " 1522,\n", + " 4734,\n", + " 2520,\n", " ],\n", " [\n", - " 4134, 2200, 1502, 3995, 1277, 1808, 1020, 2078, 2999, 1605, 1697,\n", - " 2323, 2268, 2288, 4856\n", + " 4134,\n", + " 2200,\n", + " 1502,\n", + " 3995,\n", + " 1277,\n", + " 1808,\n", + " 1020,\n", + " 2078,\n", + " 2999,\n", + " 1605,\n", + " 1697,\n", + " 2323,\n", + " 2268,\n", + " 2288,\n", + " 4856,\n", " ],\n", " [\n", - " 4974, 2480, 2492, 4088, 2587, 4652, 1478, 3942, 1222, 3305, 1206,\n", - " 1024, 2605, 3080, 3516\n", + " 4974,\n", + " 2480,\n", + " 2492,\n", + " 4088,\n", + " 2587,\n", + " 4652,\n", + " 1478,\n", + " 3942,\n", + " 1222,\n", + " 3305,\n", + " 1206,\n", + " 1024,\n", + " 2605,\n", + " 3080,\n", + " 3516,\n", " ],\n", " [\n", - " 1903, 2584, 2104, 1609, 4745, 2691, 1539, 2544, 2499, 2074, 4793,\n", - " 1756, 2190, 1298, 2605\n", + " 1903,\n", + " 2584,\n", + " 2104,\n", + " 1609,\n", + " 4745,\n", + " 2691,\n", + " 1539,\n", + " 2544,\n", + " 2499,\n", + " 2074,\n", + " 4793,\n", + " 1756,\n", + " 2190,\n", + " 1298,\n", + " 2605,\n", " ],\n", " [\n", - " 1407, 2536, 2296, 1769, 1449, 3386, 3046, 1180, 4132, 4783, 3386,\n", - " 3429, 2450, 3376, 3719\n", + " 1407,\n", + " 2536,\n", + " 2296,\n", + " 1769,\n", + " 1449,\n", + " 3386,\n", + " 3046,\n", + " 1180,\n", + " 4132,\n", + " 4783,\n", + " 3386,\n", + " 3429,\n", + " 2450,\n", + " 3376,\n", + " 3719,\n", " ],\n", " [\n", - " 3026, 1637, 3628, 3096, 1498, 4947, 1912, 3703, 4107, 4730, 1805,\n", - " 2189, 1789, 1985, 3586\n", + " 3026,\n", + " 1637,\n", + " 3628,\n", + " 3096,\n", + " 1498,\n", + " 4947,\n", + " 1912,\n", + " 3703,\n", + " 4107,\n", + " 4730,\n", + " 1805,\n", + " 2189,\n", + " 1789,\n", + " 1985,\n", + " 3586,\n", " ],\n", " [\n", - " 3940, 1342, 1601, 2737, 1748, 3771, 4052, 1619, 2558, 3782, 4383,\n", - " 3451, 4904, 1108, 1750\n", + " 3940,\n", + " 1342,\n", + " 1601,\n", + " 2737,\n", + " 1748,\n", + " 3771,\n", + " 4052,\n", + " 1619,\n", + " 2558,\n", + " 3782,\n", + " 4383,\n", + " 3451,\n", + " 4904,\n", + " 1108,\n", + " 1750,\n", " ],\n", " [\n", - " 1348, 3162, 1507, 3936, 1453, 2953, 4182, 2968, 3134, 1042, 3175,\n", - " 2805, 4901, 1735, 1654\n", + " 1348,\n", + " 3162,\n", + " 1507,\n", + " 3936,\n", + " 1453,\n", + " 2953,\n", + " 4182,\n", + " 2968,\n", + " 3134,\n", + " 1042,\n", + " 3175,\n", + " 2805,\n", + " 4901,\n", + " 1735,\n", + " 1654,\n", " ],\n", " [\n", - " 1099, 1711, 1245, 1067, 4343, 3407, 1108, 1784, 4803, 2342, 3377,\n", - " 2037, 3563, 1621, 2840\n", + " 1099,\n", + " 1711,\n", + " 1245,\n", + " 1067,\n", + " 4343,\n", + " 3407,\n", + " 1108,\n", + " 1784,\n", + " 4803,\n", + " 2342,\n", + " 3377,\n", + " 2037,\n", + " 3563,\n", + " 1621,\n", + " 2840,\n", " ],\n", " [\n", - " 2573, 4222, 3164, 2563, 3231, 4731, 2395, 1033, 4795, 3288, 2335,\n", - " 4935, 4066, 1440, 4979\n", + " 2573,\n", + " 4222,\n", + " 3164,\n", + " 2563,\n", + " 3231,\n", + " 4731,\n", + " 2395,\n", + " 1033,\n", + " 4795,\n", + " 3288,\n", + " 2335,\n", + " 4935,\n", + " 4066,\n", + " 1440,\n", + " 4979,\n", " ],\n", " [\n", - " 3321, 1666, 3573, 2377, 4649, 4600, 1065, 2475, 3658, 3374, 1138,\n", - " 4367, 4728, 3032, 2198\n", + " 3321,\n", + " 1666,\n", + " 3573,\n", + " 2377,\n", + " 4649,\n", + " 4600,\n", + " 1065,\n", + " 2475,\n", + " 3658,\n", + " 3374,\n", + " 1138,\n", + " 4367,\n", + " 4728,\n", + " 3032,\n", + " 2198,\n", " ],\n", " [\n", - " 2986, 1180, 4095, 3132, 3987, 3880, 3526, 1460, 4885, 3827, 4945,\n", - " 4419, 3486, 3805, 3804\n", + " 2986,\n", + " 1180,\n", + " 4095,\n", + " 3132,\n", + " 3987,\n", + " 3880,\n", + " 3526,\n", + " 1460,\n", + " 4885,\n", + " 3827,\n", + " 4945,\n", + " 4419,\n", + " 3486,\n", + " 3805,\n", + " 3804,\n", " ],\n", " [\n", - " 4163, 3441, 1217, 2941, 1210, 3794, 1779, 1904, 4255, 4967, 4003,\n", - " 3873, 1002, 2055, 4295\n", + " 4163,\n", + " 3441,\n", + " 1217,\n", + " 2941,\n", + " 1210,\n", + " 3794,\n", + " 1779,\n", + " 1904,\n", + " 4255,\n", + " 4967,\n", + " 4003,\n", + " 3873,\n", + " 1002,\n", + " 2055,\n", + " 4295,\n", " ],\n", " ]\n", "\n", " due_dates = [\n", - " -1, -1, 28569, -1, 98104, 27644, 55274, 57364, -1, -1, 60875, 96637,\n", - " 77888, -1, -1\n", - " ]\n", - " release_dates = [\n", - " 0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0\n", + " -1,\n", + " -1,\n", + " 28569,\n", + " -1,\n", + " 98104,\n", + " 27644,\n", + " 55274,\n", + " 57364,\n", + " -1,\n", + " -1,\n", + " 60875,\n", + " 96637,\n", + " 77888,\n", + " -1,\n", + " -1,\n", " ]\n", + " release_dates = [0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0]\n", "\n", " precedences = [(0, 2), (1, 2)]\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Helper data.\n", " num_jobs = len(job_durations)\n", " all_jobs = range(num_jobs)\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Preprocess.\n", " if _PREPROCESS.value:\n", " for job_id in all_jobs:\n", " min_incoming_setup = min(\n", - " setup_times[j][job_id] for j in range(num_jobs + 1))\n", + " setup_times[j][job_id] for j in range(num_jobs + 1)\n", + " )\n", " if release_dates[job_id] != 0:\n", - " min_incoming_setup = min(min_incoming_setup,\n", - " release_dates[job_id])\n", + " min_incoming_setup = min(min_incoming_setup, release_dates[job_id])\n", " if min_incoming_setup == 0:\n", " continue\n", "\n", - " print('job %i has a min incoming setup of %i' %\n", - " (job_id, min_incoming_setup))\n", + " print(\n", + " \"job %i has a min incoming setup of %i\" % (job_id, min_incoming_setup)\n", + " )\n", " # We can transfer some setup times to the duration of the job.\n", " job_durations[job_id] += min_incoming_setup\n", " # Decrease corresponding incoming setup times.\n", @@ -233,36 +472,37 @@ " if release_dates[job_id] != 0:\n", " release_dates[job_id] -= min_incoming_setup\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Model.\n", " model = cp_model.CpModel()\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Compute a maximum makespan greedily.\n", " horizon = sum(job_durations) + sum(\n", - " max(setup_times[i][j]\n", - " for i in range(num_jobs + 1))\n", - " for j in range(num_jobs))\n", - " print('Greedy horizon =', horizon)\n", + " max(setup_times[i][j] for i in range(num_jobs + 1)) for j in range(num_jobs)\n", + " )\n", + " print(\"Greedy horizon =\", horizon)\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Global storage of variables.\n", " intervals = []\n", " starts = []\n", " ends = []\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Scan the jobs and create the relevant variables and intervals.\n", " for job_id in all_jobs:\n", " duration = job_durations[job_id]\n", " release_date = release_dates[job_id]\n", " due_date = due_dates[job_id] if due_dates[job_id] != -1 else horizon\n", - " print('job %2i: start = %5i, duration = %4i, end = %6i' %\n", - " (job_id, release_date, duration, due_date))\n", - " name_suffix = '_%i' % job_id\n", - " start = model.NewIntVar(release_date, due_date, 's' + name_suffix)\n", - " end = model.NewIntVar(release_date, due_date, 'e' + name_suffix)\n", - " interval = model.NewIntervalVar(start, duration, end, 'i' + name_suffix)\n", + " print(\n", + " \"job %2i: start = %5i, duration = %4i, end = %6i\"\n", + " % (job_id, release_date, duration, due_date)\n", + " )\n", + " name_suffix = \"_%i\" % job_id\n", + " start = model.NewIntVar(release_date, due_date, \"s\" + name_suffix)\n", + " end = model.NewIntVar(release_date, due_date, \"e\" + name_suffix)\n", + " interval = model.NewIntervalVar(start, duration, end, \"i\" + name_suffix)\n", " starts.append(start)\n", " ends.append(end)\n", " intervals.append(interval)\n", @@ -270,59 +510,61 @@ " # No overlap constraint.\n", " model.AddNoOverlap(intervals)\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Transition times using a circuit constraint.\n", " arcs = []\n", " for i in all_jobs:\n", " # Initial arc from the dummy node (0) to a task.\n", - " start_lit = model.NewBoolVar('')\n", - " arcs.append([0, i + 1, start_lit])\n", + " start_lit = model.NewBoolVar(\"\")\n", + " arcs.append((0, i + 1, start_lit))\n", " # If this task is the first, set to minimum starting time.\n", " min_start_time = max(release_dates[i], setup_times[0][i])\n", " model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit)\n", " # Final arc from an arc to the dummy node.\n", - " arcs.append([i + 1, 0, model.NewBoolVar('')])\n", + " arcs.append((i + 1, 0, model.NewBoolVar(\"\")))\n", "\n", " for j in all_jobs:\n", " if i == j:\n", " continue\n", "\n", - " lit = model.NewBoolVar('%i follows %i' % (j, i))\n", - " arcs.append([i + 1, j + 1, lit])\n", + " lit = model.NewBoolVar(\"%i follows %i\" % (j, i))\n", + " arcs.append((i + 1, j + 1, lit))\n", "\n", " # We add the reified precedence to link the literal with the times of the\n", " # two tasks.\n", " # If release_dates[j] == 0, we can strenghten this precedence into an\n", " # equality as we are minimizing the makespan.\n", " if release_dates[j] == 0:\n", - " model.Add(starts[j] == ends[i] +\n", - " setup_times[i + 1][j]).OnlyEnforceIf(lit)\n", + " model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n", + " lit\n", + " )\n", " else:\n", - " model.Add(starts[j] >= ends[i] +\n", - " setup_times[i + 1][j]).OnlyEnforceIf(lit)\n", + " model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n", + " lit\n", + " )\n", "\n", " model.AddCircuit(arcs)\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Precedences.\n", " for before, after in precedences:\n", - " print('job %i is after job %i' % (after, before))\n", + " print(\"job %i is after job %i\" % (after, before))\n", " model.Add(ends[before] <= starts[after])\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Objective.\n", - " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " makespan = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(makespan, ends)\n", " model.Minimize(makespan)\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Write problem to file.\n", " if output_proto_file:\n", - " print('Writing proto to %s' % output_proto_file)\n", - " with open(output_proto_file, 'w') as text_file:\n", + " print(\"Writing proto to %s\" % output_proto_file)\n", + " with open(output_proto_file, \"w\") as text_file:\n", " text_file.write(str(model))\n", "\n", - " #----------------------------------------------------------------------------\n", + " # ----------------------------------------------------------------------------\n", " # Solve.\n", " solver = cp_model.CpSolver()\n", " if parameters:\n", @@ -331,13 +573,14 @@ " solver.Solve(model, solution_printer)\n", " for job_id in all_jobs:\n", " print(\n", - " 'job %i starts at %i end ends at %i' %\n", - " (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id])))\n", + " \"job %i starts at %i end ends at %i\"\n", + " % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id]))\n", + " )\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " single_machine_scheduling()\n", "\n", "\n", diff --git a/examples/notebook/examples/spread_robots_sat.ipynb b/examples/notebook/examples/spread_robots_sat.ipynb index 0d72c04dde..e205d0e682 100644 --- a/examples/notebook/examples/spread_robots_sat.ipynb +++ b/examples/notebook/examples/spread_robots_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Maximize the minimum of pairwise distances between n robots in a square space.\n" ] }, @@ -87,14 +88,14 @@ "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "_NUM_ROBOTS = flags.DEFINE_integer('num_robots', 8,\n", - " 'Number of robots to place.')\n", - "_ROOM_SIZE = flags.DEFINE_integer('room_size', 20,\n", - " 'Size of the square room where robots are.')\n", + "_NUM_ROBOTS = flags.DEFINE_integer(\"num_robots\", 8, \"Number of robots to place.\")\n", + "_ROOM_SIZE = flags.DEFINE_integer(\n", + " \"room_size\", 20, \"Size of the square room where robots are.\"\n", + ")\n", "_PARAMS = flags.DEFINE_string(\n", - " 'params',\n", - " 'num_search_workers:16, max_time_in_seconds:20',\n", - " 'Sat solver parameters.',\n", + " \"params\",\n", + " \"num_search_workers:16, max_time_in_seconds:20\",\n", + " \"Sat solver parameters.\",\n", ")\n", "\n", "\n", @@ -103,8 +104,8 @@ " model = cp_model.CpModel()\n", "\n", " # Create the list of coordinates (x, y) for each robot.\n", - " x = [model.NewIntVar(1, room_size, f'x_{i}') for i in range(num_robots)]\n", - " y = [model.NewIntVar(1, room_size, f'y_{i}') for i in range(num_robots)]\n", + " x = [model.NewIntVar(1, room_size, f\"x_{i}\") for i in range(num_robots)]\n", + " y = [model.NewIntVar(1, room_size, f\"y_{i}\") for i in range(num_robots)]\n", "\n", " # The specification of the problem is to maximize the minimum euclidian\n", " # distance between any two robots. Unfortunately, the euclidian distance\n", @@ -123,30 +124,30 @@ " # forall i:\n", " # scaled_min_square_distance <= scaling * (x_diff_sq[i] + y_diff_sq[i])\n", " scaling = 1000\n", - " scaled_min_square_distance = model.NewIntVar(0, 2 * scaling * room_size**2,\n", - " 'scaled_min_square_distance')\n", + " scaled_min_square_distance = model.NewIntVar(\n", + " 0, 2 * scaling * room_size**2, \"scaled_min_square_distance\"\n", + " )\n", "\n", " # Build intermediate variables and get the list of squared distances on\n", " # each dimension.\n", " for i in range(num_robots - 1):\n", " for j in range(i + 1, num_robots):\n", " # Compute the distance on each dimension between robot i and robot j.\n", - " x_diff = model.NewIntVar(-room_size, room_size, f'x_diff{i}')\n", - " y_diff = model.NewIntVar(-room_size, room_size, f'y_diff{i}')\n", + " x_diff = model.NewIntVar(-room_size, room_size, f\"x_diff{i}\")\n", + " y_diff = model.NewIntVar(-room_size, room_size, f\"y_diff{i}\")\n", " model.Add(x_diff == x[i] - x[j])\n", " model.Add(y_diff == y[i] - y[j])\n", "\n", " # Compute the square of the previous differences.\n", - " x_diff_sq = model.NewIntVar(0, room_size**2, f'x_diff_sq{i}')\n", - " y_diff_sq = model.NewIntVar(0, room_size**2, f'y_diff_sq{i}')\n", + " x_diff_sq = model.NewIntVar(0, room_size**2, f\"x_diff_sq{i}\")\n", + " y_diff_sq = model.NewIntVar(0, room_size**2, f\"y_diff_sq{i}\")\n", " model.AddMultiplicationEquality(x_diff_sq, x_diff, x_diff)\n", " model.AddMultiplicationEquality(y_diff_sq, y_diff, y_diff)\n", "\n", " # We just need to be <= to the scaled square distance as we are\n", " # maximizing the min distance, which is equivalent as maximizing the min\n", " # square distance.\n", - " model.Add(scaled_min_square_distance <= scaling *\n", - " (x_diff_sq + y_diff_sq))\n", + " model.Add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq))\n", "\n", " # Naive symmetry breaking.\n", " for i in range(1, num_robots):\n", @@ -165,17 +166,19 @@ "\n", " # Prints the solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Spread {num_robots} with a min pairwise distance of'\n", - " f' {math.sqrt(solver.ObjectiveValue() / scaling)}')\n", + " print(\n", + " f\"Spread {num_robots} with a min pairwise distance of\"\n", + " f\" {math.sqrt(solver.ObjectiveValue() / scaling)}\"\n", + " )\n", " for i in range(num_robots):\n", - " print(f'robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}')\n", + " print(f\"robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", "\n", " spread_robots(_NUM_ROBOTS.value, _ROOM_SIZE.value, _PARAMS.value)\n", "\n", diff --git a/examples/notebook/examples/steel_mill_slab_sat.ipynb b/examples/notebook/examples/steel_mill_slab_sat.ipynb index 677a8f9263..d2d4391b99 100644 --- a/examples/notebook/examples/steel_mill_slab_sat.ipynb +++ b/examples/notebook/examples/steel_mill_slab_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves the Stell Mill Slab problem with 4 different techniques.\n", "\n" ] @@ -86,230 +87,94 @@ "import collections\n", "import time\n", "\n", + "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "_PROBLEM = flags.DEFINE_integer('problem', 2, 'Problem id to solve.')\n", + "\n", + "_PROBLEM = flags.DEFINE_integer(\"problem\", 2, \"Problem id to solve.\")\n", "_BREAK_SYMMETRIES = flags.DEFINE_boolean(\n", - " 'break_symmetries', True, 'Break symmetries between equivalent orders.')\n", + " \"break_symmetries\", True, \"Break symmetries between equivalent orders.\"\n", + ")\n", "_SOLVER = flags.DEFINE_string(\n", - " 'solver', 'sat_column', 'Method used to solve: sat, sat_table, sat_column.')\n", + " \"solver\", \"sat_column\", \"Method used to solve: sat, sat_table, sat_column.\"\n", + ")\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\",\n", + " \"max_time_in_seconds:20,num_workers:8,log_search_progress:true\",\n", + " \"CP-SAT parameters.\",\n", + ")\n", "\n", "\n", "def build_problem(problem_id):\n", " \"\"\"Build problem data.\"\"\"\n", + " capacities = None\n", + " num_colors = None\n", + " num_slabs = None\n", + " orders = None\n", + "\n", " if problem_id == 0:\n", " capacities = [\n", - " 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35,\n", - " 39, 42, 43, 44\n", + " # fmt:off\n", + " 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, 39, 42, 43, 44,\n", + " # fmt:on\n", " ]\n", " num_colors = 88\n", " num_slabs = 111\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15),\n", - " (22, 16),\n", - " (22, 17),\n", - " (22, 18),\n", - " (20, 19),\n", - " (22, 20),\n", - " (5, 21),\n", - " (4, 22),\n", - " (10, 23),\n", - " (26, 24),\n", - " (17, 25),\n", - " (20, 26),\n", - " (16, 27),\n", - " (10, 28),\n", - " (19, 29),\n", - " (10, 30),\n", - " (10, 31),\n", - " (23, 32),\n", - " (22, 33),\n", - " (26, 34),\n", - " (27, 35),\n", - " (22, 36),\n", - " (27, 37),\n", - " (22, 38),\n", - " (22, 39),\n", - " (13, 40),\n", - " (14, 41),\n", - " (16, 27),\n", - " (26, 34),\n", - " (26, 42),\n", - " (27, 35),\n", - " (22, 36),\n", - " (20, 43),\n", - " (26, 24),\n", - " (22, 44),\n", - " (13, 45),\n", - " (19, 46),\n", - " (20, 47),\n", - " (16, 48),\n", - " (15, 49),\n", - " (17, 50),\n", - " (10, 28),\n", - " (20, 51),\n", - " (5, 52),\n", - " (26, 24),\n", - " (19, 53),\n", - " (15, 54),\n", - " (10, 55),\n", - " (10, 56),\n", - " (13, 57),\n", - " (13, 58),\n", - " (13, 59),\n", - " (12, 60),\n", - " (12, 61),\n", - " (18, 62),\n", - " (10, 63),\n", - " (18, 64),\n", - " (16, 65),\n", - " (20, 66),\n", - " (12, 67),\n", - " (6, 68),\n", - " (6, 68),\n", - " (15, 69),\n", - " (15, 70),\n", - " (15, 70),\n", - " (21, 71),\n", - " (30, 72),\n", - " (30, 73),\n", - " (30, 74),\n", - " (30, 75),\n", - " (23, 76),\n", - " (15, 77),\n", - " (15, 78),\n", - " (27, 79),\n", - " (27, 80),\n", - " (27, 81),\n", - " (27, 82),\n", - " (27, 83),\n", - " (27, 84),\n", - " (27, 79),\n", - " (27, 85),\n", - " (27, 86),\n", - " (10, 87),\n", - " (3, 88)\n", + " orders = [ # (size, color)\n", + " # fmt:off\n", + " (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7),\n", + " (7, 4), (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11),\n", + " (4, 7), (7, 11), (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13),\n", + " (3, 6), (22, 14), (19, 15), (19, 15), (22, 16), (22, 17), (22, 18),\n", + " (20, 19), (22, 20), (5, 21), (4, 22), (10, 23), (26, 24), (17, 25),\n", + " (20, 26), (16, 27), (10, 28), (19, 29), (10, 30), (10, 31), (23, 32),\n", + " (22, 33), (26, 34), (27, 35), (22, 36), (27, 37), (22, 38), (22, 39),\n", + " (13, 40), (14, 41), (16, 27), (26, 34), (26, 42), (27, 35), (22, 36),\n", + " (20, 43), (26, 24), (22, 44), (13, 45), (19, 46), (20, 47), (16, 48),\n", + " (15, 49), (17, 50), (10, 28), (20, 51), (5, 52), (26, 24), (19, 53),\n", + " (15, 54), (10, 55), (10, 56), (13, 57), (13, 58), (13, 59), (12, 60),\n", + " (12, 61), (18, 62), (10, 63), (18, 64), (16, 65), (20, 66), (12, 67),\n", + " (6, 68), (6, 68), (15, 69), (15, 70), (15, 70), (21, 71), (30, 72),\n", + " (30, 73), (30, 74), (30, 75), (23, 76), (15, 77), (15, 78), (27, 79),\n", + " (27, 80), (27, 81), (27, 82), (27, 83), (27, 84), (27, 79), (27, 85),\n", + " (27, 86), (10, 87), (3, 88),\n", + " # fmt:on\n", " ]\n", " elif problem_id == 1:\n", " capacities = [0, 17, 44]\n", " num_colors = 23\n", " num_slabs = 30\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15),\n", - " (22, 16),\n", - " (22, 17),\n", - " (22, 18),\n", - " (20, 19),\n", - " (22, 20),\n", - " (5, 21),\n", - " (4, 22),\n", - " (10, 23)\n", + " orders = [ # (size, color)\n", + " # fmt:off\n", + " (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7), (7, 4),\n", + " (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11), (4, 7), (7, 11),\n", + " (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13), (3, 6), (22, 14),\n", + " (19, 15), (19, 15), (22, 16), (22, 17), (22, 18), (20, 19), (22, 20),\n", + " (5, 21), (4, 22), (10, 23),\n", + " # fmt:on\n", " ]\n", " elif problem_id == 2:\n", " capacities = [0, 17, 44]\n", " num_colors = 15\n", " num_slabs = 20\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15)\n", + " orders = [ # (size, color)\n", + " # fmt:off\n", + " (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7), (7, 4),\n", + " (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11), (4, 7), (7, 11),\n", + " (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13), (3, 6), (22, 14),\n", + " (19, 15), (19, 15),\n", + " # fmt:on\n", " ]\n", "\n", " elif problem_id == 3:\n", " capacities = [0, 17, 44]\n", " num_colors = 8\n", " num_slabs = 10\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6)\n", + " orders = [ # (size, color)\n", + " # fmt:off\n", + " (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7),\n", + " (7, 4), (7, 8), (3, 6),\n", + " # fmt:on\n", " ]\n", "\n", " return (num_slabs, capacities, num_colors, orders)\n", @@ -333,21 +198,29 @@ " \"\"\"Called on each new solution.\"\"\"\n", " current_time = time.time()\n", " objective = sum(self.Value(l) for l in self.__loss)\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, current_time - self.__start_time,\n", - " objective))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, current_time - self.__start_time, objective)\n", + " )\n", " self.__solution_count += 1\n", - " orders_in_slab = [[\n", - " o for o in self.__all_orders if self.Value(self.__assign[o][s])\n", - " ] for s in self.__all_slabs]\n", + " orders_in_slab = [\n", + " [o for o in self.__all_orders if self.Value(self.__assign[o][s])]\n", + " for s in self.__all_slabs\n", + " ]\n", " for s in self.__all_slabs:\n", " if orders_in_slab[s]:\n", - " line = ' - slab %i, load = %i, loss = %i, orders = [' % (\n", - " s, self.Value(self.__load[s]), self.Value(self.__loss[s]))\n", + " line = \" - slab %i, load = %i, loss = %i, orders = [\" % (\n", + " s,\n", + " self.Value(self.__load[s]),\n", + " self.Value(self.__loss[s]),\n", + " )\n", " for o in orders_in_slab[s]:\n", - " line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0],\n", - " self.__orders[o][1])\n", - " line += ']'\n", + " line += \"#%i(w%i, c%i) \" % (\n", + " o,\n", + " self.__orders[o][0],\n", + " self.__orders[o][1],\n", + " )\n", + " line += \"]\"\n", " print(line)\n", "\n", "\n", @@ -361,16 +234,17 @@ " all_slabs = range(num_slabs)\n", " all_colors = range(num_colors)\n", " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", + " print(\n", + " \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n", + " % (num_orders, num_slabs, num_capacities - 1)\n", + " )\n", "\n", " # Compute auxiliary data.\n", " widths = [x[0] for x in orders]\n", " colors = [x[1] for x in orders]\n", " max_capacity = max(capacities)\n", " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n", - " 1)\n", + " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", " ]\n", " max_loss = max(loss_array)\n", " orders_per_color = [\n", @@ -384,16 +258,15 @@ "\n", " # Create the model and the decision variables.\n", " model = cp_model.CpModel()\n", - " assign = [[\n", - " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", - " ] for o in all_orders]\n", - " loads = [\n", - " model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s)\n", + " assign = [\n", + " [model.NewBoolVar(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n", + " for o in all_orders\n", + " ]\n", + " loads = [model.NewIntVar(0, max_capacity, \"load_of_slab_%i\" % s) for s in all_slabs]\n", + " color_is_in_slab = [\n", + " [model.NewBoolVar(\"color_%i_in_slab_%i\" % (c + 1, s)) for c in all_colors]\n", " for s in all_slabs\n", " ]\n", - " color_is_in_slab = [[\n", - " model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s)) for c in all_colors\n", - " ] for s in all_slabs]\n", "\n", " # Compute load of all slabs.\n", " for s in all_slabs:\n", @@ -411,8 +284,7 @@ " for s in all_slabs:\n", " for o in orders_per_color[c]:\n", " model.AddImplication(assign[o][s], color_is_in_slab[s][c])\n", - " model.AddImplication(color_is_in_slab[s][c].Not(),\n", - " assign[o][s].Not())\n", + " model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not())\n", "\n", " # At most two colors per slab.\n", " for s in all_slabs:\n", @@ -447,35 +319,39 @@ " if w not in local_width_to_order:\n", " local_width_to_order[w] = []\n", " local_width_to_order[w].append(o)\n", - " for w, os in local_width_to_order.items():\n", + " for _, os in local_width_to_order.items():\n", " if len(os) > 1:\n", " for p in range(len(os) - 1):\n", " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - " for w, os in width_to_unique_color_order.items():\n", + " for _, os in width_to_unique_color_order.items():\n", " if len(os) > 1:\n", " for p in range(len(os) - 1):\n", " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", "\n", " # Create position variables if there are symmetries to be broken.\n", " if break_symmetries and ordered_equivalent_orders:\n", - " print(' - creating %i symmetry breaking constraints' %\n", - " len(ordered_equivalent_orders))\n", + " print(\n", + " \" - creating %i symmetry breaking constraints\"\n", + " % len(ordered_equivalent_orders)\n", + " )\n", " positions = {}\n", " for p in ordered_equivalent_orders:\n", " if p[0] not in positions:\n", - " positions[p[0]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[0])\n", + " positions[p[0]] = model.NewIntVar(\n", + " 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n", + " )\n", " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", " if p[1] not in positions:\n", - " positions[p[1]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[1])\n", + " positions[p[1]] = model.NewIntVar(\n", + " 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n", + " )\n", " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", " # Finally add the symmetry breaking constraint.\n", " model.Add(positions[p[0]] <= positions[p[1]])\n", "\n", " # Objective.\n", - " obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')\n", - " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", + " obj = model.NewIntVar(0, num_slabs * max_loss, \"obj\")\n", + " losses = [model.NewIntVar(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n", " for s in all_slabs:\n", " model.AddElement(loads[s], loss_array, losses[s])\n", " model.Add(obj == sum(losses))\n", @@ -483,17 +359,19 @@ "\n", " ### Solve model.\n", " solver = cp_model.CpSolver()\n", - " solver.parameters.num_search_workers = 8\n", + " if _PARAMS.value:\n", + " text_format.Parse(_PARAMS.value, solver.parameters)\n", " objective_printer = cp_model.ObjectiveSolutionPrinter()\n", " status = solver.Solve(model, objective_printer)\n", "\n", " ### Output the solution.\n", " if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n", " print(\n", - " 'Loss = %i, time = %f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " \"Loss = %i, time = %f s, %i conflicts\"\n", + " % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n", + " )\n", " else:\n", - " print('No solution')\n", + " print(\"No solution\")\n", "\n", "\n", "def collect_valid_slabs_dp(capacities, colors, widths, loss_array):\n", @@ -502,13 +380,11 @@ "\n", " max_capacity = max(capacities)\n", "\n", - " valid_assignment = collections.namedtuple('valid_assignment',\n", - " 'orders load colors')\n", + " valid_assignment = collections.namedtuple(\"valid_assignment\", \"orders load colors\")\n", " all_valid_assignments = [valid_assignment(orders=[], load=0, colors=[])]\n", "\n", - " for order_id in range(len(colors)):\n", + " for order_id, new_color in enumerate(colors):\n", " new_width = widths[order_id]\n", - " new_color = colors[order_id]\n", " new_assignments = []\n", " for assignment in all_valid_assignments:\n", " if assignment.load + new_width > max_capacity:\n", @@ -518,18 +394,21 @@ " new_colors.append(new_color)\n", " if len(new_colors) > 2:\n", " continue\n", - " new_assignment = valid_assignment(orders=assignment.orders +\n", - " [order_id],\n", - " load=assignment.load + new_width,\n", - " colors=new_colors)\n", + " new_assignment = valid_assignment(\n", + " orders=assignment.orders + [order_id],\n", + " load=assignment.load + new_width,\n", + " colors=new_colors,\n", + " )\n", " new_assignments.append(new_assignment)\n", " all_valid_assignments.extend(new_assignments)\n", "\n", - " print('%i assignments created in %.2f s' %\n", - " (len(all_valid_assignments), time.time() - start_time))\n", + " print(\n", + " \"%i assignments created in %.2f s\"\n", + " % (len(all_valid_assignments), time.time() - start_time)\n", + " )\n", " tuples = []\n", " for assignment in all_valid_assignments:\n", - " solution = [0 for _ in range(len(colors))]\n", + " solution = [0] * len(colors)\n", " for i in assignment.orders:\n", " solution[i] = 1\n", " solution.append(loss_array[assignment.load])\n", @@ -549,16 +428,17 @@ " all_slabs = range(num_slabs)\n", " all_colors = range(num_colors)\n", " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", + " print(\n", + " \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n", + " % (num_orders, num_slabs, num_capacities - 1)\n", + " )\n", "\n", " # Compute auxiliary data.\n", " widths = [x[0] for x in orders]\n", " colors = [x[1] for x in orders]\n", " max_capacity = max(capacities)\n", " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n", - " 1)\n", + " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", " ]\n", " max_loss = max(loss_array)\n", "\n", @@ -566,21 +446,23 @@ "\n", " # Create the model and the decision variables.\n", " model = cp_model.CpModel()\n", - " assign = [[\n", - " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", - " ] for o in all_orders]\n", - " loads = [model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs]\n", - " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", + " assign = [\n", + " [model.NewBoolVar(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n", + " for o in all_orders\n", + " ]\n", + " loads = [model.NewIntVar(0, max_capacity, \"load_%i\" % s) for s in all_slabs]\n", + " losses = [model.NewIntVar(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n", "\n", - " unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n", - " loss_array)\n", + " unsorted_valid_slabs = collect_valid_slabs_dp(\n", + " capacities, colors, widths, loss_array\n", + " )\n", " # Sort slab by descending load/loss. Remove duplicates.\n", - " valid_slabs = sorted(unsorted_valid_slabs,\n", - " key=lambda c: 1000 * c[-1] + c[-2])\n", + " valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", "\n", " for s in all_slabs:\n", - " model.AddAllowedAssignments([assign[o][s] for o in all_orders] +\n", - " [losses[s], loads[s]], valid_slabs)\n", + " model.AddAllowedAssignments(\n", + " [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs\n", + " )\n", "\n", " # Orders are assigned to one slab.\n", " for o in all_orders:\n", @@ -595,7 +477,7 @@ "\n", " # Collect equivalent orders.\n", " if break_symmetries:\n", - " print('Breaking symmetries')\n", + " print(\"Breaking symmetries\")\n", " width_to_unique_color_order = {}\n", " ordered_equivalent_orders = []\n", " orders_per_color = [\n", @@ -619,28 +501,32 @@ " if w not in local_width_to_order:\n", " local_width_to_order[w] = []\n", " local_width_to_order[w].append(o)\n", - " for w, os in local_width_to_order.items():\n", + " for _, os in local_width_to_order.items():\n", " if len(os) > 1:\n", " for p in range(len(os) - 1):\n", " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - " for w, os in width_to_unique_color_order.items():\n", + " for _, os in width_to_unique_color_order.items():\n", " if len(os) > 1:\n", " for p in range(len(os) - 1):\n", " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", "\n", " # Create position variables if there are symmetries to be broken.\n", " if ordered_equivalent_orders:\n", - " print(' - creating %i symmetry breaking constraints' %\n", - " len(ordered_equivalent_orders))\n", + " print(\n", + " \" - creating %i symmetry breaking constraints\"\n", + " % len(ordered_equivalent_orders)\n", + " )\n", " positions = {}\n", " for p in ordered_equivalent_orders:\n", " if p[0] not in positions:\n", " positions[p[0]] = model.NewIntVar(\n", - " 0, num_slabs - 1, 'position_of_slab_%i' % p[0])\n", + " 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n", + " )\n", " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", " if p[1] not in positions:\n", " positions[p[1]] = model.NewIntVar(\n", - " 0, num_slabs - 1, 'position_of_slab_%i' % p[1])\n", + " 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n", + " )\n", " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", " # Finally add the symmetry breaking constraint.\n", " model.Add(positions[p[0]] <= positions[p[1]])\n", @@ -648,22 +534,24 @@ " # Objective.\n", " model.Minimize(sum(losses))\n", "\n", - " print('Model created')\n", + " print(\"Model created\")\n", "\n", " ### Solve model.\n", " solver = cp_model.CpSolver()\n", - " solver.num_search_workers = 8\n", - " solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads,\n", - " losses)\n", + " if _PARAMS.value:\n", + " text_format.Parse(_PARAMS.value, solver.parameters)\n", + "\n", + " solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\n", " status = solver.Solve(model, solution_printer)\n", "\n", " ### Output the solution.\n", " if status == cp_model.OPTIMAL:\n", " print(\n", - " 'Loss = %i, time = %.2f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " \"Loss = %i, time = %.2f s, %i conflicts\"\n", + " % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n", + " )\n", " else:\n", - " print('No solution')\n", + " print(\"No solution\")\n", "\n", "\n", "def steel_mill_slab_with_column_generation(problem):\n", @@ -674,76 +562,76 @@ " num_orders = len(orders)\n", " num_capacities = len(capacities)\n", " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", + " print(\n", + " \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n", + " % (num_orders, num_slabs, num_capacities - 1)\n", + " )\n", "\n", " # Compute auxiliary data.\n", " widths = [x[0] for x in orders]\n", " colors = [x[1] for x in orders]\n", " max_capacity = max(capacities)\n", " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n", - " 1)\n", + " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", " ]\n", "\n", " ### Model problem.\n", "\n", " # Generate all valid slabs (columns)\n", - " unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n", - " loss_array)\n", + " unsorted_valid_slabs = collect_valid_slabs_dp(\n", + " capacities, colors, widths, loss_array\n", + " )\n", "\n", " # Sort slab by descending load/loss. Remove duplicates.\n", - " valid_slabs = sorted(unsorted_valid_slabs,\n", - " key=lambda c: 1000 * c[-1] + c[-2])\n", + " valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", " all_valid_slabs = range(len(valid_slabs))\n", "\n", " # create model and decision variables.\n", " model = cp_model.CpModel()\n", - " selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs]\n", + " selected = [model.NewBoolVar(\"selected_%i\" % i) for i in all_valid_slabs]\n", "\n", " for order_id in all_orders:\n", " model.Add(\n", - " sum(selected[i]\n", - " for i, slab in enumerate(valid_slabs)\n", - " if slab[order_id]) == 1)\n", + " sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id])\n", + " == 1\n", + " )\n", "\n", " # Redundant constraint (sum of loads == sum of widths).\n", " model.Add(\n", - " sum(selected[i] * valid_slabs[i][-1]\n", - " for i in all_valid_slabs) == sum(widths))\n", + " sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)\n", + " )\n", "\n", " # Objective.\n", - " model.Minimize(\n", - " sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", + " model.Minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", "\n", - " print('Model created')\n", + " print(\"Model created\")\n", "\n", " ### Solve model.\n", " solver = cp_model.CpSolver()\n", - " solver.parameters.num_search_workers = 8\n", - " solver.parameters.log_search_progress = True\n", + " if _PARAMS.value:\n", + " text_format.Parse(_PARAMS.value, solver.parameters)\n", " solution_printer = cp_model.ObjectiveSolutionPrinter()\n", " status = solver.Solve(model, solution_printer)\n", "\n", " ### Output the solution.\n", " if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n", " print(\n", - " 'Loss = %i, time = %.2f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " \"Loss = %i, time = %.2f s, %i conflicts\"\n", + " % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n", + " )\n", " else:\n", - " print('No solution')\n", + " print(\"No solution\")\n", "\n", "\n", "def main(_):\n", - " if _SOLVER.value == 'sat':\n", + " if _SOLVER.value == \"sat\":\n", " steel_mill_slab(_PROBLEM.value, _BREAK_SYMMETRIES.value)\n", - " elif _SOLVER.value == 'sat_table':\n", - " steel_mill_slab_with_valid_slabs(_PROBLEM.value,\n", - " _BREAK_SYMMETRIES.value)\n", - " elif _SOLVER.value == 'sat_column':\n", + " elif _SOLVER.value == \"sat_table\":\n", + " steel_mill_slab_with_valid_slabs(_PROBLEM.value, _BREAK_SYMMETRIES.value)\n", + " elif _SOLVER.value == \"sat_column\":\n", " steel_mill_slab_with_column_generation(_PROBLEM.value)\n", " else:\n", - " print(f'Unknown model {_SOLVER.value}')\n", + " print(f\"Unknown model {_SOLVER.value}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/sudoku_sat.ipynb b/examples/notebook/examples/sudoku_sat.ipynb index 2ed1f17253..aac0afad93 100644 --- a/examples/notebook/examples/sudoku_sat.ipynb +++ b/examples/notebook/examples/sudoku_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This model implements a sudoku solver.\n" ] }, @@ -95,16 +96,22 @@ " line = list(range(0, line_size))\n", " cell = list(range(0, cell_size))\n", "\n", - " initial_grid = [[0, 6, 0, 0, 5, 0, 0, 2, 0], [0, 0, 0, 3, 0, 0, 0, 9, 0],\n", - " [7, 0, 0, 6, 0, 0, 0, 1, 0], [0, 0, 6, 0, 3, 0, 4, 0, 0],\n", - " [0, 0, 4, 0, 7, 0, 1, 0, 0], [0, 0, 5, 0, 9, 0, 8, 0, 0],\n", - " [0, 4, 0, 0, 0, 1, 0, 0, 6], [0, 3, 0, 0, 0, 8, 0, 0, 0],\n", - " [0, 2, 0, 0, 4, 0, 0, 5, 0]]\n", + " initial_grid = [\n", + " [0, 6, 0, 0, 5, 0, 0, 2, 0],\n", + " [0, 0, 0, 3, 0, 0, 0, 9, 0],\n", + " [7, 0, 0, 6, 0, 0, 0, 1, 0],\n", + " [0, 0, 6, 0, 3, 0, 4, 0, 0],\n", + " [0, 0, 4, 0, 7, 0, 1, 0, 0],\n", + " [0, 0, 5, 0, 9, 0, 8, 0, 0],\n", + " [0, 4, 0, 0, 0, 1, 0, 0, 6],\n", + " [0, 3, 0, 0, 0, 8, 0, 0, 0],\n", + " [0, 2, 0, 0, 4, 0, 0, 5, 0],\n", + " ]\n", "\n", " grid = {}\n", " for i in line:\n", " for j in line:\n", - " grid[(i, j)] = model.NewIntVar(1, line_size, 'grid %i %i' % (i, j))\n", + " grid[(i, j)] = model.NewIntVar(1, line_size, \"grid %i %i\" % (i, j))\n", "\n", " # AllDifferent on rows.\n", " for i in line:\n", @@ -120,8 +127,7 @@ " one_cell = []\n", " for di in cell:\n", " for dj in cell:\n", - " one_cell.append(grid[(i * cell_size + di,\n", - " j * cell_size + dj)])\n", + " one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)])\n", "\n", " model.AddAllDifferent(one_cell)\n", "\n", diff --git a/examples/notebook/examples/task_allocation_sat.ipynb b/examples/notebook/examples/task_allocation_sat.ipynb index 986dfe664d..829f048a32 100644 --- a/examples/notebook/examples/task_allocation_sat.ipynb +++ b/examples/notebook/examples/task_allocation_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "CP-SAT model for task allocation problem.\n", "\n", "see http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc-\n", @@ -93,256 +94,210 @@ "def task_allocation_sat():\n", " \"\"\"Solves the task allocation problem.\"\"\"\n", " # Availability matrix.\n", - " available = [[\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0\n", + " available = [\n", + " # fmt:off\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", - " 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 1, 1, 1, 1, 1, 1, 1, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n", - " 1, 1, 1, 1, 1, 1, 1, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", - " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", - " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 1, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n", - " 1, 1, 1, 1, 1, 1, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 1, 1, 1, 1, 1, 1, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n", - " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n", - " 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n", - " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ],\n", - " [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,\n", - " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", - " ]]\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 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\n", + " ],\n", + " [\n", + " 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,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n", + " ],\n", + " # fmt:on\n", + " ]\n", "\n", " ntasks = len(available)\n", " nslots = len(available[0])\n", @@ -359,30 +314,31 @@ " assign = {}\n", " for task in all_tasks:\n", " for slot in all_slots:\n", - " assign[(task, slot)] = model.NewBoolVar('x[%i][%i]' % (task, slot))\n", - " count = model.NewIntVar(0, nslots, 'count')\n", - " slot_used = [model.NewBoolVar('slot_used[%i]' % s) for s in all_slots]\n", + " assign[(task, slot)] = model.NewBoolVar(\"x[%i][%i]\" % (task, slot))\n", + " count = model.NewIntVar(0, nslots, \"count\")\n", + " slot_used = [model.NewBoolVar(\"slot_used[%i]\" % s) for s in all_slots]\n", "\n", " for task in all_tasks:\n", " model.Add(\n", - " sum(assign[(task, slot)]\n", - " for slot in all_slots\n", - " if available[task][slot] == 1) == 1)\n", + " sum(\n", + " assign[(task, slot)] for slot in all_slots if available[task][slot] == 1\n", + " )\n", + " == 1\n", + " )\n", "\n", " for slot in all_slots:\n", " model.Add(\n", - " sum(assign[(task, slot)]\n", - " for task in all_tasks\n", - " if available[task][slot] == 1) <= capacity)\n", - " model.AddBoolOr([\n", - " assign[(task, slot)]\n", - " for task in all_tasks\n", - " if available[task][slot] == 1\n", - " ]).OnlyEnforceIf(slot_used[slot])\n", + " sum(\n", + " assign[(task, slot)] for task in all_tasks if available[task][slot] == 1\n", + " )\n", + " <= capacity\n", + " )\n", + " model.AddBoolOr(\n", + " [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1]\n", + " ).OnlyEnforceIf(slot_used[slot])\n", " for task in all_tasks:\n", " if available[task][slot] == 1:\n", - " model.AddImplication(slot_used[slot].Not(),\n", - " assign[(task, slot)].Not())\n", + " model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not())\n", " else:\n", " model.Add(assign[(task, slot)] == 0)\n", "\n", @@ -399,15 +355,15 @@ " solver.parameters.num_search_workers = 16\n", " status = solver.Solve(model)\n", "\n", - " print('Statistics')\n", - " print(' - status =', solver.StatusName(status))\n", - " print(' - optimal solution =', solver.ObjectiveValue())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(\"Statistics\")\n", + " print(\" - status =\", solver.StatusName(status))\n", + " print(\" - optimal solution =\", solver.ObjectiveValue())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " task_allocation_sat()\n", "\n", "\n", diff --git a/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb b/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb index d6946484e9..a79312e924 100644 --- a/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb +++ b/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Tasks and workers to group assignment to average sum(cost) / #workers.\n" ] }, @@ -94,8 +95,10 @@ " self.__solution_count = 0\n", "\n", " def on_solution_callback(self):\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n", + " )\n", " self.__solution_count += 1\n", "\n", "\n", @@ -118,13 +121,13 @@ " x = {}\n", " for i in all_workers:\n", " for j in all_groups:\n", - " x[i, j] = model.NewBoolVar('x[%i,%i]' % (i, j))\n", + " x[i, j] = model.NewBoolVar(\"x[%i,%i]\" % (i, j))\n", "\n", " ## y_kj is 1 if task k is assigned to group j\n", " y = {}\n", " for k in all_tasks:\n", " for j in all_groups:\n", - " y[k, j] = model.NewBoolVar('x[%i,%i]' % (k, j))\n", + " y[k, j] = model.NewBoolVar(\"x[%i,%i]\" % (k, j))\n", "\n", " # Constraints\n", "\n", @@ -143,13 +146,11 @@ " scaled_sum_of_costs_in_group = []\n", " scaling = 1000 # We introduce scaling to deal with floating point average.\n", " for j in all_groups:\n", - " n = model.NewIntVar(1, num_workers, 'num_workers_in_group_%i' % j)\n", + " n = model.NewIntVar(1, num_workers, \"num_workers_in_group_%i\" % j)\n", " model.Add(n == sum(x[i, j] for i in all_workers))\n", - " c = model.NewIntVar(0, sum_of_costs * scaling,\n", - " 'sum_of_costs_of_group_%i' % j)\n", + " c = model.NewIntVar(0, sum_of_costs * scaling, \"sum_of_costs_of_group_%i\" % j)\n", " model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks))\n", - " a = model.NewIntVar(0, sum_of_costs * scaling,\n", - " 'average_cost_of_group_%i' % j)\n", + " a = model.NewIntVar(0, sum_of_costs * scaling, \"average_cost_of_group_%i\" % j)\n", " model.AddDivisionEquality(a, c, n)\n", "\n", " averages.append(a)\n", @@ -160,7 +161,7 @@ " model.Add(sum(num_workers_in_group) == num_workers)\n", "\n", " # Objective.\n", - " obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj')\n", + " obj = model.NewIntVar(0, sum_of_costs * scaling, \"obj\")\n", " model.AddMaxEquality(obj, averages)\n", " model.Minimize(obj)\n", "\n", @@ -173,17 +174,18 @@ "\n", " if status == cp_model.OPTIMAL:\n", " for j in all_groups:\n", - " print('Group %i' % j)\n", + " print(\"Group %i\" % j)\n", " for i in all_workers:\n", " if solver.BooleanValue(x[i, j]):\n", - " print(' - worker %i' % i)\n", + " print(\" - worker %i\" % i)\n", " for k in all_tasks:\n", " if solver.BooleanValue(y[k, j]):\n", - " print(' - task %i with cost %i' % (k, task_cost[k]))\n", - " print(' - sum_of_costs = %i' %\n", - " (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling))\n", - " print(' - average cost = %f' %\n", - " (solver.Value(averages[j]) * 1.0 / scaling))\n", + " print(\" - task %i with cost %i\" % (k, task_cost[k]))\n", + " print(\n", + " \" - sum_of_costs = %i\"\n", + " % (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling)\n", + " )\n", + " print(\" - average cost = %f\" % (solver.Value(averages[j]) * 1.0 / scaling))\n", "\n", "\n", "tasks_and_workers_assignment_sat()\n", @@ -191,7 +193,7 @@ "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " tasks_and_workers_assignment_sat()\n", "\n", "\n", diff --git a/examples/notebook/examples/tsp_sat.ipynb b/examples/notebook/examples/tsp_sat.ipynb index dfcae4ed6a..74f5095d74 100644 --- a/examples/notebook/examples/tsp_sat.ipynb +++ b/examples/notebook/examples/tsp_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Simple travelling salesman problem between cities.\n" ] }, @@ -85,6 +86,7 @@ "from ortools.sat.python import cp_model\n", "\n", "DISTANCE_MATRIX = [\n", + " # fmt:off\n", " [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],\n", " [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],\n", " [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],\n", @@ -125,14 +127,15 @@ " [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],\n", " [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],\n", " [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],\n", - "] # yapf: disable\n", + " # fmt:on\n", + "]\n", "\n", "\n", "def main():\n", " \"\"\"Entry point of the program.\"\"\"\n", " num_nodes = len(DISTANCE_MATRIX)\n", " all_nodes = range(num_nodes)\n", - " print('Num nodes =', num_nodes)\n", + " print(\"Num nodes =\", num_nodes)\n", "\n", " # Model.\n", " model = cp_model.CpModel()\n", @@ -148,8 +151,8 @@ " if i == j:\n", " continue\n", "\n", - " lit = model.NewBoolVar('%i follows %i' % (j, i))\n", - " arcs.append([i, j, lit])\n", + " lit = model.NewBoolVar(\"%i follows %i\" % (j, i))\n", + " arcs.append((i, j, lit))\n", " arc_literals[i, j] = lit\n", "\n", " obj_vars.append(lit)\n", @@ -158,8 +161,7 @@ " model.AddCircuit(arcs)\n", "\n", " # Minimize weighted sum of arcs. Because this s\n", - " model.Minimize(\n", - " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", "\n", " # Solve and print out the solution.\n", " solver = cp_model.CpSolver()\n", @@ -171,7 +173,7 @@ " print(solver.ResponseStats())\n", "\n", " current_node = 0\n", - " str_route = '%i' % current_node\n", + " str_route = \"%i\" % current_node\n", " route_is_finished = False\n", " route_distance = 0\n", " while not route_is_finished:\n", @@ -179,15 +181,15 @@ " if i == current_node:\n", " continue\n", " if solver.BooleanValue(arc_literals[current_node, i]):\n", - " str_route += ' -> %i' % i\n", + " str_route += \" -> %i\" % i\n", " route_distance += DISTANCE_MATRIX[current_node][i]\n", " current_node = i\n", " if current_node == 0:\n", " route_is_finished = True\n", " break\n", "\n", - " print('Route:', str_route)\n", - " print('Travelled distance:', route_distance)\n", + " print(\"Route:\", str_route)\n", + " print(\"Travelled distance:\", route_distance)\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/examples/vendor_scheduling_sat.ipynb b/examples/notebook/examples/vendor_scheduling_sat.ipynb index c28f018447..4756aad908 100644 --- a/examples/notebook/examples/vendor_scheduling_sat.ipynb +++ b/examples/notebook/examples/vendor_scheduling_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves a simple shift scheduling problem.\n" ] }, @@ -89,8 +90,15 @@ "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", " \"\"\"Print intermediate solutions.\"\"\"\n", "\n", - " def __init__(self, num_vendors, num_hours, possible_schedules,\n", - " selected_schedules, hours_stat, min_vendors):\n", + " def __init__(\n", + " self,\n", + " num_vendors,\n", + " num_hours,\n", + " possible_schedules,\n", + " selected_schedules,\n", + " hours_stat,\n", + " min_vendors,\n", + " ):\n", " cp_model.CpSolverSolutionCallback.__init__(self)\n", " self.__solution_count = 0\n", " self.__num_vendors = num_vendors\n", @@ -103,17 +111,18 @@ " def on_solution_callback(self):\n", " \"\"\"Called at each new solution.\"\"\"\n", " self.__solution_count += 1\n", - " print('Solution %i: ', self.__solution_count)\n", - " print(' min vendors:', self.__min_vendors)\n", + " print(\"Solution %i: \", self.__solution_count)\n", + " print(\" min vendors:\", self.__min_vendors)\n", " for i in range(self.__num_vendors):\n", " print(\n", - " ' - vendor %i: ' % i, self.__possible_schedules[self.Value(\n", - " self.__selected_schedules[i])])\n", + " \" - vendor %i: \" % i,\n", + " self.__possible_schedules[self.Value(self.__selected_schedules[i])],\n", + " )\n", " print()\n", "\n", " for j in range(self.__num_hours):\n", - " print(' - # workers on day%2i: ' % j, end=' ')\n", - " print(self.Value(self.__hours_stat[j]), end=' ')\n", + " print(\" - # workers on day%2i: \" % j, end=\" \")\n", + " print(self.Value(self.__hours_stat[j]), end=\" \")\n", " print()\n", " print()\n", "\n", @@ -140,12 +149,14 @@ " # Last columns are :\n", " # index_of_the_schedule, sum of worked hours (per work type).\n", " # The index is useful for branching.\n", - " possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n", - " [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n", - " [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n", - " [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n", - " [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]\n", + " possible_schedules = [\n", + " [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n", + " [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n", + " [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n", + " [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n", + " [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0],\n", + " ]\n", "\n", " num_possible_schedules = len(possible_schedules)\n", " selected_schedules = []\n", @@ -165,11 +176,10 @@ " for v in all_vendors:\n", " tmp = []\n", " for h in all_hours:\n", - " x[v, h] = model.NewIntVar(0, num_work_types, 'x[%i,%i]' % (v, h))\n", + " x[v, h] = model.NewIntVar(0, num_work_types, \"x[%i,%i]\" % (v, h))\n", " tmp.append(x[v, h])\n", - " selected_schedule = model.NewIntVar(0, num_possible_schedules - 1,\n", - " 's[%i]' % v)\n", - " hours = model.NewIntVar(0, num_hours, 'h[%i]' % v)\n", + " selected_schedule = model.NewIntVar(0, num_possible_schedules - 1, \"s[%i]\" % v)\n", + " hours = model.NewIntVar(0, num_hours, \"h[%i]\" % v)\n", " selected_schedules.append(selected_schedule)\n", " vendors_stat.append(hours)\n", " tmp.append(selected_schedule)\n", @@ -181,7 +191,7 @@ " # Statistics and constraints for each hour\n", " #\n", " for h in all_hours:\n", - " workers = model.NewIntVar(0, 1000, 'workers[%i]' % h)\n", + " workers = model.NewIntVar(0, 1000, \"workers[%i]\" % h)\n", " model.Add(workers == sum(x[v, h] for v in all_vendors))\n", " hours_stat.append(workers)\n", " model.Add(workers * max_traffic_per_vendor >= traffic[h])\n", @@ -195,23 +205,27 @@ " # Solve model.\n", " solver = cp_model.CpSolver()\n", " solver.parameters.enumerate_all_solutions = True\n", - " solution_printer = SolutionPrinter(num_vendors, num_hours,\n", - " possible_schedules, selected_schedules,\n", - " hours_stat, min_vendors)\n", + " solution_printer = SolutionPrinter(\n", + " num_vendors,\n", + " num_hours,\n", + " possible_schedules,\n", + " selected_schedules,\n", + " hours_stat,\n", + " min_vendors,\n", + " )\n", " status = solver.Solve(model, solution_printer)\n", - " print('Status = %s' % solver.StatusName(status))\n", + " print(\"Status = %s\" % solver.StatusName(status))\n", "\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", - " print(' - number of solutions found: %i' %\n", - " solution_printer.solution_count())\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", + " print(\" - number of solutions found: %i\" % solution_printer.solution_count())\n", "\n", "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", " vendor_scheduling_sat()\n", "\n", "\n", diff --git a/examples/notebook/examples/wedding_optimal_chart_sat.ipynb b/examples/notebook/examples/wedding_optimal_chart_sat.ipynb index fa4db04dd9..beb62e3d07 100644 --- a/examples/notebook/examples/wedding_optimal_chart_sat.ipynb +++ b/examples/notebook/examples/wedding_optimal_chart_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Finding an optimal wedding seating chart.\n", "\n", "From\n", @@ -124,9 +125,10 @@ " def on_solution_callback(self):\n", " current_time = time.time()\n", " objective = self.ObjectiveValue()\n", - " print(\"Solution %i, time = %f s, objective = %i\" %\n", - " (self.__solution_count, current_time - self.__start_time,\n", - " objective))\n", + " print(\n", + " \"Solution %i, time = %f s, objective = %i\"\n", + " % (self.__solution_count, current_time - self.__start_time, objective)\n", + " )\n", " self.__solution_count += 1\n", "\n", " for t in range(self.__num_tables):\n", @@ -153,38 +155,52 @@ "\n", " # Connection matrix: who knows who, and how strong\n", " # is the relation\n", - " connections = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]\n", + " connections = [\n", + " [1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " ]\n", "\n", " # Names of the guests. B: Bride side, G: Groom side\n", " names = [\n", - " \"Deb (B)\", \"John (B)\", \"Martha (B)\", \"Travis (B)\", \"Allan (B)\",\n", - " \"Lois (B)\", \"Jayne (B)\", \"Brad (B)\", \"Abby (B)\", \"Mary Helen (G)\",\n", - " \"Lee (G)\", \"Annika (G)\", \"Carl (G)\", \"Colin (G)\", \"Shirley (G)\",\n", - " \"DeAnn (G)\", \"Lori (G)\"\n", + " \"Deb (B)\",\n", + " \"John (B)\",\n", + " \"Martha (B)\",\n", + " \"Travis (B)\",\n", + " \"Allan (B)\",\n", + " \"Lois (B)\",\n", + " \"Jayne (B)\",\n", + " \"Brad (B)\",\n", + " \"Abby (B)\",\n", + " \"Mary Helen (G)\",\n", + " \"Lee (G)\",\n", + " \"Annika (G)\",\n", + " \"Carl (G)\",\n", + " \"Colin (G)\",\n", + " \"Shirley (G)\",\n", + " \"DeAnn (G)\",\n", + " \"Lori (G)\",\n", " ]\n", " return num_tables, table_capacity, min_known_neighbors, connections, names\n", "\n", "\n", "def solve_with_discrete_model():\n", " \"\"\"Discrete approach.\"\"\"\n", - " num_tables, table_capacity, min_known_neighbors, connections, names = build_data(\n", - " )\n", + " num_tables, table_capacity, min_known_neighbors, connections, names = build_data()\n", "\n", " num_guests = len(connections)\n", "\n", @@ -200,28 +216,32 @@ " seats = {}\n", " for t in all_tables:\n", " for g in all_guests:\n", - " seats[(t,\n", - " g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g, t))\n", + " seats[(t, g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g, t))\n", "\n", " colocated = {}\n", " for g1 in range(num_guests - 1):\n", " for g2 in range(g1 + 1, num_guests):\n", " colocated[(g1, g2)] = model.NewBoolVar(\n", - " \"guest %i seats with guest %i\" % (g1, g2))\n", + " \"guest %i seats with guest %i\" % (g1, g2)\n", + " )\n", "\n", " same_table = {}\n", " for g1 in range(num_guests - 1):\n", " for g2 in range(g1 + 1, num_guests):\n", " for t in all_tables:\n", " same_table[(g1, g2, t)] = model.NewBoolVar(\n", - " \"guest %i seats with guest %i on table %i\" % (g1, g2, t))\n", + " \"guest %i seats with guest %i on table %i\" % (g1, g2, t)\n", + " )\n", "\n", " # Objective\n", " model.Maximize(\n", - " sum(connections[g1][g2] * colocated[g1, g2]\n", + " sum(\n", + " connections[g1][g2] * colocated[g1, g2]\n", " for g1 in range(num_guests - 1)\n", " for g2 in range(g1 + 1, num_guests)\n", - " if connections[g1][g2] > 0))\n", + " if connections[g1][g2] > 0\n", + " )\n", + " )\n", "\n", " #\n", " # Constraints\n", @@ -240,29 +260,38 @@ " for g2 in range(g1 + 1, num_guests):\n", " for t in all_tables:\n", " # Link same_table and seats.\n", - " model.AddBoolOr([\n", - " seats[(t, g1)].Not(), seats[(t, g2)].Not(),\n", - " same_table[(g1, g2, t)]\n", - " ])\n", + " model.AddBoolOr(\n", + " [\n", + " seats[(t, g1)].Not(),\n", + " seats[(t, g2)].Not(),\n", + " same_table[(g1, g2, t)],\n", + " ]\n", + " )\n", " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)])\n", " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)])\n", "\n", " # Link colocated and same_table.\n", " model.Add(\n", - " sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(\n", - " g1, g2)])\n", + " sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1, g2)]\n", + " )\n", "\n", " # Min known neighbors rule.\n", " for g in all_guests:\n", " model.Add(\n", - " sum(same_table[(g, g2, t)]\n", + " sum(\n", + " same_table[(g, g2, t)]\n", " for g2 in range(g + 1, num_guests)\n", " for t in all_tables\n", - " if connections[g][g2] > 0) +\n", - " sum(same_table[(g1, g, t)]\n", + " if connections[g][g2] > 0\n", + " )\n", + " + sum(\n", + " same_table[(g1, g, t)]\n", " for g1 in range(g)\n", " for t in all_tables\n", - " if connections[g1][g] > 0) >= min_known_neighbors)\n", + " if connections[g1][g] > 0\n", + " )\n", + " >= min_known_neighbors\n", + " )\n", "\n", " # Symmetry breaking. First guest seats on the first table.\n", " model.Add(seats[(0, 0)] == 1)\n", diff --git a/examples/notebook/examples/weighted_latency_problem_sat.ipynb b/examples/notebook/examples/weighted_latency_problem_sat.ipynb index 0c131d2712..dfb20664d1 100644 --- a/examples/notebook/examples/weighted_latency_problem_sat.ipynb +++ b/examples/notebook/examples/weighted_latency_problem_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solve a random Weighted Latency problem with the CP-SAT solver.\n" ] }, @@ -87,16 +88,16 @@ "from google.protobuf import text_format\n", "from ortools.sat.python import cp_model\n", "\n", - "_NUM_NODES = flags.DEFINE_integer('num_nodes', 12, 'Number of nodes to visit.')\n", - "_GRID_SIZE = flags.DEFINE_integer('grid_size', 20,\n", - " 'Size of the grid where nodes are.')\n", - "_PROFIT_RANGE = flags.DEFINE_integer('profit_range', 50, 'Range of profit.')\n", - "_SEED = flags.DEFINE_integer('seed', 0, 'Random seed.')\n", - "_PARAMS = flags.DEFINE_string('params',\n", - " 'num_search_workers:16, max_time_in_seconds:5',\n", - " 'Sat solver parameters.')\n", + "_NUM_NODES = flags.DEFINE_integer(\"num_nodes\", 12, \"Number of nodes to visit.\")\n", + "_GRID_SIZE = flags.DEFINE_integer(\"grid_size\", 20, \"Size of the grid where nodes are.\")\n", + "_PROFIT_RANGE = flags.DEFINE_integer(\"profit_range\", 50, \"Range of profit.\")\n", + "_SEED = flags.DEFINE_integer(\"seed\", 0, \"Random seed.\")\n", + "_PARAMS = flags.DEFINE_string(\n", + " \"params\", \"num_search_workers:16, max_time_in_seconds:5\", \"Sat solver parameters.\"\n", + ")\n", "_PROTO_FILE = flags.DEFINE_string(\n", - " 'proto_file', '', 'If not empty, output the proto to this file.')\n", + " \"proto_file\", \"\", \"If not empty, output the proto to this file.\"\n", + ")\n", "\n", "\n", "def build_model():\n", @@ -126,10 +127,7 @@ "\n", " # because of the manhattan distance, the sum of distances is bounded by this.\n", " horizon = _GRID_SIZE.value * 2 * _NUM_NODES.value\n", - " times = [\n", - " model.NewIntVar(0, horizon, f'x_{i}')\n", - " for i in range(_NUM_NODES.value + 1)\n", - " ]\n", + " times = [model.NewIntVar(0, horizon, f\"x_{i}\") for i in range(_NUM_NODES.value + 1)]\n", "\n", " # Node 0 is the start node.\n", " model.Add(times[0] == 0)\n", @@ -142,7 +140,7 @@ " continue\n", " # We use a manhattan distance between nodes.\n", " distance = abs(x[i] - x[j]) + abs(y[i] - y[j])\n", - " lit = model.NewBoolVar(f'{i}_to_{j}')\n", + " lit = model.NewBoolVar(f\"{i}_to_{j}\")\n", " arcs.append((i, j, lit))\n", "\n", " # Add transitions between nodes.\n", @@ -169,7 +167,7 @@ "\n", "def main(argv: Sequence[str]) -> None:\n", " if len(argv) > 1:\n", - " raise app.UsageError('Too many command-line arguments.')\n", + " raise app.UsageError(\"Too many command-line arguments.\")\n", "\n", " x, y, profits = build_model()\n", " solve_with_cp_sat(x, y, profits)\n", diff --git a/examples/notebook/examples/zebra_sat.ipynb b/examples/notebook/examples/zebra_sat.ipynb index 16a3460c27..e727d0f2a6 100644 --- a/examples/notebook/examples/zebra_sat.ipynb +++ b/examples/notebook/examples/zebra_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "This is the zebra problem as invented by Lewis Caroll.\n", "\n", "There are five houses.\n", @@ -112,42 +113,41 @@ " # Create the model.\n", " model = cp_model.CpModel()\n", "\n", - " red = model.NewIntVar(1, 5, 'red')\n", - " green = model.NewIntVar(1, 5, 'green')\n", - " yellow = model.NewIntVar(1, 5, 'yellow')\n", - " blue = model.NewIntVar(1, 5, 'blue')\n", - " ivory = model.NewIntVar(1, 5, 'ivory')\n", + " red = model.NewIntVar(1, 5, \"red\")\n", + " green = model.NewIntVar(1, 5, \"green\")\n", + " yellow = model.NewIntVar(1, 5, \"yellow\")\n", + " blue = model.NewIntVar(1, 5, \"blue\")\n", + " ivory = model.NewIntVar(1, 5, \"ivory\")\n", "\n", - " englishman = model.NewIntVar(1, 5, 'englishman')\n", - " spaniard = model.NewIntVar(1, 5, 'spaniard')\n", - " japanese = model.NewIntVar(1, 5, 'japanese')\n", - " ukrainian = model.NewIntVar(1, 5, 'ukrainian')\n", - " norwegian = model.NewIntVar(1, 5, 'norwegian')\n", + " englishman = model.NewIntVar(1, 5, \"englishman\")\n", + " spaniard = model.NewIntVar(1, 5, \"spaniard\")\n", + " japanese = model.NewIntVar(1, 5, \"japanese\")\n", + " ukrainian = model.NewIntVar(1, 5, \"ukrainian\")\n", + " norwegian = model.NewIntVar(1, 5, \"norwegian\")\n", "\n", - " dog = model.NewIntVar(1, 5, 'dog')\n", - " snails = model.NewIntVar(1, 5, 'snails')\n", - " fox = model.NewIntVar(1, 5, 'fox')\n", - " zebra = model.NewIntVar(1, 5, 'zebra')\n", - " horse = model.NewIntVar(1, 5, 'horse')\n", + " dog = model.NewIntVar(1, 5, \"dog\")\n", + " snails = model.NewIntVar(1, 5, \"snails\")\n", + " fox = model.NewIntVar(1, 5, \"fox\")\n", + " zebra = model.NewIntVar(1, 5, \"zebra\")\n", + " horse = model.NewIntVar(1, 5, \"horse\")\n", "\n", - " tea = model.NewIntVar(1, 5, 'tea')\n", - " coffee = model.NewIntVar(1, 5, 'coffee')\n", - " water = model.NewIntVar(1, 5, 'water')\n", - " milk = model.NewIntVar(1, 5, 'milk')\n", - " fruit_juice = model.NewIntVar(1, 5, 'fruit juice')\n", + " tea = model.NewIntVar(1, 5, \"tea\")\n", + " coffee = model.NewIntVar(1, 5, \"coffee\")\n", + " water = model.NewIntVar(1, 5, \"water\")\n", + " milk = model.NewIntVar(1, 5, \"milk\")\n", + " fruit_juice = model.NewIntVar(1, 5, \"fruit juice\")\n", "\n", - " old_gold = model.NewIntVar(1, 5, 'old gold')\n", - " kools = model.NewIntVar(1, 5, 'kools')\n", - " chesterfields = model.NewIntVar(1, 5, 'chesterfields')\n", - " lucky_strike = model.NewIntVar(1, 5, 'lucky strike')\n", - " parliaments = model.NewIntVar(1, 5, 'parliaments')\n", + " old_gold = model.NewIntVar(1, 5, \"old gold\")\n", + " kools = model.NewIntVar(1, 5, \"kools\")\n", + " chesterfields = model.NewIntVar(1, 5, \"chesterfields\")\n", + " lucky_strike = model.NewIntVar(1, 5, \"lucky strike\")\n", + " parliaments = model.NewIntVar(1, 5, \"parliaments\")\n", "\n", " model.AddAllDifferent(red, green, yellow, blue, ivory)\n", " model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian)\n", " model.AddAllDifferent(dog, snails, fox, zebra, horse)\n", " model.AddAllDifferent(tea, coffee, water, milk, fruit_juice)\n", - " model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike,\n", - " old_gold)\n", + " model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold)\n", "\n", " model.Add(englishman == red)\n", " model.Add(spaniard == dog)\n", @@ -159,18 +159,18 @@ " model.Add(milk == 3)\n", " model.Add(norwegian == 1)\n", "\n", - " diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields')\n", + " diff_fox_chesterfields = model.NewIntVar(-4, 4, \"diff_fox_chesterfields\")\n", " model.Add(diff_fox_chesterfields == fox - chesterfields)\n", " model.AddAbsEquality(1, diff_fox_chesterfields)\n", "\n", - " diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools')\n", + " diff_horse_kools = model.NewIntVar(-4, 4, \"diff_horse_kools\")\n", " model.Add(diff_horse_kools == horse - kools)\n", " model.AddAbsEquality(1, diff_horse_kools)\n", "\n", " model.Add(lucky_strike == fruit_juice)\n", " model.Add(japanese == parliaments)\n", "\n", - " diff_norwegian_blue = model.NewIntVar(-4, 4, 'diff_norwegian_blue')\n", + " diff_norwegian_blue = model.NewIntVar(-4, 4, \"diff_norwegian_blue\")\n", " model.Add(diff_norwegian_blue == norwegian - blue)\n", " model.AddAbsEquality(1, diff_norwegian_blue)\n", "\n", @@ -180,16 +180,12 @@ "\n", " if status == cp_model.OPTIMAL:\n", " people = [englishman, spaniard, japanese, ukrainian, norwegian]\n", - " water_drinker = [\n", - " p for p in people if solver.Value(p) == solver.Value(water)\n", - " ][0]\n", - " zebra_owner = [\n", - " p for p in people if solver.Value(p) == solver.Value(zebra)\n", - " ][0]\n", - " print('The', water_drinker.Name(), 'drinks water.')\n", - " print('The', zebra_owner.Name(), 'owns the zebra.')\n", + " water_drinker = [p for p in people if solver.Value(p) == solver.Value(water)][0]\n", + " zebra_owner = [p for p in people if solver.Value(p) == solver.Value(zebra)][0]\n", + " print(\"The\", water_drinker.Name(), \"drinks water.\")\n", + " print(\"The\", zebra_owner.Name(), \"owns the zebra.\")\n", " else:\n", - " print('No solutions to the zebra problem, this is unusual!')\n", + " print(\"No solutions to the zebra problem, this is unusual!\")\n", "\n", "\n", "solve_zebra()\n", diff --git a/examples/notebook/graph/assignment_linear_sum_assignment.ipynb b/examples/notebook/graph/assignment_linear_sum_assignment.ipynb index 71c3ec7362..65308d8038 100644 --- a/examples/notebook/graph/assignment_linear_sum_assignment.ipynb +++ b/examples/notebook/graph/assignment_linear_sum_assignment.ipynb @@ -92,17 +92,20 @@ " \"\"\"Linear Sum Assignment example.\"\"\"\n", " assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n", "\n", - " costs = np.array([\n", - " [90, 76, 75, 70],\n", - " [35, 85, 55, 65],\n", - " [125, 95, 90, 105],\n", - " [45, 110, 95, 115],\n", - " ])\n", + " costs = np.array(\n", + " [\n", + " [90, 76, 75, 70],\n", + " [35, 85, 55, 65],\n", + " [125, 95, 90, 105],\n", + " [45, 110, 95, 115],\n", + " ]\n", + " )\n", "\n", " # Let's transform this into 3 parallel vectors (start_nodes, end_nodes,\n", " # arc_costs)\n", " end_nodes_unraveled, start_nodes_unraveled = np.meshgrid(\n", - " np.arange(costs.shape[1]), np.arange(costs.shape[0]))\n", + " np.arange(costs.shape[1]), np.arange(costs.shape[0])\n", + " )\n", " start_nodes = start_nodes_unraveled.ravel()\n", " end_nodes = end_nodes_unraveled.ravel()\n", " arc_costs = costs.ravel()\n", @@ -112,15 +115,16 @@ " status = assignment.solve()\n", "\n", " if status == assignment.OPTIMAL:\n", - " print(f'Total cost = {assignment.optimal_cost()}\\n')\n", + " print(f\"Total cost = {assignment.optimal_cost()}\\n\")\n", " for i in range(0, assignment.num_nodes()):\n", - " print(f'Worker {i} assigned to task {assignment.right_mate(i)}.' +\n", - " f' Cost = {assignment.assignment_cost(i)}')\n", + " print(\n", + " f\"Worker {i} assigned to task {assignment.right_mate(i)}.\"\n", + " + f\" Cost = {assignment.assignment_cost(i)}\"\n", + " )\n", " elif status == assignment.INFEASIBLE:\n", - " print('No assignment is possible.')\n", + " print(\"No assignment is possible.\")\n", " elif status == assignment.POSSIBLE_OVERFLOW:\n", - " print(\n", - " 'Some input costs are too large and may cause an integer overflow.')\n", + " print(\"Some input costs are too large and may cause an integer overflow.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/graph/assignment_min_flow.ipynb b/examples/notebook/graph/assignment_min_flow.ipynb index 337a44f023..31d69648b9 100644 --- a/examples/notebook/graph/assignment_min_flow.ipynb +++ b/examples/notebook/graph/assignment_min_flow.ipynb @@ -92,18 +92,20 @@ " smcf = min_cost_flow.SimpleMinCostFlow()\n", "\n", " # Define the directed graph for the flow.\n", - " start_nodes = [0, 0, 0, 0] + [\n", - " 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4\n", - " ] + [5, 6, 7, 8]\n", - " end_nodes = [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8\n", - " ] + [9, 9, 9, 9]\n", - " capacities = [1, 1, 1, 1] + [\n", - " 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ] + [1, 1, 1, 1]\n", + " start_nodes = (\n", + " [0, 0, 0, 0] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + [5, 6, 7, 8]\n", + " )\n", + " end_nodes = (\n", + " [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8] + [9, 9, 9, 9]\n", + " )\n", + " capacities = (\n", + " [1, 1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1]\n", + " )\n", " costs = (\n", - " [0, 0, 0, 0] +\n", - " [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115] +\n", - " [0, 0, 0, 0])\n", + " [0, 0, 0, 0]\n", + " + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115]\n", + " + [0, 0, 0, 0]\n", + " )\n", "\n", " source = 0\n", " sink = 9\n", @@ -112,8 +114,9 @@ "\n", " # Add each arc.\n", " for i in range(len(start_nodes)):\n", - " smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],\n", - " capacities[i], costs[i])\n", + " smcf.add_arc_with_capacity_and_unit_cost(\n", + " start_nodes[i], end_nodes[i], capacities[i], costs[i]\n", + " )\n", " # Add node supplies.\n", " for i in range(len(supplies)):\n", " smcf.set_node_supply(i, supplies[i])\n", @@ -122,20 +125,21 @@ " status = smcf.solve()\n", "\n", " if status == smcf.OPTIMAL:\n", - " print('Total cost = ', smcf.optimal_cost())\n", + " print(\"Total cost = \", smcf.optimal_cost())\n", " print()\n", " for arc in range(smcf.num_arcs()):\n", " # Can ignore arcs leading out of source or into sink.\n", " if smcf.tail(arc) != source and smcf.head(arc) != sink:\n", - "\n", " # Arcs in the solution have a flow value of 1. Their start and end nodes\n", " # give an assignment of worker to task.\n", " if smcf.flow(arc) > 0:\n", - " print('Worker %d assigned to task %d. Cost = %d' %\n", - " (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))\n", + " print(\n", + " \"Worker %d assigned to task %d. Cost = %d\"\n", + " % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc))\n", + " )\n", " else:\n", - " print('There was an issue with the min cost flow input.')\n", - " print(f'Status: {status}')\n", + " print(\"There was an issue with the min cost flow input.\")\n", + " print(f\"Status: {status}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/graph/balance_min_flow.ipynb b/examples/notebook/graph/balance_min_flow.ipynb index 8ef3b2fbef..6905a62cfd 100644 --- a/examples/notebook/graph/balance_min_flow.ipynb +++ b/examples/notebook/graph/balance_min_flow.ipynb @@ -94,20 +94,42 @@ " team_a = [1, 3, 5]\n", " team_b = [2, 4, 6]\n", "\n", - " start_nodes = ([0, 0] + [11, 11, 11] + [12, 12, 12] + [\n", - " 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6\n", - " ] + [7, 8, 9, 10])\n", - " end_nodes = ([11, 12] + team_a + team_b + [\n", - " 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8,\n", - " 9, 10\n", - " ] + [13, 13, 13, 13])\n", - " capacities = ([2, 2] + [1, 1, 1] + [1, 1, 1] + [\n", - " 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ] + [1, 1, 1, 1])\n", - " costs = ([0, 0] + [0, 0, 0] + [0, 0, 0] + [\n", - " 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60,\n", - " 105, 80, 75, 45, 65, 110, 95\n", - " ] + [0, 0, 0, 0])\n", + " start_nodes = (\n", + " # fmt: off\n", + " [0, 0]\n", + " + [11, 11, 11]\n", + " + [12, 12, 12]\n", + " + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]\n", + " + [7, 8, 9, 10]\n", + " # fmt: on\n", + " )\n", + " end_nodes = (\n", + " # fmt: off\n", + " [11, 12]\n", + " + team_a\n", + " + team_b\n", + " + [7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10]\n", + " + [13, 13, 13, 13]\n", + " # fmt: on\n", + " )\n", + " capacities = (\n", + " # fmt: off\n", + " [2, 2]\n", + " + [1, 1, 1]\n", + " + [1, 1, 1]\n", + " + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", + " + [1, 1, 1, 1]\n", + " # fmt: on\n", + " )\n", + " costs = (\n", + " # fmt: off\n", + " [0, 0]\n", + " + [0, 0, 0]\n", + " + [0, 0, 0]\n", + " + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95]\n", + " + [0, 0, 0, 0]\n", + " # fmt: on\n", + " )\n", "\n", " source = 0\n", " sink = 13\n", @@ -117,8 +139,9 @@ "\n", " # Add each arc.\n", " for i in range(0, len(start_nodes)):\n", - " smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],\n", - " capacities[i], costs[i])\n", + " smcf.add_arc_with_capacity_and_unit_cost(\n", + " start_nodes[i], end_nodes[i], capacities[i], costs[i]\n", + " )\n", "\n", " # Add node supplies.\n", " for i in range(0, len(supplies)):\n", @@ -128,21 +151,26 @@ " status = smcf.solve()\n", "\n", " if status == smcf.OPTIMAL:\n", - " print('Total cost = ', smcf.optimal_cost())\n", + " print(\"Total cost = \", smcf.optimal_cost())\n", " print()\n", " for arc in range(smcf.num_arcs()):\n", " # Can ignore arcs leading out of source or intermediate, or into sink.\n", - " if (smcf.tail(arc) != source and smcf.tail(arc) != 11 and\n", - " smcf.tail(arc) != 12 and smcf.head(arc) != sink):\n", - "\n", + " if (\n", + " smcf.tail(arc) != source\n", + " and smcf.tail(arc) != 11\n", + " and smcf.tail(arc) != 12\n", + " and smcf.head(arc) != sink\n", + " ):\n", " # Arcs in the solution will have a flow value of 1.\n", " # There start and end nodes give an assignment of worker to task.\n", " if smcf.flow(arc) > 0:\n", - " print('Worker %d assigned to task %d. Cost = %d' %\n", - " (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))\n", + " print(\n", + " \"Worker %d assigned to task %d. Cost = %d\"\n", + " % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc))\n", + " )\n", " else:\n", - " print('There was an issue with the min cost flow input.')\n", - " print(f'Status: {status}')\n", + " print(\"There was an issue with the min cost flow input.\")\n", + " print(f\"Status: {status}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/graph/simple_max_flow_program.ipynb b/examples/notebook/graph/simple_max_flow_program.ipynb index b93b87c792..c1b43b2721 100644 --- a/examples/notebook/graph/simple_max_flow_program.ipynb +++ b/examples/notebook/graph/simple_max_flow_program.ipynb @@ -108,17 +108,17 @@ " status = smf.solve(0, 4)\n", "\n", " if status != smf.OPTIMAL:\n", - " print('There was an issue with the max flow input.')\n", - " print(f'Status: {status}')\n", + " print(\"There was an issue with the max flow input.\")\n", + " print(f\"Status: {status}\")\n", " exit(1)\n", - " print('Max flow:', smf.optimal_flow())\n", - " print('')\n", - " print(' Arc Flow / Capacity')\n", + " print(\"Max flow:\", smf.optimal_flow())\n", + " print(\"\")\n", + " print(\" Arc Flow / Capacity\")\n", " solution_flows = smf.flows(all_arcs)\n", " for arc, flow, capacity in zip(all_arcs, solution_flows, capacities):\n", - " print(f'{smf.tail(arc)} / {smf.head(arc)} {flow:3} / {capacity:3}')\n", - " print('Source side min-cut:', smf.get_source_side_min_cut())\n", - " print('Sink side min-cut:', smf.get_sink_side_min_cut())\n", + " print(f\"{smf.tail(arc)} / {smf.head(arc)} {flow:3} / {capacity:3}\")\n", + " print(\"Source side min-cut:\", smf.get_source_side_min_cut())\n", + " print(\"Sink side min-cut:\", smf.get_sink_side_min_cut())\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/graph/simple_min_cost_flow_program.ipynb b/examples/notebook/graph/simple_min_cost_flow_program.ipynb index 86cbbaa613..a91f6a7629 100644 --- a/examples/notebook/graph/simple_min_cost_flow_program.ipynb +++ b/examples/notebook/graph/simple_min_cost_flow_program.ipynb @@ -106,7 +106,8 @@ "\n", " # Add arcs, capacities and costs in bulk using numpy.\n", " all_arcs = smcf.add_arcs_with_capacity_and_unit_cost(\n", - " start_nodes, end_nodes, capacities, unit_costs)\n", + " start_nodes, end_nodes, capacities, unit_costs\n", + " )\n", "\n", " # Add supply for each nodes.\n", " smcf.set_nodes_supplies(np.arange(0, len(supplies)), supplies)\n", @@ -115,17 +116,17 @@ " status = smcf.solve()\n", "\n", " if status != smcf.OPTIMAL:\n", - " print('There was an issue with the min cost flow input.')\n", - " print(f'Status: {status}')\n", + " print(\"There was an issue with the min cost flow input.\")\n", + " print(f\"Status: {status}\")\n", " exit(1)\n", - " print(f'Minimum cost: {smcf.optimal_cost()}')\n", - " print('')\n", - " print(' Arc Flow / Capacity Cost')\n", + " print(f\"Minimum cost: {smcf.optimal_cost()}\")\n", + " print(\"\")\n", + " print(\" Arc Flow / Capacity Cost\")\n", " solution_flows = smcf.flows(all_arcs)\n", " costs = solution_flows * unit_costs\n", " for arc, flow, cost in zip(all_arcs, solution_flows, costs):\n", " print(\n", - " f'{smcf.tail(arc):1} -> {smcf.head(arc)} {flow:3} / {smcf.capacity(arc):3} {cost}'\n", + " f\"{smcf.tail(arc):1} -> {smcf.head(arc)} {flow:3} / {smcf.capacity(arc):3} {cost}\"\n", " )\n", "\n", "\n", diff --git a/examples/notebook/linear_solver/assignment_groups_mip.ipynb b/examples/notebook/linear_solver/assignment_groups_mip.ipynb index 396e5d236e..ef3fc24e36 100644 --- a/examples/notebook/linear_solver/assignment_groups_mip.ipynb +++ b/examples/notebook/linear_solver/assignment_groups_mip.ipynb @@ -132,7 +132,7 @@ "\n", " # Solver.\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if not solver:\n", " return\n", "\n", @@ -142,27 +142,26 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # The total size of the tasks each worker takes on is at most total_size_max.\n", " for worker in range(num_workers):\n", - " solver.Add(\n", - " solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n", + " solver.Add(solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n", "\n", " # Each task is assigned to exactly one worker.\n", " for task in range(num_tasks):\n", - " solver.Add(\n", - " solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", + " solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", "\n", " # Create variables for each worker, indicating whether they work on some task.\n", " work = {}\n", " for worker in range(num_workers):\n", - " work[worker] = solver.BoolVar(f'work[{worker}]')\n", + " work[worker] = solver.BoolVar(f\"work[{worker}]\")\n", "\n", " for worker in range(num_workers):\n", - " solver.Add(work[worker] == solver.Sum(\n", - " [x[worker, task] for task in range(num_tasks)]))\n", + " solver.Add(\n", + " work[worker] == solver.Sum([x[worker, task] for task in range(num_tasks)])\n", + " )\n", "\n", " # Group1\n", " constraint_g1 = solver.Constraint(1, 1)\n", @@ -172,7 +171,7 @@ " constraint = solver.Constraint(0, 1)\n", " constraint.SetCoefficient(work[group1[i][0]], 1)\n", " constraint.SetCoefficient(work[group1[i][1]], 1)\n", - " p = solver.BoolVar(f'g1_p{i}')\n", + " p = solver.BoolVar(f\"g1_p{i}\")\n", " constraint.SetCoefficient(p, -2)\n", "\n", " constraint_g1.SetCoefficient(p, 1)\n", @@ -185,7 +184,7 @@ " constraint = solver.Constraint(0, 1)\n", " constraint.SetCoefficient(work[group2[i][0]], 1)\n", " constraint.SetCoefficient(work[group2[i][1]], 1)\n", - " p = solver.BoolVar(f'g2_p{i}')\n", + " p = solver.BoolVar(f\"g2_p{i}\")\n", " constraint.SetCoefficient(p, -2)\n", "\n", " constraint_g2.SetCoefficient(p, 1)\n", @@ -198,7 +197,7 @@ " constraint = solver.Constraint(0, 1)\n", " constraint.SetCoefficient(work[group3[i][0]], 1)\n", " constraint.SetCoefficient(work[group3[i][1]], 1)\n", - " p = solver.BoolVar(f'g3_p{i}')\n", + " p = solver.BoolVar(f\"g3_p{i}\")\n", " constraint.SetCoefficient(p, -2)\n", "\n", " constraint_g3.SetCoefficient(p, 1)\n", @@ -215,14 +214,16 @@ "\n", " # Print solution.\n", " if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n", - " print(f'Total cost = {solver.Objective().Value()}\\n')\n", + " print(f\"Total cost = {solver.Objective().Value()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if x[worker, task].solution_value() > 0.5:\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost: {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost: {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/assignment_mb.ipynb b/examples/notebook/linear_solver/assignment_mb.ipynb index a6b3a94542..c7139b409f 100644 --- a/examples/notebook/linear_solver/assignment_mb.ipynb +++ b/examples/notebook/linear_solver/assignment_mb.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "MIP example that solves an assignment problem." ] }, @@ -82,59 +83,76 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", + "import io\n", + "\n", + "import pandas as pd\n", "\n", "from ortools.linear_solver.python import model_builder\n", "\n", "\n", "def main():\n", " # Data\n", - " costs = np.array([\n", - " [90, 80, 75, 70],\n", - " [35, 85, 55, 65],\n", - " [125, 95, 90, 95],\n", - " [45, 110, 95, 115],\n", - " [50, 100, 90, 100],\n", - " ])\n", - " num_workers, num_tasks = costs.shape\n", + " data_str = \"\"\"\n", + " worker task cost\n", + " w1 t1 90\n", + " w1 t2 80\n", + " w1 t3 75\n", + " w1 t4 70\n", + " w2 t1 35\n", + " w2 t2 85\n", + " w2 t3 55\n", + " w2 t4 65\n", + " w3 t1 125\n", + " w3 t2 95\n", + " w3 t3 90\n", + " w3 t4 95\n", + " w4 t1 45\n", + " w4 t2 110\n", + " w4 t3 95\n", + " w4 t4 115\n", + " w5 t1 50\n", + " w5 t2 110\n", + " w5 t3 90\n", + " w5 t4 100\n", + " \"\"\"\n", + "\n", + " data = pd.read_table(io.StringIO(data_str), sep=r\"\\s+\")\n", "\n", - " # Solver\n", " # Create the model.\n", " model = model_builder.ModelBuilder()\n", "\n", " # Variables\n", " # x[i, j] is an array of 0-1 variables, which will be 1\n", " # if worker i is assigned to task j.\n", - " x = model.new_bool_var_array(shape=[num_workers, num_tasks], name='x') # pytype: disable=wrong-arg-types # numpy-scalars\n", + " x = model.new_bool_var_series(name=\"x\", index=data.index)\n", "\n", " # Constraints\n", " # Each worker is assigned to at most 1 task.\n", - " for i in range(num_workers):\n", - " model.add(np.sum(x[i, :]) <= 1)\n", + " for unused_name, tasks in data.groupby(\"worker\"):\n", + " model.add(x[tasks.index].sum() <= 1)\n", "\n", " # Each task is assigned to exactly one worker.\n", - " for j in range(num_tasks):\n", - " model.add(np.sum(x[:, j]) == 1)\n", + " for unused_name, workers in data.groupby(\"task\"):\n", + " model.add(x[workers.index].sum() == 1)\n", "\n", " # Objective\n", - " model.minimize(np.dot(x.flatten(), costs.flatten()))\n", + " model.minimize(data.cost.dot(x))\n", "\n", " # Create the solver with the CP-SAT backend, and solve the model.\n", - " solver = model_builder.ModelSolver('sat')\n", + " solver = model_builder.ModelSolver(\"sat\")\n", " status = solver.solve(model)\n", "\n", " # Print solution.\n", - " if (status == model_builder.SolveStatus.OPTIMAL or\n", - " status == model_builder.SolveStatus.FEASIBLE):\n", - " print(f'Total cost = {solver.objective_value}\\n')\n", - " for i in range(num_workers):\n", - " for j in range(num_tasks):\n", - " # Test if x[i,j] is 1 (with tolerance for floating point arithmetic).\n", - " if solver.value(x[i, j]) > 0.5:\n", - " print(f'Worker {i} assigned to task {j}.' +\n", - " f' Cost: {costs[i][j]}')\n", + " if (\n", + " status == model_builder.SolveStatus.OPTIMAL\n", + " or status == model_builder.SolveStatus.FEASIBLE\n", + " ):\n", + " print(f\"Total cost = {solver.objective_value}\\n\")\n", + " selected = data.loc[solver.values(x).loc[lambda x: x == 1].index]\n", + " for unused_index, row in selected.iterrows():\n", + " print(f\"{row.task} assigned to {row.worker} with a cost of {row.cost}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/assignment_mip.ipynb b/examples/notebook/linear_solver/assignment_mip.ipynb index 50de3f1c98..7bdd291294 100644 --- a/examples/notebook/linear_solver/assignment_mip.ipynb +++ b/examples/notebook/linear_solver/assignment_mip.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "MIP example that solves an assignment problem." ] }, @@ -99,7 +100,7 @@ "\n", " # Solver\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", "\n", " if not solver:\n", " return\n", @@ -110,7 +111,7 @@ " x = {}\n", " for i in range(num_workers):\n", " for j in range(num_tasks):\n", - " x[i, j] = solver.IntVar(0, 1, '')\n", + " x[i, j] = solver.IntVar(0, 1, \"\")\n", "\n", " # Constraints\n", " # Each worker is assigned to at most 1 task.\n", @@ -133,15 +134,14 @@ "\n", " # Print solution.\n", " if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n", - " print(f'Total cost = {solver.Objective().Value()}\\n')\n", + " print(f\"Total cost = {solver.Objective().Value()}\\n\")\n", " for i in range(num_workers):\n", " for j in range(num_tasks):\n", " # Test if x[i,j] is 1 (with tolerance for floating point arithmetic).\n", " if x[i, j].solution_value() > 0.5:\n", - " print(f'Worker {i} assigned to task {j}.' +\n", - " f' Cost: {costs[i][j]}')\n", + " print(f\"Worker {i} assigned to task {j}.\" + f\" Cost: {costs[i][j]}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/assignment_task_sizes_mip.ipynb b/examples/notebook/linear_solver/assignment_task_sizes_mip.ipynb index 9e1fb3f00d..8733988bb0 100644 --- a/examples/notebook/linear_solver/assignment_task_sizes_mip.ipynb +++ b/examples/notebook/linear_solver/assignment_task_sizes_mip.ipynb @@ -109,7 +109,7 @@ "\n", " # Solver\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", "\n", " if not solver:\n", " return\n", @@ -120,20 +120,21 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # The total size of the tasks each worker takes on is at most total_size_max.\n", " for worker in range(num_workers):\n", " solver.Add(\n", - " solver.Sum([\n", - " task_sizes[task] * x[worker, task] for task in range(num_tasks)\n", - " ]) <= total_size_max)\n", + " solver.Sum(\n", + " [task_sizes[task] * x[worker, task] for task in range(num_tasks)]\n", + " )\n", + " <= total_size_max\n", + " )\n", "\n", " # Each task is assigned to exactly one worker.\n", " for task in range(num_tasks):\n", - " solver.Add(\n", - " solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", + " solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", "\n", " # Objective\n", " objective_terms = []\n", @@ -147,14 +148,16 @@ "\n", " # Print solution.\n", " if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n", - " print(f'Total cost = {solver.Objective().Value()}\\n')\n", + " print(f\"Total cost = {solver.Objective().Value()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if x[worker, task].solution_value() > 0.5:\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost: {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost: {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/assignment_teams_mip.ipynb b/examples/notebook/linear_solver/assignment_teams_mip.ipynb index bf18513ecd..2a6922073b 100644 --- a/examples/notebook/linear_solver/assignment_teams_mip.ipynb +++ b/examples/notebook/linear_solver/assignment_teams_mip.ipynb @@ -106,7 +106,7 @@ "\n", " # Solver\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if not solver:\n", " return\n", "\n", @@ -116,18 +116,16 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # Each worker is assigned at most 1 task.\n", " for worker in range(num_workers):\n", - " solver.Add(\n", - " solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n", + " solver.Add(solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n", "\n", " # Each task is assigned to exactly one worker.\n", " for task in range(num_tasks):\n", - " solver.Add(\n", - " solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", + " solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n", "\n", " # Each team takes at most two tasks.\n", " team1_tasks = []\n", @@ -154,15 +152,17 @@ "\n", " # Print solution.\n", " if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n", - " print(f'Total cost = {solver.Objective().Value()}\\n')\n", + " print(f\"Total cost = {solver.Objective().Value()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if x[worker, task].solution_value() > 0.5:\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost = {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost = {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", - " print(f'Time = {solver.WallTime()} ms')\n", + " print(\"No solution found.\")\n", + " print(f\"Time = {solver.WallTime()} ms\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/basic_example.ipynb b/examples/notebook/linear_solver/basic_example.ipynb index d81901ea99..c427a5f714 100644 --- a/examples/notebook/linear_solver/basic_example.ipynb +++ b/examples/notebook/linear_solver/basic_example.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Minimal example to call the GLOP solver." ] }, @@ -83,27 +84,26 @@ "outputs": [], "source": [ "from ortools.linear_solver import pywraplp\n", - "from ortools.init import pywrapinit\n", "\n", "\n", "def main():\n", " # Create the linear solver with the GLOP backend.\n", - " solver = pywraplp.Solver.CreateSolver('GLOP')\n", + " solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n", " if not solver:\n", " return\n", "\n", " # Create the variables x and y.\n", - " x = solver.NumVar(0, 1, 'x')\n", - " y = solver.NumVar(0, 2, 'y')\n", + " x = solver.NumVar(0, 1, \"x\")\n", + " y = solver.NumVar(0, 2, \"y\")\n", "\n", - " print('Number of variables =', solver.NumVariables())\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", " # Create a linear constraint, 0 <= x + y <= 2.\n", - " ct = solver.Constraint(0, 2, 'ct')\n", + " ct = solver.Constraint(0, 2, \"ct\")\n", " ct.SetCoefficient(x, 1)\n", " ct.SetCoefficient(y, 1)\n", "\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", "\n", " # Create the objective function, 3 * x + y.\n", " objective = solver.Objective()\n", @@ -113,18 +113,12 @@ "\n", " solver.Solve()\n", "\n", - " print('Solution:')\n", - " print('Objective value =', objective.Value())\n", - " print('x =', x.solution_value())\n", - " print('y =', y.solution_value())\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", objective.Value())\n", + " print(\"x =\", x.solution_value())\n", + " print(\"y =\", y.solution_value())\n", "\n", "\n", - "pywrapinit.CppBridge.InitLogging('basic_example.py')\n", - "cpp_flags = pywrapinit.CppFlags()\n", - "cpp_flags.logtostderr = True\n", - "cpp_flags.log_prefix = False\n", - "pywrapinit.CppBridge.SetFlags(cpp_flags)\n", - "\n", "main()\n", "\n" ] diff --git a/examples/notebook/linear_solver/bin_packing_mb.ipynb b/examples/notebook/linear_solver/bin_packing_mb.ipynb index b993b3acb2..6a98da73cf 100644 --- a/examples/notebook/linear_solver/bin_packing_mb.ipynb +++ b/examples/notebook/linear_solver/bin_packing_mb.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solve a simple bin packing problem using a MIP solver." ] }, @@ -82,76 +83,103 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", + "import io\n", + "\n", + "import pandas as pd\n", "\n", "from ortools.linear_solver.python import model_builder\n", "\n", "\n", "def create_data_model():\n", " \"\"\"Create the data for the example.\"\"\"\n", - " data = {}\n", - " weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]\n", - " data['weights'] = weights\n", - " data['items'] = list(range(len(weights)))\n", - " data['bins'] = data['items']\n", - " data['bin_capacity'] = 100\n", - " return data\n", "\n", + " items_str = \"\"\"\n", + " item weight\n", + " i1 48\n", + " i2 30\n", + " i3 19\n", + " i4 36\n", + " i5 36\n", + " i6 27\n", + " i7 42\n", + " i8 42\n", + " i9 36\n", + " i10 24\n", + " i11 30\n", + " \"\"\"\n", + "\n", + " bins_str = \"\"\"\n", + " bin capacity\n", + " b1 100\n", + " b2 100\n", + " b3 100\n", + " b4 100\n", + " b5 100\n", + " b6 100\n", + " b7 100\n", + " \"\"\"\n", + "\n", + " items = pd.read_table(io.StringIO(items_str), index_col=0, sep=r\"\\s+\")\n", + " bins = pd.read_table(io.StringIO(bins_str), index_col=0, sep=r\"\\s+\")\n", + " return items, bins\n", "\n", "\n", "def main():\n", - " data = create_data_model()\n", - " num_items = len(data['items'])\n", - " num_bins = len(data['bins'])\n", + " items, bins = create_data_model()\n", "\n", " # Create the model.\n", " model = model_builder.ModelBuilder()\n", "\n", " # Variables\n", " # x[i, j] = 1 if item i is packed in bin j.\n", - " x = model.new_bool_var_array(shape=[num_items, num_bins], name='x')\n", + " items_x_bins = pd.MultiIndex.from_product(\n", + " [items.index, bins.index], names=[\"item\", \"bin\"]\n", + " )\n", + " x = model.new_bool_var_series(name=\"x\", index=items_x_bins)\n", "\n", " # y[j] = 1 if bin j is used.\n", - " y = model.new_bool_var_array(shape=[num_bins], name='y')\n", + " y = model.new_bool_var_series(name=\"y\", index=bins.index)\n", "\n", " # Constraints\n", " # Each item must be in exactly one bin.\n", - " for i in data['items']:\n", - " model.add(np.sum(x[i, :]) == 1)\n", + " for unused_name, all_copies in x.groupby(\"item\"):\n", + " model.add(x[all_copies.index].sum() == 1)\n", "\n", " # The amount packed in each bin cannot exceed its capacity.\n", - " for j in data['bins']:\n", + " for selected_bin in bins.index:\n", + " items_in_bin = x.xs(selected_bin, level=\"bin\")\n", " model.add(\n", - " np.dot(x[:, j], data['weights']) <= data['bin_capacity'] * y[j])\n", + " items_in_bin.dot(items.weight)\n", + " <= bins.loc[selected_bin].capacity * y[selected_bin]\n", + " )\n", "\n", " # Objective: minimize the number of bins used.\n", - " model.minimize(np.sum(y))\n", + " model.minimize(y.sum())\n", "\n", " # Create the solver with the CP-SAT backend, and solve the model.\n", - " solver = model_builder.ModelSolver('sat')\n", + " solver = model_builder.ModelSolver(\"sat\")\n", " status = solver.solve(model)\n", "\n", " if status == model_builder.SolveStatus.OPTIMAL:\n", - " num_bins = 0.\n", - " for j in data['bins']:\n", - " if solver.value(y[j]) == 1:\n", - " bin_items = []\n", - " bin_weight = 0\n", - " for i in data['items']:\n", - " if solver.value(x[i, j]) > 0:\n", - " bin_items.append(i)\n", - " bin_weight += data['weights'][i]\n", - " if bin_weight > 0:\n", - " num_bins += 1\n", - " print('Bin number', j)\n", - " print(' Items packed:', bin_items)\n", - " print(' Total weight:', bin_weight)\n", - " print()\n", + " print(f\"Number of bins used = {solver.objective_value}\")\n", + "\n", + " x_values = solver.values(x)\n", + " y_values = solver.values(y)\n", + " active_bins = y_values.loc[lambda x: x == 1].index\n", + "\n", + " for b in active_bins:\n", + " print(f\"Bin {b}\")\n", + " items_in_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x == 1].index\n", + " for item in items_in_bin:\n", + " print(f\" Item {item} - weight {items.loc[item].weight}\")\n", + " print(f\" Packed items weight: {items.loc[items_in_bin].sum().to_string()}\")\n", + " print()\n", + "\n", + " print(f\"Total packed weight: {items.weight.sum()}\")\n", " print()\n", - " print('Number of bins used:', num_bins)\n", - " print('Time = ', solver.wall_time, ' seconds')\n", + " print(f\"Time = {solver.wall_time} seconds\")\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/bin_packing_mip.ipynb b/examples/notebook/linear_solver/bin_packing_mip.ipynb index cae82bf39f..8b9b282784 100644 --- a/examples/notebook/linear_solver/bin_packing_mip.ipynb +++ b/examples/notebook/linear_solver/bin_packing_mip.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solve a simple bin packing problem using a MIP solver." ] }, @@ -89,10 +90,10 @@ " \"\"\"Create the data for the example.\"\"\"\n", " data = {}\n", " weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]\n", - " data['weights'] = weights\n", - " data['items'] = list(range(len(weights)))\n", - " data['bins'] = data['items']\n", - " data['bin_capacity'] = 100\n", + " data[\"weights\"] = weights\n", + " data[\"items\"] = list(range(len(weights)))\n", + " data[\"bins\"] = data[\"items\"]\n", + " data[\"bin_capacity\"] = 100\n", " return data\n", "\n", "\n", @@ -101,7 +102,7 @@ " data = create_data_model()\n", "\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", "\n", " if not solver:\n", " return\n", @@ -109,52 +110,53 @@ " # Variables\n", " # x[i, j] = 1 if item i is packed in bin j.\n", " x = {}\n", - " for i in data['items']:\n", - " for j in data['bins']:\n", - " x[(i, j)] = solver.IntVar(0, 1, 'x_%i_%i' % (i, j))\n", + " for i in data[\"items\"]:\n", + " for j in data[\"bins\"]:\n", + " x[(i, j)] = solver.IntVar(0, 1, \"x_%i_%i\" % (i, j))\n", "\n", " # y[j] = 1 if bin j is used.\n", " y = {}\n", - " for j in data['bins']:\n", - " y[j] = solver.IntVar(0, 1, 'y[%i]' % j)\n", + " for j in data[\"bins\"]:\n", + " y[j] = solver.IntVar(0, 1, \"y[%i]\" % j)\n", "\n", " # Constraints\n", " # Each item must be in exactly one bin.\n", - " for i in data['items']:\n", - " solver.Add(sum(x[i, j] for j in data['bins']) == 1)\n", + " for i in data[\"items\"]:\n", + " solver.Add(sum(x[i, j] for j in data[\"bins\"]) == 1)\n", "\n", " # The amount packed in each bin cannot exceed its capacity.\n", - " for j in data['bins']:\n", + " for j in data[\"bins\"]:\n", " solver.Add(\n", - " sum(x[(i, j)] * data['weights'][i] for i in data['items']) <= y[j] *\n", - " data['bin_capacity'])\n", + " sum(x[(i, j)] * data[\"weights\"][i] for i in data[\"items\"])\n", + " <= y[j] * data[\"bin_capacity\"]\n", + " )\n", "\n", " # Objective: minimize the number of bins used.\n", - " solver.Minimize(solver.Sum([y[j] for j in data['bins']]))\n", + " solver.Minimize(solver.Sum([y[j] for j in data[\"bins\"]]))\n", "\n", " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", " num_bins = 0\n", - " for j in data['bins']:\n", + " for j in data[\"bins\"]:\n", " if y[j].solution_value() == 1:\n", " bin_items = []\n", " bin_weight = 0\n", - " for i in data['items']:\n", + " for i in data[\"items\"]:\n", " if x[i, j].solution_value() > 0:\n", " bin_items.append(i)\n", - " bin_weight += data['weights'][i]\n", + " bin_weight += data[\"weights\"][i]\n", " if bin_items:\n", " num_bins += 1\n", - " print('Bin number', j)\n", - " print(' Items packed:', bin_items)\n", - " print(' Total weight:', bin_weight)\n", + " print(\"Bin number\", j)\n", + " print(\" Items packed:\", bin_items)\n", + " print(\" Total weight:\", bin_weight)\n", " print()\n", " print()\n", - " print('Number of bins used:', num_bins)\n", - " print('Time = ', solver.WallTime(), ' milliseconds')\n", + " print(\"Number of bins used:\", num_bins)\n", + " print(\"Time = \", solver.WallTime(), \" milliseconds\")\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/integer_programming_example.ipynb b/examples/notebook/linear_solver/integer_programming_example.ipynb index fc75c3f2b5..692ea2e089 100644 --- a/examples/notebook/linear_solver/integer_programming_example.ipynb +++ b/examples/notebook/linear_solver/integer_programming_example.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Small example to illustrate solving a MIP problem." ] }, @@ -88,14 +89,14 @@ "def IntegerProgrammingExample():\n", " \"\"\"Integer programming sample.\"\"\"\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if not solver:\n", " return\n", "\n", " # x, y, and z are non-negative integer variables.\n", - " x = solver.IntVar(0.0, solver.infinity(), 'x')\n", - " y = solver.IntVar(0.0, solver.infinity(), 'y')\n", - " z = solver.IntVar(0.0, solver.infinity(), 'z')\n", + " x = solver.IntVar(0.0, solver.infinity(), \"x\")\n", + " y = solver.IntVar(0.0, solver.infinity(), \"y\")\n", + " z = solver.IntVar(0.0, solver.infinity(), \"z\")\n", "\n", " # 2*x + 7*y + 3*z <= 50\n", " constraint0 = solver.Constraint(-solver.infinity(), 50)\n", @@ -125,11 +126,11 @@ " # Solve the problem and print the solution.\n", " solver.Solve()\n", " # Print the objective value of the solution.\n", - " print('Maximum objective function value = %d' % solver.Objective().Value())\n", + " print(\"Maximum objective function value = %d\" % solver.Objective().Value())\n", " print()\n", " # Print the value of each variable in the solution.\n", " for variable in [x, y, z]:\n", - " print('%s = %d' % (variable.name(), variable.solution_value()))\n", + " print(\"%s = %d\" % (variable.name(), variable.solution_value()))\n", "\n", "\n", "IntegerProgrammingExample()\n", diff --git a/examples/notebook/linear_solver/linear_programming_example.ipynb b/examples/notebook/linear_solver/linear_programming_example.ipynb index 907d51a6f0..f2604f5f90 100644 --- a/examples/notebook/linear_solver/linear_programming_example.ipynb +++ b/examples/notebook/linear_solver/linear_programming_example.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Linear optimization example." ] }, @@ -88,15 +89,15 @@ "def LinearProgrammingExample():\n", " \"\"\"Linear programming sample.\"\"\"\n", " # Instantiate a Glop solver, naming it LinearExample.\n", - " solver = pywraplp.Solver.CreateSolver('GLOP')\n", + " solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n", " if not solver:\n", " return\n", "\n", " # Create the two variables and let them take on any non-negative value.\n", - " x = solver.NumVar(0, solver.infinity(), 'x')\n", - " y = solver.NumVar(0, solver.infinity(), 'y')\n", + " x = solver.NumVar(0, solver.infinity(), \"x\")\n", + " y = solver.NumVar(0, solver.infinity(), \"y\")\n", "\n", - " print('Number of variables =', solver.NumVariables())\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", " # Constraint 0: x + 2y <= 14.\n", " solver.Add(x + 2 * y <= 14.0)\n", @@ -107,7 +108,7 @@ " # Constraint 2: x - y <= 2.\n", " solver.Add(x - y <= 2.0)\n", "\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", "\n", " # Objective function: 3x + 4y.\n", " solver.Maximize(3 * x + 4 * y)\n", @@ -116,16 +117,16 @@ " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", - " print('Solution:')\n", - " print('Objective value =', solver.Objective().Value())\n", - " print('x =', x.solution_value())\n", - " print('y =', y.solution_value())\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", solver.Objective().Value())\n", + " print(\"x =\", x.solution_value())\n", + " print(\"y =\", y.solution_value())\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", - " print('Problem solved in %d iterations' % solver.iterations())\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", + " print(\"Problem solved in %d iterations\" % solver.iterations())\n", "\n", "\n", "LinearProgrammingExample()\n", diff --git a/examples/notebook/linear_solver/mip_var_array.ipynb b/examples/notebook/linear_solver/mip_var_array.ipynb index 68291b30b6..5b0b1dd6b8 100644 --- a/examples/notebook/linear_solver/mip_var_array.ipynb +++ b/examples/notebook/linear_solver/mip_var_array.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "MIP example that uses a variable array." ] }, @@ -88,16 +89,16 @@ "def create_data_model():\n", " \"\"\"Stores the data for the problem.\"\"\"\n", " data = {}\n", - " data['constraint_coeffs'] = [\n", + " data[\"constraint_coeffs\"] = [\n", " [5, 7, 9, 2, 1],\n", " [18, 4, -9, 10, 12],\n", " [4, 7, 3, 8, 5],\n", " [5, 13, 16, 3, -7],\n", " ]\n", - " data['bounds'] = [250, 285, 211, 315]\n", - " data['obj_coeffs'] = [7, 8, 2, 9, 6]\n", - " data['num_vars'] = 5\n", - " data['num_constraints'] = 4\n", + " data[\"bounds\"] = [250, 285, 211, 315]\n", + " data[\"obj_coeffs\"] = [7, 8, 2, 9, 6]\n", + " data[\"num_vars\"] = 5\n", + " data[\"num_constraints\"] = 4\n", " return data\n", "\n", "\n", @@ -105,21 +106,21 @@ "def main():\n", " data = create_data_model()\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if not solver:\n", " return\n", "\n", " infinity = solver.infinity()\n", " x = {}\n", - " for j in range(data['num_vars']):\n", - " x[j] = solver.IntVar(0, infinity, 'x[%i]' % j)\n", - " print('Number of variables =', solver.NumVariables())\n", + " for j in range(data[\"num_vars\"]):\n", + " x[j] = solver.IntVar(0, infinity, \"x[%i]\" % j)\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", - " for i in range(data['num_constraints']):\n", - " constraint = solver.RowConstraint(0, data['bounds'][i], '')\n", - " for j in range(data['num_vars']):\n", - " constraint.SetCoefficient(x[j], data['constraint_coeffs'][i][j])\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " for i in range(data[\"num_constraints\"]):\n", + " constraint = solver.RowConstraint(0, data[\"bounds\"][i], \"\")\n", + " for j in range(data[\"num_vars\"]):\n", + " constraint.SetCoefficient(x[j], data[\"constraint_coeffs\"][i][j])\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", " # In Python, you can also set the constraints as follows.\n", " # for i in range(data['num_constraints']):\n", " # constraint_expr = \\\n", @@ -127,8 +128,8 @@ " # solver.Add(sum(constraint_expr) <= data['bounds'][i])\n", "\n", " objective = solver.Objective()\n", - " for j in range(data['num_vars']):\n", - " objective.SetCoefficient(x[j], data['obj_coeffs'][j])\n", + " for j in range(data[\"num_vars\"]):\n", + " objective.SetCoefficient(x[j], data[\"obj_coeffs\"][j])\n", " objective.SetMaximization()\n", " # In Python, you can also set the objective as follows.\n", " # obj_expr = [data['obj_coeffs'][j] * x[j] for j in range(data['num_vars'])]\n", @@ -137,15 +138,15 @@ " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", - " print('Objective value =', solver.Objective().Value())\n", - " for j in range(data['num_vars']):\n", - " print(x[j].name(), ' = ', x[j].solution_value())\n", + " print(\"Objective value =\", solver.Objective().Value())\n", + " for j in range(data[\"num_vars\"]):\n", + " print(x[j].name(), \" = \", x[j].solution_value())\n", " print()\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", - " print('Problem solved in %d iterations' % solver.iterations())\n", - " print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", + " print(\"Problem solved in %d iterations\" % solver.iterations())\n", + " print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb b/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb index f56b790cbb..fc595783db 100644 --- a/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb +++ b/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb @@ -88,74 +88,71 @@ "\n", "def main():\n", " data = {}\n", - " data['weights'] = [\n", - " 48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36\n", - " ]\n", - " data['values'] = [\n", - " 10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25\n", - " ]\n", - " assert len(data['weights']) == len(data['values'])\n", - " data['num_items'] = len(data['weights'])\n", - " data['all_items'] = range(data['num_items'])\n", + " data[\"weights\"] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n", + " data[\"values\"] = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]\n", + " assert len(data[\"weights\"]) == len(data[\"values\"])\n", + " data[\"num_items\"] = len(data[\"weights\"])\n", + " data[\"all_items\"] = range(data[\"num_items\"])\n", "\n", - " data['bin_capacities'] = [100, 100, 100, 100, 100]\n", - " data['num_bins'] = len(data['bin_capacities'])\n", - " data['all_bins'] = range(data['num_bins'])\n", + " data[\"bin_capacities\"] = [100, 100, 100, 100, 100]\n", + " data[\"num_bins\"] = len(data[\"bin_capacities\"])\n", + " data[\"all_bins\"] = range(data[\"num_bins\"])\n", "\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SCIP')\n", + " solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n", " if solver is None:\n", - " print('SCIP solver unavailable.')\n", + " print(\"SCIP solver unavailable.\")\n", " return\n", "\n", " # Variables.\n", " # x[i, b] = 1 if item i is packed in bin b.\n", " x = {}\n", - " for i in data['all_items']:\n", - " for b in data['all_bins']:\n", - " x[i, b] = solver.BoolVar(f'x_{i}_{b}')\n", + " for i in data[\"all_items\"]:\n", + " for b in data[\"all_bins\"]:\n", + " x[i, b] = solver.BoolVar(f\"x_{i}_{b}\")\n", "\n", " # Constraints.\n", " # Each item is assigned to at most one bin.\n", - " for i in data['all_items']:\n", - " solver.Add(sum(x[i, b] for b in data['all_bins']) <= 1)\n", + " for i in data[\"all_items\"]:\n", + " solver.Add(sum(x[i, b] for b in data[\"all_bins\"]) <= 1)\n", "\n", " # The amount packed in each bin cannot exceed its capacity.\n", - " for b in data['all_bins']:\n", + " for b in data[\"all_bins\"]:\n", " solver.Add(\n", - " sum(x[i, b] * data['weights'][i]\n", - " for i in data['all_items']) <= data['bin_capacities'][b])\n", + " sum(x[i, b] * data[\"weights\"][i] for i in data[\"all_items\"])\n", + " <= data[\"bin_capacities\"][b]\n", + " )\n", "\n", " # Objective.\n", " # Maximize total value of packed items.\n", " objective = solver.Objective()\n", - " for i in data['all_items']:\n", - " for b in data['all_bins']:\n", - " objective.SetCoefficient(x[i, b], data['values'][i])\n", + " for i in data[\"all_items\"]:\n", + " for b in data[\"all_bins\"]:\n", + " objective.SetCoefficient(x[i, b], data[\"values\"][i])\n", " objective.SetMaximization()\n", "\n", " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", - " print(f'Total packed value: {objective.Value()}')\n", + " print(f\"Total packed value: {objective.Value()}\")\n", " total_weight = 0\n", - " for b in data['all_bins']:\n", - " print(f'Bin {b}')\n", + " for b in data[\"all_bins\"]:\n", + " print(f\"Bin {b}\")\n", " bin_weight = 0\n", " bin_value = 0\n", - " for i in data['all_items']:\n", + " for i in data[\"all_items\"]:\n", " if x[i, b].solution_value() > 0:\n", " print(\n", " f\"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}\"\n", " )\n", - " bin_weight += data['weights'][i]\n", - " bin_value += data['values'][i]\n", - " print(f'Packed bin weight: {bin_weight}')\n", - " print(f'Packed bin value: {bin_value}\\n')\n", + " bin_weight += data[\"weights\"][i]\n", + " bin_value += data[\"values\"][i]\n", + " print(f\"Packed bin weight: {bin_weight}\")\n", + " print(f\"Packed bin value: {bin_value}\\n\")\n", " total_weight += bin_weight\n", - " print(f'Total packed weight: {total_weight}')\n", + " print(f\"Total packed weight: {total_weight}\")\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/simple_lp_program.ipynb b/examples/notebook/linear_solver/simple_lp_program.ipynb index 8143f0b9d9..d73b98e8bf 100644 --- a/examples/notebook/linear_solver/simple_lp_program.ipynb +++ b/examples/notebook/linear_solver/simple_lp_program.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Minimal example to call the GLOP solver." ] }, @@ -87,16 +88,16 @@ "\n", "def main():\n", " # Create the linear solver with the GLOP backend.\n", - " solver = pywraplp.Solver.CreateSolver('GLOP')\n", + " solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n", " if not solver:\n", " return\n", "\n", " infinity = solver.infinity()\n", " # Create the variables x and y.\n", - " x = solver.NumVar(0.0, infinity, 'x')\n", - " y = solver.NumVar(0.0, infinity, 'y')\n", + " x = solver.NumVar(0.0, infinity, \"x\")\n", + " y = solver.NumVar(0.0, infinity, \"y\")\n", "\n", - " print('Number of variables =', solver.NumVariables())\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", " # x + 7 * y <= 17.5.\n", " solver.Add(x + 7 * y <= 17.5)\n", @@ -104,25 +105,25 @@ " # x <= 3.5.\n", " solver.Add(x <= 3.5)\n", "\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", "\n", " # Maximize x + 10 * y.\n", " solver.Maximize(x + 10 * y)\n", "\n", - " print(f'Solving with {solver.SolverVersion()}')\n", + " print(f\"Solving with {solver.SolverVersion()}\")\n", " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", - " print('Solution:')\n", - " print('Objective value =', solver.Objective().Value())\n", - " print('x =', x.solution_value())\n", - " print('y =', y.solution_value())\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", solver.Objective().Value())\n", + " print(\"x =\", x.solution_value())\n", + " print(\"y =\", y.solution_value())\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", - " print('Problem solved in %d iterations' % solver.iterations())\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", + " print(\"Problem solved in %d iterations\" % solver.iterations())\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/simple_lp_program_mb.ipynb b/examples/notebook/linear_solver/simple_lp_program_mb.ipynb index 4f742877f6..63739fe614 100644 --- a/examples/notebook/linear_solver/simple_lp_program_mb.ipynb +++ b/examples/notebook/linear_solver/simple_lp_program_mb.ipynb @@ -93,10 +93,10 @@ " model = model_builder.ModelBuilder()\n", "\n", " # Create the variables x and y.\n", - " x = model.new_num_var(0.0, math.inf, 'x')\n", - " y = model.new_num_var(0.0, math.inf, 'y')\n", + " x = model.new_num_var(0.0, math.inf, \"x\")\n", + " y = model.new_num_var(0.0, math.inf, \"y\")\n", "\n", - " print('Number of variables =', model.num_variables)\n", + " print(\"Number of variables =\", model.num_variables)\n", "\n", " # x + 7 * y <= 17.5.\n", " ct = model.add(x + 7 * y <= 17.5)\n", @@ -104,28 +104,28 @@ " # x <= 3.5.\n", " model.add(x <= 3.5)\n", "\n", - " print('Number of constraints =', model.num_constraints)\n", + " print(\"Number of constraints =\", model.num_constraints)\n", "\n", " # Maximize x + 10 * y.\n", " model.maximize(x + 10 * y)\n", "\n", " # Create the solver with the GLOP backend, and solve the model.\n", - " solver = model_builder.ModelSolver('glop')\n", + " solver = model_builder.ModelSolver(\"glop\")\n", " status = solver.solve(model)\n", "\n", " if status == model_builder.SolveStatus.OPTIMAL:\n", - " print('Solution:')\n", - " print('Objective value =', solver.objective_value)\n", - " print('x =', solver.value(x))\n", - " print('y =', solver.value(y))\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", solver.objective_value)\n", + " print(\"x =\", solver.value(x))\n", + " print(\"y =\", solver.value(y))\n", "\n", - " print('dual_value(ct) =', solver.dual_value(ct))\n", - " print('reduced_cost(x) =', solver.reduced_cost(x))\n", + " print(\"dual_value(ct) =\", solver.dual_value(ct))\n", + " print(\"reduced_cost(x) =\", solver.reduced_cost(x))\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in %f seconds' % solver.wall_time)\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in %f seconds\" % solver.wall_time)\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/simple_mip_program.ipynb b/examples/notebook/linear_solver/simple_mip_program.ipynb index fe16491469..54bbe63a16 100644 --- a/examples/notebook/linear_solver/simple_mip_program.ipynb +++ b/examples/notebook/linear_solver/simple_mip_program.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Integer programming examples that show how to use the APIs." ] }, @@ -87,16 +88,16 @@ "\n", "def main():\n", " # Create the mip solver with the SCIP backend.\n", - " solver = pywraplp.Solver.CreateSolver('SAT')\n", + " solver = pywraplp.Solver.CreateSolver(\"SAT\")\n", " if not solver:\n", " return\n", "\n", " infinity = solver.infinity()\n", " # x and y are integer non-negative variables.\n", - " x = solver.IntVar(0.0, infinity, 'x')\n", - " y = solver.IntVar(0.0, infinity, 'y')\n", + " x = solver.IntVar(0.0, infinity, \"x\")\n", + " y = solver.IntVar(0.0, infinity, \"y\")\n", "\n", - " print('Number of variables =', solver.NumVariables())\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", " # x + 7 * y <= 17.5.\n", " solver.Add(x + 7 * y <= 17.5)\n", @@ -104,26 +105,26 @@ " # x <= 3.5.\n", " solver.Add(x <= 3.5)\n", "\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", "\n", " # Maximize x + 10 * y.\n", " solver.Maximize(x + 10 * y)\n", "\n", - " print(f'Solving with {solver.SolverVersion()}')\n", + " print(f\"Solving with {solver.SolverVersion()}\")\n", " status = solver.Solve()\n", "\n", " if status == pywraplp.Solver.OPTIMAL:\n", - " print('Solution:')\n", - " print('Objective value =', solver.Objective().Value())\n", - " print('x =', x.solution_value())\n", - " print('y =', y.solution_value())\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", solver.Objective().Value())\n", + " print(\"x =\", x.solution_value())\n", + " print(\"y =\", y.solution_value())\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in %f milliseconds' % solver.wall_time())\n", - " print('Problem solved in %d iterations' % solver.iterations())\n", - " print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n", + " print(\"Problem solved in %d iterations\" % solver.iterations())\n", + " print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/simple_mip_program_mb.ipynb b/examples/notebook/linear_solver/simple_mip_program_mb.ipynb index abe134e277..547f157144 100644 --- a/examples/notebook/linear_solver/simple_mip_program_mb.ipynb +++ b/examples/notebook/linear_solver/simple_mip_program_mb.ipynb @@ -93,10 +93,10 @@ " model = model_builder.ModelBuilder()\n", "\n", " # x and y are integer non-negative variables.\n", - " x = model.new_int_var(0.0, math.inf, 'x')\n", - " y = model.new_int_var(0.0, math.inf, 'y')\n", + " x = model.new_int_var(0.0, math.inf, \"x\")\n", + " y = model.new_int_var(0.0, math.inf, \"y\")\n", "\n", - " print('Number of variables =', model.num_variables)\n", + " print(\"Number of variables =\", model.num_variables)\n", "\n", " # x + 7 * y <= 17.5.\n", " model.add(x + 7 * y <= 17.5)\n", @@ -104,25 +104,25 @@ " # x <= 3.5.\n", " model.add(x <= 3.5)\n", "\n", - " print('Number of constraints =', model.num_constraints)\n", + " print(\"Number of constraints =\", model.num_constraints)\n", "\n", " # Maximize x + 10 * y.\n", " model.maximize(x + 10 * y)\n", "\n", " # Create the solver with the SCIP backend, and solve the model.\n", - " solver = model_builder.ModelSolver('scip')\n", + " solver = model_builder.ModelSolver(\"scip\")\n", " status = solver.solve(model)\n", "\n", " if status == model_builder.SolveStatus.OPTIMAL:\n", - " print('Solution:')\n", - " print('Objective value =', solver.objective_value)\n", - " print('x =', solver.value(x))\n", - " print('y =', solver.value(y))\n", + " print(\"Solution:\")\n", + " print(\"Objective value =\", solver.objective_value)\n", + " print(\"x =\", solver.value(x))\n", + " print(\"y =\", solver.value(y))\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in %f seconds' % solver.wall_time)\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in %f seconds\" % solver.wall_time)\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/linear_solver/stigler_diet.ipynb b/examples/notebook/linear_solver/stigler_diet.ipynb index b3b4413ebe..1df445d673 100644 --- a/examples/notebook/linear_solver/stigler_diet.ipynb +++ b/examples/notebook/linear_solver/stigler_diet.ipynb @@ -94,208 +94,111 @@ " # Instantiate the data problem.\n", " # Nutrient minimums.\n", " nutrients = [\n", - " ['Calories (kcal)', 3],\n", - " ['Protein (g)', 70],\n", - " ['Calcium (g)', 0.8],\n", - " ['Iron (mg)', 12],\n", - " ['Vitamin A (KIU)', 5],\n", - " ['Vitamin B1 (mg)', 1.8],\n", - " ['Vitamin B2 (mg)', 2.7],\n", - " ['Niacin (mg)', 18],\n", - " ['Vitamin C (mg)', 75],\n", + " [\"Calories (kcal)\", 3],\n", + " [\"Protein (g)\", 70],\n", + " [\"Calcium (g)\", 0.8],\n", + " [\"Iron (mg)\", 12],\n", + " [\"Vitamin A (KIU)\", 5],\n", + " [\"Vitamin B1 (mg)\", 1.8],\n", + " [\"Vitamin B2 (mg)\", 2.7],\n", + " [\"Niacin (mg)\", 18],\n", + " [\"Vitamin C (mg)\", 75],\n", " ]\n", "\n", " # Commodity, Unit, 1939 price (cents), Calories (kcal), Protein (g),\n", " # Calcium (g), Iron (mg), Vitamin A (KIU), Vitamin B1 (mg), Vitamin B2 (mg),\n", " # Niacin (mg), Vitamin C (mg)\n", " data = [\n", - " [\n", - " 'Wheat Flour (Enriched)', '10 lb.', 36, 44.7, 1411, 2, 365, 0, 55.4,\n", - " 33.3, 441, 0\n", - " ],\n", - " ['Macaroni', '1 lb.', 14.1, 11.6, 418, 0.7, 54, 0, 3.2, 1.9, 68, 0],\n", - " [\n", - " 'Wheat Cereal (Enriched)', '28 oz.', 24.2, 11.8, 377, 14.4, 175, 0,\n", - " 14.4, 8.8, 114, 0\n", - " ],\n", - " ['Corn Flakes', '8 oz.', 7.1, 11.4, 252, 0.1, 56, 0, 13.5, 2.3, 68, 0],\n", - " [\n", - " 'Corn Meal', '1 lb.', 4.6, 36.0, 897, 1.7, 99, 30.9, 17.4, 7.9, 106,\n", - " 0\n", - " ],\n", - " [\n", - " 'Hominy Grits', '24 oz.', 8.5, 28.6, 680, 0.8, 80, 0, 10.6, 1.6,\n", - " 110, 0\n", - " ],\n", - " ['Rice', '1 lb.', 7.5, 21.2, 460, 0.6, 41, 0, 2, 4.8, 60, 0],\n", - " ['Rolled Oats', '1 lb.', 7.1, 25.3, 907, 5.1, 341, 0, 37.1, 8.9, 64, 0],\n", - " [\n", - " 'White Bread (Enriched)', '1 lb.', 7.9, 15.0, 488, 2.5, 115, 0,\n", - " 13.8, 8.5, 126, 0\n", - " ],\n", - " [\n", - " 'Whole Wheat Bread', '1 lb.', 9.1, 12.2, 484, 2.7, 125, 0, 13.9,\n", - " 6.4, 160, 0\n", - " ],\n", - " ['Rye Bread', '1 lb.', 9.1, 12.4, 439, 1.1, 82, 0, 9.9, 3, 66, 0],\n", - " ['Pound Cake', '1 lb.', 24.8, 8.0, 130, 0.4, 31, 18.9, 2.8, 3, 17, 0],\n", - " ['Soda Crackers', '1 lb.', 15.1, 12.5, 288, 0.5, 50, 0, 0, 0, 0, 0],\n", - " ['Milk', '1 qt.', 11, 6.1, 310, 10.5, 18, 16.8, 4, 16, 7, 177],\n", - " [\n", - " 'Evaporated Milk (can)', '14.5 oz.', 6.7, 8.4, 422, 15.1, 9, 26, 3,\n", - " 23.5, 11, 60\n", - " ],\n", - " ['Butter', '1 lb.', 30.8, 10.8, 9, 0.2, 3, 44.2, 0, 0.2, 2, 0],\n", - " ['Oleomargarine', '1 lb.', 16.1, 20.6, 17, 0.6, 6, 55.8, 0.2, 0, 0, 0],\n", - " ['Eggs', '1 doz.', 32.6, 2.9, 238, 1.0, 52, 18.6, 2.8, 6.5, 1, 0],\n", - " [\n", - " 'Cheese (Cheddar)', '1 lb.', 24.2, 7.4, 448, 16.4, 19, 28.1, 0.8,\n", - " 10.3, 4, 0\n", - " ],\n", - " ['Cream', '1/2 pt.', 14.1, 3.5, 49, 1.7, 3, 16.9, 0.6, 2.5, 0, 17],\n", - " [\n", - " 'Peanut Butter', '1 lb.', 17.9, 15.7, 661, 1.0, 48, 0, 9.6, 8.1,\n", - " 471, 0\n", - " ],\n", - " ['Mayonnaise', '1/2 pt.', 16.7, 8.6, 18, 0.2, 8, 2.7, 0.4, 0.5, 0, 0],\n", - " ['Crisco', '1 lb.', 20.3, 20.1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " ['Lard', '1 lb.', 9.8, 41.7, 0, 0, 0, 0.2, 0, 0.5, 5, 0],\n", - " [\n", - " 'Sirloin Steak', '1 lb.', 39.6, 2.9, 166, 0.1, 34, 0.2, 2.1, 2.9,\n", - " 69, 0\n", - " ],\n", - " ['Round Steak', '1 lb.', 36.4, 2.2, 214, 0.1, 32, 0.4, 2.5, 2.4, 87, 0],\n", - " ['Rib Roast', '1 lb.', 29.2, 3.4, 213, 0.1, 33, 0, 0, 2, 0, 0],\n", - " ['Chuck Roast', '1 lb.', 22.6, 3.6, 309, 0.2, 46, 0.4, 1, 4, 120, 0],\n", - " ['Plate', '1 lb.', 14.6, 8.5, 404, 0.2, 62, 0, 0.9, 0, 0, 0],\n", - " [\n", - " 'Liver (Beef)', '1 lb.', 26.8, 2.2, 333, 0.2, 139, 169.2, 6.4, 50.8,\n", - " 316, 525\n", - " ],\n", - " ['Leg of Lamb', '1 lb.', 27.6, 3.1, 245, 0.1, 20, 0, 2.8, 3.9, 86, 0],\n", - " [\n", - " 'Lamb Chops (Rib)', '1 lb.', 36.6, 3.3, 140, 0.1, 15, 0, 1.7, 2.7,\n", - " 54, 0\n", - " ],\n", - " ['Pork Chops', '1 lb.', 30.7, 3.5, 196, 0.2, 30, 0, 17.4, 2.7, 60, 0],\n", - " [\n", - " 'Pork Loin Roast', '1 lb.', 24.2, 4.4, 249, 0.3, 37, 0, 18.2, 3.6,\n", - " 79, 0\n", - " ],\n", - " ['Bacon', '1 lb.', 25.6, 10.4, 152, 0.2, 23, 0, 1.8, 1.8, 71, 0],\n", - " ['Ham, smoked', '1 lb.', 27.4, 6.7, 212, 0.2, 31, 0, 9.9, 3.3, 50, 0],\n", - " ['Salt Pork', '1 lb.', 16, 18.8, 164, 0.1, 26, 0, 1.4, 1.8, 0, 0],\n", - " [\n", - " 'Roasting Chicken', '1 lb.', 30.3, 1.8, 184, 0.1, 30, 0.1, 0.9, 1.8,\n", - " 68, 46\n", - " ],\n", - " ['Veal Cutlets', '1 lb.', 42.3, 1.7, 156, 0.1, 24, 0, 1.4, 2.4, 57, 0],\n", - " [\n", - " 'Salmon, Pink (can)', '16 oz.', 13, 5.8, 705, 6.8, 45, 3.5, 1, 4.9,\n", - " 209, 0\n", - " ],\n", - " ['Apples', '1 lb.', 4.4, 5.8, 27, 0.5, 36, 7.3, 3.6, 2.7, 5, 544],\n", - " ['Bananas', '1 lb.', 6.1, 4.9, 60, 0.4, 30, 17.4, 2.5, 3.5, 28, 498],\n", - " ['Lemons', '1 doz.', 26, 1.0, 21, 0.5, 14, 0, 0.5, 0, 4, 952],\n", - " ['Oranges', '1 doz.', 30.9, 2.2, 40, 1.1, 18, 11.1, 3.6, 1.3, 10, 1998],\n", - " ['Green Beans', '1 lb.', 7.1, 2.4, 138, 3.7, 80, 69, 4.3, 5.8, 37, 862],\n", - " ['Cabbage', '1 lb.', 3.7, 2.6, 125, 4.0, 36, 7.2, 9, 4.5, 26, 5369],\n", - " ['Carrots', '1 bunch', 4.7, 2.7, 73, 2.8, 43, 188.5, 6.1, 4.3, 89, 608],\n", - " ['Celery', '1 stalk', 7.3, 0.9, 51, 3.0, 23, 0.9, 1.4, 1.4, 9, 313],\n", - " ['Lettuce', '1 head', 8.2, 0.4, 27, 1.1, 22, 112.4, 1.8, 3.4, 11, 449],\n", - " ['Onions', '1 lb.', 3.6, 5.8, 166, 3.8, 59, 16.6, 4.7, 5.9, 21, 1184],\n", - " [\n", - " 'Potatoes', '15 lb.', 34, 14.3, 336, 1.8, 118, 6.7, 29.4, 7.1, 198,\n", - " 2522\n", - " ],\n", - " ['Spinach', '1 lb.', 8.1, 1.1, 106, 0, 138, 918.4, 5.7, 13.8, 33, 2755],\n", - " [\n", - " 'Sweet Potatoes', '1 lb.', 5.1, 9.6, 138, 2.7, 54, 290.7, 8.4, 5.4,\n", - " 83, 1912\n", - " ],\n", - " [\n", - " 'Peaches (can)', 'No. 2 1/2', 16.8, 3.7, 20, 0.4, 10, 21.5, 0.5, 1,\n", - " 31, 196\n", - " ],\n", - " [\n", - " 'Pears (can)', 'No. 2 1/2', 20.4, 3.0, 8, 0.3, 8, 0.8, 0.8, 0.8, 5,\n", - " 81\n", - " ],\n", - " [\n", - " 'Pineapple (can)', 'No. 2 1/2', 21.3, 2.4, 16, 0.4, 8, 2, 2.8, 0.8,\n", - " 7, 399\n", - " ],\n", - " [\n", - " 'Asparagus (can)', 'No. 2', 27.7, 0.4, 33, 0.3, 12, 16.3, 1.4, 2.1,\n", - " 17, 272\n", - " ],\n", - " [\n", - " 'Green Beans (can)', 'No. 2', 10, 1.0, 54, 2, 65, 53.9, 1.6, 4.3,\n", - " 32, 431\n", - " ],\n", - " [\n", - " 'Pork and Beans (can)', '16 oz.', 7.1, 7.5, 364, 4, 134, 3.5, 8.3,\n", - " 7.7, 56, 0\n", - " ],\n", - " ['Corn (can)', 'No. 2', 10.4, 5.2, 136, 0.2, 16, 12, 1.6, 2.7, 42, 218],\n", - " [\n", - " 'Peas (can)', 'No. 2', 13.8, 2.3, 136, 0.6, 45, 34.9, 4.9, 2.5, 37,\n", - " 370\n", - " ],\n", - " [\n", - " 'Tomatoes (can)', 'No. 2', 8.6, 1.3, 63, 0.7, 38, 53.2, 3.4, 2.5,\n", - " 36, 1253\n", - " ],\n", - " [\n", - " 'Tomato Soup (can)', '10 1/2 oz.', 7.6, 1.6, 71, 0.6, 43, 57.9, 3.5,\n", - " 2.4, 67, 862\n", - " ],\n", - " [\n", - " 'Peaches, Dried', '1 lb.', 15.7, 8.5, 87, 1.7, 173, 86.8, 1.2, 4.3,\n", - " 55, 57\n", - " ],\n", - " [\n", - " 'Prunes, Dried', '1 lb.', 9, 12.8, 99, 2.5, 154, 85.7, 3.9, 4.3, 65,\n", - " 257\n", - " ],\n", - " [\n", - " 'Raisins, Dried', '15 oz.', 9.4, 13.5, 104, 2.5, 136, 4.5, 6.3, 1.4,\n", - " 24, 136\n", - " ],\n", - " [\n", - " 'Peas, Dried', '1 lb.', 7.9, 20.0, 1367, 4.2, 345, 2.9, 28.7, 18.4,\n", - " 162, 0\n", - " ],\n", - " [\n", - " 'Lima Beans, Dried', '1 lb.', 8.9, 17.4, 1055, 3.7, 459, 5.1, 26.9,\n", - " 38.2, 93, 0\n", - " ],\n", - " [\n", - " 'Navy Beans, Dried', '1 lb.', 5.9, 26.9, 1691, 11.4, 792, 0, 38.4,\n", - " 24.6, 217, 0\n", - " ],\n", - " ['Coffee', '1 lb.', 22.4, 0, 0, 0, 0, 0, 4, 5.1, 50, 0],\n", - " ['Tea', '1/4 lb.', 17.4, 0, 0, 0, 0, 0, 0, 2.3, 42, 0],\n", - " ['Cocoa', '8 oz.', 8.6, 8.7, 237, 3, 72, 0, 2, 11.9, 40, 0],\n", - " ['Chocolate', '8 oz.', 16.2, 8.0, 77, 1.3, 39, 0, 0.9, 3.4, 14, 0],\n", - " ['Sugar', '10 lb.', 51.7, 34.9, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " ['Corn Syrup', '24 oz.', 13.7, 14.7, 0, 0.5, 74, 0, 0, 0, 5, 0],\n", - " ['Molasses', '18 oz.', 13.6, 9.0, 0, 10.3, 244, 0, 1.9, 7.5, 146, 0],\n", - " [\n", - " 'Strawberry Preserves', '1 lb.', 20.5, 6.4, 11, 0.4, 7, 0.2, 0.2,\n", - " 0.4, 3, 0\n", - " ],\n", + " # fmt: off\n", + " ['Wheat Flour (Enriched)', '10 lb.', 36, 44.7, 1411, 2, 365, 0, 55.4, 33.3, 441, 0],\n", + " ['Macaroni', '1 lb.', 14.1, 11.6, 418, 0.7, 54, 0, 3.2, 1.9, 68, 0],\n", + " ['Wheat Cereal (Enriched)', '28 oz.', 24.2, 11.8, 377, 14.4, 175, 0, 14.4, 8.8, 114, 0],\n", + " ['Corn Flakes', '8 oz.', 7.1, 11.4, 252, 0.1, 56, 0, 13.5, 2.3, 68, 0],\n", + " ['Corn Meal', '1 lb.', 4.6, 36.0, 897, 1.7, 99, 30.9, 17.4, 7.9, 106, 0],\n", + " ['Hominy Grits', '24 oz.', 8.5, 28.6, 680, 0.8, 80, 0, 10.6, 1.6, 110, 0],\n", + " ['Rice', '1 lb.', 7.5, 21.2, 460, 0.6, 41, 0, 2, 4.8, 60, 0],\n", + " ['Rolled Oats', '1 lb.', 7.1, 25.3, 907, 5.1, 341, 0, 37.1, 8.9, 64, 0],\n", + " ['White Bread (Enriched)', '1 lb.', 7.9, 15.0, 488, 2.5, 115, 0, 13.8, 8.5, 126, 0],\n", + " ['Whole Wheat Bread', '1 lb.', 9.1, 12.2, 484, 2.7, 125, 0, 13.9, 6.4, 160, 0],\n", + " ['Rye Bread', '1 lb.', 9.1, 12.4, 439, 1.1, 82, 0, 9.9, 3, 66, 0],\n", + " ['Pound Cake', '1 lb.', 24.8, 8.0, 130, 0.4, 31, 18.9, 2.8, 3, 17, 0],\n", + " ['Soda Crackers', '1 lb.', 15.1, 12.5, 288, 0.5, 50, 0, 0, 0, 0, 0],\n", + " ['Milk', '1 qt.', 11, 6.1, 310, 10.5, 18, 16.8, 4, 16, 7, 177],\n", + " ['Evaporated Milk (can)', '14.5 oz.', 6.7, 8.4, 422, 15.1, 9, 26, 3, 23.5, 11, 60],\n", + " ['Butter', '1 lb.', 30.8, 10.8, 9, 0.2, 3, 44.2, 0, 0.2, 2, 0],\n", + " ['Oleomargarine', '1 lb.', 16.1, 20.6, 17, 0.6, 6, 55.8, 0.2, 0, 0, 0],\n", + " ['Eggs', '1 doz.', 32.6, 2.9, 238, 1.0, 52, 18.6, 2.8, 6.5, 1, 0],\n", + " ['Cheese (Cheddar)', '1 lb.', 24.2, 7.4, 448, 16.4, 19, 28.1, 0.8, 10.3, 4, 0],\n", + " ['Cream', '1/2 pt.', 14.1, 3.5, 49, 1.7, 3, 16.9, 0.6, 2.5, 0, 17],\n", + " ['Peanut Butter', '1 lb.', 17.9, 15.7, 661, 1.0, 48, 0, 9.6, 8.1, 471, 0],\n", + " ['Mayonnaise', '1/2 pt.', 16.7, 8.6, 18, 0.2, 8, 2.7, 0.4, 0.5, 0, 0],\n", + " ['Crisco', '1 lb.', 20.3, 20.1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " ['Lard', '1 lb.', 9.8, 41.7, 0, 0, 0, 0.2, 0, 0.5, 5, 0],\n", + " ['Sirloin Steak', '1 lb.', 39.6, 2.9, 166, 0.1, 34, 0.2, 2.1, 2.9, 69, 0],\n", + " ['Round Steak', '1 lb.', 36.4, 2.2, 214, 0.1, 32, 0.4, 2.5, 2.4, 87, 0],\n", + " ['Rib Roast', '1 lb.', 29.2, 3.4, 213, 0.1, 33, 0, 0, 2, 0, 0],\n", + " ['Chuck Roast', '1 lb.', 22.6, 3.6, 309, 0.2, 46, 0.4, 1, 4, 120, 0],\n", + " ['Plate', '1 lb.', 14.6, 8.5, 404, 0.2, 62, 0, 0.9, 0, 0, 0],\n", + " ['Liver (Beef)', '1 lb.', 26.8, 2.2, 333, 0.2, 139, 169.2, 6.4, 50.8, 316, 525],\n", + " ['Leg of Lamb', '1 lb.', 27.6, 3.1, 245, 0.1, 20, 0, 2.8, 3.9, 86, 0],\n", + " ['Lamb Chops (Rib)', '1 lb.', 36.6, 3.3, 140, 0.1, 15, 0, 1.7, 2.7, 54, 0],\n", + " ['Pork Chops', '1 lb.', 30.7, 3.5, 196, 0.2, 30, 0, 17.4, 2.7, 60, 0],\n", + " ['Pork Loin Roast', '1 lb.', 24.2, 4.4, 249, 0.3, 37, 0, 18.2, 3.6, 79, 0],\n", + " ['Bacon', '1 lb.', 25.6, 10.4, 152, 0.2, 23, 0, 1.8, 1.8, 71, 0],\n", + " ['Ham, smoked', '1 lb.', 27.4, 6.7, 212, 0.2, 31, 0, 9.9, 3.3, 50, 0],\n", + " ['Salt Pork', '1 lb.', 16, 18.8, 164, 0.1, 26, 0, 1.4, 1.8, 0, 0],\n", + " ['Roasting Chicken', '1 lb.', 30.3, 1.8, 184, 0.1, 30, 0.1, 0.9, 1.8, 68, 46],\n", + " ['Veal Cutlets', '1 lb.', 42.3, 1.7, 156, 0.1, 24, 0, 1.4, 2.4, 57, 0],\n", + " ['Salmon, Pink (can)', '16 oz.', 13, 5.8, 705, 6.8, 45, 3.5, 1, 4.9, 209, 0],\n", + " ['Apples', '1 lb.', 4.4, 5.8, 27, 0.5, 36, 7.3, 3.6, 2.7, 5, 544],\n", + " ['Bananas', '1 lb.', 6.1, 4.9, 60, 0.4, 30, 17.4, 2.5, 3.5, 28, 498],\n", + " ['Lemons', '1 doz.', 26, 1.0, 21, 0.5, 14, 0, 0.5, 0, 4, 952],\n", + " ['Oranges', '1 doz.', 30.9, 2.2, 40, 1.1, 18, 11.1, 3.6, 1.3, 10, 1998],\n", + " ['Green Beans', '1 lb.', 7.1, 2.4, 138, 3.7, 80, 69, 4.3, 5.8, 37, 862],\n", + " ['Cabbage', '1 lb.', 3.7, 2.6, 125, 4.0, 36, 7.2, 9, 4.5, 26, 5369],\n", + " ['Carrots', '1 bunch', 4.7, 2.7, 73, 2.8, 43, 188.5, 6.1, 4.3, 89, 608],\n", + " ['Celery', '1 stalk', 7.3, 0.9, 51, 3.0, 23, 0.9, 1.4, 1.4, 9, 313],\n", + " ['Lettuce', '1 head', 8.2, 0.4, 27, 1.1, 22, 112.4, 1.8, 3.4, 11, 449],\n", + " ['Onions', '1 lb.', 3.6, 5.8, 166, 3.8, 59, 16.6, 4.7, 5.9, 21, 1184],\n", + " ['Potatoes', '15 lb.', 34, 14.3, 336, 1.8, 118, 6.7, 29.4, 7.1, 198, 2522],\n", + " ['Spinach', '1 lb.', 8.1, 1.1, 106, 0, 138, 918.4, 5.7, 13.8, 33, 2755],\n", + " ['Sweet Potatoes', '1 lb.', 5.1, 9.6, 138, 2.7, 54, 290.7, 8.4, 5.4, 83, 1912],\n", + " ['Peaches (can)', 'No. 2 1/2', 16.8, 3.7, 20, 0.4, 10, 21.5, 0.5, 1, 31, 196],\n", + " ['Pears (can)', 'No. 2 1/2', 20.4, 3.0, 8, 0.3, 8, 0.8, 0.8, 0.8, 5, 81],\n", + " ['Pineapple (can)', 'No. 2 1/2', 21.3, 2.4, 16, 0.4, 8, 2, 2.8, 0.8, 7, 399],\n", + " ['Asparagus (can)', 'No. 2', 27.7, 0.4, 33, 0.3, 12, 16.3, 1.4, 2.1, 17, 272],\n", + " ['Green Beans (can)', 'No. 2', 10, 1.0, 54, 2, 65, 53.9, 1.6, 4.3, 32, 431],\n", + " ['Pork and Beans (can)', '16 oz.', 7.1, 7.5, 364, 4, 134, 3.5, 8.3, 7.7, 56, 0],\n", + " ['Corn (can)', 'No. 2', 10.4, 5.2, 136, 0.2, 16, 12, 1.6, 2.7, 42, 218],\n", + " ['Peas (can)', 'No. 2', 13.8, 2.3, 136, 0.6, 45, 34.9, 4.9, 2.5, 37, 370],\n", + " ['Tomatoes (can)', 'No. 2', 8.6, 1.3, 63, 0.7, 38, 53.2, 3.4, 2.5, 36, 1253],\n", + " ['Tomato Soup (can)', '10 1/2 oz.', 7.6, 1.6, 71, 0.6, 43, 57.9, 3.5, 2.4, 67, 862],\n", + " ['Peaches, Dried', '1 lb.', 15.7, 8.5, 87, 1.7, 173, 86.8, 1.2, 4.3, 55, 57],\n", + " ['Prunes, Dried', '1 lb.', 9, 12.8, 99, 2.5, 154, 85.7, 3.9, 4.3, 65, 257],\n", + " ['Raisins, Dried', '15 oz.', 9.4, 13.5, 104, 2.5, 136, 4.5, 6.3, 1.4, 24, 136],\n", + " ['Peas, Dried', '1 lb.', 7.9, 20.0, 1367, 4.2, 345, 2.9, 28.7, 18.4, 162, 0],\n", + " ['Lima Beans, Dried', '1 lb.', 8.9, 17.4, 1055, 3.7, 459, 5.1, 26.9, 38.2, 93, 0],\n", + " ['Navy Beans, Dried', '1 lb.', 5.9, 26.9, 1691, 11.4, 792, 0, 38.4, 24.6, 217, 0],\n", + " ['Coffee', '1 lb.', 22.4, 0, 0, 0, 0, 0, 4, 5.1, 50, 0],\n", + " ['Tea', '1/4 lb.', 17.4, 0, 0, 0, 0, 0, 0, 2.3, 42, 0],\n", + " ['Cocoa', '8 oz.', 8.6, 8.7, 237, 3, 72, 0, 2, 11.9, 40, 0],\n", + " ['Chocolate', '8 oz.', 16.2, 8.0, 77, 1.3, 39, 0, 0.9, 3.4, 14, 0],\n", + " ['Sugar', '10 lb.', 51.7, 34.9, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " ['Corn Syrup', '24 oz.', 13.7, 14.7, 0, 0.5, 74, 0, 0, 0, 5, 0],\n", + " ['Molasses', '18 oz.', 13.6, 9.0, 0, 10.3, 244, 0, 1.9, 7.5, 146, 0],\n", + " ['Strawberry Preserves', '1 lb.', 20.5, 6.4, 11, 0.4, 7, 0.2, 0.2, 0.4, 3, 0],\n", + " # fmt: on\n", " ]\n", "\n", " # Instantiate a Glop solver and naming it.\n", - " solver = pywraplp.Solver.CreateSolver('GLOP')\n", + " solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n", " if not solver:\n", " return\n", "\n", " # Declare an array to hold our variables.\n", " foods = [solver.NumVar(0.0, solver.infinity(), item[0]) for item in data]\n", "\n", - " print('Number of variables =', solver.NumVariables())\n", + " print(\"Number of variables =\", solver.NumVariables())\n", "\n", " # Create the constraints, one per nutrient.\n", " constraints = []\n", @@ -304,7 +207,7 @@ " for j, item in enumerate(data):\n", " constraints[i].SetCoefficient(foods[j], item[i + 3])\n", "\n", - " print('Number of constraints =', solver.NumConstraints())\n", + " print(\"Number of constraints =\", solver.NumConstraints())\n", "\n", " # Objective function: Minimize the sum of (price-normalized) foods.\n", " objective = solver.Objective()\n", @@ -316,31 +219,32 @@ "\n", " # Check that the problem has an optimal solution.\n", " if status != solver.OPTIMAL:\n", - " print('The problem does not have an optimal solution!')\n", + " print(\"The problem does not have an optimal solution!\")\n", " if status == solver.FEASIBLE:\n", - " print('A potentially suboptimal solution was found.')\n", + " print(\"A potentially suboptimal solution was found.\")\n", " else:\n", - " print('The solver could not solve the problem.')\n", + " print(\"The solver could not solve the problem.\")\n", " exit(1)\n", "\n", " # Display the amounts (in dollars) to purchase of each food.\n", " nutrients_result = [0] * len(nutrients)\n", - " print('\\nAnnual Foods:')\n", + " print(\"\\nAnnual Foods:\")\n", " for i, food in enumerate(foods):\n", " if food.solution_value() > 0.0:\n", - " print('{}: ${}'.format(data[i][0], 365. * food.solution_value()))\n", + " print(\"{}: ${}\".format(data[i][0], 365.0 * food.solution_value()))\n", " for j, _ in enumerate(nutrients):\n", " nutrients_result[j] += data[i][j + 3] * food.solution_value()\n", - " print('\\nOptimal annual price: ${:.4f}'.format(365. * objective.Value()))\n", + " print(\"\\nOptimal annual price: ${:.4f}\".format(365.0 * objective.Value()))\n", "\n", - " print('\\nNutrients per day:')\n", + " print(\"\\nNutrients per day:\")\n", " for i, nutrient in enumerate(nutrients):\n", - " print('{}: {:.2f} (min {})'.format(nutrient[0], nutrients_result[i],\n", - " nutrient[1]))\n", + " print(\n", + " \"{}: {:.2f} (min {})\".format(nutrient[0], nutrients_result[i], nutrient[1])\n", + " )\n", "\n", - " print('\\nAdvanced usage:')\n", - " print('Problem solved in ', solver.wall_time(), ' milliseconds')\n", - " print('Problem solved in ', solver.iterations(), ' iterations')\n", + " print(\"\\nAdvanced usage:\")\n", + " print(\"Problem solved in \", solver.wall_time(), \" milliseconds\")\n", + " print(\"Problem solved in \", solver.iterations(), \" iterations\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/pdlp/simple_pdlp_program.ipynb b/examples/notebook/pdlp/simple_pdlp_program.ipynb index 5bac6ab268..4a4a3b6375 100644 --- a/examples/notebook/pdlp/simple_pdlp_program.ipynb +++ b/examples/notebook/pdlp/simple_pdlp_program.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves a simple LP using PDLP's direct Python API.\n", "\n", "Note: The direct API is generally for advanced use cases. It is matrix-based,\n", @@ -93,24 +94,24 @@ "\n", "from ortools.pdlp import solve_log_pb2\n", "from ortools.pdlp import solvers_pb2\n", - "from ortools.pdlp.python import pywrap_pdlp\n", - "from ortools.init import pywrapinit\n", + "from ortools.pdlp.python import pdlp\n", + "from ortools.init.python import init\n", "\n", "\n", - "def simple_lp() -> pywrap_pdlp.QuadraticProgram:\n", + "def simple_lp() -> pdlp.QuadraticProgram:\n", " \"\"\"Returns a small LP.\n", "\n", - " min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t.\n", - " 2 x_0 + x_1 + x_2 + 2 x_3 = 12\n", - " x_0 + x_2 <= 7\n", - " 4 x_0 >= -4\n", - " -1 <= 1.5 x_2 - x_3 <= 1\n", - " -infinity <= x_0 <= infinity\n", - " -2 <= x_1 <= infinity\n", - " -infinity <= x_2 <= 6\n", - " 2.5 <= x_3 <= 3.5\n", - " \"\"\"\n", - " lp = pywrap_pdlp.QuadraticProgram()\n", + " min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t.\n", + " 2 x_0 + x_1 + x_2 + 2 x_3 = 12\n", + " x_0 + x_2 <= 7\n", + " 4 x_0 >= -4\n", + " -1 <= 1.5 x_2 - x_3 <= 1\n", + " -infinity <= x_0 <= infinity\n", + " -2 <= x_1 <= infinity\n", + " -infinity <= x_2 <= 6\n", + " 2.5 <= x_3 <= 3.5\n", + " \"\"\"\n", + " lp = pdlp.QuadraticProgram()\n", " lp.objective_offset = -14\n", " lp.objective_vector = [5.5, -2, -1, 1]\n", " lp.constraint_lower_bounds = [12, -np.inf, -4, -1]\n", @@ -120,8 +121,9 @@ " # Most use cases should initialize the sparse constraint matrix without\n", " # constructing a dense matrix first! We use a np.array here for convenience\n", " # only.\n", - " constraint_matrix = np.array([[2, 1, 1, 2], [1, 0, 1, 0], [4, 0, 0, 0],\n", - " [0, 0, 1.5, -1]])\n", + " constraint_matrix = np.array(\n", + " [[2, 1, 1, 2], [1, 0, 1, 0], [4, 0, 0, 0], [0, 0, 1.5, -1]]\n", + " )\n", " lp.constraint_matrix = scipy.sparse.csc_matrix(constraint_matrix)\n", " return lp\n", "\n", @@ -140,41 +142,41 @@ "\n", " # Call the main solve function. Note that a quirk of the pywrap11 API forces\n", " # us to serialize the `params` and deserialize the `solve_log` proto messages.\n", - " result = pywrap_pdlp.primal_dual_hybrid_gradient(simple_lp(),\n", - " params.SerializeToString())\n", + " result = pdlp.primal_dual_hybrid_gradient(simple_lp(), params.SerializeToString())\n", " solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str)\n", "\n", " if solve_log.termination_reason == solve_log_pb2.TERMINATION_REASON_OPTIMAL:\n", - " print('Solve successful')\n", + " print(\"Solve successful\")\n", " else:\n", " print(\n", - " 'Solve not successful. Status:',\n", - " solve_log_pb2.TerminationReason.Name(solve_log.termination_reason))\n", + " \"Solve not successful. Status:\",\n", + " solve_log_pb2.TerminationReason.Name(solve_log.termination_reason),\n", + " )\n", "\n", " # Solutions vectors are always returned. *However*, their interpretation\n", " # depends on termination_reason! See primal_dual_hybrid_gradient.h for more\n", " # details on what the vectors mean if termination_reason is not\n", " # TERMINATION_REASON_OPTIMAL.\n", - " print('Primal solution:', result.primal_solution)\n", - " print('Dual solution:', result.dual_solution)\n", - " print('Reduced costs:', result.reduced_costs)\n", + " print(\"Primal solution:\", result.primal_solution)\n", + " print(\"Dual solution:\", result.dual_solution)\n", + " print(\"Reduced costs:\", result.reduced_costs)\n", "\n", " solution_type = solve_log.solution_type\n", - " print('Solution type:', solve_log_pb2.PointType.Name(solution_type))\n", + " print(\"Solution type:\", solve_log_pb2.PointType.Name(solution_type))\n", " for ci in solve_log.solution_stats.convergence_information:\n", " if ci.candidate_type == solution_type:\n", - " print('Primal objective:', ci.primal_objective)\n", - " print('Dual objective:', ci.dual_objective)\n", + " print(\"Primal objective:\", ci.primal_objective)\n", + " print(\"Dual objective:\", ci.dual_objective)\n", "\n", - " print('Iterations:', solve_log.iteration_count)\n", - " print('Solve time (sec):', solve_log.solve_time_sec)\n", + " print(\"Iterations:\", solve_log.iteration_count)\n", + " print(\"Solve time (sec):\", solve_log.solve_time_sec)\n", "\n", "\n", - "pywrapinit.CppBridge.InitLogging('simple_pdlp_program.py')\n", - "cpp_flags = pywrapinit.CppFlags()\n", - "cpp_flags.logtostderr = True\n", + "init.CppBridge.init_logging(\"simple_pdlp_program.py\")\n", + "cpp_flags = init.CppFlags()\n", + "cpp_flags.stderrthreshold = 0\n", "cpp_flags.log_prefix = False\n", - "pywrapinit.CppBridge.SetFlags(cpp_flags)\n", + "init.CppBridge.set_flags(cpp_flags)\n", "main()\n", "\n" ] diff --git a/examples/notebook/sat/assignment_groups_sat.ipynb b/examples/notebook/sat/assignment_groups_sat.ipynb index 36f2640bcf..f0bdfb2868 100644 --- a/examples/notebook/sat/assignment_groups_sat.ipynb +++ b/examples/notebook/sat/assignment_groups_sat.ipynb @@ -137,7 +137,7 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = model.NewBoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # Each worker is assigned to at most one task.\n", @@ -151,12 +151,11 @@ " # Create variables for each worker, indicating whether they work on some task.\n", " work = {}\n", " for worker in range(num_workers):\n", - " work[worker] = model.NewBoolVar(f'work[{worker}]')\n", + " work[worker] = model.NewBoolVar(f\"work[{worker}]\")\n", "\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " model.Add(work[worker] == sum(\n", - " x[worker, task] for task in range(num_tasks)))\n", + " model.Add(work[worker] == sum(x[worker, task] for task in range(num_tasks)))\n", "\n", " # Define the allowed groups of worders\n", " model.AddAllowedAssignments([work[0], work[1], work[2], work[3]], group1)\n", @@ -176,14 +175,16 @@ "\n", " # Print solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Total cost = {solver.ObjectiveValue()}\\n')\n", + " print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if solver.BooleanValue(x[worker, task]):\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost = {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost = {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/assignment_sat.ipynb b/examples/notebook/sat/assignment_sat.ipynb index d7217a1ff8..61dc39007b 100644 --- a/examples/notebook/sat/assignment_sat.ipynb +++ b/examples/notebook/sat/assignment_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solve a simple assignment problem." ] }, @@ -82,47 +83,58 @@ "metadata": {}, "outputs": [], "source": [ + "import io\n", + "\n", + "import pandas as pd\n", + "\n", "from ortools.sat.python import cp_model\n", "\n", "\n", "def main():\n", " # Data\n", - " costs = [\n", - " [90, 80, 75, 70],\n", - " [35, 85, 55, 65],\n", - " [125, 95, 90, 95],\n", - " [45, 110, 95, 115],\n", - " [50, 100, 90, 100],\n", - " ]\n", - " num_workers = len(costs)\n", - " num_tasks = len(costs[0])\n", + " data_str = \"\"\"\n", + " worker task cost\n", + " w1 t1 90\n", + " w1 t2 80\n", + " w1 t3 75\n", + " w1 t4 70\n", + " w2 t1 35\n", + " w2 t2 85\n", + " w2 t3 55\n", + " w2 t4 65\n", + " w3 t1 125\n", + " w3 t2 95\n", + " w3 t3 90\n", + " w3 t4 95\n", + " w4 t1 45\n", + " w4 t2 110\n", + " w4 t3 95\n", + " w4 t4 115\n", + " w5 t1 50\n", + " w5 t2 110\n", + " w5 t3 90\n", + " w5 t4 100\n", + " \"\"\"\n", + "\n", + " data = pd.read_table(io.StringIO(data_str), sep=r\"\\s+\")\n", "\n", " # Model\n", " model = cp_model.CpModel()\n", "\n", " # Variables\n", - " x = []\n", - " for i in range(num_workers):\n", - " t = []\n", - " for j in range(num_tasks):\n", - " t.append(model.NewBoolVar(f'x[{i},{j}]'))\n", - " x.append(t)\n", + " x = model.NewBoolVarSeries(name=\"x\", index=data.index)\n", "\n", " # Constraints\n", " # Each worker is assigned to at most one task.\n", - " for i in range(num_workers):\n", - " model.AddAtMostOne(x[i][j] for j in range(num_tasks))\n", + " for unused_name, tasks in data.groupby(\"worker\"):\n", + " model.AddAtMostOne(x[tasks.index])\n", "\n", " # Each task is assigned to exactly one worker.\n", - " for j in range(num_tasks):\n", - " model.AddExactlyOne(x[i][j] for i in range(num_workers))\n", + " for unused_name, workers in data.groupby(\"task\"):\n", + " model.AddExactlyOne(x[workers.index])\n", "\n", " # Objective\n", - " objective_terms = []\n", - " for i in range(num_workers):\n", - " for j in range(num_tasks):\n", - " objective_terms.append(costs[i][j] * x[i][j])\n", - " model.Minimize(sum(objective_terms))\n", + " model.Minimize(data.cost.dot(x))\n", "\n", " # Solve\n", " solver = cp_model.CpSolver()\n", @@ -130,15 +142,12 @@ "\n", " # Print solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Total cost = {solver.ObjectiveValue()}')\n", - " print()\n", - " for i in range(num_workers):\n", - " for j in range(num_tasks):\n", - " if solver.BooleanValue(x[i][j]):\n", - " print(\n", - " f'Worker {i} assigned to task {j} Cost = {costs[i][j]}')\n", + " print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n", + " selected = data.loc[solver.BooleanValues(x).loc[lambda x: x].index]\n", + " for unused_index, row in selected.iterrows():\n", + " print(f\"{row.task} assigned to {row.worker} with a cost of {row.cost}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/assignment_task_sizes_sat.ipynb b/examples/notebook/sat/assignment_task_sizes_sat.ipynb index d60e6acfe8..740531d1de 100644 --- a/examples/notebook/sat/assignment_task_sizes_sat.ipynb +++ b/examples/notebook/sat/assignment_task_sizes_sat.ipynb @@ -114,14 +114,15 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = model.NewBoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # Each worker is assigned to at most one task.\n", " for worker in range(num_workers):\n", " model.Add(\n", - " sum(task_sizes[task] * x[worker, task]\n", - " for task in range(num_tasks)) <= total_size_max)\n", + " sum(task_sizes[task] * x[worker, task] for task in range(num_tasks))\n", + " <= total_size_max\n", + " )\n", "\n", " # Each task is assigned to exactly one worker.\n", " for task in range(num_tasks):\n", @@ -140,14 +141,16 @@ "\n", " # Print solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Total cost = {solver.ObjectiveValue()}\\n')\n", + " print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if solver.BooleanValue(x[worker, task]):\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost = {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost = {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/assignment_teams_sat.ipynb b/examples/notebook/sat/assignment_teams_sat.ipynb index 46919a4888..5ae5c0fe43 100644 --- a/examples/notebook/sat/assignment_teams_sat.ipynb +++ b/examples/notebook/sat/assignment_teams_sat.ipynb @@ -111,7 +111,7 @@ " x = {}\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", - " x[worker, task] = model.NewBoolVar(f'x[{worker},{task}]')\n", + " x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n", "\n", " # Constraints\n", " # Each worker is assigned to at most one task.\n", @@ -148,14 +148,16 @@ "\n", " # Print solution.\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Total cost = {solver.ObjectiveValue()}\\n')\n", + " print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n", " for worker in range(num_workers):\n", " for task in range(num_tasks):\n", " if solver.BooleanValue(x[worker, task]):\n", - " print(f'Worker {worker} assigned to task {task}.' +\n", - " f' Cost = {costs[worker][task]}')\n", + " print(\n", + " f\"Worker {worker} assigned to task {task}.\"\n", + " + f\" Cost = {costs[worker][task]}\"\n", + " )\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/assumptions_sample_sat.ipynb b/examples/notebook/sat/assumptions_sample_sat.ipynb index 6fa9af8386..3651c0ddc1 100644 --- a/examples/notebook/sat/assumptions_sample_sat.ipynb +++ b/examples/notebook/sat/assumptions_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample that solves a model and gets the infeasibility assumptions." ] }, @@ -91,12 +92,12 @@ " model = cp_model.CpModel()\n", "\n", " # Creates the variables.\n", - " x = model.NewIntVar(0, 10, 'x')\n", - " y = model.NewIntVar(0, 10, 'y')\n", - " z = model.NewIntVar(0, 10, 'z')\n", - " a = model.NewBoolVar('a')\n", - " b = model.NewBoolVar('b')\n", - " c = model.NewBoolVar('c')\n", + " x = model.NewIntVar(0, 10, \"x\")\n", + " y = model.NewIntVar(0, 10, \"y\")\n", + " z = model.NewIntVar(0, 10, \"z\")\n", + " a = model.NewBoolVar(\"a\")\n", + " b = model.NewBoolVar(\"b\")\n", + " c = model.NewBoolVar(\"c\")\n", "\n", " # Creates the constraints.\n", " model.Add(x > y).OnlyEnforceIf(a)\n", @@ -111,10 +112,12 @@ " status = solver.Solve(model)\n", "\n", " # Print solution.\n", - " print(f'Status = {solver.StatusName(status)}')\n", + " print(f\"Status = {solver.StatusName(status)}\")\n", " if status == cp_model.INFEASIBLE:\n", - " print('SufficientAssumptionsForInfeasibility = '\n", - " f'{solver.SufficientAssumptionsForInfeasibility()}')\n", + " print(\n", + " \"SufficientAssumptionsForInfeasibility = \"\n", + " f\"{solver.SufficientAssumptionsForInfeasibility()}\"\n", + " )\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/bin_packing_sat.ipynb b/examples/notebook/sat/bin_packing_sat.ipynb new file mode 100644 index 0000000000..9c942fff91 --- /dev/null +++ b/examples/notebook/sat/bin_packing_sat.ipynb @@ -0,0 +1,193 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "google", + "metadata": {}, + "source": [ + "##### Copyright 2022 Google LLC." + ] + }, + { + "cell_type": "markdown", + "id": "apache", + "metadata": {}, + "source": [ + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License.\n" + ] + }, + { + "cell_type": "markdown", + "id": "basename", + "metadata": {}, + "source": [ + "# bin_packing_sat" + ] + }, + { + "cell_type": "markdown", + "id": "link", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "
\n", + "Run in Google Colab\n", + "\n", + "View source on GitHub\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "doc", + "metadata": {}, + "source": [ + "First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "install", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install ortools" + ] + }, + { + "cell_type": "markdown", + "id": "description", + "metadata": {}, + "source": [ + "\n", + "Solve a simple bin packing problem using a MIP solver." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "code", + "metadata": {}, + "outputs": [], + "source": [ + "import io\n", + "\n", + "import pandas as pd\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def create_data_model():\n", + " \"\"\"Create the data for the example.\"\"\"\n", + "\n", + " items_str = \"\"\"\n", + " item weight\n", + " i1 48\n", + " i2 30\n", + " i3 19\n", + " i4 36\n", + " i5 36\n", + " i6 27\n", + " i7 42\n", + " i8 42\n", + " i9 36\n", + " i10 24\n", + " i11 30\n", + " \"\"\"\n", + "\n", + " bins_str = \"\"\"\n", + " bin capacity\n", + " b1 100\n", + " b2 100\n", + " b3 100\n", + " b4 100\n", + " b5 100\n", + " b6 100\n", + " b7 100\n", + " \"\"\"\n", + "\n", + " items = pd.read_table(io.StringIO(items_str), index_col=0, sep=r\"\\s+\")\n", + " bins = pd.read_table(io.StringIO(bins_str), index_col=0, sep=r\"\\s+\")\n", + " return items, bins\n", + "\n", + "\n", + "def main():\n", + " items, bins = create_data_model()\n", + "\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " # Variables\n", + " # x[i, j] = 1 if item i is packed in bin j.\n", + " items_x_bins = pd.MultiIndex.from_product(\n", + " [items.index, bins.index], names=[\"item\", \"bin\"]\n", + " )\n", + " x = model.NewBoolVarSeries(name=\"x\", index=items_x_bins)\n", + "\n", + " # y[j] = 1 if bin j is used.\n", + " y = model.NewBoolVarSeries(name=\"y\", index=bins.index)\n", + "\n", + " # Constraints\n", + " # Each item must be in exactly one bin.\n", + " for unused_name, all_copies in x.groupby(\"item\"):\n", + " model.AddExactlyOne(x[all_copies.index])\n", + "\n", + " # The amount packed in each bin cannot exceed its capacity.\n", + " for selected_bin in bins.index:\n", + " items_in_bin = x.xs(selected_bin, level=\"bin\")\n", + " model.Add(\n", + " items_in_bin.dot(items.weight)\n", + " <= bins.loc[selected_bin].capacity * y[selected_bin]\n", + " )\n", + "\n", + " # Objective: minimize the number of bins used.\n", + " model.Minimize(y.sum())\n", + "\n", + " # Create the solver with the CP-SAT backend, and solve the model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " print(f\"Number of bins used = {solver.ObjectiveValue()}\")\n", + "\n", + " x_values = solver.BooleanValues(x)\n", + " y_values = solver.BooleanValues(y)\n", + " active_bins = y_values.loc[lambda x: x].index\n", + "\n", + " for b in active_bins:\n", + " print(f\"Bin {b}\")\n", + " items_in_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x].index\n", + " for item in items_in_bin:\n", + " print(f\" Item {item} - weight {items.loc[item].weight}\")\n", + " print(f\" Packed items weight: {items.loc[items_in_bin].sum().to_string()}\")\n", + " print()\n", + "\n", + " print(f\"Total packed weight: {items.weight.sum()}\")\n", + " print()\n", + " print(f\"Time = {solver.WallTime()} seconds\")\n", + " else:\n", + " print(\"The problem does not have an optimal solution.\")\n", + "\n", + "\n", + "main()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebook/sat/binpacking_problem_sat.ipynb b/examples/notebook/sat/binpacking_problem_sat.ipynb index e32355b77c..6bb71b2ba3 100644 --- a/examples/notebook/sat/binpacking_problem_sat.ipynb +++ b/examples/notebook/sat/binpacking_problem_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Solves a binpacking problem using the CP-SAT solver.\n" + "\n", + "Solves a binpacking problem using the CP-SAT solver.\n", + "\n" ] }, { @@ -105,13 +107,13 @@ " for i in all_items:\n", " num_copies = items[i][1]\n", " for b in all_bins:\n", - " x[(i, b)] = model.NewIntVar(0, num_copies, 'x_%i_%i' % (i, b))\n", + " x[(i, b)] = model.NewIntVar(0, num_copies, f\"x[{i},{b}]\")\n", "\n", " # Load variables.\n", - " load = [model.NewIntVar(0, bin_capacity, 'load_%i' % b) for b in all_bins]\n", + " load = [model.NewIntVar(0, bin_capacity, f\"load[{b}]\") for b in all_bins]\n", "\n", " # Slack variables.\n", - " slacks = [model.NewBoolVar('slack_%i' % b) for b in all_bins]\n", + " slacks = [model.NewBoolVar(f\"slack[{b}]\") for b in all_bins]\n", "\n", " # Links load and x.\n", " for b in all_bins:\n", @@ -135,13 +137,13 @@ " # Solves and prints out the solution.\n", " solver = cp_model.CpSolver()\n", " status = solver.Solve(model)\n", - " print('Solve status: %s' % solver.StatusName(status))\n", + " print(f\"Solve status: {solver.StatusName(status)}\")\n", " if status == cp_model.OPTIMAL:\n", - " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", + " print(f\"Optimal objective value: {solver.ObjectiveValue()}\")\n", + " print(\"Statistics\")\n", + " print(f\" - conflicts : {solver.NumConflicts()}\")\n", + " print(f\" - branches : {solver.NumBranches()}\")\n", + " print(f\" - wall time : {solver.WallTime()}s\")\n", "\n", "\n", "BinpackingProblemSat()\n", diff --git a/examples/notebook/sat/bool_or_sample_sat.ipynb b/examples/notebook/sat/bool_or_sample_sat.ipynb index d75b962489..ac4bd4d5d2 100644 --- a/examples/notebook/sat/bool_or_sample_sat.ipynb +++ b/examples/notebook/sat/bool_or_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Code sample to demonstrates a simple Boolean constraint.\n" + "\n", + "Code sample to demonstrates a simple Boolean constraint.\n", + "\n" ] }, { @@ -88,8 +90,8 @@ "def BoolOrSampleSat():\n", " model = cp_model.CpModel()\n", "\n", - " x = model.NewBoolVar('x')\n", - " y = model.NewBoolVar('y')\n", + " x = model.NewBoolVar(\"x\")\n", + " y = model.NewBoolVar(\"y\")\n", "\n", " model.AddBoolOr([x, y.Not()])\n", "\n", diff --git a/examples/notebook/sat/boolean_product_sample_sat.ipynb b/examples/notebook/sat/boolean_product_sample_sat.ipynb index 85b7b73b1f..5f175225d0 100644 --- a/examples/notebook/sat/boolean_product_sample_sat.ipynb +++ b/examples/notebook/sat/boolean_product_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Code sample that encodes the product of two Boolean variables.\n" + "\n", + "Code sample that encodes the product of two Boolean variables.\n", + "\n" ] }, { @@ -88,12 +90,12 @@ "def BooleanProductSampleSat():\n", " \"\"\"Encoding of the product of two Boolean variables.\n", "\n", - " p == x * y, which is the same as p <=> x and y\n", - " \"\"\"\n", + " p == x * y, which is the same as p <=> x and y\n", + " \"\"\"\n", " model = cp_model.CpModel()\n", - " x = model.NewBoolVar('x')\n", - " y = model.NewBoolVar('y')\n", - " p = model.NewBoolVar('p')\n", + " x = model.NewBoolVar(\"x\")\n", + " y = model.NewBoolVar(\"y\")\n", + " p = model.NewBoolVar(\"p\")\n", "\n", " # x and y implies p, rewrite as not(x and y) or p.\n", " model.AddBoolOr(x.Not(), y.Not(), p)\n", diff --git a/examples/notebook/sat/channeling_sample_sat.ipynb b/examples/notebook/sat/channeling_sample_sat.ipynb index 77b73816e1..6e8ff9d0ed 100644 --- a/examples/notebook/sat/channeling_sample_sat.ipynb +++ b/examples/notebook/sat/channeling_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Link integer constraints together.\n" + "\n", + "Link integer constraints together.\n", + "\n" ] }, { @@ -96,7 +98,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -110,11 +112,11 @@ " model = cp_model.CpModel()\n", "\n", " # Declare our two primary variables.\n", - " x = model.NewIntVar(0, 10, 'x')\n", - " y = model.NewIntVar(0, 10, 'y')\n", + " x = model.NewIntVar(0, 10, \"x\")\n", + " y = model.NewIntVar(0, 10, \"y\")\n", "\n", " # Declare our intermediate boolean variable.\n", - " b = model.NewBoolVar('b')\n", + " b = model.NewBoolVar(\"b\")\n", "\n", " # Implement b == (x >= 5).\n", " model.Add(x >= 5).OnlyEnforceIf(b)\n", @@ -127,8 +129,7 @@ " model.Add(y == 0).OnlyEnforceIf(b.Not())\n", "\n", " # Search for x values in increasing order.\n", - " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", - " cp_model.SELECT_MIN_VALUE)\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n", "\n", " # Create a solver and solve with a fixed search.\n", " solver = cp_model.CpSolver()\n", diff --git a/examples/notebook/sat/copy_model_sample_sat.ipynb b/examples/notebook/sat/copy_model_sample_sat.ipynb index cf7e4c5f77..bb699df6dc 100644 --- a/examples/notebook/sat/copy_model_sample_sat.ipynb +++ b/examples/notebook/sat/copy_model_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Showcases deep copying of a model.\n" ] }, @@ -92,9 +93,9 @@ "\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Creates the constraints.\n", " model.Add(x != y)\n", @@ -106,8 +107,7 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('Optimal value of the original model: {}'.format(\n", - " solver.ObjectiveValue()))\n", + " print(\"Optimal value of the original model: {}\".format(solver.ObjectiveValue()))\n", "\n", " # Copy the model.\n", " copy = cp_model.CpModel()\n", @@ -120,8 +120,7 @@ " status = solver.Solve(copy)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('Optimal value of the modified model: {}'.format(\n", - " solver.ObjectiveValue()))\n", + " print(\"Optimal value of the modified model: {}\".format(solver.ObjectiveValue()))\n", "\n", "\n", "CopyModelSat()\n", diff --git a/examples/notebook/sat/cp_is_fun_sat.ipynb b/examples/notebook/sat/cp_is_fun_sat.ipynb index 71453b7a7c..93ec40089e 100644 --- a/examples/notebook/sat/cp_is_fun_sat.ipynb +++ b/examples/notebook/sat/cp_is_fun_sat.ipynb @@ -102,7 +102,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -116,16 +116,16 @@ "\n", " base = 10\n", "\n", - " c = model.NewIntVar(1, base - 1, 'C')\n", - " p = model.NewIntVar(0, base - 1, 'P')\n", - " i = model.NewIntVar(1, base - 1, 'I')\n", - " s = model.NewIntVar(0, base - 1, 'S')\n", - " f = model.NewIntVar(1, base - 1, 'F')\n", - " u = model.NewIntVar(0, base - 1, 'U')\n", - " n = model.NewIntVar(0, base - 1, 'N')\n", - " t = model.NewIntVar(1, base - 1, 'T')\n", - " r = model.NewIntVar(0, base - 1, 'R')\n", - " e = model.NewIntVar(0, base - 1, 'E')\n", + " c = model.NewIntVar(1, base - 1, \"C\")\n", + " p = model.NewIntVar(0, base - 1, \"P\")\n", + " i = model.NewIntVar(1, base - 1, \"I\")\n", + " s = model.NewIntVar(0, base - 1, \"S\")\n", + " f = model.NewIntVar(1, base - 1, \"F\")\n", + " u = model.NewIntVar(0, base - 1, \"U\")\n", + " n = model.NewIntVar(0, base - 1, \"N\")\n", + " t = model.NewIntVar(1, base - 1, \"T\")\n", + " r = model.NewIntVar(0, base - 1, \"R\")\n", + " e = model.NewIntVar(0, base - 1, \"E\")\n", "\n", " # We need to group variables in a list to use the constraint AllDifferent.\n", " letters = [c, p, i, s, f, u, n, t, r, e]\n", @@ -137,8 +137,10 @@ " model.AddAllDifferent(letters)\n", "\n", " # CP + IS + FUN = TRUE\n", - " model.Add(c * base + p + i * base + s + f * base * base + u * base +\n", - " n == t * base * base * base + r * base * base + u * base + e)\n", + " model.Add(\n", + " c * base + p + i * base + s + f * base * base + u * base + n\n", + " == t * base * base * base + r * base * base + u * base + e\n", + " )\n", "\n", " # Creates a solver and solves the model.\n", " solver = cp_model.CpSolver()\n", @@ -149,12 +151,12 @@ " status = solver.Solve(model, solution_printer)\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(f' status : {solver.StatusName(status)}')\n", - " print(f' conflicts: {solver.NumConflicts()}')\n", - " print(f' branches : {solver.NumBranches()}')\n", - " print(f' wall time: {solver.WallTime()} s')\n", - " print(f' sol found: {solution_printer.solution_count()}')\n", + " print(\"\\nStatistics\")\n", + " print(f\" status : {solver.StatusName(status)}\")\n", + " print(f\" conflicts: {solver.NumConflicts()}\")\n", + " print(f\" branches : {solver.NumBranches()}\")\n", + " print(f\" wall time: {solver.WallTime()} s\")\n", + " print(f\" sol found: {solution_printer.solution_count()}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/cp_sat_example.ipynb b/examples/notebook/sat/cp_sat_example.ipynb index f16ebe7888..6efc105794 100644 --- a/examples/notebook/sat/cp_sat_example.ipynb +++ b/examples/notebook/sat/cp_sat_example.ipynb @@ -93,9 +93,9 @@ "\n", " # Creates the variables.\n", " var_upper_bound = max(50, 45, 37)\n", - " x = model.NewIntVar(0, var_upper_bound, 'x')\n", - " y = model.NewIntVar(0, var_upper_bound, 'y')\n", - " z = model.NewIntVar(0, var_upper_bound, 'z')\n", + " x = model.NewIntVar(0, var_upper_bound, \"x\")\n", + " y = model.NewIntVar(0, var_upper_bound, \"y\")\n", + " z = model.NewIntVar(0, var_upper_bound, \"z\")\n", "\n", " # Creates the constraints.\n", " model.Add(2 * x + 7 * y + 3 * z <= 50)\n", @@ -109,19 +109,19 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print(f'Maximum of objective function: {solver.ObjectiveValue()}\\n')\n", - " print(f'x = {solver.Value(x)}')\n", - " print(f'y = {solver.Value(y)}')\n", - " print(f'z = {solver.Value(z)}')\n", + " print(f\"Maximum of objective function: {solver.ObjectiveValue()}\\n\")\n", + " print(f\"x = {solver.Value(x)}\")\n", + " print(f\"y = {solver.Value(y)}\")\n", + " print(f\"z = {solver.Value(z)}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(f' status : {solver.StatusName(status)}')\n", - " print(f' conflicts: {solver.NumConflicts()}')\n", - " print(f' branches : {solver.NumBranches()}')\n", - " print(f' wall time: {solver.WallTime()} s')\n", + " print(\"\\nStatistics\")\n", + " print(f\" status : {solver.StatusName(status)}\")\n", + " print(f\" conflicts: {solver.NumConflicts()}\")\n", + " print(f\" branches : {solver.NumBranches()}\")\n", + " print(f\" wall time: {solver.WallTime()} s\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb index 999c98e8b2..8434d96f2e 100644 --- a/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb +++ b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Encodes an convex piecewise linear function.\n" + "\n", + "Encodes a convex piecewise linear function.\n", + "\n" ] }, { @@ -96,7 +98,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -115,7 +117,7 @@ " model = cp_model.CpModel()\n", "\n", " # Declare our primary variable.\n", - " x = model.NewIntVar(0, 20, 'x')\n", + " x = model.NewIntVar(0, 20, \"x\")\n", "\n", " # Create the expression variable and implement the piecewise linear function.\n", " #\n", @@ -124,25 +126,24 @@ " # ed ld\n", " #\n", " large_constant = 1000\n", - " expr = model.NewIntVar(0, large_constant, 'expr')\n", + " expr = model.NewIntVar(0, large_constant, \"expr\")\n", "\n", " # First segment.\n", - " s1 = model.NewIntVar(-large_constant, large_constant, 's1')\n", + " s1 = model.NewIntVar(-large_constant, large_constant, \"s1\")\n", " model.Add(s1 == earliness_cost * (earliness_date - x))\n", "\n", " # Second segment.\n", " s2 = 0\n", "\n", " # Third segment.\n", - " s3 = model.NewIntVar(-large_constant, large_constant, 's3')\n", + " s3 = model.NewIntVar(-large_constant, large_constant, \"s3\")\n", " model.Add(s3 == lateness_cost * (x - lateness_date))\n", "\n", " # Link together expr and x through s1, s2, and s3.\n", " model.AddMaxEquality(expr, [s1, s2, s3])\n", "\n", " # Search for x values in increasing order.\n", - " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", - " cp_model.SELECT_MIN_VALUE)\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n", "\n", " # Create a solver and solve with a fixed search.\n", " solver = cp_model.CpSolver()\n", diff --git a/examples/notebook/sat/interval_sample_sat.ipynb b/examples/notebook/sat/interval_sample_sat.ipynb index 358ea96d6e..dc06fe8269 100644 --- a/examples/notebook/sat/interval_sample_sat.ipynb +++ b/examples/notebook/sat/interval_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Code sample to demonstrates how to build an interval.\n" + "\n", + "Code sample to demonstrates how to build an interval.\n", + "\n" ] }, { @@ -91,23 +93,23 @@ " horizon = 100\n", "\n", " # An interval can be created from three affine expressions.\n", - " start_var = model.NewIntVar(0, horizon, 'start')\n", + " start_var = model.NewIntVar(0, horizon, \"start\")\n", " duration = 10 # Python cp/sat code accept integer variables or constants.\n", - " end_var = model.NewIntVar(0, horizon, 'end')\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var + 2,\n", - " 'interval')\n", + " end_var = model.NewIntVar(0, horizon, \"end\")\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, \"interval\")\n", "\n", - " print(f'interval = {repr(interval_var)}')\n", + " print(f\"interval = {repr(interval_var)}\")\n", "\n", " # If the size is fixed, a simpler version uses the start expression and the\n", " # size.\n", " fixed_size_interval_var = model.NewFixedSizeIntervalVar(\n", - " start_var, 10, 'fixed_size_interval_var')\n", - " print(f'fixed_size_interval_var = {repr(fixed_size_interval_var)}')\n", + " start_var, 10, \"fixed_size_interval_var\"\n", + " )\n", + " print(f\"fixed_size_interval_var = {repr(fixed_size_interval_var)}\")\n", "\n", " # A fixed interval can be created using the same API.\n", - " fixed_interval = model.NewFixedSizeIntervalVar(5, 10, 'fixed_interval')\n", - " print(f'fixed_interval = {repr(fixed_interval)}')\n", + " fixed_interval = model.NewFixedSizeIntervalVar(5, 10, \"fixed_interval\")\n", + " print(f\"fixed_interval = {repr(fixed_interval)}\")\n", "\n", "\n", "IntervalSampleSat()\n", diff --git a/examples/notebook/sat/literal_sample_sat.ipynb b/examples/notebook/sat/literal_sample_sat.ipynb index c6740c6258..148338b52d 100644 --- a/examples/notebook/sat/literal_sample_sat.ipynb +++ b/examples/notebook/sat/literal_sample_sat.ipynb @@ -72,7 +72,9 @@ "id": "description", "metadata": {}, "source": [ - "Code sample to demonstrate Boolean variable and literals.\n" + "\n", + "Code sample to demonstrate Boolean variable and literals.\n", + "\n" ] }, { @@ -87,7 +89,7 @@ "\n", "def LiteralSampleSat():\n", " model = cp_model.CpModel()\n", - " x = model.NewBoolVar('x')\n", + " x = model.NewBoolVar(\"x\")\n", " not_x = x.Not()\n", " print(x)\n", " print(not_x)\n", diff --git a/examples/notebook/sat/minimal_jobshop_sat.ipynb b/examples/notebook/sat/minimal_jobshop_sat.ipynb index 63ee095f97..39624b4aa0 100644 --- a/examples/notebook/sat/minimal_jobshop_sat.ipynb +++ b/examples/notebook/sat/minimal_jobshop_sat.ipynb @@ -93,7 +93,7 @@ " jobs_data = [ # task = (machine_id, processing_time).\n", " [(0, 3), (1, 2), (2, 2)], # Job0\n", " [(0, 2), (2, 1), (1, 4)], # Job1\n", - " [(1, 4), (2, 3)] # Job2\n", + " [(1, 4), (2, 3)], # Job2\n", " ]\n", "\n", " machines_count = 1 + max(task[0] for job in jobs_data for task in job)\n", @@ -105,10 +105,11 @@ " model = cp_model.CpModel()\n", "\n", " # Named tuple to store information about created variables.\n", - " task_type = collections.namedtuple('task_type', 'start end interval')\n", + " task_type = collections.namedtuple(\"task_type\", \"start end interval\")\n", " # Named tuple to manipulate solution information.\n", - " assigned_task_type = collections.namedtuple('assigned_task_type',\n", - " 'start job index duration')\n", + " assigned_task_type = collections.namedtuple(\n", + " \"assigned_task_type\", \"start job index duration\"\n", + " )\n", "\n", " # Creates job intervals and add to the corresponding machine lists.\n", " all_tasks = {}\n", @@ -116,16 +117,16 @@ "\n", " for job_id, job in enumerate(jobs_data):\n", " for task_id, task in enumerate(job):\n", - " machine = task[0]\n", - " duration = task[1]\n", - " suffix = '_%i_%i' % (job_id, task_id)\n", - " start_var = model.NewIntVar(0, horizon, 'start' + suffix)\n", - " end_var = model.NewIntVar(0, horizon, 'end' + suffix)\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval' + suffix)\n", - " all_tasks[job_id, task_id] = task_type(start=start_var,\n", - " end=end_var,\n", - " interval=interval_var)\n", + " machine, duration = task\n", + " suffix = f\"_{job_id}_{task_id}\"\n", + " start_var = model.NewIntVar(0, horizon, \"start\" + suffix)\n", + " end_var = model.NewIntVar(0, horizon, \"end\" + suffix)\n", + " interval_var = model.NewIntervalVar(\n", + " start_var, duration, end_var, \"interval\" + suffix\n", + " )\n", + " all_tasks[job_id, task_id] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var\n", + " )\n", " machine_to_intervals[machine].append(interval_var)\n", "\n", " # Create and add disjunctive constraints.\n", @@ -135,15 +136,16 @@ " # Precedences inside a job.\n", " for job_id, job in enumerate(jobs_data):\n", " for task_id in range(len(job) - 1):\n", - " model.Add(all_tasks[job_id, task_id +\n", - " 1].start >= all_tasks[job_id, task_id].end)\n", + " model.Add(\n", + " all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end\n", + " )\n", "\n", " # Makespan objective.\n", - " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", - " model.AddMaxEquality(obj_var, [\n", - " all_tasks[job_id, len(job) - 1].end\n", - " for job_id, job in enumerate(jobs_data)\n", - " ])\n", + " obj_var = model.NewIntVar(0, horizon, \"makespan\")\n", + " model.AddMaxEquality(\n", + " obj_var,\n", + " [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)],\n", + " )\n", " model.Minimize(obj_var)\n", "\n", " # Creates the solver and solve.\n", @@ -151,55 +153,56 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print('Solution:')\n", + " print(\"Solution:\")\n", " # Create one list of assigned tasks per machine.\n", " assigned_jobs = collections.defaultdict(list)\n", " for job_id, job in enumerate(jobs_data):\n", " for task_id, task in enumerate(job):\n", " machine = task[0]\n", " assigned_jobs[machine].append(\n", - " assigned_task_type(start=solver.Value(\n", - " all_tasks[job_id, task_id].start),\n", - " job=job_id,\n", - " index=task_id,\n", - " duration=task[1]))\n", + " assigned_task_type(\n", + " start=solver.Value(all_tasks[job_id, task_id].start),\n", + " job=job_id,\n", + " index=task_id,\n", + " duration=task[1],\n", + " )\n", + " )\n", "\n", " # Create per machine output lines.\n", - " output = ''\n", + " output = \"\"\n", " for machine in all_machines:\n", " # Sort by starting time.\n", " assigned_jobs[machine].sort()\n", - " sol_line_tasks = 'Machine ' + str(machine) + ': '\n", - " sol_line = ' '\n", + " sol_line_tasks = \"Machine \" + str(machine) + \": \"\n", + " sol_line = \" \"\n", "\n", " for assigned_task in assigned_jobs[machine]:\n", - " name = 'job_%i_task_%i' % (assigned_task.job,\n", - " assigned_task.index)\n", + " name = f\"job_{assigned_task.job}_task_{assigned_task.index}\"\n", " # Add spaces to output to align columns.\n", - " sol_line_tasks += '%-15s' % name\n", + " sol_line_tasks += f\"{name:15}\"\n", "\n", " start = assigned_task.start\n", " duration = assigned_task.duration\n", - " sol_tmp = '[%i,%i]' % (start, start + duration)\n", + " sol_tmp = f\"[{start},{start + duration}]\"\n", " # Add spaces to output to align columns.\n", - " sol_line += '%-15s' % sol_tmp\n", + " sol_line += f\"{sol_tmp:15}\"\n", "\n", - " sol_line += '\\n'\n", - " sol_line_tasks += '\\n'\n", + " sol_line += \"\\n\"\n", + " sol_line_tasks += \"\\n\"\n", " output += sol_line_tasks\n", " output += sol_line\n", "\n", " # Finally print the solution found.\n", - " print(f'Optimal Schedule Length: {solver.ObjectiveValue()}')\n", + " print(f\"Optimal Schedule Length: {solver.ObjectiveValue()}\")\n", " print(output)\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(' - conflicts: %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time: %f s' % solver.WallTime())\n", + " print(\"\\nStatistics\")\n", + " print(f\" - conflicts: {solver.NumConflicts()}\")\n", + " print(f\" - branches : {solver.NumBranches()}\")\n", + " print(f\" - wall time: {solver.WallTime()}s\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/multiple_knapsack_sat.ipynb b/examples/notebook/sat/multiple_knapsack_sat.ipynb index 3efba48f50..b4984254b2 100644 --- a/examples/notebook/sat/multiple_knapsack_sat.ipynb +++ b/examples/notebook/sat/multiple_knapsack_sat.ipynb @@ -88,72 +88,68 @@ "\n", "def main():\n", " data = {}\n", - " data['weights'] = [\n", - " 48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36\n", - " ]\n", - " data['values'] = [\n", - " 10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25\n", - " ]\n", - " assert len(data['weights']) == len(data['values'])\n", - " data['num_items'] = len(data['weights'])\n", - " data['all_items'] = range(data['num_items'])\n", + " data[\"weights\"] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n", + " data[\"values\"] = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]\n", + " assert len(data[\"weights\"]) == len(data[\"values\"])\n", + " data[\"num_items\"] = len(data[\"weights\"])\n", + " data[\"all_items\"] = range(data[\"num_items\"])\n", "\n", - " data['bin_capacities'] = [100, 100, 100, 100, 100]\n", - " data['num_bins'] = len(data['bin_capacities'])\n", - " data['all_bins'] = range(data['num_bins'])\n", + " data[\"bin_capacities\"] = [100, 100, 100, 100, 100]\n", + " data[\"num_bins\"] = len(data[\"bin_capacities\"])\n", + " data[\"all_bins\"] = range(data[\"num_bins\"])\n", "\n", " model = cp_model.CpModel()\n", "\n", " # Variables.\n", " # x[i, b] = 1 if item i is packed in bin b.\n", " x = {}\n", - " for i in data['all_items']:\n", - " for b in data['all_bins']:\n", - " x[i, b] = model.NewBoolVar(f'x_{i}_{b}')\n", + " for i in data[\"all_items\"]:\n", + " for b in data[\"all_bins\"]:\n", + " x[i, b] = model.NewBoolVar(f\"x_{i}_{b}\")\n", "\n", " # Constraints.\n", " # Each item is assigned to at most one bin.\n", - " for i in data['all_items']:\n", - " model.AddAtMostOne(x[i, b] for b in data['all_bins'])\n", + " for i in data[\"all_items\"]:\n", + " model.AddAtMostOne(x[i, b] for b in data[\"all_bins\"])\n", "\n", " # The amount packed in each bin cannot exceed its capacity.\n", - " for b in data['all_bins']:\n", + " for b in data[\"all_bins\"]:\n", " model.Add(\n", - " sum(x[i, b] * data['weights'][i]\n", - " for i in data['all_items']) <= data['bin_capacities'][b])\n", + " sum(x[i, b] * data[\"weights\"][i] for i in data[\"all_items\"])\n", + " <= data[\"bin_capacities\"][b]\n", + " )\n", "\n", " # Objective.\n", " # Maximize total value of packed items.\n", " objective = []\n", - " for i in data['all_items']:\n", - " for b in data['all_bins']:\n", - " objective.append(\n", - " cp_model.LinearExpr.Term(x[i, b], data['values'][i]))\n", + " for i in data[\"all_items\"]:\n", + " for b in data[\"all_bins\"]:\n", + " objective.append(cp_model.LinearExpr.Term(x[i, b], data[\"values\"][i]))\n", " model.Maximize(cp_model.LinearExpr.Sum(objective))\n", "\n", " solver = cp_model.CpSolver()\n", " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print(f'Total packed value: {solver.ObjectiveValue()}')\n", + " print(f\"Total packed value: {solver.ObjectiveValue()}\")\n", " total_weight = 0\n", - " for b in data['all_bins']:\n", - " print(f'Bin {b}')\n", + " for b in data[\"all_bins\"]:\n", + " print(f\"Bin {b}\")\n", " bin_weight = 0\n", " bin_value = 0\n", - " for i in data['all_items']:\n", + " for i in data[\"all_items\"]:\n", " if solver.Value(x[i, b]) > 0:\n", " print(\n", " f\"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}\"\n", " )\n", - " bin_weight += data['weights'][i]\n", - " bin_value += data['values'][i]\n", - " print(f'Packed bin weight: {bin_weight}')\n", - " print(f'Packed bin value: {bin_value}\\n')\n", + " bin_weight += data[\"weights\"][i]\n", + " bin_value += data[\"values\"][i]\n", + " print(f\"Packed bin weight: {bin_weight}\")\n", + " print(f\"Packed bin value: {bin_value}\\n\")\n", " total_weight += bin_weight\n", - " print(f'Total packed weight: {total_weight}')\n", + " print(f\"Total packed weight: {total_weight}\")\n", " else:\n", - " print('The problem does not have an optimal solution.')\n", + " print(\"The problem does not have an optimal solution.\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/no_overlap_sample_sat.ipynb b/examples/notebook/sat/no_overlap_sample_sat.ipynb index b880cdff15..6375a78a29 100644 --- a/examples/notebook/sat/no_overlap_sample_sat.ipynb +++ b/examples/notebook/sat/no_overlap_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample to demonstrate how to build a NoOverlap constraint.\n" ] }, @@ -91,33 +92,32 @@ " horizon = 21 # 3 weeks.\n", "\n", " # Task 0, duration 2.\n", - " start_0 = model.NewIntVar(0, horizon, 'start_0')\n", + " start_0 = model.NewIntVar(0, horizon, \"start_0\")\n", " duration_0 = 2 # Python cp/sat code accepts integer variables or constants.\n", - " end_0 = model.NewIntVar(0, horizon, 'end_0')\n", - " task_0 = model.NewIntervalVar(start_0, duration_0, end_0, 'task_0')\n", + " end_0 = model.NewIntVar(0, horizon, \"end_0\")\n", + " task_0 = model.NewIntervalVar(start_0, duration_0, end_0, \"task_0\")\n", " # Task 1, duration 4.\n", - " start_1 = model.NewIntVar(0, horizon, 'start_1')\n", + " start_1 = model.NewIntVar(0, horizon, \"start_1\")\n", " duration_1 = 4 # Python cp/sat code accepts integer variables or constants.\n", - " end_1 = model.NewIntVar(0, horizon, 'end_1')\n", - " task_1 = model.NewIntervalVar(start_1, duration_1, end_1, 'task_1')\n", + " end_1 = model.NewIntVar(0, horizon, \"end_1\")\n", + " task_1 = model.NewIntervalVar(start_1, duration_1, end_1, \"task_1\")\n", "\n", " # Task 2, duration 3.\n", - " start_2 = model.NewIntVar(0, horizon, 'start_2')\n", + " start_2 = model.NewIntVar(0, horizon, \"start_2\")\n", " duration_2 = 3 # Python cp/sat code accepts integer variables or constants.\n", - " end_2 = model.NewIntVar(0, horizon, 'end_2')\n", - " task_2 = model.NewIntervalVar(start_2, duration_2, end_2, 'task_2')\n", + " end_2 = model.NewIntVar(0, horizon, \"end_2\")\n", + " task_2 = model.NewIntervalVar(start_2, duration_2, end_2, \"task_2\")\n", "\n", " # Weekends.\n", - " weekend_0 = model.NewIntervalVar(5, 2, 7, 'weekend_0')\n", - " weekend_1 = model.NewIntervalVar(12, 2, 14, 'weekend_1')\n", - " weekend_2 = model.NewIntervalVar(19, 2, 21, 'weekend_2')\n", + " weekend_0 = model.NewIntervalVar(5, 2, 7, \"weekend_0\")\n", + " weekend_1 = model.NewIntervalVar(12, 2, 14, \"weekend_1\")\n", + " weekend_2 = model.NewIntervalVar(19, 2, 21, \"weekend_2\")\n", "\n", " # No Overlap constraint.\n", - " model.AddNoOverlap(\n", - " [task_0, task_1, task_2, weekend_0, weekend_1, weekend_2])\n", + " model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2])\n", "\n", " # Makespan objective.\n", - " obj = model.NewIntVar(0, horizon, 'makespan')\n", + " obj = model.NewIntVar(0, horizon, \"makespan\")\n", " model.AddMaxEquality(obj, [end_0, end_1, end_2])\n", " model.Minimize(obj)\n", "\n", @@ -127,12 +127,12 @@ "\n", " if status == cp_model.OPTIMAL:\n", " # Print out makespan and the start times for all tasks.\n", - " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", - " print('Task 0 starts at %i' % solver.Value(start_0))\n", - " print('Task 1 starts at %i' % solver.Value(start_1))\n", - " print('Task 2 starts at %i' % solver.Value(start_2))\n", + " print(f\"Optimal Schedule Length: {solver.ObjectiveValue()}\")\n", + " print(f\"Task 0 starts at {solver.Value(start_0)}\")\n", + " print(f\"Task 1 starts at {solver.Value(start_1)}\")\n", + " print(f\"Task 2 starts at {solver.Value(start_2)}\")\n", " else:\n", - " print('Solver exited with nonoptimal status: %i' % status)\n", + " print(f\"Solver exited with nonoptimal status: {status}\")\n", "\n", "\n", "NoOverlapSampleSat()\n", diff --git a/examples/notebook/sat/non_linear_sat.ipynb b/examples/notebook/sat/non_linear_sat.ipynb index 68ed4f838f..56713a44f0 100644 --- a/examples/notebook/sat/non_linear_sat.ipynb +++ b/examples/notebook/sat/non_linear_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Non linear example.\n", "\n", "Finds a rectangle with maximum available area for given perimeter using\n", @@ -95,11 +96,11 @@ "\n", " model = cp_model.CpModel()\n", "\n", - " x = model.NewIntVar(0, perimeter, 'x')\n", - " y = model.NewIntVar(0, perimeter, 'y')\n", + " x = model.NewIntVar(0, perimeter, \"x\")\n", + " y = model.NewIntVar(0, perimeter, \"y\")\n", " model.Add(2 * (x + y) == perimeter)\n", "\n", - " area = model.NewIntVar(0, perimeter * perimeter, 's')\n", + " area = model.NewIntVar(0, perimeter * perimeter, \"s\")\n", " model.AddMultiplicationEquality(area, x, y)\n", "\n", " model.Maximize(area)\n", @@ -109,11 +110,11 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print('x = %i' % solver.Value(x))\n", - " print('y = %i' % solver.Value(y))\n", - " print('s = %i' % solver.Value(area))\n", + " print(f\"x = {solver.Value(x)}\")\n", + " print(f\"y = {solver.Value(y)}\")\n", + " print(f\"s = {solver.Value(area)}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "non_linear_sat()\n", diff --git a/examples/notebook/sat/nqueens_sat.ipynb b/examples/notebook/sat/nqueens_sat.ipynb index 8b03e581bf..505203a1b9 100644 --- a/examples/notebook/sat/nqueens_sat.ipynb +++ b/examples/notebook/sat/nqueens_sat.ipynb @@ -102,8 +102,10 @@ "\n", " def on_solution_callback(self):\n", " current_time = time.time()\n", - " print('Solution %i, time = %f s' %\n", - " (self.__solution_count, current_time - self.__start_time))\n", + " print(\n", + " f\"Solution {self.__solution_count}, \"\n", + " f\"time = {current_time - self.__start_time} s\"\n", + " )\n", " self.__solution_count += 1\n", "\n", " all_queens = range(len(self.__queens))\n", @@ -111,9 +113,9 @@ " for j in all_queens:\n", " if self.Value(self.__queens[j]) == i:\n", " # There is a queen in column j, row i.\n", - " print('Q', end=' ')\n", + " print(\"Q\", end=\" \")\n", " else:\n", - " print('_', end=' ')\n", + " print(\"_\", end=\" \")\n", " print()\n", " print()\n", "\n", @@ -126,9 +128,7 @@ " # Creates the variables.\n", " # There are `board_size` number of variables, one for a queen in each column\n", " # of the board. The value of each variable is the row that the queen is in.\n", - " queens = [\n", - " model.NewIntVar(0, board_size - 1, 'x%i' % i) for i in range(board_size)\n", - " ]\n", + " queens = [model.NewIntVar(0, board_size - 1, f\"x_{i}\") for i in range(board_size)]\n", "\n", " # Creates the constraints.\n", " # All rows must be different.\n", @@ -145,11 +145,11 @@ " solver.Solve(model, solution_printer)\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(f' conflicts : {solver.NumConflicts()}')\n", - " print(f' branches : {solver.NumBranches()}')\n", - " print(f' wall time : {solver.WallTime()} s')\n", - " print(f' solutions found: {solution_printer.solution_count()}')\n", + " print(\"\\nStatistics\")\n", + " print(f\" conflicts : {solver.NumConflicts()}\")\n", + " print(f\" branches : {solver.NumBranches()}\")\n", + " print(f\" wall time : {solver.WallTime()} s\")\n", + " print(f\" solutions found: {solution_printer.solution_count()}\")\n", "\n", "\n", "# By default, solve the 8x8 problem.\n", diff --git a/examples/notebook/sat/nurses_sat.ipynb b/examples/notebook/sat/nurses_sat.ipynb index eb57060fdf..010643b90d 100644 --- a/examples/notebook/sat/nurses_sat.ipynb +++ b/examples/notebook/sat/nurses_sat.ipynb @@ -104,8 +104,7 @@ " for n in all_nurses:\n", " for d in all_days:\n", " for s in all_shifts:\n", - " shifts[(n, d,\n", - " s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))\n", + " shifts[(n, d, s)] = model.NewBoolVar(f\"shift_n{n}_d{d}_s{s}\")\n", "\n", " # Each shift is assigned to exactly one nurse in the schedule period.\n", " for d in all_days:\n", @@ -140,7 +139,6 @@ " # Enumerate all solutions.\n", " solver.parameters.enumerate_all_solutions = True\n", "\n", - "\n", " class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", " \"\"\"Print intermediate solutions.\"\"\"\n", "\n", @@ -155,19 +153,19 @@ "\n", " def on_solution_callback(self):\n", " self._solution_count += 1\n", - " print('Solution %i' % self._solution_count)\n", + " print(f\"Solution {self._solution_count}\")\n", " for d in range(self._num_days):\n", - " print('Day %i' % d)\n", + " print(f\"Day {d}\")\n", " for n in range(self._num_nurses):\n", " is_working = False\n", " for s in range(self._num_shifts):\n", " if self.Value(self._shifts[(n, d, s)]):\n", " is_working = True\n", - " print(' Nurse %i works shift %i' % (n, s))\n", + " print(f\" Nurse {n} works shift {s}\")\n", " if not is_working:\n", - " print(' Nurse {} does not work'.format(n))\n", + " print(f\" Nurse {n} does not work\")\n", " if self._solution_count >= self._solution_limit:\n", - " print('Stop search after %i solutions' % self._solution_limit)\n", + " print(f\"Stop search after {self._solution_limit} solutions\")\n", " self.StopSearch()\n", "\n", " def solution_count(self):\n", @@ -175,18 +173,18 @@ "\n", " # Display the first five solutions.\n", " solution_limit = 5\n", - " solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses,\n", - " num_days, num_shifts,\n", - " solution_limit)\n", + " solution_printer = NursesPartialSolutionPrinter(\n", + " shifts, num_nurses, num_days, num_shifts, solution_limit\n", + " )\n", "\n", " solver.Solve(model, solution_printer)\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", - " print(' - solutions found: %i' % solution_printer.solution_count())\n", + " print(\"\\nStatistics\")\n", + " print(f\" - conflicts : {solver.NumConflicts()}\")\n", + " print(f\" - branches : {solver.NumBranches()}\")\n", + " print(f\" - wall time : {solver.WallTime()} s\")\n", + " print(f\" - solutions found: {solution_printer.solution_count()}\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/optional_interval_sample_sat.ipynb b/examples/notebook/sat/optional_interval_sample_sat.ipynb index 877b0b823b..a7983f313d 100644 --- a/examples/notebook/sat/optional_interval_sample_sat.ipynb +++ b/examples/notebook/sat/optional_interval_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample to demonstrates how to build an optional interval.\n" ] }, @@ -91,26 +92,28 @@ " horizon = 100\n", "\n", " # An interval can be created from three affine expressions.\n", - " start_var = model.NewIntVar(0, horizon, 'start')\n", + " start_var = model.NewIntVar(0, horizon, \"start\")\n", " duration = 10 # Python cp/sat code accept integer variables or constants.\n", - " end_var = model.NewIntVar(0, horizon, 'end')\n", - " presence_var = model.NewBoolVar('presence')\n", - " interval_var = model.NewOptionalIntervalVar(start_var, duration,\n", - " end_var + 2, presence_var,\n", - " 'interval')\n", + " end_var = model.NewIntVar(0, horizon, \"end\")\n", + " presence_var = model.NewBoolVar(\"presence\")\n", + " interval_var = model.NewOptionalIntervalVar(\n", + " start_var, duration, end_var + 2, presence_var, \"interval\"\n", + " )\n", "\n", - " print(f'interval = {repr(interval_var)}')\n", + " print(f\"interval = {repr(interval_var)}\")\n", "\n", " # If the size is fixed, a simpler version uses the start expression and the\n", " # size.\n", " fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar(\n", - " start_var, 10, presence_var, 'fixed_size_interval_var')\n", - " print(f'fixed_size_interval_var = {repr(fixed_size_interval_var)}')\n", + " start_var, 10, presence_var, \"fixed_size_interval_var\"\n", + " )\n", + " print(f\"fixed_size_interval_var = {repr(fixed_size_interval_var)}\")\n", "\n", " # A fixed interval can be created using the same API.\n", " fixed_interval = model.NewOptionalFixedSizeIntervalVar(\n", - " 5, 10, presence_var, 'fixed_interval')\n", - " print(f'fixed_interval = {repr(fixed_interval)}')\n", + " 5, 10, presence_var, \"fixed_interval\"\n", + " )\n", + " print(f\"fixed_interval = {repr(fixed_interval)}\")\n", "\n", "\n", "OptionalIntervalSampleSat()\n", diff --git a/examples/notebook/sat/overlapping_intervals_sample_sat.ipynb b/examples/notebook/sat/overlapping_intervals_sample_sat.ipynb index 81ffdf4a57..df7ed8f617 100644 --- a/examples/notebook/sat/overlapping_intervals_sample_sat.ipynb +++ b/examples/notebook/sat/overlapping_intervals_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample to demonstrates how to detect if two intervals overlap.\n" ] }, @@ -96,7 +97,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -110,31 +111,33 @@ " horizon = 7\n", "\n", " # First interval.\n", - " start_var_a = model.NewIntVar(0, horizon, 'start_a')\n", + " start_var_a = model.NewIntVar(0, horizon, \"start_a\")\n", " duration_a = 3\n", - " end_var_a = model.NewIntVar(0, horizon, 'end_a')\n", - " unused_interval_var_a = model.NewIntervalVar(start_var_a, duration_a,\n", - " end_var_a, 'interval_a')\n", + " end_var_a = model.NewIntVar(0, horizon, \"end_a\")\n", + " unused_interval_var_a = model.NewIntervalVar(\n", + " start_var_a, duration_a, end_var_a, \"interval_a\"\n", + " )\n", "\n", " # Second interval.\n", - " start_var_b = model.NewIntVar(0, horizon, 'start_b')\n", + " start_var_b = model.NewIntVar(0, horizon, \"start_b\")\n", " duration_b = 2\n", - " end_var_b = model.NewIntVar(0, horizon, 'end_b')\n", - " unused_interval_var_b = model.NewIntervalVar(start_var_b, duration_b,\n", - " end_var_b, 'interval_b')\n", + " end_var_b = model.NewIntVar(0, horizon, \"end_b\")\n", + " unused_interval_var_b = model.NewIntervalVar(\n", + " start_var_b, duration_b, end_var_b, \"interval_b\"\n", + " )\n", "\n", " # a_after_b Boolean variable.\n", - " a_after_b = model.NewBoolVar('a_after_b')\n", + " a_after_b = model.NewBoolVar(\"a_after_b\")\n", " model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b)\n", " model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not())\n", "\n", " # b_after_a Boolean variable.\n", - " b_after_a = model.NewBoolVar('b_after_a')\n", + " b_after_a = model.NewBoolVar(\"b_after_a\")\n", " model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a)\n", " model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not())\n", "\n", " # Result Boolean variable.\n", - " a_overlaps_b = model.NewBoolVar('a_overlaps_b')\n", + " a_overlaps_b = model.NewBoolVar(\"a_overlaps_b\")\n", "\n", " # Option a: using only clauses\n", " model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b)\n", @@ -145,8 +148,9 @@ " # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b)\n", "\n", " # Search for start values in increasing order for the two intervals.\n", - " model.AddDecisionStrategy([start_var_a, start_var_b], cp_model.CHOOSE_FIRST,\n", - " cp_model.SELECT_MIN_VALUE)\n", + " model.AddDecisionStrategy(\n", + " [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n", + " )\n", "\n", " # Create a solver and solve with a fixed search.\n", " solver = cp_model.CpSolver()\n", @@ -157,8 +161,7 @@ " solver.parameters.enumerate_all_solutions = True\n", "\n", " # Search and print out all solutions.\n", - " solution_printer = VarArraySolutionPrinter(\n", - " [start_var_a, start_var_b, a_overlaps_b])\n", + " solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b])\n", " solver.Solve(model, solution_printer)\n", "\n", "\n", diff --git a/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb index 00b9c2b892..8ffebc82fb 100644 --- a/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb +++ b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Rabbits and Pheasants quizz.\n" ] }, @@ -89,8 +90,8 @@ " \"\"\"Solves the rabbits + pheasants problem.\"\"\"\n", " model = cp_model.CpModel()\n", "\n", - " r = model.NewIntVar(0, 100, 'r')\n", - " p = model.NewIntVar(0, 100, 'p')\n", + " r = model.NewIntVar(0, 100, \"r\")\n", + " p = model.NewIntVar(0, 100, \"p\")\n", "\n", " # 20 heads.\n", " model.Add(r + p == 20)\n", @@ -102,8 +103,7 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('%i rabbits and %i pheasants' %\n", - " (solver.Value(r), solver.Value(p)))\n", + " print(f\"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants\")\n", "\n", "\n", "RabbitsAndPheasantsSat()\n", diff --git a/examples/notebook/sat/ranking_sample_sat.ipynb b/examples/notebook/sat/ranking_sample_sat.ipynb index 21eee475ba..f079bcd0cd 100644 --- a/examples/notebook/sat/ranking_sample_sat.ipynb +++ b/examples/notebook/sat/ranking_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample to demonstrates how to rank intervals.\n" ] }, @@ -88,16 +89,16 @@ "def RankTasks(model, starts, presences, ranks):\n", " \"\"\"This method adds constraints and variables to links tasks and ranks.\n", "\n", - " This method assumes that all starts are disjoint, meaning that all tasks have\n", - " a strictly positive duration, and they appear in the same NoOverlap\n", - " constraint.\n", + " This method assumes that all starts are disjoint, meaning that all tasks have\n", + " a strictly positive duration, and they appear in the same NoOverlap\n", + " constraint.\n", "\n", - " Args:\n", - " model: The CpModel to add the constraints to.\n", - " starts: The array of starts variables of all tasks.\n", - " presences: The array of presence variables of all tasks.\n", - " ranks: The array of rank variables of all tasks.\n", - " \"\"\"\n", + " Args:\n", + " model: The CpModel to add the constraints to.\n", + " starts: The array of starts variables of all tasks.\n", + " presences: The array of presence variables of all tasks.\n", + " ranks: The array of rank variables of all tasks.\n", + " \"\"\"\n", "\n", " num_tasks = len(starts)\n", " all_tasks = range(num_tasks)\n", @@ -109,7 +110,7 @@ " if i == j:\n", " precedences[(i, j)] = presences[i]\n", " else:\n", - " prec = model.NewBoolVar('%i before %i' % (i, j))\n", + " prec = model.NewBoolVar(f\"{i} before {j}\")\n", " precedences[(i, j)] = prec\n", " model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec)\n", "\n", @@ -120,17 +121,13 @@ " if not cp_model.ObjectIsATrueLiteral(presences[i]):\n", " tmp_array.append(presences[i].Not())\n", " # Makes sure that if i is not performed, all precedences are false.\n", - " model.AddImplication(presences[i].Not(),\n", - " precedences[(i, j)].Not())\n", - " model.AddImplication(presences[i].Not(),\n", - " precedences[(j, i)].Not())\n", + " model.AddImplication(presences[i].Not(), precedences[(i, j)].Not())\n", + " model.AddImplication(presences[i].Not(), precedences[(j, i)].Not())\n", " if not cp_model.ObjectIsATrueLiteral(presences[j]):\n", " tmp_array.append(presences[j].Not())\n", " # Makes sure that if j is not performed, all precedences are false.\n", - " model.AddImplication(presences[j].Not(),\n", - " precedences[(i, j)].Not())\n", - " model.AddImplication(presences[j].Not(),\n", - " precedences[(j, i)].Not())\n", + " model.AddImplication(presences[j].Not(), precedences[(i, j)].Not())\n", + " model.AddImplication(presences[j].Not(), precedences[(j, i)].Not())\n", " # The following bool_or will enforce that for any two intervals:\n", " # i precedes j or j precedes i or at least one interval is not\n", " # performed.\n", @@ -161,25 +158,24 @@ "\n", " # Creates intervals, half of them are optional.\n", " for t in all_tasks:\n", - " start = model.NewIntVar(0, horizon, 'start_%i' % t)\n", + " start = model.NewIntVar(0, horizon, f\"start[{t}]\")\n", " duration = t + 1\n", - " end = model.NewIntVar(0, horizon, 'end_%i' % t)\n", + " end = model.NewIntVar(0, horizon, f\"end[{t}]\")\n", " if t < num_tasks // 2:\n", - " interval = model.NewIntervalVar(start, duration, end,\n", - " 'interval_%i' % t)\n", + " interval = model.NewIntervalVar(start, duration, end, f\"interval[{t}]\")\n", " presence = True\n", " else:\n", - " presence = model.NewBoolVar('presence_%i' % t)\n", - " interval = model.NewOptionalIntervalVar(start, duration, end,\n", - " presence,\n", - " 'o_interval_%i' % t)\n", + " presence = model.NewBoolVar(f\"presence[{t}]\")\n", + " interval = model.NewOptionalIntervalVar(\n", + " start, duration, end, presence, f\"o_interval[{t}]\"\n", + " )\n", " starts.append(start)\n", " ends.append(end)\n", " intervals.append(interval)\n", " presences.append(presence)\n", "\n", " # Ranks = -1 if and only if the tasks is not performed.\n", - " ranks.append(model.NewIntVar(-1, num_tasks - 1, 'rank_%i' % t))\n", + " ranks.append(model.NewIntVar(-1, num_tasks - 1, f\"rank[{t}]\"))\n", "\n", " # Adds NoOverlap constraint.\n", " model.AddNoOverlap(intervals)\n", @@ -191,7 +187,7 @@ " model.Add(ranks[0] < ranks[1])\n", "\n", " # Creates makespan variable.\n", - " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " makespan = model.NewIntVar(0, horizon, \"makespan\")\n", " for t in all_tasks:\n", " model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t])\n", "\n", @@ -206,17 +202,21 @@ "\n", " if status == cp_model.OPTIMAL:\n", " # Prints out the makespan and the start times and ranks of all tasks.\n", - " print('Optimal cost: %i' % solver.ObjectiveValue())\n", - " print('Makespan: %i' % solver.Value(makespan))\n", + " print(f\"Optimal cost: {solver.ObjectiveValue()}\")\n", + " print(f\"Makespan: {solver.Value(makespan)}\")\n", " for t in all_tasks:\n", " if solver.Value(presences[t]):\n", - " print('Task %i starts at %i with rank %i' %\n", - " (t, solver.Value(starts[t]), solver.Value(ranks[t])))\n", + " print(\n", + " f\"Task {t} starts at {solver.Value(starts[t])} \"\n", + " f\"with rank {solver.Value(ranks[t])}\"\n", + " )\n", " else:\n", - " print('Task %i in not performed and ranked at %i' %\n", - " (t, solver.Value(ranks[t])))\n", + " print(\n", + " f\"Task {t} in not performed \"\n", + " f\"and ranked at {solver.Value(ranks[t])}\"\n", + " )\n", " else:\n", - " print('Solver exited with nonoptimal status: %i' % status)\n", + " print(f\"Solver exited with nonoptimal status: {status}\")\n", "\n", "\n", "RankingSampleSat()\n", diff --git a/examples/notebook/sat/reified_sample_sat.ipynb b/examples/notebook/sat/reified_sample_sat.ipynb index a8e6d01e9e..4ae4603d11 100644 --- a/examples/notebook/sat/reified_sample_sat.ipynb +++ b/examples/notebook/sat/reified_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Simple model with a reified constraint.\n" ] }, @@ -89,9 +90,9 @@ " \"\"\"Showcase creating a reified constraint.\"\"\"\n", " model = cp_model.CpModel()\n", "\n", - " x = model.NewBoolVar('x')\n", - " y = model.NewBoolVar('y')\n", - " b = model.NewBoolVar('b')\n", + " x = model.NewBoolVar(\"x\")\n", + " y = model.NewBoolVar(\"y\")\n", + " b = model.NewBoolVar(\"b\")\n", "\n", " # First version using a half-reified bool and.\n", " model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b)\n", diff --git a/examples/notebook/sat/schedule_requests_sat.ipynb b/examples/notebook/sat/schedule_requests_sat.ipynb index 80b5228cd4..0732f016e7 100644 --- a/examples/notebook/sat/schedule_requests_sat.ipynb +++ b/examples/notebook/sat/schedule_requests_sat.ipynb @@ -97,16 +97,13 @@ " all_nurses = range(num_nurses)\n", " all_shifts = range(num_shifts)\n", " all_days = range(num_days)\n", - " shift_requests = [[[0, 0, 1], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 1],\n", - " [0, 1, 0], [0, 0, 1]],\n", - " [[0, 0, 0], [0, 0, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0],\n", - " [0, 0, 0], [0, 0, 1]],\n", - " [[0, 1, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0], [0, 0, 0],\n", - " [0, 1, 0], [0, 0, 0]],\n", - " [[0, 0, 1], [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 0],\n", - " [1, 0, 0], [0, 0, 0]],\n", - " [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 0], [1, 0, 0],\n", - " [0, 1, 0], [0, 0, 0]]]\n", + " shift_requests = [\n", + " [[0, 0, 1], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1]],\n", + " [[0, 0, 0], [0, 0, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 0, 0], [0, 0, 1]],\n", + " [[0, 1, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0], [0, 0, 0], [0, 1, 0], [0, 0, 0]],\n", + " [[0, 0, 1], [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0], [0, 0, 0]],\n", + " [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 0]],\n", + " ]\n", "\n", " # Creates the model.\n", " model = cp_model.CpModel()\n", @@ -117,8 +114,7 @@ " for n in all_nurses:\n", " for d in all_days:\n", " for s in all_shifts:\n", - " shifts[(n, d,\n", - " s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))\n", + " shifts[(n, d, s)] = model.NewBoolVar(f\"shift_n{n}_d{d}_s{s}\")\n", "\n", " # Each shift is assigned to exactly one nurse in .\n", " for d in all_days:\n", @@ -149,36 +145,42 @@ "\n", " # pylint: disable=g-complex-comprehension\n", " model.Maximize(\n", - " sum(shift_requests[n][d][s] * shifts[(n, d, s)] for n in all_nurses\n", - " for d in all_days for s in all_shifts))\n", + " sum(\n", + " shift_requests[n][d][s] * shifts[(n, d, s)]\n", + " for n in all_nurses\n", + " for d in all_days\n", + " for s in all_shifts\n", + " )\n", + " )\n", "\n", " # Creates the solver and solve.\n", " solver = cp_model.CpSolver()\n", " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('Solution:')\n", + " print(\"Solution:\")\n", " for d in all_days:\n", - " print('Day', d)\n", + " print(\"Day\", d)\n", " for n in all_nurses:\n", " for s in all_shifts:\n", " if solver.Value(shifts[(n, d, s)]) == 1:\n", " if shift_requests[n][d][s] == 1:\n", - " print('Nurse', n, 'works shift', s, '(requested).')\n", + " print(\"Nurse\", n, \"works shift\", s, \"(requested).\")\n", " else:\n", - " print('Nurse', n, 'works shift', s,\n", - " '(not requested).')\n", + " print(\"Nurse\", n, \"works shift\", s, \"(not requested).\")\n", " print()\n", - " print(f'Number of shift requests met = {solver.ObjectiveValue()}',\n", - " f'(out of {num_nurses * min_shifts_per_nurse})')\n", + " print(\n", + " f\"Number of shift requests met = {solver.ObjectiveValue()}\",\n", + " f\"(out of {num_nurses * min_shifts_per_nurse})\",\n", + " )\n", " else:\n", - " print('No optimal solution found !')\n", + " print(\"No optimal solution found !\")\n", "\n", " # Statistics.\n", - " print('\\nStatistics')\n", - " print(' - conflicts: %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time: %f s' % solver.WallTime())\n", + " print(\"\\nStatistics\")\n", + " print(f\" - conflicts: {solver.NumConflicts()}\")\n", + " print(f\" - branches : {solver.NumBranches()}\")\n", + " print(f\" - wall time: {solver.WallTime()}s\")\n", "\n", "\n", "main()\n", diff --git a/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb index a58da5eec7..4a4dc9aba5 100644 --- a/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb +++ b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample to demonstrate how an interval can span across a break.\n" ] }, @@ -96,7 +97,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -116,23 +117,24 @@ " # Because of the break, work cannot start at 13h.\n", "\n", " start = model.NewIntVarFromDomain(\n", - " cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), 'start')\n", - " duration = model.NewIntVar(3, 4, 'duration')\n", - " end = model.NewIntVar(8, 18, 'end')\n", - " unused_interval = model.NewIntervalVar(start, duration, end, 'interval')\n", + " cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), \"start\"\n", + " )\n", + " duration = model.NewIntVar(3, 4, \"duration\")\n", + " end = model.NewIntVar(8, 18, \"end\")\n", + " unused_interval = model.NewIntervalVar(start, duration, end, \"interval\")\n", "\n", " # We have 2 states (spanning across lunch or not)\n", - " across = model.NewBoolVar('across')\n", + " across = model.NewBoolVar(\"across\")\n", " non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15])\n", " model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf(\n", - " across.Not())\n", + " across.Not()\n", + " )\n", " model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across)\n", " model.Add(duration == 3).OnlyEnforceIf(across.Not())\n", " model.Add(duration == 4).OnlyEnforceIf(across)\n", "\n", " # Search for x values in increasing order.\n", - " model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST,\n", - " cp_model.SELECT_MIN_VALUE)\n", + " model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n", "\n", " # Create a solver and solve with a fixed search.\n", " solver = cp_model.CpSolver()\n", diff --git a/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb index 5444cc4ebf..8893f0488f 100644 --- a/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb +++ b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample that solves a model and displays all solutions.\n" ] }, @@ -96,7 +97,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -110,9 +111,9 @@ "\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Create the constraints.\n", " model.Add(x != y)\n", @@ -125,8 +126,8 @@ " # Solve.\n", " status = solver.Solve(model, solution_printer)\n", "\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + " print(f\"Status = {solver.StatusName(status)}\")\n", + " print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n", "\n", "\n", "SearchForAllSolutionsSampleSat()\n", diff --git a/examples/notebook/sat/simple_sat_program.ipynb b/examples/notebook/sat/simple_sat_program.ipynb index b8b56e7372..8d1f98e25c 100644 --- a/examples/notebook/sat/simple_sat_program.ipynb +++ b/examples/notebook/sat/simple_sat_program.ipynb @@ -93,9 +93,9 @@ "\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Creates the constraints.\n", " model.Add(x != y)\n", @@ -105,11 +105,11 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", - " print('x = %i' % solver.Value(x))\n", - " print('y = %i' % solver.Value(y))\n", - " print('z = %i' % solver.Value(z))\n", + " print(f\"x = {solver.Value(x)}\")\n", + " print(f\"y = {solver.Value(y)}\")\n", + " print(f\"z = {solver.Value(z)}\")\n", " else:\n", - " print('No solution found.')\n", + " print(\"No solution found.\")\n", "\n", "\n", "SimpleSatProgram()\n", diff --git a/examples/notebook/sat/solution_hinting_sample_sat.ipynb b/examples/notebook/sat/solution_hinting_sample_sat.ipynb index 61f2fdefbc..baa7fc218c 100644 --- a/examples/notebook/sat/solution_hinting_sample_sat.ipynb +++ b/examples/notebook/sat/solution_hinting_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Code sample that solves a model using solution hinting.\n" ] }, @@ -92,9 +93,9 @@ "\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Creates the constraints.\n", " model.Add(x != y)\n", @@ -110,8 +111,8 @@ " solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z])\n", " status = solver.Solve(model, solution_printer)\n", "\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + " print(f\"Status = {solver.StatusName(status)}\")\n", + " print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n", "\n", "\n", "SolutionHintingSampleSat()\n", diff --git a/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb index db8ca7bba9..9744ddf2a9 100644 --- a/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb +++ b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Solves an optimization problem and displays all intermediate solutions.\n" ] }, @@ -95,10 +96,10 @@ " self.__solution_count = 0\n", "\n", " def on_solution_callback(self):\n", - " print('Solution %i' % self.__solution_count)\n", - " print(' objective value = %i' % self.ObjectiveValue())\n", + " print(f\"Solution {self.__solution_count}\")\n", + " print(f\" objective value = {self.ObjectiveValue()}\")\n", " for v in self.__variables:\n", - " print(' %s = %i' % (v, self.Value(v)), end=' ')\n", + " print(f\" {v}={self.Value(v)}\", end=\" \")\n", " print()\n", " self.__solution_count += 1\n", "\n", @@ -113,9 +114,9 @@ "\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Creates the constraints.\n", " model.Add(x != y)\n", @@ -127,8 +128,8 @@ " solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z])\n", " status = solver.Solve(model, solution_printer)\n", "\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + " print(f\"Status = {solver.StatusName(status)}\")\n", + " print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n", "\n", "\n", "SolveAndPrintIntermediateSolutionsSampleSat()\n", diff --git a/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb index e96f17cb51..0fce1d2a2c 100644 --- a/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb +++ b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb @@ -92,9 +92,9 @@ " model = cp_model.CpModel()\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", " # Adds an all-different constraint.\n", " model.Add(x != y)\n", "\n", @@ -107,9 +107,9 @@ " status = solver.Solve(model)\n", "\n", " if status == cp_model.OPTIMAL:\n", - " print('x = %i' % solver.Value(x))\n", - " print('y = %i' % solver.Value(y))\n", - " print('z = %i' % solver.Value(z))\n", + " print(f\"x = {solver.Value(x)}\")\n", + " print(f\"y = {solver.Value(y)}\")\n", + " print(f\"z = {solver.Value(z)}\")\n", "\n", "\n", "SolveWithTimeLimitSampleSat()\n", diff --git a/examples/notebook/sat/step_function_sample_sat.ipynb b/examples/notebook/sat/step_function_sample_sat.ipynb index a8a0931974..45bd53dff0 100644 --- a/examples/notebook/sat/step_function_sample_sat.ipynb +++ b/examples/notebook/sat/step_function_sample_sat.ipynb @@ -72,6 +72,7 @@ "id": "description", "metadata": {}, "source": [ + "\n", "Implements a step function.\n" ] }, @@ -96,7 +97,7 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", "\n", " def solution_count(self):\n", @@ -110,7 +111,7 @@ " model = cp_model.CpModel()\n", "\n", " # Declare our primary variable.\n", - " x = model.NewIntVar(0, 20, 'x')\n", + " x = model.NewIntVar(0, 20, \"x\")\n", "\n", " # Create the expression variable and implement the step function\n", " # Note it is not defined for x == 2.\n", @@ -121,23 +122,24 @@ " # -- --- 0\n", " # 0 ================ 20\n", " #\n", - " expr = model.NewIntVar(0, 3, 'expr')\n", + " expr = model.NewIntVar(0, 3, \"expr\")\n", "\n", " # expr == 0 on [5, 6] U [8, 10]\n", - " b0 = model.NewBoolVar('b0')\n", + " b0 = model.NewBoolVar(\"b0\")\n", " model.AddLinearExpressionInDomain(\n", - " x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)])).OnlyEnforceIf(b0)\n", + " x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)])\n", + " ).OnlyEnforceIf(b0)\n", " model.Add(expr == 0).OnlyEnforceIf(b0)\n", "\n", " # expr == 2 on [0, 1] U [3, 4] U [11, 20]\n", - " b2 = model.NewBoolVar('b2')\n", + " b2 = model.NewBoolVar(\"b2\")\n", " model.AddLinearExpressionInDomain(\n", - " x, cp_model.Domain.FromIntervals([(0, 1), (3, 4),\n", - " (11, 20)])).OnlyEnforceIf(b2)\n", + " x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)])\n", + " ).OnlyEnforceIf(b2)\n", " model.Add(expr == 2).OnlyEnforceIf(b2)\n", "\n", " # expr == 3 when x == 7\n", - " b3 = model.NewBoolVar('b3')\n", + " b3 = model.NewBoolVar(\"b3\")\n", " model.Add(x == 7).OnlyEnforceIf(b3)\n", " model.Add(expr == 3).OnlyEnforceIf(b3)\n", "\n", @@ -145,8 +147,7 @@ " model.AddBoolOr(b0, b2, b3)\n", "\n", " # Search for x values in increasing order.\n", - " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", - " cp_model.SELECT_MIN_VALUE)\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n", "\n", " # Create a solver and solve with a fixed search.\n", " solver = cp_model.CpSolver()\n", diff --git a/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb index 4ac2dec17f..2effb8e508 100644 --- a/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb +++ b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb @@ -98,10 +98,10 @@ " def on_solution_callback(self):\n", " self.__solution_count += 1\n", " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print(f\"{v}={self.Value(v)}\", end=\" \")\n", " print()\n", " if self.__solution_count >= self.__solution_limit:\n", - " print('Stop search after %i solutions' % self.__solution_limit)\n", + " print(f\"Stop search after {self.__solution_limit} solutions\")\n", " self.StopSearch()\n", "\n", " def solution_count(self):\n", @@ -114,9 +114,9 @@ " model = cp_model.CpModel()\n", " # Creates the variables.\n", " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " x = model.NewIntVar(0, num_vals - 1, \"x\")\n", + " y = model.NewIntVar(0, num_vals - 1, \"y\")\n", + " z = model.NewIntVar(0, num_vals - 1, \"z\")\n", "\n", " # Create a solver and solve.\n", " solver = cp_model.CpSolver()\n", @@ -125,8 +125,8 @@ " solver.parameters.enumerate_all_solutions = True\n", " # Solve.\n", " status = solver.Solve(model, solution_printer)\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + " print(f\"Status = {solver.StatusName(status)}\")\n", + " print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n", " assert solution_printer.solution_count() == 5\n", "\n", "\n",