├── LR_Python_Peter ├── data │ ├── station.csv │ ├── section.csv │ └── train.csv ├── __pycache__ │ ├── Arc.cpython-38.pyc │ ├── Arc.cpython-39.pyc │ ├── Node.cpython-38.pyc │ ├── Node.cpython-39.pyc │ ├── Train.cpython-38.pyc │ └── Train.cpython-39.pyc ├── Arc.py ├── Node.py ├── Train.py └── main.py └── LICENSE /LR_Python_Peter/data/station.csv: -------------------------------------------------------------------------------- 1 | station,mile 2 | A,0 3 | B,50 4 | C,100 5 | D,170 6 | E,220 7 | -------------------------------------------------------------------------------- /LR_Python_Peter/data/section.csv: -------------------------------------------------------------------------------- 1 | station,runtime(300),runtime(350) 2 | A-B,8,7 3 | B-C,12,11 4 | C-D,13,12 5 | D-E,8,10 6 | -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Arc.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Arc.cpython-38.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Arc.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Arc.cpython-39.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Node.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Node.cpython-38.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Node.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Node.cpython-39.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Train.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Train.cpython-38.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/__pycache__/Train.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peter1sCoding/TrainTimetable_LagrangianRelaxation/HEAD/LR_Python_Peter/__pycache__/Train.cpython-39.pyc -------------------------------------------------------------------------------- /LR_Python_Peter/data/train.csv: -------------------------------------------------------------------------------- 1 | trainNO,speed,A,B,C,D,E 2 | G1,350,1,0,0,0,1 3 | G3,300,1,1,1,1,1 4 | G5,300,1,1,1,1,1 5 | G7,300,1,1,1,1,1 6 | G9,300,1,1,1,1,1 7 | G11,300,1,1,1,1,1 8 | -------------------------------------------------------------------------------- /LR_Python_Peter/Arc.py: -------------------------------------------------------------------------------- 1 | ## 弧集合 2 | class Arc(): 3 | def __init__(self, trainBelong, staBelong_preSta, staBelong_next, timeBelong_pre, timeBelong_next, arc_length): 4 | self.trainBelong = trainBelong 5 | self.staBelong_pre = staBelong_preSta 6 | self.staBelong_next = staBelong_next 7 | self.timeBelong_pre = timeBelong_pre 8 | self.timeBelong_next = timeBelong_next 9 | self.arc_length = arc_length 10 | self.isChosen_LR = 0 # 0为没选,1为选 11 | self.before_occupy_dep = 1 # 前1 12 | self.after_occupy_dep = 2 # 后2 13 | self.before_occupy_arr = 1 # 前1 14 | self.after_occupy_arr = 2 # 后2 15 | self.node_occupied = [] # 该弧参与的约束的集合,约束此时已经转为node-related,所以以node的乘子来表示约束的乘子 16 | 17 | def __repr__(self): 18 | pre_t = str(self.timeBelong_pre) 19 | next_t = str(self.timeBelong_next) 20 | return self.trainBelong + ": " + self.staBelong_pre + "(" + pre_t + ") => " + self.staBelong_next + "(" + next_t \ 21 | + ")" 22 | -------------------------------------------------------------------------------- /LR_Python_Peter/Node.py: -------------------------------------------------------------------------------- 1 | # 节点类,储存流入与流出该节点的弧集 2 | 3 | class Node(): 4 | def __init__(self, sta, t): 5 | self.sta_located = sta 6 | self.t_located = t 7 | self.in_arcs = {} # 流入该节点的弧集,以trainNo为key,弧为value 8 | self.out_arcs = {} # 流出该节点的弧集,以trainNo为key, 弧为value 9 | self.incompatible_arcs = [] # 该节点对应资源占用<=1的约束中,不相容弧的集合,以trainNo为索引,子字典以arc_length为key 10 | self.multiplier = 0 # 该节点对应约束的拉格朗日乘子 11 | self.name = [self.sta_located, self.t_located] 12 | self.isOccupied = False # 可行解中,该节点是否已经被占据 13 | 14 | def __repr__(self): 15 | return "Node: " + str(self.sta_located) + " at time " + str(self.t_located) 16 | 17 | def __str__(self): 18 | return "Node: sta_" + self.sta_located + ";" + "t_" + self.t_located 19 | 20 | def associate_with_incoming_arcs(self, train): 21 | ''' 22 | associate node with train arcs, add incoming arcs to nodes 23 | :param train: 24 | :return: 25 | ''' 26 | sta_node = self.sta_located 27 | t_node = self.t_located 28 | 29 | if sta_node not in train.v_staList: # 若该车不经过该站,直接退出 30 | return -1 31 | 32 | # associate incoming arcs 33 | # train arc structure: key:[dep, arr], value为弧集字典(key: [t], value: arc字典, key为arc_length) 34 | if sta_node != train.v_staList[0]: # 不为第一站,则拥有上一站 35 | preSta = train.v_staList[train.v_staList.index(sta_node) - 1] # 已经考虑列车停站情况的车站集 36 | curSta = sta_node 37 | cur_arcs = train.arcs[preSta, curSta] # 这个区间/车站的所有弧 38 | for start_t in cur_arcs.keys(): 39 | arcs_from_start_t = cur_arcs[start_t] # 从上一节点在start_t伸出来的arc,包括区间的一个arc和停站弧的若干个arc 40 | for arc_length, arc_var in arcs_from_start_t.items(): 41 | if arc_length + start_t == t_node: # 若该弧流入该节点 42 | # 若不包含该车的弧列表,则先生成弧列表 43 | if train.traNo not in self.in_arcs.keys(): 44 | self.in_arcs[train.traNo] = {} 45 | self.in_arcs[train.traNo][arc_length] = arc_var 46 | 47 | def associate_with_outgoing_arcs(self, train): 48 | ''' 49 | associate node with train arcs, add outgoing arcs to nodes 50 | :param train: 51 | :return: 52 | ''' 53 | sta_node = self.sta_located 54 | t_node = self.t_located # 所在点 55 | 56 | if sta_node not in train.v_staList or sta_node == train.v_staList[-1]: # 列车径路不包含该站 或 该站为最后一站,则都不会有流出弧 57 | return -1 58 | 59 | curSta = sta_node 60 | nextSta = train.v_staList[train.v_staList.index(sta_node) + 1] 61 | cur_arcs = train.arcs[curSta, nextSta] 62 | if sta_node == train.v_staList[-2]: 63 | b = 0 64 | if t_node in cur_arcs.keys(): # 如果点t在列车区间/车站弧集当中,source node就是-1 65 | self.out_arcs[train.traNo] = {} 66 | for arc_length, arc_var in cur_arcs[t_node].items(): 67 | self.out_arcs[train.traNo][arc_length] = arc_var 68 | 69 | -------------------------------------------------------------------------------- /LR_Python_Peter/Train.py: -------------------------------------------------------------------------------- 1 | # Here defines the info about the trains 2 | import copy 3 | 4 | from Arc import * 5 | ## 所有的arc取值就是0或1,即选或不选 6 | 7 | class Train(): 8 | def __init__(self, traNo, dep_LB, dep_UB): 9 | ''' 10 | construct 11 | :param traNo: 12 | :param dep_LB: 13 | :param dep_UB: 14 | ''' 15 | self.traNo = traNo # 列车车次 16 | self.dep_LB = dep_LB # 始发时间窗下界 17 | self.dep_UB = dep_UB # 始发时间窗上界 18 | self.arcs = {} # 内含弧两个边界点的key:[dep, arr], value为弧集字典(key: [t], value: arc字典, key为arc_length) 三层字典嵌套: dep-arr => t => span 19 | self.stop_addTime = 3 # 停车附加时分 20 | self.start_addTime = 2 # 起车附加时分 21 | self.min_dwellTime = 2 # 最小停站时分 22 | self.max_dwellTime = 6 # 最大停站时分 23 | self.secTimes = {} 24 | self.right_time_bound = {} # 各站通过线路时间窗和列车始发时间窗综合确定的右侧边界 25 | self.depSta = None 26 | self.arrSta = None 27 | self.v_staList = [] # dual stations 28 | self.staList = [] # actual stations 29 | self.linePlan = {} # 开行方案字典 30 | self.opt_path_LR = None # LR 中的最短路径 31 | self.last_opt_path_LR = None 32 | self.opt_cost_LR = 0 33 | self.feasible_path = None # 可行解中的最短路径 34 | self.last_feasible_path = None # 上一个可行解的最短路径,用于置0 35 | self.feasible_cost = 0 36 | self.timetable = {} # 以virtual station为key,存int值 37 | self.speed = None # 列车速度,300,350 38 | 39 | def __repr__(self): 40 | return self.traNo 41 | 42 | def init_traStaList(self, allStaList): 43 | ''' 44 | create train staList, include s_, _t, only contains nodes associated with this train 45 | :param allStaList: 46 | :return: 47 | ''' 48 | for sta in allStaList: 49 | if sta in self.linePlan.keys(): 50 | self.staList.append(sta) 51 | self.v_staList.append('s_') 52 | for i in range(len(self.staList)): 53 | if i != 0: 54 | self.v_staList.append('_' + self.staList[i]) 55 | if i != len(self.staList) - 1: # 若不为实际车站的最后一站,则加上sta_ 56 | self.v_staList.append(self.staList[i] + '_') 57 | self.v_staList.append('_t') 58 | 59 | def create_arcs_LR(self, secTimes, TimeSpan): 60 | self.depSta = self.staList[0] 61 | self.arrSta = self.staList[-1] 62 | self.secTimes = secTimes 63 | self.truncate_train_time_bound(TimeSpan) 64 | ''' 65 | create train arcs 66 | :param v_staList: 67 | :param secTimes: 68 | :param model: 69 | :return: 70 | ''' 71 | minArr = self.dep_LB # for curSta(judge by dep) 72 | ''' 73 | create arcs involving node s 74 | ''' 75 | self.arcs['s_', self.staList[0] + '_'] = {} 76 | self.arcs['s_', self.staList[0] + '_'][-1] = {} # source node流出弧, 只有t=-1,因为source node与时间无关 77 | for t in range(minArr, self.right_time_bound[self.v_staList[1]]): 78 | self.arcs['s_', self.staList[0] + '_'][-1][t] = Arc(self.traNo, 's_', self.staList[0] + '_', -1, t, 0) 79 | # 声明弧长为t,实际length为0 80 | ''' 81 | create arcs between real stations 82 | ''' 83 | for i in range(len(self.staList) - 1): 84 | curSta = self.staList[i] 85 | nextSta = self.staList[i + 1] 86 | # virtual dual stations 87 | curSta_dep = curSta + "_" 88 | nextSta_arr = "_" + nextSta 89 | nextSta_dep = nextSta + "_" 90 | 91 | secRunTime = secTimes[curSta, nextSta] # 区间运行时分 92 | 93 | # 创建两个弧, 一个运行弧,一个停站弧 94 | ''' 95 | curSta_dep-->nextSta_arr区间运行弧 96 | ''' 97 | self.arcs[curSta_dep, nextSta_arr] = {} 98 | secRunTime += self.stop_addTime # 添加停车附加时分 99 | 100 | if self.linePlan[curSta] == 1: # 本站停车, 加起停附加时分 101 | secRunTime += self.start_addTime 102 | # 设置d-a的区间运行弧 103 | for t in range(minArr, self.right_time_bound[curSta_dep]): 104 | if t + secRunTime >= self.right_time_bound[nextSta_arr]: # 范围为0 => TimeSpan - 1 105 | break 106 | self.arcs[curSta_dep, nextSta_arr][t] = {} # dep-arr在node t的弧集,固定区间运行时分默认只有一个元素 107 | self.arcs[curSta_dep, nextSta_arr][t][secRunTime] = Arc(self.traNo, curSta_dep, nextSta_arr, t, t+secRunTime, secRunTime) 108 | # update cur time window 109 | minArr += secRunTime 110 | 111 | ''' 112 | nextSta_arr-->nextSta_dep车站停站弧 113 | ''' 114 | if i + 1 == len(self.staList) - 1: # 若停站, 但已经是最后一个站了,不需停站弧 115 | break 116 | 117 | self.arcs[nextSta_arr, nextSta_dep] = {} 118 | if self.linePlan[nextSta] == 1: # 该站停车,创建多个停站时间长度的停站弧 119 | for t in range(minArr, self.right_time_bound[nextSta_arr]): 120 | if t + self.min_dwellTime >= self.right_time_bound[nextSta_dep]: # 当前t加上最短停站时分都超了,break掉 121 | break 122 | self.arcs[nextSta_arr, nextSta_dep][t] = {} 123 | for span in range(self.min_dwellTime, self.max_dwellTime): 124 | if t + span >= self.right_time_bound[nextSta_dep]: 125 | break 126 | self.arcs[nextSta_arr, nextSta_dep][t][span] = Arc(self.traNo, nextSta_arr, nextSta_dep, t, t+span, span) 127 | else: # 该站不停车,只创建一个竖直弧,长度为0 128 | for t in range(minArr, self.right_time_bound[nextSta_arr]): 129 | self.arcs[nextSta_arr, nextSta_dep][t] = {} 130 | self.arcs[nextSta_arr, nextSta_dep][t][0] = Arc(self.traNo, nextSta_arr, nextSta_dep, t, t, 0) 131 | # update cur time window 132 | minArr += self.min_dwellTime 133 | 134 | ''' 135 | create arcs involving node t 136 | ''' 137 | self.arcs['_' + self.staList[-1], '_t'] = {} 138 | for t in range(minArr, self.right_time_bound[self.v_staList[-2]]): 139 | self.arcs['_' + self.staList[-1], '_t'][t] = {} # dep-arr在node t的弧集,固定区间运行时分默认只有一个元素 140 | self.arcs['_' + self.staList[-1], '_t'][t][0] = Arc(self.traNo, self.staList[-1], '_t', t, -1, 0) 141 | 142 | 143 | 144 | def update_arc_chosen(self): 145 | ''' 146 | 通过获取的opt_path,将路径中包含的弧的 isChosen 属性更新 147 | :return: 148 | ''' 149 | # 先把上一轮的path清零 150 | # 内含弧两个边界点的key:[dep, arr], value为弧集字典(key: [t], value: arc字典, key为arc_length) 三层字典嵌套: dep-arr => t => span 151 | if self.last_opt_path_LR is not None: # 第一轮循环还没有,不用设为0 152 | for node_id in range(1, len(self.last_opt_path_LR.node_passed) - 2): 153 | node_name = self.last_opt_path_LR.node_passed[node_id] 154 | next_node_name = self.last_opt_path_LR.node_passed[node_id + 1] 155 | self.arcs[node_name[0], next_node_name[0]][node_name[1]][next_node_name[1] - node_name[1]].isChosen_LR = 0 156 | 157 | # 再把这一轮的设为1 158 | # 内含弧两个边界点的key:[dep, arr], value为弧集字典(key: [t], value: arc字典, key为arc_length) 三层字典嵌套: dep-arr => t => span 159 | for node_id in range(1, len(self.opt_path_LR.node_passed) - 2): 160 | node_name = self.opt_path_LR.node_passed[node_id] 161 | next_node_name = self.opt_path_LR.node_passed[node_id + 1] 162 | self.arcs[node_name[0], next_node_name[0]][node_name[1]][next_node_name[1] - node_name[1]].isChosen_LR = 1 163 | self.last_opt_path_LR = copy.deepcopy(self.opt_path_LR) #将上一个最优记录下来,下一次先把上一次路径的chosen清零 164 | 165 | def truncate_train_time_bound(self, TimeSpan): 166 | right_bound_by_sink = [] # 从总天窗时间右端反推至该站的右侧边界,按运行最快了算 167 | accum_time = 0 168 | right_bound_by_sink.append(TimeSpan - accum_time) # 最后一站的到达 169 | for sta_id in range(len(self.staList) - 1, 0, -1): 170 | accum_time += self.secTimes[self.staList[sta_id - 1], self.staList[sta_id]] 171 | right_bound_by_sink.append(TimeSpan - accum_time) 172 | if sta_id != 1: # 最后一站不用加上停站时分了 173 | if self.linePlan[self.staList[sta_id - 1]] == 1: #若停站了则加一个2 174 | accum_time += self.min_dwellTime 175 | else: 176 | accum_time += 0 177 | right_bound_by_sink.append(TimeSpan - accum_time) 178 | right_bound_by_sink = list(reversed(right_bound_by_sink)) 179 | 180 | 181 | right_bound_by_dep = [] 182 | right_bound_by_dep.append(self.dep_UB) # 第一个站 183 | accum_time = self.dep_UB 184 | for sta_id in range(0, len(self.staList) - 1): # 最后一个站不考虑 185 | accum_time += self.secTimes[self.staList[sta_id], self.staList[sta_id + 1]] 186 | right_bound_by_dep.append(accum_time) 187 | if sta_id != len(self.staList) - 2: # 最后一个区间,不加停站时分 188 | accum_time += self.min_dwellTime 189 | right_bound_by_dep.append(accum_time) 190 | 191 | for sta in self.v_staList: 192 | if sta == self.v_staList[-1] or sta == self.v_staList[0]: 193 | continue 194 | right_bound_dep = right_bound_by_dep[self.v_staList.index(sta) - 1] 195 | right_bound_sink = right_bound_by_sink[self.v_staList.index(sta) - 1] 196 | self.right_time_bound[sta] = min(right_bound_dep, right_bound_sink) 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LR_Python_Peter/main.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import re 3 | from Train import * 4 | from Node import * 5 | import copy 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import time 9 | 10 | TimeSpan = 100 11 | ''' 12 | initialize stations, sections, trains and train arcs 13 | ''' 14 | staList = [] # 实际车站列表 15 | v_staList = [] # 时空网车站列表,车站一分为二 # 源节点s, 终结点t 16 | secTimes = {} # total miles for stations 17 | miles = [] 18 | trainList = [] 19 | node_dic = {} # 先用车站做key,再用t做key索引到node 20 | start_time = time.time() 21 | 22 | def read_station(path): 23 | f = open(path, 'r') 24 | lines = f.readlines() 25 | count = 0 26 | for line in lines: 27 | if count == 0: 28 | count += 1 29 | continue 30 | 31 | line = line[:-1] 32 | str = re.split(r",", line) 33 | staList.append(str[0]) 34 | miles.append(int(str[1])) 35 | v_staList.append('_s') 36 | for sta in staList: 37 | if staList.index(sta) != 0: # 不为首站,有到达 38 | v_staList.append('_' + sta) 39 | if staList.index(sta) != len(staList) - 1: 40 | v_staList.append(sta + '_') # 不为尾站,又出发 41 | v_staList.append('_t') 42 | 43 | 44 | def read_section(path): 45 | f = open(path, 'r') 46 | lines = f.readlines() 47 | count = 0 48 | for line in lines: 49 | if count == 0: 50 | count += 1 51 | continue 52 | line = line[:-1] 53 | str = re.split(r",", line) 54 | pair = re.split(r"-", str[0]) 55 | secTimes[pair[0], pair[1]] = int(str[1]) 56 | 57 | 58 | def read_train(path): 59 | f = open(path, 'r') 60 | lines = f.readlines() 61 | count = 0 62 | for line in lines: 63 | if count == 0: 64 | count += 1 65 | continue 66 | line = line[:-1] 67 | str = re.split(r",", line) 68 | train = Train(str[0], 0, TimeSpan) 69 | train.speed = str[1] 70 | for i in range(0, len(staList)): 71 | if (str[i + 2] == '1'): 72 | train.linePlan[staList[i]] = 1 73 | else: 74 | train.linePlan[staList[i]] = 0 75 | train.init_traStaList(staList) 76 | train.create_arcs_LR(secTimes, TimeSpan) 77 | trainList.append(train) 78 | 79 | 80 | read_station('data/station.csv') 81 | read_section('data/section.csv') 82 | read_train('data/train.csv') 83 | 84 | 85 | def init_nodes(): 86 | ''' 87 | initialize nodes, associated with incoming nad outgoing train arcs 88 | ''' 89 | # source node 90 | node_dic['s_'] = {} 91 | node_dic['s_'][-1] = Node('s_', -1) 92 | # initialize node dictionary with key [sta][t] 93 | for sta in v_staList: # 循环车站 94 | node_dic[sta] = {} 95 | for t in range(0, TimeSpan): # 循环时刻t 96 | node = Node(sta, t) 97 | node_dic[sta][t] = node 98 | # sink node 99 | node_dic['_t'] = {} 100 | node_dic['_t'][-1] = Node('_t', -1) 101 | 102 | 103 | # associate node with train arcs, add incoming and outgoing arcs to nodes 104 | def add_arcs_to_nodes_by_flow(): 105 | for nodes_sta in node_dic.values(): 106 | for node in nodes_sta.values(): 107 | for train in trainList: 108 | node.associate_with_outgoing_arcs(train) 109 | node.associate_with_incoming_arcs(train) 110 | 111 | 112 | # 通过列车弧的资源占用特性,将arc与node的关系建立 113 | def associate_arcs_nodes_by_resource_occupation(): 114 | for sta in v_staList: 115 | if sta != v_staList[0] and sta.endswith('_'): # all section departure stations 116 | for t in range(0, TimeSpan): 117 | # 先用车站做key,再用t做key索引到node 118 | cur_node = node_dic[sta][t] 119 | 120 | if len(cur_node.out_arcs) == 0: # 没有弧 121 | continue 122 | 123 | for tra in cur_node.out_arcs.keys(): # 先索引包含的列车 124 | for out_arc in cur_node.out_arcs[tra].values(): # 遍历这个列车在这个点的所有弧 125 | before_occupy = out_arc.before_occupy_dep 126 | after_occupy = out_arc.after_occupy_dep 127 | for i in range(0, before_occupy + 1): # 前面节点占用,在这里把自己加上,可以取到0 128 | if t - i >= 0: 129 | node_dic[sta][t - i].incompatible_arcs.append(out_arc) 130 | out_arc.node_occupied.append(node_dic[sta][t - i]) 131 | else: 132 | break 133 | for i in range(1, after_occupy + 1): # 后面节点占用,这里就不取0了 134 | if t + i < TimeSpan: 135 | node_dic[sta][t + i].incompatible_arcs.append(out_arc) 136 | out_arc.node_occupied.append(node_dic[sta][t + i]) 137 | else: 138 | break 139 | 140 | elif sta != v_staList[-1] and sta.startswith('_'): # all section arrival stations 141 | for t in range(0, TimeSpan): 142 | # 先用车站做key,再用t做key索引到node 143 | cur_node = node_dic[sta][t] 144 | 145 | if len(cur_node.in_arcs) == 0: # 没有弧 146 | continue 147 | 148 | for tra in cur_node.in_arcs.keys(): # 先索引包含的列车 149 | for in_arc in cur_node.in_arcs[tra].values(): # 遍历这个列车在这个点的所有弧 150 | before_occupy = in_arc.before_occupy_arr 151 | after_occupy = in_arc.after_occupy_arr 152 | for i in range(0, before_occupy + 1): # 前面节点占用 153 | if t - i >= 0: 154 | node_dic[sta][t - i].incompatible_arcs.append(in_arc) 155 | in_arc.node_occupied.append(node_dic[sta][t - i]) 156 | else: 157 | break 158 | for i in range(1, after_occupy + 1): # 后面节点占用 159 | if t + i < TimeSpan: 160 | node_dic[sta][t + i].incompatible_arcs.append(in_arc) 161 | in_arc.node_occupied.append(node_dic[sta][t + i]) 162 | else: 163 | break 164 | 165 | 166 | # def get_train_timetable_from_result(): 167 | # for train in trainList: 168 | # print("===============Tra_" + train.traNo + "======================") 169 | # for i in range(len(train.v_staList) - 1): 170 | # curSta = train.v_staList[i] 171 | # nextSta = train.v_staList[i + 1] 172 | # for t, arcs_t in train.arcs[curSta, nextSta].items(): 173 | # for arc_length, arc in arcs_t.items(): 174 | # if arc.isChosen_LR == 1: 175 | # print(curSta + "(" + str(t) + ") => " + nextSta + "(" + str(t + arc_length) + ")") 176 | # train.timetable[curSta] = t 177 | # train.timetable[nextSta] = t + arc_length 178 | def get_train_timetable_from_result(): 179 | for train in trainList: 180 | print("===============Tra_" + train.traNo + "======================") 181 | for node in train.feasible_path.node_passed: 182 | train.timetable[node[0]] = node[1] 183 | 184 | 185 | # Labelling Algorithm 186 | class Label(object): 187 | def __init__(self): 188 | self.node_passed = [] # 该标记的path,即从source node走到该点的路径,存储一系列node名称(sta, t),防止deepcopy效率太低! 189 | self.cost = 0 190 | 191 | def __repr__(self): 192 | temp = "" 193 | for node_name in self.node_passed: 194 | temp += node_name[0] + "," + str(node_name[1]) 195 | if node_name != self.node_passed[-1]: 196 | temp += " => " 197 | return temp 198 | 199 | 200 | ''' 201 | labelling_SPPRC: apply labelling correction algorithm to solve SPPRC(shortest path problem with resource constraint) 202 | param: 203 | Graph: network object 204 | org: string, source node 205 | des: string destination node 206 | rmp_pi: dict, the dual price of each constraint in RMP 207 | ''' 208 | 209 | 210 | def label_correcting_shortest_path(summary_interval, org, des, train): 211 | ''' 212 | get the shortest path for the specific train 213 | :param summary_interval: 214 | :param org: source node name [sta, t] 215 | :param des: sink node name [sta, t] 216 | :param train: train to generate train time space network 217 | :return: 218 | ''' 219 | # initialize Queue 220 | # c_time = time.time() 221 | Queue = collections.deque() 222 | # SE_list = [] 223 | # summary_interval = summary_interval 224 | # create initial label 225 | label = Label() 226 | label.node_passed = [org] 227 | Queue.append(label) # add initial label into Queue, Queue存储各个label,各个label含各自的路径信息 228 | Paths = [] # all complete paths 229 | # cnt = 0 230 | cnt2 = 0 231 | # main loop of the algorithm 232 | while len(Queue) > 0: 233 | current_path = Queue.pop() # 当前的label 234 | # extend the label 235 | last_node_name = current_path.node_passed[-1] # (station, time) 236 | last_node = node_dic[last_node_name[0]][last_node_name[1]] # 当前点的对象 237 | if train.traNo in last_node.out_arcs.keys(): # 以traNo为Key,该节点有该列车的流出弧的话,才进行后续节点的加入 238 | for out_arc in last_node.out_arcs[train.traNo].values(): # 遍历当前点的流出弧,找到下一节点 239 | child_node = node_dic[out_arc.staBelong_next][out_arc.timeBelong_next].name # 找到该弧的终止节点 240 | cnt2 += 1 241 | extended_path = copy.deepcopy(current_path) # 不变current_path,其在循环中还要继续使用 242 | extended_path.node_passed.append(child_node) # 将终止节点加入(str) 243 | extended_path.cost += out_arc.arc_length 244 | for occupy_node in out_arc.node_occupied: 245 | extended_path.cost += occupy_node.multiplier 246 | 247 | Queue.append(extended_path) 248 | 249 | # if child_node in SE_list: 250 | # Queue.appendleft(extended_path) 251 | # else: 252 | # SE_list.append(child_node) 253 | # Queue.append(extended_path) 254 | # if cnt2 % summary_interval == 0: 255 | # print('extended_path:', extended_path.__repr__()) 256 | 257 | if current_path.node_passed[-1][0] == des[0]: # 注意不能直接path[-1] == des,引用类型相同是判断地址相同 258 | Paths.append(current_path) 259 | 260 | # choose optimal solution 261 | opt_path = None 262 | min_cost = 10000000 263 | for path in Paths: 264 | if path.cost < min_cost: 265 | min_cost = path.cost 266 | opt_path = path 267 | path_cost = opt_path.cost 268 | # a_time = time.time() 269 | # print(a_time - c_time) 270 | return opt_path, path_cost 271 | 272 | def label_correcting_shortest_path_with_forbidden(summary_interval, org, des, train): 273 | ''' 274 | get the shortest path for the specific train with the remained subgraph 275 | :param summary_interval: 276 | :param org: source node name [sta, t] 277 | :param des: sink node name [sta, t] 278 | :param train: train to generate train time space network 279 | :return: 280 | ''' 281 | # initialize Queue 282 | # c_time = time.time() 283 | Queue = collections.deque() 284 | SE_list = [] 285 | summary_interval = summary_interval 286 | # create initial label 287 | label = Label() 288 | label.node_passed = [org] # 存字符串,没存节点对象 289 | Queue.append(label) # add initial label into Queue, Queue存储各个label,各个label含各自的路径信息 290 | Paths = [] # all complete paths 291 | cnt = 0 292 | cnt2 = 0 293 | # main loop of the algorithm 294 | while len(Queue) > 0: 295 | current_path = Queue.pop() # 当前的label 296 | # extend the label 297 | last_node_name = current_path.node_passed[-1] 298 | last_node = node_dic[last_node_name[0]][last_node_name[1]] # 当前点 TODO 节点查找 299 | 300 | if train.traNo in last_node.out_arcs.keys(): # 该节点有该列车的流出弧的话,才进行后续节点的加入 TODO 判断列车车次在不在节点流出弧中 301 | for out_arc in last_node.out_arcs[train.traNo].values(): # 遍历当前点的流出弧,找到下一节点 TODO 遍历车次不同长度的弧 302 | if node_dic[out_arc.staBelong_next][out_arc.timeBelong_next].isOccupied: # 若下一节点已经被占用 TODO 节点查找 303 | continue 304 | child_node = node_dic[out_arc.staBelong_next][out_arc.timeBelong_next].name # TODO 节点查找 305 | cnt2 += 1 306 | extended_path = copy.deepcopy(current_path) # 新label # TODO deepcopy 307 | extended_path.node_passed.append(child_node) 308 | extended_path.cost += out_arc.arc_length 309 | for occupy_node in out_arc.node_occupied: # TODO 弧相关节点的遍历 310 | extended_path.cost += occupy_node.multiplier 311 | 312 | # if child_node in SE_list: 313 | # Queue.appendleft(extended_path) 314 | # else: 315 | # SE_list.append(child_node) 316 | # Queue.append(extended_path) 317 | Queue.append(extended_path) 318 | 319 | # if cnt2 % summary_interval == 0: 320 | # print('extended_path:', extended_path.__repr__()) 321 | 322 | 323 | if current_path.node_passed[-1][0] == des[0]: # 注意不能直接path[-1] == des,引用类型相同是判断地址相同 324 | Paths.append(current_path) 325 | 326 | # choose optimal solution 327 | opt_path = None 328 | min_cost = 10000000 329 | for path in Paths: 330 | if path.cost < min_cost: 331 | min_cost = path.cost 332 | opt_path = path 333 | path_cost = opt_path.node_passed[-2][1] - opt_path.node_passed[1][1] 334 | 335 | # a_time = time.time() 336 | # print(a_time - c_time) 337 | return opt_path, path_cost 338 | 339 | 340 | ''' 341 | initialization 342 | ''' 343 | # init_trains() 344 | init_nodes() 345 | add_arcs_to_nodes_by_flow() 346 | associate_arcs_nodes_by_resource_occupation() 347 | 348 | ''' 349 | Lagrangian relaxation approach 350 | ''' 351 | LB = [] 352 | UB = [] 353 | 354 | def update_lagrangian_multipliers(alpha): 355 | total_cost = 0 356 | for sta in v_staList: 357 | if sta == v_staList[0] or sta == v_staList[-1]: # s_和_t并没有乘子 358 | continue 359 | for node in node_dic[sta].values(): # 这个站的各个t的node 360 | temp = 0 361 | for arc in node.incompatible_arcs: 362 | temp += arc.isChosen_LR 363 | 364 | node.multiplier = max(0, node.multiplier + alpha * (temp - 1)) # 1为node capacity 365 | total_cost += node.multiplier 366 | return total_cost 367 | 368 | def set_node_occupation(train): 369 | for node_id in range(1, len(train.feasible_path.node_passed) - 2): 370 | node = train.feasible_path.node_passed[node_id] 371 | next_node = train.feasible_path.node_passed[node_id + 1] 372 | for node in train.arcs[node[0], next_node[0]][node[1]][next_node[1] - node[1]].node_occupied: 373 | node.isOccupied = True 374 | train.last_feasible_path = copy.deepcopy(train.feasible_path) 375 | 376 | def clear_node_occupation(): 377 | for train in trainList: 378 | if train.last_feasible_path is not None: 379 | for node_id in range(1, len(train.last_feasible_path.node_passed) - 2): 380 | node = train.last_feasible_path.node_passed[node_id] 381 | next_node = train.last_feasible_path.node_passed[node_id + 1] 382 | for node in train.arcs[node[0], next_node[0]][node[1]][next_node[1] - node[1]].node_occupied: 383 | node.isOccupied = False 384 | 385 | minGap = 0.1 386 | gap = 100 387 | alpha = 0 388 | iter = 0 389 | interval = 1 390 | last_feasible_cost = np.inf 391 | while gap > minGap: 392 | # LR: train sub-problems solving 393 | path_cost_LR = 0 394 | for train in trainList: 395 | train.opt_path_LR, train.opt_cost_LR = label_correcting_shortest_path(20, node_dic['s_'][-1].name, node_dic['_t'][-1].name, train) 396 | train.update_arc_chosen() # LR中的arc_chosen,用于更新乘子 397 | path_cost_LR += train.opt_cost_LR 398 | 399 | # feasible solutions 400 | path_cost_feasible = 0 401 | for train in trainList: 402 | train.feasible_path, train.feasible_cost = label_correcting_shortest_path_with_forbidden(20, node_dic['s_'][-1].name, node_dic['_t'][-1].name, train) 403 | set_node_occupation(train) # 可行解不需要arc_chosen,用opt_path即可 404 | path_cost_feasible += train.feasible_cost 405 | clear_node_occupation() # 清除不能在循环内,会将同一轮次的上一列车的占用给清空了 406 | if path_cost_feasible <= last_feasible_cost: 407 | last_feasible_cost = path_cost_feasible 408 | UB.append(last_feasible_cost) 409 | 410 | 411 | # update lagrangian multipliers 412 | if iter < 20: 413 | alpha = 0.5 / (iter + 1) 414 | else: 415 | alpha = 0.5 / 20 416 | multiplier_cost = update_lagrangian_multipliers(alpha) 417 | LB.append(path_cost_LR - multiplier_cost) 418 | 419 | iter += 1 420 | gap = (UB[-1] - LB[-1]) / LB[-1] 421 | 422 | if iter % interval == 0: 423 | print("================== iteration " + str(iter) + " ==================") 424 | print(" current gap: " + str(round(gap * 100, 5)) + "% \n") 425 | 426 | get_train_timetable_from_result() 427 | print("================== solution found ==================") 428 | print(" final gap: " + str(round(gap * 100, 5)) + "% \n") 429 | 430 | 431 | ''' 432 | draw timetable 433 | ''' 434 | fig = plt.figure(figsize=(7, 7), dpi=200) 435 | color_value = { 436 | '0': 'midnightblue', 437 | '1': 'mediumblue', 438 | '2': 'c', 439 | '3': 'orangered', 440 | '4': 'm', 441 | '5': 'fuchsia', 442 | '6': 'olive' 443 | } 444 | 445 | xlist = [] 446 | ylist = [] 447 | for i in range(len(trainList)): 448 | train = trainList[i] 449 | xlist = [] 450 | ylist = [] 451 | for sta_id in range(len(train.staList)): 452 | sta = train.staList[sta_id] 453 | if sta_id != 0: # 不为首站, 有到达 454 | if "_" + sta in train.v_staList: 455 | xlist.append(train.timetable["_" + sta]) 456 | ylist.append(miles[staList.index(sta)]) 457 | if sta_id != len(train.staList) - 1: # 不为末站,有出发 458 | if sta + "_" in train.v_staList: 459 | xlist.append(train.timetable[sta + "_"]) 460 | ylist.append(miles[staList.index(sta)]) 461 | plt.plot(xlist, ylist, color=color_value[str(i % 7)], linewidth=1.5) 462 | plt.text(xlist[0] + 0.8, ylist[0] + 4, train.traNo, ha='center', va='bottom', 463 | color=color_value[str(i % 7)], weight='bold', family='Times new roman', fontsize=9) 464 | 465 | plt.grid(True) # show the grid 466 | plt.ylim(0, miles[-1]) # y range 467 | 468 | plt.xlim(0, TimeSpan) # x range 469 | plt.xticks(np.linspace(0, TimeSpan, int(TimeSpan / 10 + 1))) 470 | 471 | plt.yticks(miles, staList, family='Times new roman') 472 | plt.xlabel('Time (min)', family='Times new roman') 473 | plt.ylabel('Space (km)', family='Times new roman') 474 | plt.show() 475 | 476 | end_time = time.time() 477 | time_elapsed = end_time - start_time 478 | print(time_elapsed) 479 | 480 | 481 | ## plot the bound updates 482 | font_dic = {"family":'Arial', 483 | "style":"oblique", 484 | "weight":"normal", 485 | "color":"green", 486 | "size":20 487 | } 488 | 489 | plt.rcParams['figure.figsize'] = (12.0,8.0) 490 | plt.rcParams["font.family"] = 'Arial' 491 | plt.rcParams["font.size"] = 16 492 | 493 | x_cor = range(1,len(LB) + 1) 494 | plt.plot(x_cor, LB, label='LB') 495 | plt.plot(x_cor, UB,label='UB') 496 | plt.legend() 497 | plt.xlabel('Iteration', fontdict=font_dic) 498 | plt.ylabel('Bounds update', fontdict=font_dic) 499 | plt.title('LR: Bounds updates \n', fontsize=23) 500 | plt.show() --------------------------------------------------------------------------------