Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Google OR-Tools
https://github.com/google/or-tools
Admin
Google Optimization Tools (OR-Tools) is an open-source software suite for solving combinatorial
...
Tokens:
515,738
Snippets:
1,086
Trust Score:
8.9
Update:
1 week ago
Context
Skills
Chat
Benchmark
86.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# OR-Tools - Google Optimization Tools OR-Tools is Google's open-source software suite for combinatorial optimization, providing fast and portable solvers for complex decision-making problems. The suite includes constraint programming solvers (CP* and CP-SAT), linear programming solvers (Glop and PDLP), mixed-integer programming wrappers, bin packing and knapsack algorithms, vehicle routing and traveling salesman problem solvers, and graph algorithms for shortest paths, network flows, and assignment problems. Written in C++ with wrappers for Python, C#, and Java, OR-Tools (version 9.15) enables developers to solve real-world optimization challenges including workforce scheduling, logistics planning, resource allocation, and production scheduling. The library integrates with commercial solvers like CPLEX, Gurobi, and SCIP while providing its own high-performance native solvers for immediate use without external dependencies. ## CP-SAT Solver - Constraint Programming with SAT The CP-SAT solver combines constraint programming with SAT solving techniques for discrete optimization problems. It supports integer variables, boolean logic, intervals, and complex constraints like all-different, no-overlap, and cumulative scheduling. ```python from ortools.sat.python import cp_model # Create the model model = cp_model.CpModel() # Create integer variables with domain [0, 2] num_vals = 3 x = model.new_int_var(0, num_vals - 1, "x") y = model.new_int_var(0, num_vals - 1, "y") z = model.new_int_var(0, num_vals - 1, "z") # Add constraint: x must not equal y model.add(x != y) # Create solver and solve solver = cp_model.CpSolver() status = solver.solve(model) # Check solution if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print(f"x = {solver.value(x)}") # Output: x = 0 print(f"y = {solver.value(y)}") # Output: y = 1 print(f"z = {solver.value(z)}") # Output: z = 0 else: print("No solution found.") ``` ## Linear Programming with GLOP Solver The GLOP solver is a simplex-based linear programming solver for continuous optimization problems. It efficiently handles large-scale LP problems with linear objectives and constraints. ```python from ortools.linear_solver import pywraplp # Create the linear solver with GLOP backend solver = pywraplp.Solver.CreateSolver("GLOP") if not solver: exit(1) infinity = solver.infinity() # Create continuous variables x = solver.NumVar(0.0, infinity, "x") y = solver.NumVar(0.0, infinity, "y") # Add constraints solver.Add(x + 7 * y <= 17.5) solver.Add(x <= 3.5) # Set objective: maximize x + 10 * y solver.Maximize(x + 10 * y) # Solve status = solver.Solve() if status == pywraplp.Solver.OPTIMAL: print(f"Objective value = {solver.Objective().Value()}") # Output: 23.0 print(f"x = {x.solution_value()}") # Output: 3.5 print(f"y = {y.solution_value()}") # Output: 2.0 print(f"Solved in {solver.wall_time()} ms") print(f"Solved in {solver.iterations()} iterations") ``` ## Mixed-Integer Programming (MIP) Solver The MIP solver handles optimization problems with integer and continuous variables. It supports various backends including CP-SAT, SCIP, CBC, and commercial solvers like Gurobi and CPLEX. ```python from ortools.linear_solver import pywraplp # Create MIP solver with CP-SAT backend solver = pywraplp.Solver.CreateSolver("SAT") if not solver: exit(1) infinity = solver.infinity() # Create integer variables x = solver.IntVar(0.0, infinity, "x") y = solver.IntVar(0.0, infinity, "y") # Add constraints solver.Add(x + 7 * y <= 17.5) solver.Add(x <= 3.5) # Set objective: maximize x + 10 * y solver.Maximize(x + 10 * y) # Solve status = solver.Solve() if status == pywraplp.Solver.OPTIMAL: print(f"Objective value = {solver.Objective().Value()}") # Output: 23.0 print(f"x = {x.solution_value()}") # Output: 3.0 print(f"y = {y.solution_value()}") # Output: 2.0 print(f"Solved in {solver.wall_time()} ms") print(f"Branch-and-bound nodes: {solver.nodes()}") ``` ## N-Queens Problem with CP-SAT The N-Queens problem demonstrates CP-SAT's all-different constraint for solving combinatorial puzzles. The solver can enumerate all solutions using a callback mechanism. ```python import time from ortools.sat.python import cp_model class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" def __init__(self, queens): cp_model.CpSolverSolutionCallback.__init__(self) self.__queens = queens self.__solution_count = 0 @property def solution_count(self): return self.__solution_count def on_solution_callback(self): self.__solution_count += 1 # Print board representation for i in range(len(self.__queens)): for j in range(len(self.__queens)): if self.value(self.__queens[j]) == i: print("Q", end=" ") else: print("_", end=" ") print() print() # Solve 8-Queens board_size = 8 model = cp_model.CpModel() # queens[i] = row position of queen in column i queens = [model.new_int_var(0, board_size - 1, f"x_{i}") for i in range(board_size)] # All rows must be different model.add_all_different(queens) # No two queens on same diagonal model.add_all_different(queens[i] + i for i in range(board_size)) model.add_all_different(queens[i] - i for i in range(board_size)) # Find all solutions solver = cp_model.CpSolver() solution_printer = NQueenSolutionPrinter(queens) solver.parameters.enumerate_all_solutions = True solver.solve(model, solution_printer) print(f"Solutions found: {solution_printer.solution_count}") # Output: 92 solutions print(f"Wall time: {solver.wall_time} s") ``` ## Nurse Scheduling with CP-SAT Employee scheduling with shift constraints demonstrates boolean variables and cardinality constraints for workforce optimization. ```python from ortools.sat.python import cp_model # Data num_nurses = 4 num_shifts = 3 num_days = 3 all_nurses = range(num_nurses) all_shifts = range(num_shifts) all_days = range(num_days) model = cp_model.CpModel() # shifts[(n, d, s)] = 1 if nurse n works shift s on day d shifts = {} for n in all_nurses: for d in all_days: for s in all_shifts: shifts[(n, d, s)] = model.new_bool_var(f"shift_n{n}_d{d}_s{s}") # Each shift is assigned to exactly one nurse for d in all_days: for s in all_shifts: model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses) # Each nurse works at most one shift per day for n in all_nurses: for d in all_days: model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts) # Distribute shifts evenly min_shifts_per_nurse = (num_shifts * num_days) // num_nurses max_shifts_per_nurse = min_shifts_per_nurse + (1 if num_shifts * num_days % num_nurses else 0) for n in all_nurses: shifts_worked = [shifts[(n, d, s)] for d in all_days for s in all_shifts] model.add(min_shifts_per_nurse <= sum(shifts_worked)) model.add(sum(shifts_worked) <= max_shifts_per_nurse) # Solve solver = cp_model.CpSolver() status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for d in all_days: print(f"Day {d}:") for n in all_nurses: for s in all_shifts: if solver.value(shifts[(n, d, s)]): print(f" Nurse {n} works shift {s}") ``` ## Job Shop Scheduling with Intervals The CP-SAT solver provides interval variables and no-overlap constraints for scheduling optimization problems like job shop scheduling. ```python import collections from ortools.sat.python import cp_model # Job shop data: [(machine_id, processing_time), ...] jobs_data = [ [(0, 3), (1, 2), (2, 2)], # Job 0 [(0, 2), (2, 1), (1, 4)], # Job 1 [(1, 4), (2, 3)], # Job 2 ] machines_count = 1 + max(task[0] for job in jobs_data for task in job) horizon = sum(task[1] for job in jobs_data for task in job) model = cp_model.CpModel() task_type = collections.namedtuple("task_type", "start end interval") all_tasks = {} machine_to_intervals = collections.defaultdict(list) # Create interval variables for each task for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): machine, duration = task suffix = f"_{job_id}_{task_id}" start_var = model.new_int_var(0, horizon, "start" + suffix) end_var = model.new_int_var(0, horizon, "end" + suffix) interval_var = model.new_interval_var(start_var, duration, end_var, "interval" + suffix) all_tasks[job_id, task_id] = task_type(start=start_var, end=end_var, interval=interval_var) machine_to_intervals[machine].append(interval_var) # No overlap on each machine for machine in range(machines_count): model.add_no_overlap(machine_to_intervals[machine]) # Precedence constraints within each job for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): model.add(all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end) # Minimize makespan obj_var = model.new_int_var(0, horizon, "makespan") model.add_max_equality(obj_var, [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)]) model.minimize(obj_var) solver = cp_model.CpSolver() status = solver.solve(model) if status == cp_model.OPTIMAL: print(f"Optimal Schedule Length: {solver.objective_value}") # Output: 11 for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): start = solver.value(all_tasks[job_id, task_id].start) print(f"Job {job_id} Task {task_id}: starts at {start}") ``` ## Multiple Knapsack Problem with MIP The bin packing and multiple knapsack problems can be efficiently solved using mixed-integer programming with boolean assignment variables. ```python from ortools.linear_solver import pywraplp # Problem data data = { "weights": [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36], "values": [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25], "bin_capacities": [100, 100, 100, 100, 100], } data["num_items"] = len(data["weights"]) data["num_bins"] = len(data["bin_capacities"]) # Create MIP solver solver = pywraplp.Solver.CreateSolver("SCIP") if not solver: exit(1) # x[i, b] = 1 if item i is packed in bin b x = {} for i in range(data["num_items"]): for b in range(data["num_bins"]): x[i, b] = solver.BoolVar(f"x_{i}_{b}") # Each item assigned to at most one bin for i in range(data["num_items"]): solver.Add(sum(x[i, b] for b in range(data["num_bins"])) <= 1) # Bin capacity constraints for b in range(data["num_bins"]): solver.Add( sum(x[i, b] * data["weights"][i] for i in range(data["num_items"])) <= data["bin_capacities"][b] ) # Maximize total value objective = solver.Objective() for i in range(data["num_items"]): for b in range(data["num_bins"]): objective.SetCoefficient(x[i, b], data["values"][i]) objective.SetMaximization() status = solver.Solve() if status == pywraplp.Solver.OPTIMAL: print(f"Total packed value: {objective.Value()}") # Output: 395.0 for b in range(data["num_bins"]): bin_weight = sum(data["weights"][i] for i in range(data["num_items"]) if x[i, b].solution_value() > 0) bin_value = sum(data["values"][i] for i in range(data["num_items"]) if x[i, b].solution_value() > 0) print(f"Bin {b}: weight={bin_weight}, value={bin_value}") ``` ## Knapsack Solver API OR-Tools provides a specialized knapsack solver with dynamic programming and branch-and-bound algorithms for single-dimensional knapsack problems. ```python from ortools.algorithms.python import knapsack_solver # Create solver using dynamic programming solver = knapsack_solver.KnapsackSolver( knapsack_solver.SolverType.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, "knapsack" ) # Problem data weights = [[565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293, 712, 147, 421, 255]] capacities = [850] values = weights[0] # In this case, value equals weight # Initialize and solve solver.init(values, weights, capacities) computed_value = solver.solve() # Get solution packed_items = [x for x in range(len(weights[0])) if solver.best_solution_contains(x)] packed_weights = [weights[0][i] for i in packed_items] print(f"Packed items: {packed_items}") # Output: [3, 9, 15] print(f"Packed weights: {packed_weights}") # Output: [130, 125, 147] print(f"Total value: {computed_value}") # Output: 850 ``` ## Vehicle Routing Problem (VRP) The routing library solves vehicle routing problems with multiple vehicles, distance constraints, and various optimization objectives. ```python from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp # Distance matrix (17 locations, distances in meters) distance_matrix = [ [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662], [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210], # ... (full matrix in actual code) ] data = { "distance_matrix": distance_matrix, "num_vehicles": 4, "depot": 0 } # Create routing index manager manager = pywrapcp.RoutingIndexManager( len(data["distance_matrix"]), data["num_vehicles"], data["depot"] ) # Create routing model routing = pywrapcp.RoutingModel(manager) # Distance callback def distance_callback(from_index, to_index): from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return data["distance_matrix"][from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) # Search parameters search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC # Solve solution = routing.SolveWithParameters(search_parameters) if solution: print(f"Objective: {solution.ObjectiveValue()}") for vehicle_id in range(data["num_vehicles"]): if not routing.IsVehicleUsed(solution, vehicle_id): continue index = routing.Start(vehicle_id) route = [] while not routing.IsEnd(index): route.append(manager.IndexToNode(index)) index = solution.Value(routing.NextVar(index)) route.append(manager.IndexToNode(index)) print(f"Vehicle {vehicle_id}: {' -> '.join(map(str, route))}") ``` ## Capacitated Vehicle Routing Problem (CVRP) CVRP adds capacity constraints to vehicle routing, ensuring each vehicle's load does not exceed its capacity while minimizing total distance. ```python from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp data = { "distance_matrix": [...], # 17x17 distance matrix "demands": [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8], "vehicle_capacities": [15, 15, 15, 15], "num_vehicles": 4, "depot": 0 } manager = pywrapcp.RoutingIndexManager( len(data["distance_matrix"]), data["num_vehicles"], data["depot"] ) routing = pywrapcp.RoutingModel(manager) # Distance callback def distance_callback(from_index, to_index): from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return data["distance_matrix"][from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) # Demand callback for capacity constraint def demand_callback(from_index): from_node = manager.IndexToNode(from_index) return data["demands"][from_node] demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback) routing.AddDimensionWithVehicleCapacity( demand_callback_index, 0, # no slack data["vehicle_capacities"], # max capacities per vehicle True, # start cumul to zero "Capacity" ) # Use guided local search metaheuristic search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH search_parameters.time_limit.FromSeconds(1) solution = routing.SolveWithParameters(search_parameters) if solution: total_distance = 0 total_load = 0 for vehicle_id in range(data["num_vehicles"]): if not routing.IsVehicleUsed(solution, vehicle_id): continue index = routing.Start(vehicle_id) route_load = 0 while not routing.IsEnd(index): node = manager.IndexToNode(index) route_load += data["demands"][node] index = solution.Value(routing.NextVar(index)) print(f"Vehicle {vehicle_id}: Load = {route_load}") ``` ## Traveling Salesman Problem (TSP) TSP is solved as a single-vehicle routing problem, finding the shortest route visiting all locations exactly once and returning to the start. ```python from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp # Location coordinates (block units) locations = [ (4, 4), # depot (2, 0), (8, 0), (0, 1), (1, 1), (5, 2), (7, 2), (3, 3), (6, 3), (5, 5), (8, 5), (1, 6), (2, 6), (3, 7), (6, 7), (0, 8), (7, 8) ] # Convert to meters (114m x 80m blocks) locations_m = [(l[0] * 114, l[1] * 80) for l in locations] # Precompute Manhattan distances distances = {} for i, from_loc in enumerate(locations_m): distances[i] = {} for j, to_loc in enumerate(locations_m): distances[i][j] = abs(from_loc[0] - to_loc[0]) + abs(from_loc[1] - to_loc[1]) manager = pywrapcp.RoutingIndexManager(len(locations), 1, 0) # 1 vehicle, depot at 0 routing = pywrapcp.RoutingModel(manager) def distance_callback(from_index, to_index): from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return distances[from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC solution = routing.SolveWithParameters(search_parameters) if solution: print(f"Objective: {solution.ObjectiveValue()}") # Total distance index = routing.Start(0) route = [] while not routing.IsEnd(index): route.append(manager.IndexToNode(index)) index = solution.Value(routing.NextVar(index)) route.append(manager.IndexToNode(index)) print(f"Route: {' -> '.join(map(str, route))}") ``` ## Maximum Flow Problem The max flow solver finds the maximum flow through a network from source to sink nodes. ```python import numpy as np from ortools.graph.python import max_flow # Create max flow solver smf = max_flow.SimpleMaxFlow() # Define network arcs with capacities start_nodes = np.array([0, 0, 0, 1, 1, 2, 2, 3, 3]) end_nodes = np.array([1, 2, 3, 2, 4, 3, 4, 2, 4]) capacities = np.array([20, 30, 10, 40, 30, 10, 20, 5, 20]) # Add arcs in bulk all_arcs = smf.add_arcs_with_capacity(start_nodes, end_nodes, capacities) # Solve: find max flow from node 0 to node 4 status = smf.solve(0, 4) if status == smf.OPTIMAL: print(f"Max flow: {smf.optimal_flow()}") # Output: 60 print("\nArc Flow / Capacity") solution_flows = smf.flows(all_arcs) for arc, flow, capacity in zip(all_arcs, solution_flows, capacities): print(f"{smf.tail(arc)} -> {smf.head(arc)} {flow:3} / {capacity:3}") print(f"Source side min-cut: {smf.get_source_side_min_cut()}") print(f"Sink side min-cut: {smf.get_sink_side_min_cut()}") ``` ## Minimum Cost Flow Problem The min cost flow solver minimizes the total cost of flowing commodities through a network with arc capacities and per-unit costs. ```python import numpy as np from ortools.graph.python import min_cost_flow # Create min cost flow solver smcf = min_cost_flow.SimpleMinCostFlow() # Define network start_nodes = np.array([0, 0, 1, 1, 1, 2, 2, 3, 4]) end_nodes = np.array([1, 2, 2, 3, 4, 3, 4, 4, 2]) capacities = np.array([15, 8, 20, 4, 10, 15, 4, 20, 5]) unit_costs = np.array([4, 4, 2, 2, 6, 1, 3, 2, 3]) # Supply/demand at each node (positive=supply, negative=demand) supplies = [20, 0, 0, -5, -15] # Add arcs with capacities and costs all_arcs = smcf.add_arcs_with_capacity_and_unit_cost( start_nodes, end_nodes, capacities, unit_costs ) # Set supplies/demands smcf.set_nodes_supplies(np.arange(len(supplies)), supplies) # Solve status = smcf.solve() if status == smcf.OPTIMAL: print(f"Minimum cost: {smcf.optimal_cost()}") # Output: 150 print("\nArc Flow / Capacity Cost") solution_flows = smcf.flows(all_arcs) costs = solution_flows * unit_costs for arc, flow, cost in zip(all_arcs, solution_flows, costs): print(f"{smcf.tail(arc)} -> {smcf.head(arc)} {flow:3} / {smcf.capacity(arc):3} {cost}") ``` ## Linear Sum Assignment Problem The linear sum assignment solver optimally assigns workers to tasks minimizing total cost, where each worker is assigned exactly one task. ```python import numpy as np from ortools.graph.python import linear_sum_assignment # Create assignment solver assignment = linear_sum_assignment.SimpleLinearSumAssignment() # Cost matrix: cost[i][j] = cost of assigning worker i to task j costs = np.array([ [90, 76, 75, 70], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115], ]) # Convert to parallel arrays end_nodes, start_nodes = np.meshgrid(np.arange(costs.shape[1]), np.arange(costs.shape[0])) start_nodes = start_nodes.ravel() end_nodes = end_nodes.ravel() arc_costs = costs.ravel() # Add arcs with costs assignment.add_arcs_with_cost(start_nodes, end_nodes, arc_costs) # Solve status = assignment.solve() if status == assignment.OPTIMAL: print(f"Total cost = {assignment.optimal_cost()}") # Output: 265 for i in range(assignment.num_nodes()): print(f"Worker {i} assigned to task {assignment.right_mate(i)} " f"(Cost = {assignment.assignment_cost(i)})") # Output: # Worker 0 assigned to task 3 (Cost = 70) # Worker 1 assigned to task 2 (Cost = 55) # Worker 2 assigned to task 1 (Cost = 95) # Worker 3 assigned to task 0 (Cost = 45) elif status == assignment.INFEASIBLE: print("No assignment is possible.") ``` ## Summary OR-Tools provides a comprehensive optimization toolkit for solving complex combinatorial and numerical optimization problems across multiple domains. The CP-SAT solver excels at constraint satisfaction and discrete optimization, supporting boolean logic, integer arithmetic, intervals, and global constraints like all-different and no-overlap. The linear and mixed-integer programming solvers handle continuous and discrete optimization with linear objectives and constraints, integrating with multiple backend solvers. The specialized routing library efficiently solves vehicle routing, traveling salesman, and pickup-delivery problems with support for time windows, capacity constraints, and multiple vehicles. Graph algorithms cover network flow optimization, shortest paths, and optimal assignment problems. The library follows a consistent pattern across all solvers: create a model, define variables with domains, add constraints, set objectives, solve, and extract solutions. Python users benefit from numpy integration for bulk data operations and callback mechanisms for solution enumeration and custom search control. OR-Tools is production-ready with extensive use in logistics, workforce scheduling, manufacturing, supply chain optimization, and telecommunications. The library's combination of high-performance C++ implementation with intuitive language bindings makes it accessible for both rapid prototyping and large-scale industrial applications requiring reliable, fast optimization.