Google OR-Tools 深潜:coomia-dip 的约束求解与决策优化
1. [OR-Tools 在 coomia-dip 中的定位](#1-or-tools-在-coomia-dip-中的定位)
Coomia发布于 2025年11月27日11 分钟阅读
分享本文Twitter / X
“系列:S8 技术组件深潜 · 第 18 篇 | 难度:高级 | 阅读时间:20 分钟
Google OR-Tools 深潜:coomia-dip 的约束求解与决策优化
#TL;DR
- Google OR-Tools 是 coomia-dip Reasoning & Decision Layer(Reasoning & Decision Layer)的核心优化引擎,为资源分配、路径规划、排程调度等决策场景提供数学优化能力
- 本文深入分析 OR-Tools 的四大求解器(LP/MIP/CP-SAT/Routing)、建模方法论、以及 coomia-dip 中 5 种典型优化场景的实现
- 涵盖目标函数设计、约束建模、求解参数调优、解的质量评估、以及与 Ontology 数据模型的集成
#目录
- OR-Tools 在 coomia-dip 中的定位
- 四大求解器
- 线性规划与混合整数规划
- CP-SAT 约束规划
- 路径优化
- 排程调度
- 资源分配
- 与 Ontology 的集成
- 求解性能调优
- 生产部署模式
- Key Takeaways
#1. OR-Tools 在 coomia-dip 中的定位
#1.1 决策优化场景
| 场景 | 求解器 | 变量规模 | 求解时间 |
|---|---|---|---|
| 供应链资源分配 | MIP | 10K-100K | 秒级 |
| 物流路径规划 | Routing | 100-10K 节点 | 秒级 |
| 人员排程 | CP-SAT | 1K-50K | 秒-分钟 |
| 库存优化 | LP | 100K+ | 毫秒级 |
| 设备维护计划 | CP-SAT | 1K-10K | 秒级 |
#1.2 架构集成
Code
┌────────────────────────────────────────┐
│ Reasoning & Decision Layer: Reasoning & Decision │
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Decision │ │ Reasoning │ │
│ │ Engine │ │ Engine │ │
│ └──────┬───────┘ └──────────────┘ │
│ │ │
│ ┌──────┴───────────────────────┐ │
│ │ OR-Tools Solver Service │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ LP │ │CP-SAT│ │Route │ │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ └──────────────────────────────┘ │
└────────────────────────────────────────┘
#2. 四大求解器
#2.1 求解器选择矩阵
| 求解器 | 变量类型 | 约束类型 | 最优性 | 速度 |
|---|---|---|---|---|
| GLOP (LP) | 连续 | 线性 | 全局最优 | 极快 |
| SCIP (MIP) | 连续+整数 | 线性 | 全局最优 | 快 |
| CP-SAT | 整数+布尔 | 线性+逻辑 | 全局最优 | 中 |
| Routing | 整数 | 路径约束 | 近似最优 | 快 |
#3. 线性规划与混合整数规划
#3.1 资源分配 LP 示例
Python
from ortools.linear_solver import pywraplp
def optimize_resource_allocation(
resources: list[dict],
tasks: list[dict],
costs: list[list[float]],
) -> dict:
"""资源分配优化:最小化总成本"""
solver = pywraplp.Solver.CreateSolver("GLOP")
n_resources = len(resources)
n_tasks = len(tasks)
# 决策变量:x[i][j] = 资源 i 分配给任务 j 的比例
x = [[solver.NumVar(0, 1, f"x_{i}_{j}")
for j in range(n_tasks)]
for i in range(n_resources)]
# 约束 1:每个任务必须被完全分配
for j in range(n_tasks):
solver.Add(sum(x[i][j] for i in range(n_resources)) == 1)
# 约束 2:每个资源的总分配不超过其容量
for i in range(n_resources):
solver.Add(
sum(x[i][j] * tasks[j]["demand"]
for j in range(n_tasks))
<= resources[i]["capacity"]
)
# 目标:最小化总成本
solver.Minimize(
sum(x[i][j] * costs[i][j]
for i in range(n_resources)
for j in range(n_tasks))
)
status = solver.Solve()
if status == pywraplp.Solver.OPTIMAL:
return {
"status": "optimal",
"total_cost": solver.Objective().Value(),
"assignments": [
{"resource": i, "task": j, "allocation": x[i][j].solution_value()}
for i in range(n_resources)
for j in range(n_tasks)
if x[i][j].solution_value() > 0.01
],
}
return {"status": "infeasible"}
#3.2 MIP 示例:带固定成本的分配
Python
def optimize_facility_location(
facilities: list[dict],
customers: list[dict],
fixed_costs: list[float],
transport_costs: list[list[float]],
) -> dict:
"""设施选址:最小化固定成本 + 运输成本"""
solver = pywraplp.Solver.CreateSolver("SCIP")
n_facilities = len(facilities)
n_customers = len(customers)
# 整数变量:y[i] = 是否开设设施 i
y = [solver.IntVar(0, 1, f"y_{i}") for i in range(n_facilities)]
# 连续变量:x[i][j] = 设施 i 服务客户 j 的比例
x = [[solver.NumVar(0, 1, f"x_{i}_{j}")
for j in range(n_customers)]
for i in range(n_facilities)]
# 约束:每个客户被完全服务
for j in range(n_customers):
solver.Add(sum(x[i][j] for i in range(n_facilities)) == 1)
# 约束:只有开设的设施才能服务
for i in range(n_facilities):
for j in range(n_customers):
solver.Add(x[i][j] <= y[i])
# 约束:设施容量
for i in range(n_facilities):
solver.Add(
sum(x[i][j] * customers[j]["demand"]
for j in range(n_customers))
<= facilities[i]["capacity"] * y[i]
)
# 目标
solver.Minimize(
sum(fixed_costs[i] * y[i] for i in range(n_facilities))
+ sum(transport_costs[i][j] * x[i][j]
for i in range(n_facilities)
for j in range(n_customers))
)
status = solver.Solve()
if status == pywraplp.Solver.OPTIMAL:
opened = [i for i in range(n_facilities) if y[i].solution_value() > 0.5]
return {"status": "optimal", "opened_facilities": opened,
"total_cost": solver.Objective().Value()}
return {"status": "infeasible"}
#4. CP-SAT 约束规划
#4.1 人员排程
Python
from ortools.sat.python import cp_model
def optimize_staff_scheduling(
employees: list[dict],
shifts: list[dict],
days: int = 7,
) -> dict:
"""员工排班优化"""
model = cp_model.CpModel()
n_employees = len(employees)
n_shifts = len(shifts)
# 决策变量:schedule[e][d][s] = 员工 e 在第 d 天是否上班次 s
schedule = {}
for e in range(n_employees):
for d in range(days):
for s in range(n_shifts):
schedule[(e, d, s)] = model.NewBoolVar(f"schedule_{e}_{d}_{s}")
# 约束 1:每个班次需要最少人数
for d in range(days):
for s in range(n_shifts):
model.Add(
sum(schedule[(e, d, s)] for e in range(n_employees))
>= shifts[s]["min_staff"]
)
# 约束 2:每人每天最多一个班次
for e in range(n_employees):
for d in range(days):
model.Add(
sum(schedule[(e, d, s)] for s in range(n_shifts)) <= 1
)
# 约束 3:每周工作天数限制
for e in range(n_employees):
model.Add(
sum(schedule[(e, d, s)]
for d in range(days) for s in range(n_shifts))
<= employees[e].get("max_days", 5)
)
# 约束 4:不能连续上夜班超过 2 天
night_shift = n_shifts - 1
for e in range(n_employees):
for d in range(days - 2):
model.Add(
schedule[(e, d, night_shift)]
+ schedule[(e, d + 1, night_shift)]
+ schedule[(e, d + 2, night_shift)]
<= 2
)
# 目标:最大化员工偏好满足度
preferences = []
for e in range(n_employees):
for d in range(days):
for s in range(n_shifts):
pref = employees[e].get("preferences", {}).get(f"{d}_{s}", 0)
preferences.append(schedule[(e, d, s)] * pref)
model.Maximize(sum(preferences))
# 求解
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 30
solver.parameters.num_workers = 4
status = solver.Solve(model)
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
result = []
for e in range(n_employees):
for d in range(days):
for s in range(n_shifts):
if solver.Value(schedule[(e, d, s)]) == 1:
result.append({
"employee": employees[e]["name"],
"day": d,
"shift": shifts[s]["name"],
})
return {"status": "optimal" if status == cp_model.OPTIMAL else "feasible",
"schedule": result, "objective": solver.ObjectiveValue()}
return {"status": "infeasible"}
#5. 路径优化
#5.1 车辆路径问题(VRP)
Python
from ortools.constraint_solver import routing_enums_pb2, pywrapcp
def optimize_vehicle_routing(
depot: int,
locations: list[tuple[float, float]],
demands: list[int],
vehicle_capacities: list[int],
num_vehicles: int,
) -> dict:
"""带容量约束的车辆路径优化"""
n = len(locations)
# 创建距离矩阵
distance_matrix = compute_distance_matrix(locations)
manager = pywrapcp.RoutingIndexManager(n, num_vehicles, depot)
routing = pywrapcp.RoutingModel(manager)
# 距离回调
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(distance_matrix[from_node][to_node])
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# 容量约束
def demand_callback(from_index):
from_node = manager.IndexToNode(from_index)
return demands[from_node]
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
routing.AddDimensionWithVehicleCapacity(
demand_callback_index, 0, vehicle_capacities, True, "Capacity"
)
# 求解参数
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.seconds = 30
solution = routing.SolveWithParameters(search_parameters)
if solution:
routes = []
for v in range(num_vehicles):
route = []
index = routing.Start(v)
while not routing.IsEnd(index):
route.append(manager.IndexToNode(index))
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
routes.append(route)
return {"status": "solved", "routes": routes,
"total_distance": solution.ObjectiveValue()}
return {"status": "no_solution"}
#6. 排程调度
#6.1 Job Shop 调度
Python
def optimize_job_shop(
jobs: list[list[tuple[int, int]]], # [(machine, duration), ...]
num_machines: int,
) -> dict:
"""Job Shop 调度优化:最小化 Makespan"""
model = cp_model.CpModel()
horizon = sum(d for job in jobs for _, d in job)
all_tasks = {}
for job_id, job in enumerate(jobs):
for task_id, (machine, duration) in enumerate(job):
start = model.NewIntVar(0, horizon, f"start_{job_id}_{task_id}")
end = model.NewIntVar(0, horizon, f"end_{job_id}_{task_id}")
interval = model.NewIntervalVar(start, duration, end,
f"interval_{job_id}_{task_id}")
all_tasks[(job_id, task_id)] = (start, end, interval, machine, duration)
# 约束:同一 Job 内的 Task 顺序执行
for job_id, job in enumerate(jobs):
for task_id in range(len(job) - 1):
model.Add(all_tasks[(job_id, task_id + 1)][0]
>= all_tasks[(job_id, task_id)][1])
# 约束:同一 Machine 上的 Task 不重叠
for m in range(num_machines):
machine_intervals = [
all_tasks[key][2]
for key in all_tasks
if all_tasks[key][3] == m
]
model.AddNoOverlap(machine_intervals)
# 目标:最小化 Makespan
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, [
all_tasks[key][1] for key in all_tasks
])
model.Minimize(makespan)
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 60
status = solver.Solve(model)
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
schedule = []
for key, (start, end, _, machine, duration) in all_tasks.items():
schedule.append({
"job": key[0], "task": key[1],
"machine": machine, "start": solver.Value(start),
"end": solver.Value(end),
})
return {"status": "optimal" if status == cp_model.OPTIMAL else "feasible",
"makespan": solver.Value(makespan), "schedule": schedule}
return {"status": "infeasible"}
#7. 资源分配
Python
def optimize_budget_allocation(
projects: list[dict],
total_budget: float,
min_allocation: float = 0,
) -> dict:
"""预算分配优化:最大化加权收益"""
solver = pywraplp.Solver.CreateSolver("GLOP")
n = len(projects)
x = [solver.NumVar(min_allocation, total_budget, f"x_{i}") for i in range(n)]
# 预算约束
solver.Add(sum(x) <= total_budget)
# 项目最低/最高约束
for i, project in enumerate(projects):
if "min_budget" in project:
solver.Add(x[i] >= project["min_budget"])
if "max_budget" in project:
solver.Add(x[i] <= project["max_budget"])
# 目标:最大化加权 ROI
solver.Maximize(sum(x[i] * projects[i]["expected_roi"] for i in range(n)))
status = solver.Solve()
if status == pywraplp.Solver.OPTIMAL:
return {"status": "optimal",
"allocations": {projects[i]["name"]: x[i].solution_value() for i in range(n)},
"total_expected_return": solver.Objective().Value()}
return {"status": "infeasible"}
#8. 与 Ontology 的集成
Python
class OntologyOptimizationService:
"""将 Ontology 数据模型与 OR-Tools 求解器集成"""
async def optimize(self, world_id: str, problem_type: str, params: dict) -> dict:
# 1. 从 Ontology 加载数据
data = await self.load_ontology_data(world_id, problem_type)
# 2. 构建优化模型
solver_map = {
"resource_allocation": optimize_resource_allocation,
"vehicle_routing": optimize_vehicle_routing,
"staff_scheduling": optimize_staff_scheduling,
"job_shop": optimize_job_shop,
"budget_allocation": optimize_budget_allocation,
}
solver_func = solver_map[problem_type]
# 3. 求解(CPU 密集,在线程池中执行)
result = await asyncio.get_event_loop().run_in_executor(
self.executor, solver_func, **data, **params
)
# 4. 将结果写回 Ontology
await self.store_decision_result(world_id, problem_type, result)
return result
#9. 求解性能调优
| 参数 | 说明 | 推荐值 |
|---|---|---|
max_time_in_seconds | 求解时间上限 | 10-60s |
num_workers | 并行求解线程 | CPU 核数 |
log_search_progress | 输出求解日志 | 开发时 True |
relative_gap_limit | 相对间隙(MIP) | 0.01 (1%) |
enumerate_all_solutions | 枚举所有解 | False |
#10. 生产部署模式
Python
# 异步包装 OR-Tools(CPU 密集型)
class SolverService:
def __init__(self, max_workers: int = 4):
self.executor = ProcessPoolExecutor(max_workers=max_workers)
async def solve(self, problem: OptimizationProblem) -> SolverResult:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
self.executor, self._sync_solve, problem
)
def _sync_solve(self, problem: OptimizationProblem) -> SolverResult:
# 在独立进程中执行,避免 GIL 影响
solver = create_solver(problem)
return solver.solve()
#11. Key Takeaways
| 主题 | 关键结论 |
|---|---|
| 求解器选择 | LP 连续 / MIP 整数 / CP-SAT 逻辑 / Routing 路径 |
| 建模 | 先定义变量和约束,最后设目标函数 |
| 性能 | 设置时间上限,接受次优解 |
| 集成 | 从 Ontology 加载数据,结果写回 |
| 部署 | ProcessPoolExecutor 避免 GIL |
| CP-SAT | 最灵活,支持逻辑约束和 NoOverlap |
| VRP | 使用 Guided Local Search 元启发式 |
“下一篇预告:S8-19 将深入 Trino 查询联邦,探讨 coomia-dip 如何实现跨引擎统一查询。