Esempio n. 1
0
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]
Esempio n. 2
0
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')