class Master: """ Master Object for initialising, solving, updating, and column creation of the tail assignment master problem""" def __init__(self, Data): """Formulate the master problem. INPUTS Data :: object, classes.Data; problem data. """ self.Data = Data self.duals = [] self.master = Model("master LP") # Init master model self.master.Params.OutputFlag = 0 # No output self._formulate_master() def _formulate_master(self): """ Formulate set partitioning model for tail assignment""" # Variable dicts # a_dict = {(s_label[1], s): s.cost for s_label, s in self.Data.scheds.items()} dum_dict = { f: 20000 + 20000 * (f.arrival - f.departure) for f in self.Data.flights } # VARIABLES # a = self.master.addVars(a_dict.keys(), name="a", vtype="B") dummy = self.master.addVars(dum_dict.keys(), name="dummy", vtype="B") # OBJECTIVE FUNCTION # self.master.setObjective(dummy.prod(dum_dict)) # FLIGHT CONSTRAINTS # self.master.addConstrs((dummy[key] >= 1 for key in dum_dict), name='flight') # AIRCRAFT CONSTRAINTS # self.master.addConstrs( (a.sum(k, '*') <= 1 for k in self.Data.aircraft), name='aircraft_schedule') self.master.update() # write to file self.master.write("./output/init.lp") def _solve_relax(self): """ Relaxes and solves the master problem. INPUTS master :: object, gurobipy.Model; master problem. RETURNS duals :: list of tuples, (dual, classes.Flight) relax :: object, gurobipy.Model; relaxed master problem. """ self.relax = self.master.relax() self.relax.update() self.relax.optimize() duals = [(float(c.Pi), [ f for f in self.Data.flights if f.i_d == self._split(c.ConstrName) ][0]) for c in self.relax.getConstrs() if "flight" in c.ConstrName] duals = sorted(duals, key=lambda x: x[1].i_d) self.duals.append([d[0] for d in duals]) return self.relax, duals def _generate_column(self, k, it, path): """ Adds column using shortest path from extended TSN. Parameters ---------- k : str, aircraft in subproblem it : int, iteration number path : list, edges in shortest path [(edge_dict, edge_weight), ..] Returns ------- master : object, gurobipy.Model; updated master problem with new column """ col = Column() # Init Column flights = self.Data.flights cost_col = 0 # count number of dummy edges for column obj coeff for p in path: # for each flight in the path if any(f._full_dict() == p[0] for f in flights): flight = [f for f in flights if f._full_dict() == p[0]][0] # Get constraints associated with flight constrs = [ c for c in self.master.getConstrs() if "flight" in c.ConstrName and self._split(c.ConstrName) == flight.i_d ] # Add terms to column for each constraint col.addTerms([1] * len(constrs), constrs) else: cost_col += p[1] # Get constraints associated with aircraft k constr = [ c for c in self.master.getConstrs() if "aircraft_schedule" in c.ConstrName and k in c.ConstrName ][0] col.addTerms(1, constr) # Get aircraft dual for cost lambda_k = [ float(c.Pi) for c in self.relax.getConstrs() if "aircraft_schedule" in c.ConstrName and k in c.ConstrName ][0] # cost of path + 2 due to first edge initialisation cost = 2 + sum(p[1] for p in path) - lambda_k if cost < 0: self.master.addVar(obj=cost_col, vtype="B", name="a[%s,s_%s_%s]" % (k, it, k), column=col) self.master.update() return self, cost @staticmethod def _split(string): """ Split integers in string and return last occurrence""" import re return list(map(int, re.findall(r'\d+', string)))[-1]
def make_model(strong_inequalities=False, relax=False, callback=False, hascapacity=1): # Relabel data commodities = data.commodities arcs = data.arcs capacity = data.capacity variable_cost = data.variable_cost fixed_cost = data.fixed_cost nodes = data.nodes demand = data.demand periods = data.periods # Create optimization model env = Env(logfilename="") m = Model('multi-period-netflow', env) # Create variables flow, arc_open = {}, {} for t in periods: for i, j in arcs: arc_open[i, j, t] = m.addVar(vtype=GRB.BINARY, lb=0.0, ub=1.0, obj=fixed_cost[(i, j), t], name='open_{0:d}_{1:d}_{2:d}'.format( i, j, t)) for h in commodities: origin, destination = [ key_val[1] for key_val in demand.keys() if key_val[0] == h ][0] upper = capacity[i, j] if has_capacity else demand[(h, (origin, destination), t)] flow[h, i, j, t] = m.addVar(obj=variable_cost[i, j], name='flow_{0:d}_{1:d}_{2:d}_{3:d}'.format( h, i, j, t)) m.update() # Arc capacity constraints and unique arc setup constraints constrs = [] for (i, j) in arcs: m.addConstr( quicksum(arc_open[i, j, l] for l in range(1, len(data.periods) + 1)) <= 1, 'unique_setup{0:d}_{1:d}'.format(i, j)) for t in periods: if not hascapacity: capacity[i, j] = sum(demand[i] for i in demand.keys() if i[2] == t) m.addConstr( quicksum(flow[h, i, j, t] for h in commodities) <= capacity[i, j] * quicksum(arc_open[i, j, s] for s in xrange(1, t + 1)), 'cap_{0:d}_{1:d}_{2:d}'.format(i, j, t)) if not callback and strong_inequalities: for (commodity, (origin, destination), period) in demand: if period == t: constrs.append( m.addConstr( flow[commodity, i, j, t] <= demand[commodity, (origin, destination), period] * quicksum(arc_open[i, j, l] for l in range(1, t + 1)), name='strong_com{0:d}_{1:d}-{2:d}_per{3:d}'. format(commodity, i, j, t))) # Flow conservation constraints for (commodity, (origin, destination), period) in demand: for j in nodes: if j == origin: node_demand = demand[commodity, (origin, destination), period] elif j == destination: node_demand = -demand[commodity, (origin, destination), period] else: node_demand = 0 h = commodity m.addConstr( -quicksum(flow[h, i, j, period] for i, j in arcs.select('*', j)) + quicksum(flow[h, j, k, period] for j, k in arcs.select(j, '*')) == node_demand, 'node_{0:d}_{1:d}_{2:d}'.format(h, j, period)) m.update() # Compute optimal solution m.setParam("TimeLimit", 7200) # m.params.NodeLimit = 1 # m.params.cuts = 0 # m.setParam("Threads", 2) m.setAttr('Lazy', constrs, [3] * len(constrs)) # m.write("eyes.lp") # try: if strong_inequalities: if not relax: # m.setParam("NodeLimit", 1000000) # m.params.Cuts = 0 if callback: print 'callback in action! :)' m.params.preCrush = 1 m.update() m._vars = m.getVars() m.optimize(strong_inequalities_callback) else: m.optimize(time_callback) else: m = m.relax() m.optimize(time_callback) else: m.optimize(time_callback) if PRINT_VARS: for var in m.getVars(): if str(var.VarName[0]) == 'f' and var.X > 0.0001: name = var.VarName.split('_') print 'arc: \t {} \t commodity: {} \t period: {} \t value: \t {}'.format( (int(name[2]), int(name[3])), int(name[1]), int(name[4]), var.x) # Grab the positive flows and see how many variables open during the first period positive_flows = [ var for var in m.getVars() if var.VarName[0] == 'o' and var.X > 0.5 ] first_period_arcs = sum([ var.X for var in positive_flows if int(var.VarName.split('_')[3]) == 1 ]) print '% of arcs that open in first period: {}%'.format( 100 * first_period_arcs / len(positive_flows)) print '% of arcs that are utilized: {}%'.format( (100. * len(positive_flows)) / len(data.arcs)) objective = m.getObjective().getValue() fixed_cost_percentage = sum([ fixed_cost[(i, j), t] * arc_open[i, j, t].X for i, j in data.arcs for t in data.periods ]) / objective print 'Fixed cost percentage: {}%'.format(fixed_cost_percentage * 100.) for var in m.getVars(): if str(var.VarName[0]) == 'o' and var.X > 0.0001: name = var.VarName.split('_') print 'Arc: \t {} \t Period: {} \t Value: \t {}'.format( (int(name[1]), int(name[2])), int(name[3]), var.X) # m.write('trial2.lp') except: if m.status == GRB.status.INFEASIBLE and DEBUG: print 'Infeasible model. Computing IIS..' m.computeIIS() m.write('trial.ilp')