PythonPuLP实战用数学建模解决7类真实选址问题当连锁便利店计划新开20家门店时如何科学布局才能最大化覆盖目标人群当物流企业需要新建区域分拨中心时怎样选择位置才能让运输成本降低15%这些看似复杂的商业决策其实都可以通过数学建模找到最优解。本文将带你用Python的PuLP库从零构建7种不同类型的选址模型解决实际业务中的空间优化难题。1. 选址问题背后的商业价值与数学原理选址问题(Facility Location Problem)是运筹学中最具实用价值的领域之一。根据麦肯锡的研究报告优秀的选址方案可以帮助零售企业提升30%的客户到店率让物流企业节省20%以上的运输成本。在公共卫生领域合理的急救中心布局甚至能缩短40%的紧急响应时间。这类问题的数学本质是在给定的空间约束条件下找到使目标函数最优的设施分布方案。常见的优化目标包括最小化总成本包括建设成本和运营成本最大化覆盖率使服务能够覆盖最多需求点平衡服务能力避免某些设施过载而其他设施闲置用数学语言描述一个典型的选址问题包含以下要素# 伪代码表示选址问题的基本结构 class FacilityLocationProblem: def __init__(self): self.facilities [] # 候选设施位置 self.customers [] # 需求点位置 self.distance_matrix [] # 距离/成本矩阵 self.demands [] # 各需求点的需求量 self.capacities [] # 各设施的服务能力 self.fixed_costs [] # 设施建设固定成本在实际应用中根据不同的业务场景会衍生出多种变体模型。下面我们就来剖析7种最常见的类型及其Python实现。2. P-中位问题连锁零售店的最优布局2.1 问题场景与数学模型假设某连锁品牌准备在城市的15个候选位置中选择8处开设新店要求所有门店到各居民区的平均距离最小。这就是典型的P-中位问题(P-median Problem)。数学模型可以表示为$$ \begin{aligned} \text{最小化} \sum_{i\in M}\sum_{j\in N} w_i d_{ij} y_{ij} \ \text{约束条件:} \ \quad \sum_{j\in N} x_j P \quad \text{(选择P个设施)} \ \quad \sum_{j\in N} y_{ij} 1 \quad \forall i \in M \quad \text{(每个需求点只分配一个设施)} \ \quad y_{ij} \leq x_j \quad \forall i\in M, j\in N \quad \text{(只有开放的设施才能服务)} \ \quad x_j, y_{ij} \in {0,1} \end{aligned} $$其中$x_j$表示是否选择位置j作为设施$y_{ij}$表示需求点i是否由设施j服务$w_i$是需求点i的权重(如人口数量)$d_{ij}$是需求点i到设施j的距离2.2 Python实现与求解使用PuLP库实现的完整代码如下import pulp import numpy as np def solve_p_median(demands, distance_matrix, p): 求解P-中位问题 n_facilities len(distance_matrix[0]) n_customers len(demands) # 创建问题实例 prob pulp.LpProblem(P_Median_Problem, pulp.LpMinimize) # 创建决策变量 x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 设置目标函数 prob pulp.lpSum([demands[i] * distance_matrix[i][j] * y[(i,j)] for i in range(n_customers) for j in range(n_facilities)]) # 添加约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for i in range(n_customers): for j in range(n_facilities): prob y[(i,j)] x[j] # 求解问题 prob.solve() # 提取结果 selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {} for i in range(n_customers): for j in range(n_facilities): if pulp.value(y[(i,j)]) 1: assignments[i] j break return selected, assignments, pulp.value(prob.objective)2.3 实际应用示例假设我们在一个5×5的网格区域中有25个候选位置需要选择3个位置建立配送中心服务周边10个社区# 生成模拟数据 np.random.seed(42) locations np.random.rand(25, 2) * 10 # 25个设施的坐标 customers np.random.rand(10, 2) * 10 # 10个需求点的坐标 demands np.random.randint(50, 200, size10) # 各需求点的需求量 # 计算距离矩阵 distance_matrix np.zeros((10, 25)) for i in range(10): for j in range(25): distance_matrix[i,j] np.linalg.norm(customers[i]-locations[j]) # 求解问题 selected, assignments, total_cost solve_p_median(demands, distance_matrix, p3) print(f选中的设施位置索引: {selected}) print(f各需求点分配方案: {assignments}) print(f总加权距离: {total_cost:.2f})运行结果可能如下选中的设施位置索引: [3, 12, 20] 各需求点分配方案: {0: 3, 1: 12, 2: 3, 3: 20, 4: 12, 5: 20, 6: 3, 7: 12, 8: 20, 9: 3} 总加权距离: 342.563. P-中心问题应急设施的公平布局3.1 问题特点与模型构建P-中心问题(P-center Problem)追求的是最差情况下的最优解即最小化所有需求点到其最近设施的最大距离。这在医院、消防站等应急设施布局中尤为重要。数学模型与P-中位问题类似但目标函数变为$$ \begin{aligned} \text{最小化} \quad D \ \text{约束条件:} \ \quad \sum_{j\in N} w_i d_{ij} y_{ij} \leq D \quad \forall i \in M \ \quad \text{(其他约束与P-中位问题相同)} \end{aligned} $$其中D表示所有需求点到最近设施的最大距离。3.2 Python实现关键点def solve_p_center(distance_matrix, p): 求解P-中心问题 n_facilities len(distance_matrix[0]) n_customers len(distance_matrix) prob pulp.LpProblem(P_Center_Problem, pulp.LpMinimize) # 决策变量 x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) D pulp.LpVariable(D, lowBound0) # 最大距离变量 # 目标函数 prob D # 约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for i in range(n_customers): for j in range(n_facilities): prob y[(i,j)] x[j] prob distance_matrix[i][j] * y[(i,j)] D prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {i: j for i in range(n_customers) for j in range(n_facilities) if pulp.value(y[(i,j)]) 1} return selected, assignments, pulp.value(D)3.3 应用案例城市消防站布局假设一个城市有8个区域需要在其中选择若干个位置建立消防站确保任何区域发生火灾时消防车都能在10分钟内到达# 消防站到各区域的最短到达时间(分钟) response_time [ [7, 12, 18, 20, 24, 26, 25, 28], # 消防站1到各区域时间 [14, 5, 8, 15, 16, 18, 18, 18], # 消防站2 [19, 9, 4, 14, 10, 22, 16, 13], # 消防站3 [14, 15, 15, 10, 18, 15, 14, 18], # 消防站4 [20, 18, 12, 20, 9, 25, 14, 12], # 消防站5 [18, 21, 20, 16, 20, 6, 10, 15], # 消防站6 [22, 20, 15, 16, 15, 5, 9, 8], # 消防站7 [30, 22, 15, 20, 14, 18, 8, 6] # 消防站8 ] # 转换为覆盖矩阵(1表示能在10分钟内到达) coverage [[1 if t 10 else 0 for t in row] for row in response_time] # 求解集合覆盖问题(最小化消防站数量) selected, _, max_time solve_p_center(np.array(response_time), p4) print(f选择的消防站位置: {selected}) print(f最长的响应时间: {max_time}分钟)4. 集合覆盖问题5G基站部署优化4.1 模型特点与适用场景集合覆盖问题(Set Covering Problem)要求在覆盖所有需求点的前提下使用最少数量的设施。这在通信基站部署、公共设施布局中很常见。数学模型为$$ \begin{aligned} \text{最小化} \sum_{j\in N} c_j x_j \ \text{约束条件:} \ \quad \sum_{j\in N_i} x_j \geq 1 \quad \forall i \in M \ \quad x_j \in {0,1} \end{aligned} $$其中$N_i {j | a_{ij}1}$是能覆盖需求点i的候选设施集合。4.2 Python实现与优化技巧def solve_set_covering(coverage_matrix, costsNone): 求解集合覆盖问题 n_facilities len(coverage_matrix) n_customers len(coverage_matrix[0]) if costs is None: costs [1] * n_facilities # 默认每个设施成本相同 prob pulp.LpProblem(Set_Covering_Problem, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) # 目标函数最小化总成本 prob pulp.lpSum([costs[j] * x[j] for j in range(n_facilities)]) # 约束条件每个需求点至少被一个设施覆盖 for i in range(n_customers): prob pulp.lpSum([x[j] for j in range(n_facilities) if coverage_matrix[j][i] 1]) 1 prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] return selected, pulp.value(prob.objective)4.3 5G基站部署实战案例假设某城区需要部署5G基站现有20个候选站址需要覆盖50个重点区域# 生成随机覆盖矩阵(20基站×50区域) np.random.seed(42) coverage np.random.randint(0, 2, size(20, 50)) # 设置部分基站成本不同(如地形因素) costs [1] * 15 [2] * 5 # 后5个基站建设成本更高 selected, total_cost solve_set_covering(coverage, costs) print(f需要建设{len(selected)}个基站) print(f选中的基站索引: {selected}) print(f总建设成本: {total_cost})5. 最大覆盖问题疫苗接种点布局策略5.1 问题定义与模型构建最大覆盖问题(Maximal Covering Problem)是在设施数量有限的情况下最大化被覆盖的需求量。适用于资源受限时的设施布局。数学模型为$$ \begin{aligned} \text{最大化} \sum_{i\in M} w_i z_i \ \text{约束条件:} \ \quad z_i \leq \sum_{j\in N_i} x_j \quad \forall i \in M \ \quad \sum_{j\in N} x_j P \ \quad x_j, z_i \in {0,1} \end{aligned} $$其中$z_i$表示需求点i是否被覆盖。5.2 Python实现与结果分析def solve_maximal_covering(demands, coverage_matrix, p): 求解最大覆盖问题 n_facilities len(coverage_matrix) n_customers len(demands) prob pulp.LpProblem(Maximal_Covering_Problem, pulp.LpMaximize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) z pulp.LpVariable.dicts(z, range(n_customers), catBinary) # 目标函数最大化覆盖的需求量 prob pulp.lpSum([demands[i] * z[i] for i in range(n_customers)]) # 约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob z[i] pulp.lpSum([x[j] for j in range(n_facilities) if coverage_matrix[j][i] 1]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] covered [i for i in range(n_customers) if pulp.value(z[i]) 1] coverage_rate sum(demands[i] for i in covered) / sum(demands) return selected, covered, coverage_rate5.3 疫苗接种点布局实战假设某地区有30个社区卫生部门计划设立5个疫苗接种点# 社区人口数据(千人) population np.random.randint(5, 50, size30) # 生成覆盖矩阵(假设每个接种点能覆盖周边8-12个社区) np.random.seed(42) coverage np.zeros((30, 30)) for j in range(30): # 随机选择10±2个社区能被该接种点覆盖 covered np.random.choice(30, np.random.randint(8, 13), replaceFalse) coverage[j, covered] 1 selected, covered, rate solve_maximal_covering(population, coverage, p5) print(f选中的接种点位置: {selected}) print(f覆盖了{len(covered)}个社区) print(f人口覆盖率: {rate:.1%})6. 高级应用带容量限制的选址问题6.1 问题扩展与模型增强现实中的设施通常有服务能力限制。带容量限制的选址问题(Capacitated Facility Location)在基础模型上增加了设施容量约束$$ \begin{aligned} \text{最小化} \sum_{j\in N} f_j x_j \sum_{i\in M}\sum_{j\in N} c_{ij} y_{ij} \ \text{约束条件:} \ \quad \sum_{j\in N} y_{ij} 1 \quad \forall i \in M \ \quad \sum_{i\in M} d_i y_{ij} \leq C_j x_j \quad \forall j \in N \ \quad x_j, y_{ij} \in {0,1} \end{aligned} $$其中$f_j$是设施j的固定开设成本$c_{ij}$是需求点i由设施j服务的成本$d_i$是需求点i的需求量$C_j$是设施j的容量6.2 Python实现与大规模问题处理def solve_capacitated_fl(demands, costs, fixed_costs, capacities): 求解带容量限制的选址问题 n_facilities len(fixed_costs) n_customers len(demands) prob pulp.LpProblem(Capacitated_FL, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 目标函数固定成本运输成本 prob (pulp.lpSum([fixed_costs[j] * x[j] for j in range(n_facilities)]) pulp.lpSum([costs[i][j] * y[(i,j)] for i in range(n_customers) for j in range(n_facilities)])) # 约束条件 for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for j in range(n_facilities): prob (pulp.lpSum([demands[i] * y[(i,j)] for i in range(n_customers)]) capacities[j] * x[j]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {i: j for i in range(n_customers) for j in range(n_facilities) if pulp.value(y[(i,j)]) 1} total_cost pulp.value(prob.objective) return selected, assignments, total_cost6.3 物流仓库选址实战案例某电商企业需要在华北地区建立区域仓库# 20个城市的需求量(万件/月) demands np.random.randint(50, 200, size20) # 10个候选仓库的固定成本(万元)和容量(万件/月) fixed_costs np.random.randint(500, 1000, size10) capacities np.random.randint(1000, 3000, size10) # 运输成本矩阵(万元/万件) transport_costs np.random.rand(20, 10) * 0.5 0.2 selected, assignments, total_cost solve_capacitated_fl( demands, transport_costs, fixed_costs, capacities) print(f选中的仓库位置: {selected}) print(f总成本(固定运输): {total_cost:.2f}万元)7. 动态与随机选址问题进阶7.1 动态选址问题动态选址考虑时间因素设施布局可能随时间变化。解决方法通常是将时间分段在每个时间段求解静态问题并考虑切换成本。7.2 随机选址问题当需求或成本不确定时可以使用随机规划。常见方法是场景法(Scenario Approach)对每种可能场景求解后取期望值。def solve_stochastic_fl(demands_scenarios, probs, costs, fixed_costs, capacities): 求解随机选址问题(场景法) n_scenarios len(demands_scenarios) n_facilities len(fixed_costs) n_customers len(demands_scenarios[0]) prob pulp.LpProblem(Stochastic_FL, pulp.LpMinimize) # 第一阶段决策设施选址(在所有场景中相同) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) # 第二阶段决策每个场景下的分配 y pulp.LpVariable.dicts(y, [(s,i,j) for s in range(n_scenarios) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 目标函数固定成本 期望运输成本 prob (pulp.lpSum([fixed_costs[j] * x[j] for j in range(n_facilities)]) pulp.lpSum([probs[s] * costs[i][j] * y[(s,i,j)] for s in range(n_scenarios) for i in range(n_customers) for j in range(n_facilities)])) # 约束条件 for s in range(n_scenarios): for i in range(n_customers): prob pulp.lpSum([y[(s,i,j)] for j in range(n_facilities)]) 1 for s in range(n_scenarios): for j in range(n_facilities): prob (pulp.lpSum([demands_scenarios[s][i] * y[(s,i,j)] for i in range(n_customers)]) capacities[j] * x[j]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] return selected7.3 实际应用季节性需求下的零售网络优化# 三种需求场景(正常、旺季、淡季)及其概率 demands_scenarios [ np.random.randint(50, 150, size15), # 正常 np.random.randint(100, 250, size15), # 旺季 np.random.randint(20, 80, size15) # 淡季 ] probs [0.6, 0.3, 0.1] # 其他参数 fixed_costs [800, 800, 1000, 1000, 1200] capacities [2000, 2000, 2500, 2500, 3000] transport_costs np.random.rand(15, 5) * 0.8 0.2 selected solve_stochastic_fl(demands_scenarios, probs, transport_costs, fixed_costs, capacities) print(f最优仓库选址: {selected})8. 性能优化与实战建议8.1 大规模问题求解技巧当问题规模较大时(如超过1000个变量)可以尝试以下优化方法启发式算法如遗传算法、模拟退火等分解算法如Benders分解商业求解器如Gurobi、CPLEX等问题简化聚类减少需求点数量8.2 数据预处理建议距离计算优化from scipy.spatial.distance import cdist # 高效计算距离矩阵 locations np.random.rand(100, 2) * 100 # 100个设施的坐标 customers np.random.rand(500, 2) * 100 # 500个需求点的坐标 distance_matrix cdist(customers, locations, euclidean)稀疏矩阵处理from scipy.sparse import csr_matrix # 将覆盖矩阵转换为稀疏格式 coverage np.random.randint(0, 2, size(50, 200)) sparse_coverage csr_matrix(coverage)8.3 模型验证与结果解读敏感性分析检查关键参数变化对结果的影响场景测试在极端情况下验证模型鲁棒性可视化验证在地图上绘制选址结果import matplotlib.pyplot as plt def plot_solution(locations, customers, selected, assignments): 可视化选址结果 plt.figure(figsize(10, 8)) # 绘制所有候选位置 plt.scatter(locations[:,0], locations[:,1], cblue, label候选位置, alpha0.5) # 绘制选中的设施 plt.scatter(locations[selected,0], locations[selected,1], cred, s100, label选中设施) # 绘制需求点及其分配 colors plt.cm.tab10(np.linspace(0, 1, len(selected))) for i, j in assignments.items(): plt.plot([customers[i,0], locations[j,0]], [customers[i,1], locations[j,1]], ccolors[selected.index(j)], alpha0.3) plt.scatter(customers[:,0], customers[:,1], cgreen, marker^, label需求点) plt.legend() plt.xlabel(经度) plt.ylabel(纬度) plt.title(选址方案可视化) plt.grid(True) plt.show() # 使用前面的P-中位问题结果进行可视化 plot_solution(locations, customers, selected, assignments)9. 行业应用全景图选址模型在各行业的典型应用场景行业应用场景典型模型关键指标零售门店布局最大覆盖客流量、覆盖率物流仓库/分拣中心带容量限制选址运输成本、响应时间医疗医院/诊所布局P-中心急救响应时间公共消防站/派出所集合覆盖全覆盖时间通信基站/机房部署最大覆盖信号覆盖率教育学校选址P-中位平均通学距离金融ATM机布局最大覆盖服务人口10. 常见问题与解决方案Q1如何处理非欧几里得距离在实际应用中距离可能不是直线距离而是道路网络距离(使用OSMNX库计算)运输时间(考虑交通状况)物流成本(结合多种运输方式)import osmnx as ox # 获取城市道路网络 G ox.graph_from_place(北京市, 中国, network_typedrive) # 计算两点间的最短路径距离 orig (39.9042, 116.4074) # 天安门 dest (39.9999, 116.3262) # 中关村 orig_node ox.distance.nearest_nodes(G, orig[1], orig[0]) dest_node ox.distance.nearest_nodes(G, dest[1], dest[0]) route ox.shortest_path(G, orig_node, dest_node, weightlength) road_distance sum(ox.utils_graph.get_route_edge_attributes(G, route, length))/1000 # kmQ2如何考虑地理限制因素可以通过以下方式在模型中融入地理限制预处理排除不可行位置添加约束条件在目标函数中加入惩罚项Q3模型求解时间过长怎么办使用启发式算法获得近似解缩小问题规模(如聚类合并需求点)尝试不同的求解器参数考虑分解算法或并行计算11. 技术选型对比不同选址问题适用的Python工具对比问题类型推荐工具优点局限性小规模精确求解PuLP简单易用接口统一大规模问题性能差中大规模问题Pyomo支持更多高级功能学习曲线较陡超大规模问题启发式算法(DEAP等)可处理复杂约束不能保证最优解地理空间分析OSMNXNetworkX真实路网建模计算复杂度高12. 前沿发展方向机器学习结合使用预测模型预估未来需求多目标优化同时考虑成本、覆盖率、公平性等鲁棒优化应对极端情况下的系统稳定性实时动态调整基于物联网数据的自适应优化# 示例使用机器学习预测需求 from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split # 假设有历史需求数据及相关特征 X np.random.rand(100, 5) # 特征人口密度、收入水平等 y np.random.randint(50, 200, size100) # 历史需求量 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) model RandomForestRegressor() model.fit(X_train, y_train) # 预测新区域的需求 new_area_features np.random.rand(10, 5) predicted_demands model.predict(new_area_features)13. 完整项目实战框架一个完整的选址分析项目通常包含以下步骤需求分析明确业务目标和约束条件数据准备收集候选位置数据获取需求点分布构建成本/距离矩阵模型构建选择合适的数学模型求解实现编写Python求解代码结果验证敏感性分析和场景测试方案部署将结果整合到决策系统典型项目目录结构/project_root │── /data │ ├── facilities.csv # 候选设施数据 │ ├── customers.csv # 需求点数据 │ └── distance_matrix.npy # 预计算的距离矩阵 │── /notebooks │ ├── 01_data_prep.ipynb # 数据预处理 │ └── 02_model_solving.ipynb # 模型求解 │── /src │ ├── location_models.py # 核心模型代码 │ └── visualization.py # 结果可视化 └── requirements.txt # 依赖库14. 关键业务指标对接将数学模型结果转化为业务决策时需要关注以下指标成本效益分析投资回报率(ROI)盈亏平衡时间服务质量评估平均服务距离/时间服务覆盖率风险指标最坏情况下的服务表现需求波动的敏感度15. 决策支持系统集成将选址模型集成到企业决策系统的常见方式REST API服务from flask import Flask, request, jsonify import numpy as np app Flask(__name__) app.route(/solve_location, methods[POST]) def solve_location(): data request.json demands np.array(data[demands]) distance_matrix np.array(data[distance_matrix]) p data[p] # 调用求解函数 selected, assignments, obj_value solve_p_median(demands, distance_matrix, p) return jsonify({ selected: selected, assignments: assignments, total_cost: obj_value }) if __name__ __main__: app.run(host0.0.0.0, port5000)可视化仪表盘使用Dash/Streamlit构建GIS系统集成与ArcGIS/QGIS对接商业BI工具插件如Tableau/Power BI扩展16. 持续优化与迭代选址方案需要定期评估和调整监控关键指标实际运营数据与模型预测对比反馈机制收集用户满意度等定性数据模型迭代根据新数据重新训练和优化A/B测试在小范围内试验新布局方案# 示例方案对比评估 def evaluate_solution(demands, distance_matrix, selected): 评估选址方案性能 n_customers len(demands) n_selected len(selected) # 计算每个需求点到最近设施的距离 min_distances np.min(distance_matrix[:, selected], axis1) metrics { total_cost: np.sum(demands * min_distances), avg_distance: np.mean(min_distances), max_distance: np.max(min_distances), p95_distance: np.percentile(min_distances, 95) } return metrics # 比较不同选址方案 metrics1 evaluate_solution(demands, distance_matrix, [3, 12, 20]) metrics2 evaluate_solution(demands, distance_matrix, [5, 15, 18]) print(方案1:, metrics1) print(方案2:, metrics2)17. 伦理与社会责任考量在应用选址模型时还需考虑公平性避免服务盲区特别是弱势群体区域环境影响评估设施建设的生态影响社区影响考虑对周边社区的综合影响长期可持续性适应未来城市发展可以通过多目标优化平衡这些因素def solve_multi_objective(demands, distance_matrix, p, equity_weight0.3): 考虑公平性的多目标选址 n_facilities len(distance_matrix[0]) n_customers len(demands) prob pulp.LpProblem(Multi_Objective_Location, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities
用Python和PuLP搞定选址问题:从消防站到仓库,一个模型解决多种场景
发布时间:2026/6/6 9:54:38
PythonPuLP实战用数学建模解决7类真实选址问题当连锁便利店计划新开20家门店时如何科学布局才能最大化覆盖目标人群当物流企业需要新建区域分拨中心时怎样选择位置才能让运输成本降低15%这些看似复杂的商业决策其实都可以通过数学建模找到最优解。本文将带你用Python的PuLP库从零构建7种不同类型的选址模型解决实际业务中的空间优化难题。1. 选址问题背后的商业价值与数学原理选址问题(Facility Location Problem)是运筹学中最具实用价值的领域之一。根据麦肯锡的研究报告优秀的选址方案可以帮助零售企业提升30%的客户到店率让物流企业节省20%以上的运输成本。在公共卫生领域合理的急救中心布局甚至能缩短40%的紧急响应时间。这类问题的数学本质是在给定的空间约束条件下找到使目标函数最优的设施分布方案。常见的优化目标包括最小化总成本包括建设成本和运营成本最大化覆盖率使服务能够覆盖最多需求点平衡服务能力避免某些设施过载而其他设施闲置用数学语言描述一个典型的选址问题包含以下要素# 伪代码表示选址问题的基本结构 class FacilityLocationProblem: def __init__(self): self.facilities [] # 候选设施位置 self.customers [] # 需求点位置 self.distance_matrix [] # 距离/成本矩阵 self.demands [] # 各需求点的需求量 self.capacities [] # 各设施的服务能力 self.fixed_costs [] # 设施建设固定成本在实际应用中根据不同的业务场景会衍生出多种变体模型。下面我们就来剖析7种最常见的类型及其Python实现。2. P-中位问题连锁零售店的最优布局2.1 问题场景与数学模型假设某连锁品牌准备在城市的15个候选位置中选择8处开设新店要求所有门店到各居民区的平均距离最小。这就是典型的P-中位问题(P-median Problem)。数学模型可以表示为$$ \begin{aligned} \text{最小化} \sum_{i\in M}\sum_{j\in N} w_i d_{ij} y_{ij} \ \text{约束条件:} \ \quad \sum_{j\in N} x_j P \quad \text{(选择P个设施)} \ \quad \sum_{j\in N} y_{ij} 1 \quad \forall i \in M \quad \text{(每个需求点只分配一个设施)} \ \quad y_{ij} \leq x_j \quad \forall i\in M, j\in N \quad \text{(只有开放的设施才能服务)} \ \quad x_j, y_{ij} \in {0,1} \end{aligned} $$其中$x_j$表示是否选择位置j作为设施$y_{ij}$表示需求点i是否由设施j服务$w_i$是需求点i的权重(如人口数量)$d_{ij}$是需求点i到设施j的距离2.2 Python实现与求解使用PuLP库实现的完整代码如下import pulp import numpy as np def solve_p_median(demands, distance_matrix, p): 求解P-中位问题 n_facilities len(distance_matrix[0]) n_customers len(demands) # 创建问题实例 prob pulp.LpProblem(P_Median_Problem, pulp.LpMinimize) # 创建决策变量 x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 设置目标函数 prob pulp.lpSum([demands[i] * distance_matrix[i][j] * y[(i,j)] for i in range(n_customers) for j in range(n_facilities)]) # 添加约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for i in range(n_customers): for j in range(n_facilities): prob y[(i,j)] x[j] # 求解问题 prob.solve() # 提取结果 selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {} for i in range(n_customers): for j in range(n_facilities): if pulp.value(y[(i,j)]) 1: assignments[i] j break return selected, assignments, pulp.value(prob.objective)2.3 实际应用示例假设我们在一个5×5的网格区域中有25个候选位置需要选择3个位置建立配送中心服务周边10个社区# 生成模拟数据 np.random.seed(42) locations np.random.rand(25, 2) * 10 # 25个设施的坐标 customers np.random.rand(10, 2) * 10 # 10个需求点的坐标 demands np.random.randint(50, 200, size10) # 各需求点的需求量 # 计算距离矩阵 distance_matrix np.zeros((10, 25)) for i in range(10): for j in range(25): distance_matrix[i,j] np.linalg.norm(customers[i]-locations[j]) # 求解问题 selected, assignments, total_cost solve_p_median(demands, distance_matrix, p3) print(f选中的设施位置索引: {selected}) print(f各需求点分配方案: {assignments}) print(f总加权距离: {total_cost:.2f})运行结果可能如下选中的设施位置索引: [3, 12, 20] 各需求点分配方案: {0: 3, 1: 12, 2: 3, 3: 20, 4: 12, 5: 20, 6: 3, 7: 12, 8: 20, 9: 3} 总加权距离: 342.563. P-中心问题应急设施的公平布局3.1 问题特点与模型构建P-中心问题(P-center Problem)追求的是最差情况下的最优解即最小化所有需求点到其最近设施的最大距离。这在医院、消防站等应急设施布局中尤为重要。数学模型与P-中位问题类似但目标函数变为$$ \begin{aligned} \text{最小化} \quad D \ \text{约束条件:} \ \quad \sum_{j\in N} w_i d_{ij} y_{ij} \leq D \quad \forall i \in M \ \quad \text{(其他约束与P-中位问题相同)} \end{aligned} $$其中D表示所有需求点到最近设施的最大距离。3.2 Python实现关键点def solve_p_center(distance_matrix, p): 求解P-中心问题 n_facilities len(distance_matrix[0]) n_customers len(distance_matrix) prob pulp.LpProblem(P_Center_Problem, pulp.LpMinimize) # 决策变量 x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) D pulp.LpVariable(D, lowBound0) # 最大距离变量 # 目标函数 prob D # 约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for i in range(n_customers): for j in range(n_facilities): prob y[(i,j)] x[j] prob distance_matrix[i][j] * y[(i,j)] D prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {i: j for i in range(n_customers) for j in range(n_facilities) if pulp.value(y[(i,j)]) 1} return selected, assignments, pulp.value(D)3.3 应用案例城市消防站布局假设一个城市有8个区域需要在其中选择若干个位置建立消防站确保任何区域发生火灾时消防车都能在10分钟内到达# 消防站到各区域的最短到达时间(分钟) response_time [ [7, 12, 18, 20, 24, 26, 25, 28], # 消防站1到各区域时间 [14, 5, 8, 15, 16, 18, 18, 18], # 消防站2 [19, 9, 4, 14, 10, 22, 16, 13], # 消防站3 [14, 15, 15, 10, 18, 15, 14, 18], # 消防站4 [20, 18, 12, 20, 9, 25, 14, 12], # 消防站5 [18, 21, 20, 16, 20, 6, 10, 15], # 消防站6 [22, 20, 15, 16, 15, 5, 9, 8], # 消防站7 [30, 22, 15, 20, 14, 18, 8, 6] # 消防站8 ] # 转换为覆盖矩阵(1表示能在10分钟内到达) coverage [[1 if t 10 else 0 for t in row] for row in response_time] # 求解集合覆盖问题(最小化消防站数量) selected, _, max_time solve_p_center(np.array(response_time), p4) print(f选择的消防站位置: {selected}) print(f最长的响应时间: {max_time}分钟)4. 集合覆盖问题5G基站部署优化4.1 模型特点与适用场景集合覆盖问题(Set Covering Problem)要求在覆盖所有需求点的前提下使用最少数量的设施。这在通信基站部署、公共设施布局中很常见。数学模型为$$ \begin{aligned} \text{最小化} \sum_{j\in N} c_j x_j \ \text{约束条件:} \ \quad \sum_{j\in N_i} x_j \geq 1 \quad \forall i \in M \ \quad x_j \in {0,1} \end{aligned} $$其中$N_i {j | a_{ij}1}$是能覆盖需求点i的候选设施集合。4.2 Python实现与优化技巧def solve_set_covering(coverage_matrix, costsNone): 求解集合覆盖问题 n_facilities len(coverage_matrix) n_customers len(coverage_matrix[0]) if costs is None: costs [1] * n_facilities # 默认每个设施成本相同 prob pulp.LpProblem(Set_Covering_Problem, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) # 目标函数最小化总成本 prob pulp.lpSum([costs[j] * x[j] for j in range(n_facilities)]) # 约束条件每个需求点至少被一个设施覆盖 for i in range(n_customers): prob pulp.lpSum([x[j] for j in range(n_facilities) if coverage_matrix[j][i] 1]) 1 prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] return selected, pulp.value(prob.objective)4.3 5G基站部署实战案例假设某城区需要部署5G基站现有20个候选站址需要覆盖50个重点区域# 生成随机覆盖矩阵(20基站×50区域) np.random.seed(42) coverage np.random.randint(0, 2, size(20, 50)) # 设置部分基站成本不同(如地形因素) costs [1] * 15 [2] * 5 # 后5个基站建设成本更高 selected, total_cost solve_set_covering(coverage, costs) print(f需要建设{len(selected)}个基站) print(f选中的基站索引: {selected}) print(f总建设成本: {total_cost})5. 最大覆盖问题疫苗接种点布局策略5.1 问题定义与模型构建最大覆盖问题(Maximal Covering Problem)是在设施数量有限的情况下最大化被覆盖的需求量。适用于资源受限时的设施布局。数学模型为$$ \begin{aligned} \text{最大化} \sum_{i\in M} w_i z_i \ \text{约束条件:} \ \quad z_i \leq \sum_{j\in N_i} x_j \quad \forall i \in M \ \quad \sum_{j\in N} x_j P \ \quad x_j, z_i \in {0,1} \end{aligned} $$其中$z_i$表示需求点i是否被覆盖。5.2 Python实现与结果分析def solve_maximal_covering(demands, coverage_matrix, p): 求解最大覆盖问题 n_facilities len(coverage_matrix) n_customers len(demands) prob pulp.LpProblem(Maximal_Covering_Problem, pulp.LpMaximize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) z pulp.LpVariable.dicts(z, range(n_customers), catBinary) # 目标函数最大化覆盖的需求量 prob pulp.lpSum([demands[i] * z[i] for i in range(n_customers)]) # 约束条件 prob pulp.lpSum([x[j] for j in range(n_facilities)]) p for i in range(n_customers): prob z[i] pulp.lpSum([x[j] for j in range(n_facilities) if coverage_matrix[j][i] 1]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] covered [i for i in range(n_customers) if pulp.value(z[i]) 1] coverage_rate sum(demands[i] for i in covered) / sum(demands) return selected, covered, coverage_rate5.3 疫苗接种点布局实战假设某地区有30个社区卫生部门计划设立5个疫苗接种点# 社区人口数据(千人) population np.random.randint(5, 50, size30) # 生成覆盖矩阵(假设每个接种点能覆盖周边8-12个社区) np.random.seed(42) coverage np.zeros((30, 30)) for j in range(30): # 随机选择10±2个社区能被该接种点覆盖 covered np.random.choice(30, np.random.randint(8, 13), replaceFalse) coverage[j, covered] 1 selected, covered, rate solve_maximal_covering(population, coverage, p5) print(f选中的接种点位置: {selected}) print(f覆盖了{len(covered)}个社区) print(f人口覆盖率: {rate:.1%})6. 高级应用带容量限制的选址问题6.1 问题扩展与模型增强现实中的设施通常有服务能力限制。带容量限制的选址问题(Capacitated Facility Location)在基础模型上增加了设施容量约束$$ \begin{aligned} \text{最小化} \sum_{j\in N} f_j x_j \sum_{i\in M}\sum_{j\in N} c_{ij} y_{ij} \ \text{约束条件:} \ \quad \sum_{j\in N} y_{ij} 1 \quad \forall i \in M \ \quad \sum_{i\in M} d_i y_{ij} \leq C_j x_j \quad \forall j \in N \ \quad x_j, y_{ij} \in {0,1} \end{aligned} $$其中$f_j$是设施j的固定开设成本$c_{ij}$是需求点i由设施j服务的成本$d_i$是需求点i的需求量$C_j$是设施j的容量6.2 Python实现与大规模问题处理def solve_capacitated_fl(demands, costs, fixed_costs, capacities): 求解带容量限制的选址问题 n_facilities len(fixed_costs) n_customers len(demands) prob pulp.LpProblem(Capacitated_FL, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 目标函数固定成本运输成本 prob (pulp.lpSum([fixed_costs[j] * x[j] for j in range(n_facilities)]) pulp.lpSum([costs[i][j] * y[(i,j)] for i in range(n_customers) for j in range(n_facilities)])) # 约束条件 for i in range(n_customers): prob pulp.lpSum([y[(i,j)] for j in range(n_facilities)]) 1 for j in range(n_facilities): prob (pulp.lpSum([demands[i] * y[(i,j)] for i in range(n_customers)]) capacities[j] * x[j]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] assignments {i: j for i in range(n_customers) for j in range(n_facilities) if pulp.value(y[(i,j)]) 1} total_cost pulp.value(prob.objective) return selected, assignments, total_cost6.3 物流仓库选址实战案例某电商企业需要在华北地区建立区域仓库# 20个城市的需求量(万件/月) demands np.random.randint(50, 200, size20) # 10个候选仓库的固定成本(万元)和容量(万件/月) fixed_costs np.random.randint(500, 1000, size10) capacities np.random.randint(1000, 3000, size10) # 运输成本矩阵(万元/万件) transport_costs np.random.rand(20, 10) * 0.5 0.2 selected, assignments, total_cost solve_capacitated_fl( demands, transport_costs, fixed_costs, capacities) print(f选中的仓库位置: {selected}) print(f总成本(固定运输): {total_cost:.2f}万元)7. 动态与随机选址问题进阶7.1 动态选址问题动态选址考虑时间因素设施布局可能随时间变化。解决方法通常是将时间分段在每个时间段求解静态问题并考虑切换成本。7.2 随机选址问题当需求或成本不确定时可以使用随机规划。常见方法是场景法(Scenario Approach)对每种可能场景求解后取期望值。def solve_stochastic_fl(demands_scenarios, probs, costs, fixed_costs, capacities): 求解随机选址问题(场景法) n_scenarios len(demands_scenarios) n_facilities len(fixed_costs) n_customers len(demands_scenarios[0]) prob pulp.LpProblem(Stochastic_FL, pulp.LpMinimize) # 第一阶段决策设施选址(在所有场景中相同) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) # 第二阶段决策每个场景下的分配 y pulp.LpVariable.dicts(y, [(s,i,j) for s in range(n_scenarios) for i in range(n_customers) for j in range(n_facilities)], catBinary) # 目标函数固定成本 期望运输成本 prob (pulp.lpSum([fixed_costs[j] * x[j] for j in range(n_facilities)]) pulp.lpSum([probs[s] * costs[i][j] * y[(s,i,j)] for s in range(n_scenarios) for i in range(n_customers) for j in range(n_facilities)])) # 约束条件 for s in range(n_scenarios): for i in range(n_customers): prob pulp.lpSum([y[(s,i,j)] for j in range(n_facilities)]) 1 for s in range(n_scenarios): for j in range(n_facilities): prob (pulp.lpSum([demands_scenarios[s][i] * y[(s,i,j)] for i in range(n_customers)]) capacities[j] * x[j]) prob.solve() selected [j for j in range(n_facilities) if pulp.value(x[j]) 1] return selected7.3 实际应用季节性需求下的零售网络优化# 三种需求场景(正常、旺季、淡季)及其概率 demands_scenarios [ np.random.randint(50, 150, size15), # 正常 np.random.randint(100, 250, size15), # 旺季 np.random.randint(20, 80, size15) # 淡季 ] probs [0.6, 0.3, 0.1] # 其他参数 fixed_costs [800, 800, 1000, 1000, 1200] capacities [2000, 2000, 2500, 2500, 3000] transport_costs np.random.rand(15, 5) * 0.8 0.2 selected solve_stochastic_fl(demands_scenarios, probs, transport_costs, fixed_costs, capacities) print(f最优仓库选址: {selected})8. 性能优化与实战建议8.1 大规模问题求解技巧当问题规模较大时(如超过1000个变量)可以尝试以下优化方法启发式算法如遗传算法、模拟退火等分解算法如Benders分解商业求解器如Gurobi、CPLEX等问题简化聚类减少需求点数量8.2 数据预处理建议距离计算优化from scipy.spatial.distance import cdist # 高效计算距离矩阵 locations np.random.rand(100, 2) * 100 # 100个设施的坐标 customers np.random.rand(500, 2) * 100 # 500个需求点的坐标 distance_matrix cdist(customers, locations, euclidean)稀疏矩阵处理from scipy.sparse import csr_matrix # 将覆盖矩阵转换为稀疏格式 coverage np.random.randint(0, 2, size(50, 200)) sparse_coverage csr_matrix(coverage)8.3 模型验证与结果解读敏感性分析检查关键参数变化对结果的影响场景测试在极端情况下验证模型鲁棒性可视化验证在地图上绘制选址结果import matplotlib.pyplot as plt def plot_solution(locations, customers, selected, assignments): 可视化选址结果 plt.figure(figsize(10, 8)) # 绘制所有候选位置 plt.scatter(locations[:,0], locations[:,1], cblue, label候选位置, alpha0.5) # 绘制选中的设施 plt.scatter(locations[selected,0], locations[selected,1], cred, s100, label选中设施) # 绘制需求点及其分配 colors plt.cm.tab10(np.linspace(0, 1, len(selected))) for i, j in assignments.items(): plt.plot([customers[i,0], locations[j,0]], [customers[i,1], locations[j,1]], ccolors[selected.index(j)], alpha0.3) plt.scatter(customers[:,0], customers[:,1], cgreen, marker^, label需求点) plt.legend() plt.xlabel(经度) plt.ylabel(纬度) plt.title(选址方案可视化) plt.grid(True) plt.show() # 使用前面的P-中位问题结果进行可视化 plot_solution(locations, customers, selected, assignments)9. 行业应用全景图选址模型在各行业的典型应用场景行业应用场景典型模型关键指标零售门店布局最大覆盖客流量、覆盖率物流仓库/分拣中心带容量限制选址运输成本、响应时间医疗医院/诊所布局P-中心急救响应时间公共消防站/派出所集合覆盖全覆盖时间通信基站/机房部署最大覆盖信号覆盖率教育学校选址P-中位平均通学距离金融ATM机布局最大覆盖服务人口10. 常见问题与解决方案Q1如何处理非欧几里得距离在实际应用中距离可能不是直线距离而是道路网络距离(使用OSMNX库计算)运输时间(考虑交通状况)物流成本(结合多种运输方式)import osmnx as ox # 获取城市道路网络 G ox.graph_from_place(北京市, 中国, network_typedrive) # 计算两点间的最短路径距离 orig (39.9042, 116.4074) # 天安门 dest (39.9999, 116.3262) # 中关村 orig_node ox.distance.nearest_nodes(G, orig[1], orig[0]) dest_node ox.distance.nearest_nodes(G, dest[1], dest[0]) route ox.shortest_path(G, orig_node, dest_node, weightlength) road_distance sum(ox.utils_graph.get_route_edge_attributes(G, route, length))/1000 # kmQ2如何考虑地理限制因素可以通过以下方式在模型中融入地理限制预处理排除不可行位置添加约束条件在目标函数中加入惩罚项Q3模型求解时间过长怎么办使用启发式算法获得近似解缩小问题规模(如聚类合并需求点)尝试不同的求解器参数考虑分解算法或并行计算11. 技术选型对比不同选址问题适用的Python工具对比问题类型推荐工具优点局限性小规模精确求解PuLP简单易用接口统一大规模问题性能差中大规模问题Pyomo支持更多高级功能学习曲线较陡超大规模问题启发式算法(DEAP等)可处理复杂约束不能保证最优解地理空间分析OSMNXNetworkX真实路网建模计算复杂度高12. 前沿发展方向机器学习结合使用预测模型预估未来需求多目标优化同时考虑成本、覆盖率、公平性等鲁棒优化应对极端情况下的系统稳定性实时动态调整基于物联网数据的自适应优化# 示例使用机器学习预测需求 from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split # 假设有历史需求数据及相关特征 X np.random.rand(100, 5) # 特征人口密度、收入水平等 y np.random.randint(50, 200, size100) # 历史需求量 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) model RandomForestRegressor() model.fit(X_train, y_train) # 预测新区域的需求 new_area_features np.random.rand(10, 5) predicted_demands model.predict(new_area_features)13. 完整项目实战框架一个完整的选址分析项目通常包含以下步骤需求分析明确业务目标和约束条件数据准备收集候选位置数据获取需求点分布构建成本/距离矩阵模型构建选择合适的数学模型求解实现编写Python求解代码结果验证敏感性分析和场景测试方案部署将结果整合到决策系统典型项目目录结构/project_root │── /data │ ├── facilities.csv # 候选设施数据 │ ├── customers.csv # 需求点数据 │ └── distance_matrix.npy # 预计算的距离矩阵 │── /notebooks │ ├── 01_data_prep.ipynb # 数据预处理 │ └── 02_model_solving.ipynb # 模型求解 │── /src │ ├── location_models.py # 核心模型代码 │ └── visualization.py # 结果可视化 └── requirements.txt # 依赖库14. 关键业务指标对接将数学模型结果转化为业务决策时需要关注以下指标成本效益分析投资回报率(ROI)盈亏平衡时间服务质量评估平均服务距离/时间服务覆盖率风险指标最坏情况下的服务表现需求波动的敏感度15. 决策支持系统集成将选址模型集成到企业决策系统的常见方式REST API服务from flask import Flask, request, jsonify import numpy as np app Flask(__name__) app.route(/solve_location, methods[POST]) def solve_location(): data request.json demands np.array(data[demands]) distance_matrix np.array(data[distance_matrix]) p data[p] # 调用求解函数 selected, assignments, obj_value solve_p_median(demands, distance_matrix, p) return jsonify({ selected: selected, assignments: assignments, total_cost: obj_value }) if __name__ __main__: app.run(host0.0.0.0, port5000)可视化仪表盘使用Dash/Streamlit构建GIS系统集成与ArcGIS/QGIS对接商业BI工具插件如Tableau/Power BI扩展16. 持续优化与迭代选址方案需要定期评估和调整监控关键指标实际运营数据与模型预测对比反馈机制收集用户满意度等定性数据模型迭代根据新数据重新训练和优化A/B测试在小范围内试验新布局方案# 示例方案对比评估 def evaluate_solution(demands, distance_matrix, selected): 评估选址方案性能 n_customers len(demands) n_selected len(selected) # 计算每个需求点到最近设施的距离 min_distances np.min(distance_matrix[:, selected], axis1) metrics { total_cost: np.sum(demands * min_distances), avg_distance: np.mean(min_distances), max_distance: np.max(min_distances), p95_distance: np.percentile(min_distances, 95) } return metrics # 比较不同选址方案 metrics1 evaluate_solution(demands, distance_matrix, [3, 12, 20]) metrics2 evaluate_solution(demands, distance_matrix, [5, 15, 18]) print(方案1:, metrics1) print(方案2:, metrics2)17. 伦理与社会责任考量在应用选址模型时还需考虑公平性避免服务盲区特别是弱势群体区域环境影响评估设施建设的生态影响社区影响考虑对周边社区的综合影响长期可持续性适应未来城市发展可以通过多目标优化平衡这些因素def solve_multi_objective(demands, distance_matrix, p, equity_weight0.3): 考虑公平性的多目标选址 n_facilities len(distance_matrix[0]) n_customers len(demands) prob pulp.LpProblem(Multi_Objective_Location, pulp.LpMinimize) x pulp.LpVariable.dicts(x, range(n_facilities), catBinary) y pulp.LpVariable.dicts(y, [(i,j) for i in range(n_customers) for j in range(n_facilities