├── madridALNS.pyc ├── plantilla.xlsx ├── README.md ├── ALNS.py └── madridALNS.py /madridALNS.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/var3z/adaptative_large_neighborhood_search_algorithm/HEAD/madridALNS.pyc -------------------------------------------------------------------------------- /plantilla.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/var3z/adaptative_large_neighborhood_search_algorithm/HEAD/plantilla.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ALNS 2 | ALNS Algorithm which optimise MINLP railroad network models (applied to Madrid's network) 3 | 4 | ## ALNS.py 5 | This file contains Python code in order to create an "Adaptatipe Large Neighborhood Search" algorithm which can manage a "Mixed Integer Non Linear Programming" model. This kind of model, doesn't have an exact solution, so should be solved with metaheuristic algorithm such as this one. 6 | 7 | ## madridALNS.py 8 | This file is the Madrid short-distance railroad model. With the aid of Pyomo (https://github.com/Pyomo) the whole model is developed. 9 | 10 | ## madridALNS.dat 11 | This file contains data for fill the model: real distances, conections (tracks) between nodes (stations), railroad lines, times, wagons, etc. 12 | 13 | ## plantilla.xlsx 14 | This is an Excel template. Only for results. 15 | -------------------------------------------------------------------------------- /ALNS.py: -------------------------------------------------------------------------------- 1 | from madridALNS import * 2 | from pyomo.opt import * 3 | from time import * 4 | from numpy import * 5 | from math import * 6 | from openpyxl import load_workbook 7 | import winsound 8 | 9 | 10 | """ 11 | Construccion del modelo 12 | """ 13 | 14 | to = clock() 15 | instance = model.create('madridALNS.dat') 16 | tf = clock() 17 | tiempo = tf - to 18 | minutos = tiempo // 60 19 | segundos = int(tiempo - minutos * 60) 20 | print('Tiempo creando el modelo: ' + str(int(minutos)) + ' minutos ' + str(segundos) + ' segundos') 21 | 22 | 23 | """ 24 | Funcion para resolver modelo 25 | """ 26 | solver='gurobi' #Debe estar instalado 27 | 28 | def solveFO(instance): 29 | opt = SolverFactory(str(solver)) 30 | results = opt.solve(instance, tee=True) 31 | instance.load(results) 32 | return float(results.Solution.Objective.__default_objective__['value']) 33 | 34 | """ 35 | Datos, variables, parametros y constantes del algoritmo ALNS 36 | """ 37 | 38 | # Datos 39 | lineas = ('1_C1', '2_C2', '3_C3', '4_C4', '5_C5', '6_C8', '7_C10') 40 | headways = (3, 4, 5, 6, 10, 12, 15, 20, 30) 41 | nvmax = 10 42 | nvmin = 0 43 | hdmax = headways[-1] 44 | hdmin = headways[0] 45 | 46 | # Contadores 47 | it_total = 150 # Numero de iteraciones maximas permitidas 48 | repmax = 50 # Numero maximo que se repite la misma solucion antes de parar 49 | tmax = 2700 # tiempo maximo para iterar en segundos 50 | it_seg = 4 # Numero de iteraciones para actualizar la matriz P(op) 51 | calentamiento = 2 # +2 = Numero de rondas iniciales que no actualiza la matriz P(op) 52 | 53 | # Parametros algoritmo 54 | fc = 0.25 # factor de escala para las sigmas 55 | 56 | sigma1 = 0.200 * fc # Puntuacion peor sol. admitida 57 | sigma2 = 0.300 * fc # Puntuacion sol. ref. nueva 58 | sigma3 = 0.400 * fc # Puntuacion sol. best. nueva 59 | C = 0.995 60 | contador = 0 # Iteraciones realizadas 61 | contador_puntuacion = 0 # Contador actualizacion puntuacion 62 | mu = 0.9 63 | 64 | # Variables del algoritmo 65 | probabilidad = ones(28) / 28 # Probabilidad segun num. operadores 66 | puntos = [0] * 28 67 | veces_oper = [0] * 28 68 | mejorsolucion = [] 69 | buvagones = [0] * len(lineas) 70 | buheadways = [0] * len(lineas) 71 | 72 | """ 73 | Salida de datos en excel 74 | """ 75 | 76 | archivo='plantilla.xlsx' #fichero preparado para guardar resultados de las distintas veces que se ejecute el algoritmo 77 | fil0=3 #fila 3 78 | col0=66 # columna ASCII B 79 | wb = load_workbook(filename = archivo) 80 | ws = wb.active 81 | 82 | # funcion escritura datos en excel 83 | def escribe_excel(): 84 | fila=fil0+contador 85 | col=col0 86 | alns1 = alns2 = 0 87 | for dato in aux: 88 | casilla = str(chr(col) + str(fila)) 89 | ws[casilla] = dato 90 | col+=1 91 | for linea in lineas: 92 | casilla = str(chr(col) + str(fila)) 93 | ws[casilla] = instance.hd[linea].value 94 | col+=1 95 | for linea in lineas: 96 | casilla = str(chr(col) + str(fila)) 97 | ws[casilla] = instance.nv[linea].value 98 | col += 1 99 | col1=col2=col0-1 100 | for proba in probabilidad: 101 | casilla = str(chr(col1) + chr(col2) + str(fila)) 102 | ws[casilla] = proba 103 | col2 += 1 104 | if col2 > 90: 105 | col1 += 1 106 | col2 = col0-1 107 | 108 | 109 | """ 110 | Calculo de solucion inicial Sref 111 | # Ej: Todas las lineas con 5 vagones y 12 minutos entre trenes 112 | *** La configuracion se fija en MadridALNS.dat 113 | """ 114 | t0 = clock() 115 | solref = solveFO(instance) 116 | tf = clock() 117 | 118 | #1a salida de datos en excel 119 | T = solref 120 | bestsol = solref 121 | sol = solref 122 | aceptada = '-' 123 | vble = 'sol. inicial' 124 | sentido = 'n/a' 125 | time = tf-t0 126 | porcentaje = 0 127 | aux = [contador, T, sol, aceptada, solref, bestsol, vble, sentido, sentido, time, porcentaje] 128 | # el 'sentido' x2 esta puesto para no crear mas vbles 129 | mejorsolucion.append(bestsol) 130 | escribe_excel() 131 | 132 | 133 | """ 134 | OPERADORES ALNS 135 | """ 136 | 137 | # Incremento de headway 138 | def incr_hd(instance, nl): 139 | posibles = [0] * len(lineas) # lineas no atacadas 140 | # Bucle para intentar atacar las 'nl' lineas 141 | while posibles.count(0) > 0: 142 | i = random.randint(0, len(lineas)) 143 | hdli = instance.hd[lineas[i]].value 144 | j = headways.index(instance.hd[lineas[i]].value) 145 | if posibles[i] == 0: 146 | if hdli < hdmax: 147 | instance.hd[lineas[i]].value = headways[j + 1] 148 | posibles[i] = 1 149 | if posibles.count(1) == nl: 150 | break 151 | instance.preprocess() 152 | return instance 153 | 154 | # Deremento de headway 155 | def decr_hd(instance, nl): 156 | posibles = [0] * len(lineas) # lineas no atacadas 157 | # Bucle para intentar atacar las 'nl' lineas 158 | while posibles.count(0) > 0: 159 | i = random.randint(0, len(lineas)) 160 | hdli = instance.hd[lineas[i]].value 161 | j = headways.index(instance.hd[lineas[i]].value) 162 | if posibles[i] == 0: 163 | if hdli > hdmin: 164 | instance.hd[lineas[i]].value = headways[j - 1] 165 | posibles[i] = 1 166 | if posibles.count(1) == nl: 167 | break 168 | instance.preprocess() 169 | return instance 170 | 171 | # Incremento de vagones 172 | def incr_vag(instance, nl): 173 | posibles = [0] * len(lineas) # lineas no atacadas 174 | # Bucle para intentar atacar las 'nl' lineas 175 | while posibles.count(0) > 0: 176 | i = random.randint(0, len(lineas)) 177 | nvli = instance.nv[lineas[i]].value 178 | if posibles[i] == 0: 179 | if nvli < nvmax: 180 | instance.nv[lineas[i]].value += 1 181 | posibles[i] = 1 182 | if posibles.count(1) == nl: 183 | break 184 | instance.preprocess() 185 | return instance 186 | 187 | # Deremento de vagones 188 | def decr_vag(instance, nl): 189 | posibles = [0] * len(lineas) # lineas no atacadas 190 | # Bucle para intentar atacar las 'nl' lineas 191 | while posibles.count(0) > 0: 192 | i = random.randint(0, len(lineas)) 193 | nvli = instance.nv[lineas[i]].value 194 | if posibles[i] == 0: 195 | if nvli > nvmin: 196 | instance.nv[lineas[i]].value -= 1 197 | posibles[i] = 1 198 | if posibles.count(1) == nl: 199 | break 200 | instance.preprocess() 201 | return instance 202 | 203 | """ 204 | ALGORITMO ALNS 205 | """ 206 | talgo = clock() 207 | j = 0 208 | operadores = range(len(probabilidad)) 209 | while it_total > 0: 210 | op = int(random.choice(range(len(probabilidad)), 1, replace=False, p=probabilidad)) 211 | veces_oper[op] += 1 212 | 213 | # Llamada a incrementar headway 214 | if 0 <= op <= len(lineas) - 1: 215 | for i in range(len(lineas)): 216 | buheadways[i] = instance.hd[lineas[i]].value # Backup de la configuracion original 217 | vble = 'headway' # Vble atacada 218 | sentido = 'Incr' # Incremento o decremento 219 | nl = op + 1 220 | instance = incr_hd(instance, nl) 221 | 222 | # Llamada a decrementar headway 223 | elif len(lineas) <= op <= 2 * len(lineas) - 1: 224 | for i in range(len(lineas)): 225 | buheadways[i] = instance.hd[lineas[i]].value 226 | vble = 'headway' 227 | sentido = 'Decr' 228 | nl = op - len(lineas) + 1 229 | instance = decr_hd(instance, nl) 230 | 231 | # Llamada a incrementar vagones 232 | elif 2 * len(lineas) <=op <= 3 * len(lineas) - 1: 233 | for i in range(len(lineas)): 234 | buvagones[i] = instance.nv[lineas[i]].value 235 | vble = 'vagones' 236 | sentido = 'Incr' 237 | nl = op - 2 * len(lineas) - 1 238 | instance = incr_vag(instance, nl) 239 | 240 | # Llamada a decrementar vagones 241 | elif 3 * len(lineas) <= op <= 4 * len(lineas) - 1: 242 | for i in range(len(lineas)): 243 | buvagones[i] = instance.nv[lineas[i]].value 244 | vble = 'vagones' 245 | sentido = 'Decr' 246 | nl = op - 3 * len(lineas) - 1 247 | instance = decr_vag(instance, nl) 248 | 249 | # Llamada a solver con nueva configuracion recibida del operador 250 | t0 = clock() 251 | print probabilidad 252 | sol = solveFO(instance) 253 | contador += 1 254 | if contador >= calentamiento: 255 | contador_puntuacion += 1 256 | T = C * T 257 | porcentaje = (1 - sol / bestsol) * 100 258 | aceptada ='-' 259 | 260 | # Comprueba soluciones y mejora la probabilidad de volver a aplicar dicho operador si procede 261 | if porcentaje > 0 : 262 | bestsol = sol 263 | puntos[op] += sigma3 264 | elif sol < solref: 265 | solref = sol 266 | puntos[op] += sigma2 267 | else: 268 | acepta = (1 - exp(- T / (sol - bestsol + 0.001))) #*C 269 | soladmisible = random.choice([0, 1], 1, replace=False, p=[1 - acepta, acepta]) 270 | if soladmisible == 1: 271 | puntos[op] += sigma1 272 | solref = sol 273 | aceptada = 'SI' 274 | # Corrige las modificaciones sobre el modelo, ya que la solucion no es mejor que la de referencia: 275 | else: 276 | aceptada = 'NO' 277 | if 0 <= op <= (2 * len(lineas) - 1): 278 | for i in range(len(lineas)): 279 | instance.hd[lineas[i]].value = buheadways[i] 280 | else: 281 | for i in range(len(lineas)): 282 | instance.nv[lineas[i]].value = buvagones[i] 283 | instance.preprocess() 284 | 285 | # Actualizacion de las probabilidades de los operadores 286 | if contador >= calentamiento and contador_puntuacion >= it_seg: 287 | for i in operadores: 288 | if veces_oper[i] != 0: 289 | probabilidad[i] = (1 - mu) * probabilidad[i] + mu * (puntos[i] / veces_oper[i]) 290 | puntos[i] = veces_oper[i] = 0 291 | contador_puntuacion = 0 292 | probabilidad = probabilidad / sum(p for p in probabilidad) 293 | mejorsolucion.append(bestsol) 294 | tf = clock() 295 | aux = [contador, T, sol, aceptada, solref, bestsol, vble, sentido, op, time, porcentaje] 296 | print aux 297 | escribe_excel() 298 | # Bucle para terminar si encuentra n veces seguidas la misma solucion optima 299 | if len(mejorsolucion) > 1: 300 | if mejorsolucion[-1] == mejorsolucion[-2]: 301 | j += 1 302 | if j == repmax: 303 | break 304 | else: 305 | j = 0 306 | if clock() - talgo > tmax: 307 | break 308 | it_total -= 1 309 | tfin = clock() 310 | ttotal = tfin - talgo 311 | minutos = int(ttotal // 60) 312 | segundos = round(ttotal - 60 * minutos,1) 313 | ws['A1'] = 't = '+str(minutos)+ '\' ' + str(segundos)+ '\"' 314 | nombrearchivo = strftime('%d%m%y - %H,%M') 315 | wb.save(nombrearchivo+'.xlsx') 316 | # Aviso sonoro de que ha terminado 317 | winsound.Beep(1000,250) 318 | winsound.Beep(37,1) 319 | winsound.Beep(1000,750) -------------------------------------------------------------------------------- /madridALNS.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # En este modulo se define el modelo de la red de cercanias de Madrid contemplando 3 | # los 800 pares origen-destino admisibles. 4 | #------------------------------------------------------------------------------- 5 | from __future__ import division 6 | from pyomo.environ import * 7 | from sys import * 8 | 9 | """ 10 | Tipo de modelo 11 | """ 12 | model = AbstractModel() 13 | 14 | """ 15 | Conjuntos 16 | """ 17 | model.N = Set() #nodos 18 | model.W = Set() #pares origen-destino 19 | model.L = Set() #lineas 20 | model.ST = Set() #RangeSet de segmentos compartidos || (i,j) ? E: sum(a[i,j,l] for l in model.L) > 1 21 | model.V = Set() #Frecuencias permitidas 2,3,4,5,6,10,12,15,20 trenes/hora 22 | model.TET = Set() #Conjunto de coeficientes tetha 23 | model.T = Set() #set 1..N, siendo N el numero maximo de vias compartidas 24 | 25 | 26 | """ 27 | Datos 28 | """ 29 | model.gamma = Param(model.L) #Longitud de la linea L 30 | model.d = Param(model.N, model.N) #Distancia del enlace (i,j) 31 | model.tetha = Param(model.TET) #Coeficientes monetarios 32 | model.tethafr = Param(model.V, model.V) #Matriz de compatibilidad de headways 33 | 34 | model.v = Param() #Velocidad de funcionamiento 35 | model.C = Param() #Capacidad del vagon 36 | model.NVmax = Param() #Vagones maximos por tren 37 | model.NVmin = Param() #Vagones minimos por tren 38 | model.Vmax = Param() #frecuencia maxima 39 | model.sft = Param() #tiempo ventana de seguridad 40 | model.dwt = Param() #Tiempo de espera en estacion 41 | 42 | model.ckm_loc= Param() #Coste variable de operacion de la linea (eur por Km plaza y hora) dependiente de la capacidad y frecuencia 43 | model.ckm_carr= Param() #Coste variable de operacion de la linea (eur por Km plaza y hora) dependiente de la capacidad y frecuencia 44 | model.Horizonte= Param() #Horizonte para computar costes de compra de rolling stocks medido en anios 45 | model.cost_loc= Param() #Coste de compra de una locomotora por 10E6 46 | model.cost_carr= Param() #Coste de compra de un vagon por 10E6 47 | model.factorg = Param() #Factor de escala de la demanda 48 | 49 | model.a = Param(model.N, model.N, model.L,default=0) #matriz de incidencia. a[i,j,l]=1 si la linea l pasa por el arco i,j 50 | model.b = Param(model.N, model.L) #matriz de incidencia. b[i,l]=1 si la linea l para en el nodo i 51 | 52 | #COnjuntos auxiliares para datos 53 | 54 | model.CT = Set() #Cabecera Tabla 55 | model.CTr = Set() #Cabecera Tracks 56 | model.tabla = Param(model.W,model.CT) #tabla del tipo [w wo wd g[w]] 57 | model.tracks = Param(model.ST,model.CTr) #tabla del tipo [segmento nodo-orig-segmento nodo-fin-segmento numero-de-vias] 58 | model.ijw = Param(model.N,model.N,model.W, default=0) #Tabla de pares validos 59 | 60 | #Conjuntos auxiliares para el modelo 61 | 62 | def indexNNW_rule(model): 63 | indices=[] 64 | for w in model.W: 65 | for i in model.N: 66 | for j in model.N: 67 | if model.ijw[i,j,w]==1: 68 | indices.append((i,j,w)) 69 | return indices 70 | model.NNW = Set(dimen=3, initialize=indexNNW_rule) #Indices simplificados - indices de las variables no nulas y pares-nodos permitidos (w=800) 71 | 72 | 73 | """ 74 | Variables 75 | """ 76 | model.f = Var(model.NNW, model.L, within=NonNegativeIntegers) #flujo de pasajeros que circulan en la linea l, por el arco i,j, con un par origen-destino w 77 | model.fo = Var(model.W, model.L, within=NonNegativeIntegers) #flujo de pasajeros desde cada origen 78 | model.fd = Var(model.W, model.L, within=NonNegativeIntegers) #flujo de pasajeros hacia cada destino 79 | model.trans = Var(model.N,model.W, model.L, model.L, within=NonNegativeIntegers) #flujo con par origen-destino w, transbordando en el nodo i, de la linea l a lp 80 | model.beta = Var(model.L, model.V, within=Boolean) #vble auxiliar. beta[l,v]=1 si la linea l circula con la frecuencia v 81 | model.delta = Var(model.ST, model.L, model.T, within=Boolean) #vble auxiliar. delta[s,l,t]=1 si el track t del segmento compartido s, esta asignado a la linea l 82 | model.h = Var(model.W,within=NonNegativeIntegers) #variables de holguras 83 | model.fr = Var(model.L, within=NonNegativeIntegers) #frecuencia para cada linea l 84 | model.frs = Var(model.ST, model.L, model.T, within=NonNegativeIntegers) #frecuencia que lleva la linea l en el track t del segmeto compartido s 85 | model.u = Var(model.W, within=NonNegativeReals) 86 | 87 | #variables de depuracion 88 | model.flujoarco = Var(model.N,model.N, within=NonNegativeReals) 89 | model.capacidadarco = Var(model.N,model.N, within=NonNegativeReals) 90 | model.FO1 = Var(within=NonNegativeReals) 91 | model.FO2 = Var(within=NonNegativeReals) 92 | model.FO3 = Var(within=NonNegativeReals) 93 | model.FO4 = Var(within=NonNegativeReals) 94 | model.FO5 = Var(within=NonNegativeReals) 95 | model.FO6 = Var(within=NonNegativeReals) 96 | model.FO7 = Var(within=NonNegativeReals) 97 | model.FO8 = Var(within=NonNegativeReals) 98 | 99 | #Variables modificadas por ALNS, se pasan como datos parametrizados 100 | model.hd = Param(model.L,mutable=True, within=NonNegativeIntegers) #headways para cada linea l 101 | model.nv = Param(model.L,mutable=True, within=NonNegativeIntegers) #numero de vagones de la linea l 102 | model.ALNS = Var(model.ST,model.T,model.L,model.L, within=Boolean) 103 | model.ALNS2 = Var(model.ST,model.T, within=Boolean) 104 | model.M = Param(mutable=True) 105 | model.M2 = Param(mutable=True) 106 | 107 | 108 | """ 109 | Restricciones 110 | """ 111 | 112 | #1 // flujo de salida desde cada origen "wo" igual a demanda del par origen destino "w" 113 | def resd1_rule(model,w): 114 | wo=model.tabla[w,'wo'] 115 | expr=0 116 | for l in model.L: 117 | if model.b[wo,l]==1: 118 | expr += model.fo[w,l] 119 | if expr!=0: 120 | return (expr + model.h[w]==model.tabla[w,'g']*model.factorg) 121 | else: 122 | return Constraint.Skip 123 | model.restr1 = Constraint(model.W,rule=resd1_rule) 124 | 125 | #2 // flujo de llegada a cada destino "wd" igual a demanda del par origen destino "w" 126 | def resd2_rule(model,w): 127 | wd=model.tabla[w,'wd'] 128 | expr=0 129 | for l in model.L: 130 | if model.b[wd,l]==1: 131 | expr += model.fd[w,l] 132 | if expr!=0: 133 | return (expr + model.h[w]==model.tabla[w,'g']*model.factorg) 134 | else: 135 | return Constraint.Skip 136 | model.restr2 = Constraint(model.W,rule=resd2_rule) 137 | 138 | #3 // conservacion del flujo teniendo en cuenta transbordos 139 | def resd3_rule(model,i,w,l): 140 | wo=model.tabla[w,'wo'] 141 | wd=model.tabla[w,'wd'] 142 | expr1=expr2=expr3=expr4=0 143 | if i!=wo and i!=wd: 144 | for j in model.N: 145 | if model.a[i,j,l]==1 and model.ijw[i,j,w]==1: 146 | expr3+=model.f[i,j,w,l] 147 | for k in model.N: 148 | if model.a[k,i,l]==1 and model.ijw[k,i,w]==1: 149 | expr1+=model.f[k,i,w,l] 150 | for lp in model.L: 151 | if l!=lp and model.b[i,lp]==1 and model.b[i,l]==1: 152 | expr2+=model.trans[i,w,lp,l] 153 | expr4+=model.trans[i,w,l,lp] 154 | if (expr2+expr3)!=0 and (expr1+expr4)!=0: 155 | return (expr1+expr2==expr3+expr4) 156 | else: 157 | return Constraint.Skip 158 | model.restr3 = Constraint(model.N,model.W,model.L, rule=resd3_rule) 159 | 160 | #4 // flujo de transporte nulo si y[i,j,w,l]==0 161 | #se elimina model.y[i,j,w,l] del termino derecho de la desigualdad 162 | def resd4_rule(model,i,j,w,l): 163 | if model.a[i,j,l]==1 and model.ijw[i,j,w]==1: 164 | return (model.f[i,j,w,l]<=model.tabla[w,'g']*model.factorg) 165 | else: 166 | return Constraint.Skip 167 | model.restr4 = Constraint(model.NNW,model.L,rule=resd4_rule) 168 | 169 | #5a // flujo saliente de cada nodo origen 170 | def resd5_rule(model,w,l): 171 | wo=model.tabla[w,'wo'] 172 | expr=0 173 | for j in model.N: 174 | if model.b[j,l]==1 and model.ijw[wo,j,w]==1: 175 | expr+=model.f[wo,j,w,l] 176 | if expr!=0: 177 | return (expr==model.fo[w,l]) 178 | else: 179 | return Constraint.Skip 180 | model.restr5 = Constraint(model.W,model.L,rule=resd5_rule) 181 | 182 | #5b // flujo entrante en cada nodo destino 183 | def resd5b_rule(model,w,l): 184 | wd=model.tabla[w,'wd'] 185 | expr=0 186 | for i in model.N: 187 | if model.a[i,wd,l]==1 and model.ijw[i,wd,w]==1: 188 | expr+=model.f[i,wd,w,l] 189 | if expr!=0: 190 | return (expr==model.fd[w,l]) 191 | else: 192 | return Constraint.Skip 193 | model.restr5b = Constraint(model.W,model.L,rule=resd5b_rule) 194 | 195 | #6 // Promedio tiempo viaje 196 | def resd6_rule(model,w): 197 | expr1=expr2=expr3=0 198 | wo=model.tabla[w,'wo'] 199 | for l in model.L: 200 | for i in model.N: 201 | for j in model.N: 202 | if model.a[i,j,l]==1 and model.ijw[i,j,w]==1: 203 | expr1+=(60/model.v)*model.d[i,j]*model.f[i,j,w,l] 204 | for lp in model.L: 205 | if lp!=l and model.b[i,lp]==1 and model.b[i,l]==1: 206 | expr3+=0.5*model.trans[i,w,l,lp]*model.hd[lp] 207 | expr2+=0.5*model.fo[w,l]*model.hd[l] 208 | return (model.u[w]==expr1+expr2+expr3) 209 | model.restr6 = Constraint(model.W,rule=resd6_rule) 210 | 211 | #7 // relacion entre frecuencias y headways 212 | def resd7_rule(model,l): 213 | return (model.fr[l]*model.hd[l]==60) 214 | model.restr7 = Constraint(model.L, rule=resd7_rule) 215 | 216 | #8a // asignacion de frecuencia a cada linea 217 | def resd8a_rule(model,l): 218 | return (model.fr[l]==sum(v*model.beta[l,v] for v in model.V)) 219 | model.restr8a = Constraint(model.L, rule=resd8a_rule) 220 | 221 | #8b // solo una frecuencia es admisible por linea 222 | def resd8b_rule(model,l): 223 | return (1==sum(model.beta[l,v] for v in model.V)) 224 | model.restr8b = Constraint(model.L, rule=resd8b_rule) 225 | 226 | #9 // asignacion de via t en el segmento compartido s a la linea l 227 | def resd9_rule(model,s,l): 228 | si=model.tracks[s,'si'] 229 | sf=model.tracks[s,'sf'] 230 | if model.a[si,sf,l]==1: 231 | vias=range(1,model.tracks[s,'vias']+1) 232 | return (1==sum(model.delta[s,l,t] for t in vias)) 233 | else: 234 | return Constraint.Skip 235 | model.restr9 = Constraint(model.ST,model.L, rule=resd9_rule) 236 | 237 | #10 // asignacion de frecuencias en segmentos compartidos 238 | def resd10_rule(model,s,l): 239 | si=model.tracks[s,'si'] 240 | sf=model.tracks[s,'sf'] 241 | if model.a[si,sf,l]==1: 242 | vias=range(1,model.tracks[s,'vias']+1) 243 | return (model.fr[l]==sum(model.frs[s,l,t] for t in vias)) 244 | else: 245 | return Constraint.Skip 246 | model.restr10 = Constraint(model.ST,model.L, rule=resd10_rule) 247 | 248 | #11 // las frecuencias en segmentos compartidos son menores o igual a la frecuencia maxima 249 | def resd11_rule(model,s,l,t): 250 | si=model.tracks[s,'si'] 251 | sf=model.tracks[s,'sf'] 252 | if t>model.tracks[s,'vias']: 253 | return Constraint.Skip 254 | if model.a[si,sf,l]==1: 255 | return (model.frs[s,l,t]<=model.Vmax*model.delta[s,l,t]) 256 | else: 257 | return Constraint.Skip 258 | model.restr11 = Constraint(model.ST,model.L, model.T, rule=resd11_rule) 259 | 260 | #12 // safety time and dwell time 261 | def resd12_rule(model,s,t): 262 | si=model.tracks[s,'si'] 263 | sf=model.tracks[s,'sf'] 264 | if t>model.tracks[s,'vias']: 265 | return Constraint.Skip 266 | expr=0 267 | for l in model.L: 268 | if model.a[si,sf,l]==1: 269 | expr+=model.frs[s,l,t] 270 | if expr!=0: 271 | return (expr*(model.sft + model.dwt)<=60 + 420 * model.ALNS2[s,t]) 272 | else: 273 | return Constraint.Skip 274 | model.restr12 = Constraint(model.ST, model.T, rule=resd12_rule) 275 | 276 | #13 // Multiplicidad 277 | def resd13_rule(model,s,l,lp,v,vp,t): 278 | if t>model.tracks[s,'vias']: 279 | return Constraint.Skip 280 | vias=range(1,model.tracks[s,'vias']+1) 281 | si=model.tracks[s,'si'] 282 | sf=model.tracks[s,'sf'] 283 | if l