Python搭建运筹模型的代码框架
前言
刚踏入职场用python写运筹模型时,基本是一个个函数堆砌,中间会出现非常多的重复数据操作,只能怪自己当初代码能力和结构思维都太弱(捶胸口)。
好在身边还是有能行的同事的,某一天看了工程出身的算法同事的模型代码,有一种“相见恨晚”的感觉,后面我基本都是参照他这个框架,在搭建运筹模型啦!
Python代码框架
class MathModel:
def __init__(self,
model_input
):
"""这里定义模型需要的一些输入"""
self.model_input = model_input
return
def solve(self):
"""模型求解的全过程"""
self._set_iterables()
self._set_variables()
if self.problem.type == 'fix_local_hub':
self._init_var()
self._set_constraints()
self._set_object()
self._set_parameter()
self.model.update()
self.model.write('facility_location.lp')
self.model.optimize()
# 具体status code说明见
# https://www.gurobi.com/documentation/8.1/refman/optimization_status_codes.html
# 以下代码只处理了常见的不可行/unbounded情况。
if self.model.status in range(3, 6):
self._calculate_iis()
return
else:
return self._post_process()
def _set_iterables(self):
self.arcs: List[Tuple[str, str]] = list()
self.demand_coverage: Dict[str, Set[str]] = dict()
self.hub_coverage: Dict[str, Set[str]] = dict()
self.arc_coverage: Dict[Tuple[str, str], Set[str]] = dict() # 构建最近覆盖约束使用
tmp = []
for customer in self.problem.demand_nodes:
for hub in self.problem.candidate_nodes:
if (customer, hub) in self.problem.possible_arcs.keys() and self.problem.distance_matrix[
(customer, hub)] <= self.problem.dist_ub:
self.arcs.append((customer, hub))
self.demand_coverage.setdefault(customer, set()).add(hub)
self.hub_coverage.setdefault(hub, set()).add(customer)
if self.problem.type == 'fix_local_hub': # 若需优化对应关系,则构建相关集合
local_hubs = set()
for i in self.problem.demand_nodes:
if i == self.problem.demand_nodes[i].local_hub.node_name:
local_hubs.add(i)
for i in self.problem.demand_nodes:
for j in local_hubs:
if j in self.demand_coverage[i]:
for k in local_hubs:
if k in self.demand_coverage[i] and self.problem.distance_matrix[(i, j)] >= \
self.problem.distance_matrix[(i, k)] + 0.5 and i != k:
self.arc_coverage.setdefault((i, j), set()).add(k)
def _set_variables(self):
self.x = self.model.addVars(self.arcs, vtype=GRB.BINARY, name='x')
self.y = self.model.addVars(self.problem.candidate_nodes, vtype=GRB.BINARY, name='y')
return
def _init_var(self):
# 固定上一步选出的点必备选择
for i in self.problem.demand_nodes:
if i == self.problem.demand_nodes[i].local_hub.node_name:
self.model.addConstr(self.y[i], GRB.EQUAL, 1, 'init_var')
else:
self.model.addConstr(self.y[i], GRB.EQUAL, 0, 'init_var')
return
def _set_constraints(self):
self._set_coverage_constr()
self._set_relation_constr()
self._set_capacity_constr()
self._set_hub_num_constr()
self._set_nearest_cover_constr()
return
def _set_coverage_constr(self):
self.model.addConstrs(
(quicksum(self.x[i, j] for j in self.demand_coverage[i]) == 1 for i in self.problem.demand_nodes),
name='cover')
return
def _set_relation_constr(self):
self.model.addConstrs(
(self.x[i, j] <= self.y[j] for i in self.problem.demand_nodes for j in self.demand_coverage[i]),
name='relation')
self.model.addConstrs(self.x[j, j] == self.y[j] for j in self.problem.candidate_nodes) # 若点被选中,必被自身使用
return
def _set_capacity_constr(self):
return
def _set_hub_num_constr(self):
return
def _set_nearest_cover_constr(self):
return
def _set_object(self):
var_cost = quicksum(self.problem.demand_nodes[i].demand[t] * self.x[i, j] * self.problem.distance_matrix[
(i, j)] * self.problem.variable_cost for (i, j) in self.arcs for t in self.problem.prod_type)
if self.problem.type in ['local_hub', 'fix_local_hub']:
vehicle_cost = quicksum(
self.z_1[j, t] * self.problem.fix_cost[j, t][0] + self.z_2[j, t] * self.problem.fix_cost[j, t][1]
for j in self.problem.candidate_nodes for t in self.problem.prod_type)
overload_cost = quicksum(
self.s[j, t] * self.problem.overload_cost[j] for j in self.problem.candidate_nodes for t in
self.problem.prod_type)
rent = quicksum(
self.y[j] * self.problem.config['cost']['hub_fix_cost']['local_hub'] for j in
self.problem.candidate_nodes)
else:
vehicle_cost = quicksum(self.z_1[j] * self.problem.fix_cost[j] for j in self.problem.candidate_nodes)
overload_cost = quicksum(
self.s[j] * self.problem.overload_cost[j] for j in self.problem.candidate_nodes)
if self.problem.type == 'access_hub':
rent = quicksum((self.problem.demand_nodes[j].cost - self.problem.demand_nodes[j].is_current_hub *
self.problem.config['cost']['hub_refund']) * self.q_2[j] + self.k_2[j] *
self.problem.config['cost']['hub_fix_cost']['access_hub'] for j in
self.problem.candidate_nodes if self.problem.demand_nodes[j].cost > 0)
obj = var_cost + vehicle_cost + overload_cost + rent
elif self.problem.type in ['local_hub', 'fix_local_hub']:
obj = var_cost + vehicle_cost + overload_cost + rent
else:
obj = var_cost + vehicle_cost + overload_cost
self.model.setObjective(obj, sense=GRB.MINIMIZE)
return
def _set_parameter(self):
self.model.Params.mip_gap = self.problem.config['grb']['mip_gap']
self.model.Params.time_limit = self.problem.config['grb']['time_limit']
def _calculate_iis(self):
# Do IIS
logging.warning('The model is infeasible; computing IIS')
self.model.computeIIS()
logging.warning('\nThe following constraint(s) cannot be satisfied:')
for c in self.model.getConstrs():
if c.IISConstr:
logging.warning('%s' % c.constrName)
return
def _post_process(self):
'''
结果处理
:return:
'''
selected_nodes = set()
for (i, j) in self.arcs:
if self.y[j].x >= 0.9 and self.x[i, j].x >= 0.9:
selected_nodes.add(j)
self.problem.demand_nodes[i].parent = self.problem.demand_nodes[j]
self.problem.demand_nodes[j].children.update({i: self.problem.demand_nodes[i]})
if self.problem.type in ['access_hub', 'pre_process']:
self.problem.demand_nodes[i].access_hub = self.problem.demand_nodes[i].parent
self.problem.demand_nodes[j].mini_truck_num = round(self.z_1[j].x)
elif self.problem.type in ['local_hub', 'fix_local_hub']:
temp = [[round(self.z_1[j, t].x), round(self.z_2[j, t].x)] for t in self.problem.prod_type]
self.problem.demand_nodes[j].truck_num = list(zip(*temp))
self.problem.demand_nodes[i].local_hub = self.problem.demand_nodes[i].parent
for k, v in self.problem.demand_nodes.items():
if v.local_hub is not None and v.local_hub == v:
v.mini_truck_num = 0
return selected_nodes