예제 #1
0
파일: DEF.py 프로젝트: swidemann/AWS-vs-UC
        for k in K:
            prt.print_aggregated_fs_aircraft_schedule(airports, N, A,
                                                      last_hour, k, n, y,
                                                      instance, images_dir)

        print("\n########### SECOND STAGE SCHEDULE ##########")
        for s in S:
            print("###### Scenario {} #####".format(s))
            for k in K:
                prt.print_aggregated_ss_aircraft_schedule(
                    airports, N, A, last_hour, k, n, y, s, instance,
                    images_dir)

    if write_cargo_routing:
        wrt.write_cargo_schedule(ods_dir,
                                 "Schedule ODs i-{}.txt".format(instance), A,
                                 S, Cargo, OD, ex, x)

    if write_retimings:
        wrt.write_retimings(retimings_dir,
                            "Retimings i-{}.txt".format(instance), S, AF, V, K,
                            y, r, zplus, zminus)

else:
    print('Optimization was stopped with status {}'.format(status))
    # compute Irreducible Inconsistent Subsystem
    model.computeIIS()
    for constr in model.getConstrs():
        if constr.IISConstr:
            print('Infeasible constraint: {}'.format(constr.constrName))
예제 #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')
def generate_multi_dim_sample(bounds, directory, num_teams, num_md_per_cycle,
                              numSam, numCycle, theoretical):

    # the list of sample dimensions, the +1
    cycle = list(range(numCycle))
    day = list(range(num_md_per_cycle))
    days = list(range(num_md_per_cycle + 1))
    home = away = list(range(num_teams))

    constrList = [
        [(0, ), (1, )],
        [(0, ), (2, )],
        [(0, ), (3, )],
        [(0, ), (1, 2)],
        [(0, ), (1, 3)],
        [(0, ), (2, 3)],
        [(0, ), (1, 2, 3)],
        [(1, ), (0, )],
        [(1, ), (2, )],
        [(1, ), (3, )],
        [(1, ), (0, 2)],
        [(1, ), (0, 3)],
        [(1, ), (2, 3)],
        [(1, ), (0, 2, 3)],
        [(2, ), (0, )],
        [(2, ), (1, )],
        [(2, ), (3, )],
        [(2, ), (0, 1)],
        [(2, ), (0, 3)],
        [(2, ), (1, 3)],
        [(2, ), (0, 1, 3)],
        [(3, ), (0, )],
        [(3, ), (1, )],
        [(3, ), (2, )],
        [(3, ), (0, 1)],
        [(3, ), (0, 2)],
        [(3, ), (1, 2)],
        [(3, ), (0, 1, 2)],
        [(0, 1), (2, )],
        [(0, 1), (3, )],
        [(0, 1), (2, 3)],
        # cons away = 31
        [(0, 2), (1, )],
        [(0, 2), (3, )],
        [(0, 2), (1, 3)],
        # cons home = 34
        [(0, 3), (1, )],
        [(0, 3), (2, )],
        [(0, 3), (1, 2)],
        [(1, 2), (0, )],
        [(1, 2), (3, )],
        [(1, 2), (0, 3)],
        [(1, 3), (0, )],
        [(1, 3), (2, )],
        [(1, 3), (0, 2)],
        [(2, 3), (0, )],
        [(2, 3), (1, )],
        [(2, 3), (0, 1)],
        [(0, 1, 2), (3, )],
        [(0, 1, 3), (2, )],
        [(0, 2, 3), (1, )],
        [(1, 2, 3), (0, )]
    ]

    try:
        model = Model("sspSolver")
        # give verbose logging when 1, otherwise 0
        model.setParam(GRB.param.OutputFlag, 1)

        ### Decision Variables ###
        # 0 = cycles, 1 = days, 2 = away, 3 = home
        # regular combinations over the 4 dimensions
        x = model.addVars(cycle,
                          day,
                          home,
                          away,
                          vtype=GRB.BINARY,
                          name="base")
        n = model.addVars(cycle, day, home, vtype=GRB.BINARY, name="n")
        o = model.addVars(cycle, day, away, vtype=GRB.BINARY, name="o")
        p = model.addVars(cycle, home, away, vtype=GRB.BINARY, name="p")
        q = model.addVars(day, home, away, vtype=GRB.BINARY, name="q")
        r = model.addVars(cycle, day, vtype=GRB.BINARY, name="r")
        s = model.addVars(cycle, home, vtype=GRB.BINARY, name="s")
        t = model.addVars(cycle, away, vtype=GRB.BINARY, name="t")
        u = model.addVars(day, home, vtype=GRB.BINARY, name="u")
        v = model.addVars(day, away, vtype=GRB.BINARY, name="v")
        w = model.addVars(home, away, vtype=GRB.BINARY, name="w")

        #y = model.addVars(cycle, day, home, away, vtype=GRB.BINARY, name="basetrans")
        #yn = model.addVars(cycle, day, home, vtype=GRB.BINARY, name="trans_n")

        cNA = model.addVars(cycle,
                            day,
                            day,
                            away,
                            vtype=GRB.BINARY,
                            name="cons")
        cNAs = model.addVars(cycle,
                             days,
                             day,
                             away,
                             vtype=GRB.BINARY,
                             name="cons_min")
        cNH = model.addVars(cycle,
                            day,
                            day,
                            home,
                            vtype=GRB.BINARY,
                            name="consHome")
        cNHs = model.addVars(cycle,
                             days,
                             day,
                             home,
                             vtype=GRB.BINARY,
                             name="consHomes")

        cZy = model.addVars(cycle,
                            day,
                            day,
                            home,
                            vtype=GRB.BINARY,
                            name="days_betw_games")
        cZys = model.addVars(cycle,
                             days,
                             day,
                             home,
                             vtype=GRB.BINARY,
                             name="days_btw_gamess")

        # transpose function
        #model.addConstrs(
        #    y[c, d, h, a] == x[c, d, h, a] + x[c, d, a, h] for c in cycle for d in day for a in away for h in home)
        #model.addConstrs((y.sum(c, d, h, '*') == yn[c, d, h] for c in cycle for d in day for h in home), "yn_y")

        model.addConstrs((x.sum(c, d, '*', a) == o[c, d, a] for c in cycle
                          for d in day for a in away), "xo")
        model.addConstrs((x.sum(c, d, h, '*') == n[c, d, h] for c in cycle
                          for d in day for h in home), "xn")
        model.addConstrs((x.sum(c, '*', h, a) == p[c, h, a] for c in cycle
                          for h in home for a in away), "xp")
        model.addConstrs((x.sum('*', d, h, a) == q[d, h, a] for d in day
                          for h in home for a in away), "xq")

        model.addConstrs(
            (r[c, d] <= n.sum(c, d, '*') for c in cycle for d in day), "rn")
        model.addConstrs(
            (t[c, a] <= n.sum(c, '*', a) for c in cycle for a in away), "tn")
        model.addConstrs(
            (v[d, a] <= n.sum('*', d, a) for d in day for a in away), "vn")

        model.addConstrs(
            (r[c, d] <= o.sum(c, d, '*') for c in cycle for d in day), "ro")
        model.addConstrs(
            (s[c, h] <= o.sum(c, '*', h) for c in cycle for h in home), "so")
        model.addConstrs(
            (u[d, h] <= o.sum('*', d, h) for d in day for h in home), "uo")

        model.addConstrs(
            (s[c, h] <= p.sum(c, h, '*') for c in cycle for h in home), "sp")
        model.addConstrs(
            (t[c, a] <= p.sum(c, '*', a) for c in cycle for a in away), "tp")
        model.addConstrs(
            (w[h, a] <= p.sum('*', h, a) for h in home for a in away), "wp")

        model.addConstrs(
            (u[d, h] <= q.sum(d, h, '*') for d in day for h in home), "uq")
        model.addConstrs(
            (v[d, a] <= q.sum(d, '*', a) for d in day for a in away), "vq")
        model.addConstrs(
            (w[h, a] <= q.sum('*', h, a) for h in home for a in away), "wq")

        if theoretical:
            ### Hard constraints -- not yet in bounds ###
            # never play yourself
            model.addConstrs(x[c, d, i, i] == 0 for c in cycle for d in day
                             for i in home)
            # only play one game per day
            model.addConstrs((x.sum(c, d, i, '*') + x.sum(c, d, '*', i) <= 1
                              for c in cycle for d in day
                              for i in home), "1gamePerDay")

        # Hard constraints from bounds files ###
        # bounds 0 = countLowerbound
        # bounds 1 = countUpperBound
        # bounds 2 = minConsZero
        # bounds 3 = maxConsZero
        # bounds 4 = minConsNonZero
        # bounds 5 = maxConsNonZero
        for i in range(len(bounds)):
            ### SEED COUNT BOUNDS: bounds[i,0] is the lowerbound, bounds[i,1] is the upperbound
            if bounds[i, 0] > 0:
                # this part covers count lowerbound
                if constrList[i] == [(0, ), (1, )]:
                    model.addConstrs((r.sum(c, '*') >= bounds[i, 0]
                                      for c in cycle), "LWR_constr-(0,), (1,)")
                elif constrList[i] == [(0, ), (2, )]:
                    model.addConstrs((t.sum(c, '*') >= bounds[i, 0]
                                      for c in cycle), "LWR_constr-(0,), (2,)")
                elif constrList[i] == [(0, ), (3, )]:
                    model.addConstrs((s.sum(c, '*') >= bounds[i, 0]
                                      for c in cycle), "LWR_constr-(0,), (3,)")
                elif constrList[i] == [(0, ), (1, 2)]:
                    model.addConstrs(
                        (o.sum(c, '*', '*') >= bounds[i, 0] for c in cycle),
                        "LWR_constr-(0,), (1,2)")
                elif constrList[i] == [(0, ), (1, 3)]:
                    model.addConstrs(
                        (n.sum(c, '*', '*') >= bounds[i, 0] for c in cycle),
                        "LWR_constr-(0,), (1,3)")
                elif constrList[i] == [(0, ), (2, 3)]:
                    model.addConstrs(
                        (p.sum(c, '*', '*') >= bounds[i, 0] for c in cycle),
                        "LWR_constr-(0,), (2,3)")
                elif constrList[i] == [(0, ), (1, 2, 3)]:
                    model.addConstrs((x.sum(c, '*', '*', '*') >= bounds[i, 0]
                                      for c in cycle),
                                     "LWR_constr-(0,), (1,2,3)")

                elif constrList[i] == [(1, ), (0, )]:
                    model.addConstrs((r.sum('*', d) >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (0,)")
                elif constrList[i] == [(1, ), (2, )]:
                    model.addConstrs((v.sum(d, '*') >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (2,)")
                elif constrList[i] == [(1, ), (3, )]:
                    model.addConstrs((u.sum(d, '*') >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (3,)")
                elif constrList[i] == [(1, ), (0, 2)]:
                    model.addConstrs((o.sum('*', d, '*') >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (0,2)")
                elif constrList[i] == [(1, ), (0, 3)]:
                    model.addConstrs((n.sum('*', d, '*') >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (0,3)")
                elif constrList[i] == [(1, ), (2, 3)]:
                    model.addConstrs((q.sum(d, '*', '*') >= bounds[i, 0]
                                      for d in day), "LWR_constr-(1,), (2,3)")
                elif constrList[i] == [(1, ), (0, 2, 3)]:
                    model.addConstrs(
                        (x.sum('*', d, '*', '*') >= bounds[i, 0] for d in day),
                        "LWR_constr-(1,), (0,2,3)")

                elif constrList[i] == [(2, ), (0, )]:
                    model.addConstrs((t.sum('*', h) >= bounds[i, 0]
                                      for h in home), "LWR_constr-(2,), (0,)")
                elif constrList[i] == [(2, ), (1, )]:
                    model.addConstrs((v.sum('*', h) >= bounds[i, 0]
                                      for h in home), "LWR_constr-(2,), (1,)")
                elif constrList[i] == [(2, ), (3, )]:
                    model.addConstrs((w.sum(h, '*') >= bounds[i, 0]
                                      for h in home), "LWR_constr--(2,), (3,)")
                elif constrList[i] == [(2, ), (0, 1)]:
                    model.addConstrs(
                        (o.sum('*', '*', h) >= bounds[i, 0] for h in home),
                        "LWR_constr--(2,), (0,1)")
                elif constrList[i] == [(2, ), (0, 3)]:
                    model.addConstrs(
                        (p.sum('*', h, '*') >= bounds[i, 0] for h in home),
                        "LWR_constr--(2,), (0,3)")
                elif constrList[i] == [(2, ), (1, 3)]:
                    model.addConstrs((q.sum('*', h, '*') >= bounds[i, 0]
                                      for h in home), "LWR_constr-(2,), (1,3)")
                elif constrList[i] == [(2, ), (0, 1, 3)]:
                    model.addConstrs((x.sum('*', '*', h, '*') >= bounds[i, 0]
                                      for h in home),
                                     "LWR_constr-(2,), (0,1,3)")

                elif constrList[i] == [(3, ), (0, )]:
                    model.addConstrs((s.sum('*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (0,)")
                elif constrList[i] == [(3, ), (1, )]:
                    model.addConstrs((u.sum('*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (1,)")
                elif constrList[i] == [(3, ), (2, )]:
                    model.addConstrs((w.sum('*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (2,)")
                elif constrList[i] == [(3, ), (0, 1)]:
                    model.addConstrs((n.sum('*', '*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (0,1)")
                elif constrList[i] == [(3, ), (0, 2)]:
                    model.addConstrs((p.sum('*', '*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (0,2)")
                elif constrList[i] == [(3, ), (1, 2)]:
                    model.addConstrs((q.sum('*', '*', a) >= bounds[i, 0]
                                      for a in away), "LWR_constr-(3,), (1,2)")
                elif constrList[i] == [(3, ), (0, 1, 2)]:
                    model.addConstrs((x.sum('*', '*', '*', a) >= bounds[i, 0]
                                      for a in away),
                                     "LWR_constr-(3,), (0,1,2)")

                elif constrList[i] == [(0, 1), (2, )]:
                    model.addConstrs((o.sum(c, d, '*') >= bounds[i, 0]
                                      for c in cycle
                                      for d in day), "LWR_constr-(0,1), (2,)")
                elif constrList[i] == [(0, 1), (3, )]:
                    model.addConstrs((n.sum(c, d, '*') >= bounds[i, 0]
                                      for c in cycle
                                      for d in day), "LWR_constr-(0,1), (3,)")
                elif constrList[i] == [(0, 1), (2, 3)]:
                    model.addConstrs((x.sum(c, d, '*', '*') >= bounds[i, 0]
                                      for c in cycle
                                      for d in day), "LWR_constr-(0,1), (2,3)")

                elif constrList[i] == [(0, 2), (1, )]:
                    bound = bounds[i, 0]
                    model.addConstrs((o.sum(c, '*', a) >= bounds[i, 0]
                                      for c in cycle
                                      for a in away), "LWR_constr-(0,2), (1,)")
                elif constrList[i] == [(0, 2), (3, )]:
                    model.addConstrs((p.sum(c, '*', a) >= bounds[i, 0]
                                      for c in cycle
                                      for a in away), "LWR_constr-(0,2), (3,)")
                elif constrList[i] == [(0, 2), (1, 3)]:
                    model.addConstrs((x.sum(c, '*', a) >= bounds[i, 0]
                                      for c in cycle for a in away),
                                     "LWR_constr-(0,2), (1,3)")

                elif constrList[i] == [(0, 3), (1, )]:
                    model.addConstrs((n.sum(c, '*', h) >= bounds[i, 0]
                                      for c in cycle
                                      for h in home), "LWR_constr-(0,3), (1,)")
                elif constrList[i] == [(0, 3), (2, )]:
                    model.addConstrs((p.sum(c, '*', h) >= bounds[i, 0]
                                      for c in cycle
                                      for h in home), "LWR_constr-(0,3), (2,)")
                elif constrList[i] == [(0, 3), (1, 2)]:
                    model.addConstrs((x.sum(c, '*', '*', h) >= bounds[i, 0]
                                      for c in cycle for h in home),
                                     "LWR_constr-(0,3), (1,2)")

                elif constrList[i] == [(1, 2), (0, )]:
                    model.addConstrs((o.sum('*', d, a) >= bounds[i, 0]
                                      for d in day
                                      for a in away), "LWR_constr-(1,2), (0,)")
                elif constrList[i] == [(1, 2), (3, )]:
                    model.addConstrs((q.sum(d, a, '*') >= bounds[i, 0]
                                      for d in day
                                      for a in away), "LWR_constr-(1,2), (3,)")
                elif constrList[i] == [(1, 2), (0, 3)]:
                    model.addConstrs((x.sum('*', d, a, '*') >= bounds[i, 0]
                                      for d in day for a in away),
                                     "LWR_constr-(1,2), (0,3)")

                elif constrList[i] == [(1, 3), (0, )]:
                    model.addConstrs((n.sum('*', d, h) >= bounds[i, 0]
                                      for d in day
                                      for h in home), "LWR_constr-(1,3), (0,)")
                elif constrList[i] == [(1, 3), (2, )]:
                    model.addConstrs((q.sum(d, '*', h) >= bounds[i, 0]
                                      for d in day
                                      for h in home), "LWR_constr-(1,3), (2,)")
                elif constrList[i] == [(1, 3), (0, 2)]:
                    model.addConstrs((x.sum('*', d, '*', h) >= bounds[i, 0]
                                      for d in day for h in home),
                                     "LWR_constr-(1,3), (0,2)")

                elif constrList[i] == [(2, 3), (0, )]:
                    model.addConstrs((p.sum('*', h, a) >= bounds[i, 0]
                                      for h in home
                                      for a in away), "LWR_constr-(2,3), (0,)")
                elif constrList[i] == [(2, 3), (1, )]:
                    model.addConstrs((q.sum('*', h, a) >= bounds[i, 0]
                                      for h in home
                                      for a in away), "LWR_constr-(2,3), (1,)")
                elif constrList[i] == [(2, 3), (0, 1)]:
                    model.addConstrs((x.sum('*', '*', h, a) >= bounds[i, 0]
                                      for h in home for a in away),
                                     "LWR_constr-(2,3), (0,1)")

                elif constrList[i] == [(0, 1, 2), (3, )]:
                    model.addConstrs((x.sum(c, d, a, '*') >= bounds[i, 0]
                                      for c in cycle for d in day
                                      for a in away),
                                     "LWR_constr-(0,1,2), (3,)")
                elif constrList[i] == [(0, 1, 3), (2, )]:
                    model.addConstrs((x.sum(c, d, '*', h) >= bounds[i, 0]
                                      for c in cycle for d in day
                                      for h in home),
                                     "LWR_constr-(0,1,3), (2,)")
                elif constrList[i] == [(0, 2, 3), (1, )]:
                    model.addConstrs((x.sum(c, '*', a, h) >= bounds[i, 0]
                                      for c in cycle for a in away
                                      for h in home),
                                     "LWR_constr-(0,2,3), (1,)")
                elif constrList[i] == [(1, 2, 3), (0, )]:
                    model.addConstrs((x.sum('*', d, a, h) >= bounds[i, 0]
                                      for d in day for a in away
                                      for h in home),
                                     "LWR_constr-(1,2,3), (0,)")
            if bounds[i, 1] > 0:
                # this part covers count lowerbound
                if constrList[i] == [(0, ), (1, )]:
                    model.addConstrs((r.sum(c, '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (1,)")
                elif constrList[i] == [(0, ), (2, )]:
                    model.addConstrs((t.sum(c, '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (2,)")
                elif constrList[i] == [(0, ), (3, )]:
                    model.addConstrs((s.sum(c, '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (3,)")
                elif constrList[i] == [(0, ), (1, 2)]:
                    model.addConstrs((o.sum(c, '*', '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (1,2)")
                elif constrList[i] == [(0, ), (1, 3)]:
                    model.addConstrs((n.sum(c, '*', '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (1,3)")
                elif constrList[i] == [(0, ), (2, 3)]:
                    model.addConstrs((p.sum(c, '*', '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (2,3)")
                elif constrList[i] == [(0, ), (1, 2, 3)]:
                    model.addConstrs((x.sum(c, '*', '*', '*') <= bounds[i, 1]
                                      for c in cycle), "constr-(0,), (1,2,3)")

                elif constrList[i] == [(1, ), (0, )]:
                    model.addConstrs((r.sum('*', d) <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (0,)")
                elif constrList[i] == [(1, ), (2, )]:
                    model.addConstrs((v.sum(d, '*') <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (2,)")
                elif constrList[i] == [(1, ), (3, )]:
                    model.addConstrs((u.sum(d, '*') <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (3,)")
                elif constrList[i] == [(1, ), (0, 2)]:
                    model.addConstrs((o.sum('*', d, '*') <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (0,2)")
                elif constrList[i] == [(1, ), (0, 3)]:
                    model.addConstrs((n.sum('*', d, '*') <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (0,3)")
                elif constrList[i] == [(1, ), (2, 3)]:
                    model.addConstrs((q.sum(d, '*', '*') <= bounds[i, 1]
                                      for d in day), "LWR_constr-(1,), (2,3)")
                elif constrList[i] == [(1, ), (0, 2, 3)]:
                    model.addConstrs(
                        (x.sum('*', d, '*', '*') <= bounds[i, 1] for d in day),
                        "LWR_constr-(1,), (0,2,3)")

                elif constrList[i] == [(2, ), (0, )]:
                    model.addConstrs((t.sum('*', h) <= bounds[i, 1]
                                      for h in home), "constr-(2,), (0,)")
                elif constrList[i] == [(2, ), (1, )]:
                    model.addConstrs((v.sum('*', h) <= bounds[i, 1]
                                      for h in home), "constr-(2,), (1,)")
                elif constrList[i] == [(2, ), (3, )]:
                    model.addConstrs((w.sum(h, '*') <= bounds[i, 1]
                                      for h in home), "constr--(2,), (3,)")
                elif constrList[i] == [(2, ), (0, 1)]:
                    model.addConstrs((o.sum('*', '*', h) <= bounds[i, 1]
                                      for h in home), "constr--(2,), (0,1)")
                elif constrList[i] == [(2, ), (0, 3)]:
                    model.addConstrs((p.sum('*', h, '*') <= bounds[i, 1]
                                      for h in home), "constr--(2,), (0,3)")
                elif constrList[i] == [(2, ), (1, 3)]:
                    model.addConstrs((q.sum('*', h, '*') <= bounds[i, 1]
                                      for h in home), "constr-(2,), (1,3)")
                elif constrList[i] == [(2, ), (0, 1, 3)]:
                    model.addConstrs((x.sum('*', '*', h, '*') <= bounds[i, 1]
                                      for h in home),
                                     "LWR_constr-(2,), (0,1,3)")

                elif constrList[i] == [(3, ), (0, )]:
                    model.addConstrs((s.sum('*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (0,)")
                elif constrList[i] == [(3, ), (1, )]:
                    model.addConstrs((u.sum('*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (1,)")
                elif constrList[i] == [(3, ), (2, )]:
                    model.addConstrs((w.sum('*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (2,)")
                elif constrList[i] == [(3, ), (0, 1)]:
                    model.addConstrs((n.sum('*', '*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (0,1)")
                elif constrList[i] == [(3, ), (0, 2)]:
                    model.addConstrs((p.sum('*', '*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (0,2)")
                elif constrList[i] == [(3, ), (1, 2)]:
                    model.addConstrs((q.sum('*', '*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (1,2)")
                elif constrList[i] == [(3, ), (0, 1, 2)]:
                    model.addConstrs((x.sum('*', '*', '*', a) <= bounds[i, 1]
                                      for a in away), "constr-(3,), (0,1,2)")

                elif constrList[i] == [(0, 1), (2, )]:
                    model.addConstrs((o.sum(c, d, '*') <= bounds[i, 1]
                                      for c in cycle
                                      for d in day), "constr-(0,1), (2,)")
                elif constrList[i] == [(0, 1), (3, )]:
                    model.addConstrs((n.sum(c, d, '*') <= bounds[i, 1]
                                      for c in cycle
                                      for d in day), "constr-(0,1), (3,)")
                elif constrList[i] == [(0, 1), (2, 3)]:
                    model.addConstrs((x.sum(c, d, '*', '*') <= bounds[i, 1]
                                      for c in cycle
                                      for d in day), "constr-(0,1), (2,3)")

                elif constrList[i] == [(0, 2), (1, )]:
                    model.addConstrs((o.sum(c, '*', a) <= bounds[i, 1]
                                      for c in cycle
                                      for a in away), "constr-(0,2), (1,)")
                elif constrList[i] == [(0, 2), (3, )]:
                    model.addConstrs((p.sum(c, '*', a) <= bounds[i, 1]
                                      for c in cycle
                                      for a in away), "constr-(0,2), (3,)")
                elif constrList[i] == [(0, 2), (1, 3)]:
                    model.addConstrs((x.sum(c, '*', a) <= bounds[i, 1]
                                      for c in cycle
                                      for a in away), "constr-(0,2), (1,3)")

                elif constrList[i] == [(0, 3), (1, )]:
                    model.addConstrs((n.sum(c, '*', h) <= bounds[i, 1]
                                      for c in cycle
                                      for h in home), "constr-(0,3), (1,)")
                elif constrList[i] == [(0, 3), (2, )]:
                    model.addConstrs((p.sum(c, '*', h) <= bounds[i, 1]
                                      for c in cycle
                                      for h in home), "constr-(0,3), (2,)")
                elif constrList[i] == [(0, 3), (1, 2)]:
                    model.addConstrs((x.sum(c, '*', '*', h) <= bounds[i, 1]
                                      for c in cycle
                                      for h in home), "constr-(0,3), (1,2)")

                elif constrList[i] == [(1, 2), (0, )]:
                    model.addConstrs((o.sum('*', d, a) <= bounds[i, 1]
                                      for d in day
                                      for a in away), "constr-(1,2), (0,)")
                elif constrList[i] == [(1, 2), (3, )]:
                    model.addConstrs((q.sum(d, a, '*') <= bounds[i, 1]
                                      for d in day
                                      for a in away), "constr-(1,2), (3,)")
                elif constrList[i] == [(1, 2), (0, 3)]:
                    model.addConstrs((x.sum('*', d, a, '*') <= bounds[i, 1]
                                      for d in day
                                      for a in away), "constr-(1,2), (0,3)")

                elif constrList[i] == [(1, 3), (0, )]:
                    model.addConstrs((n.sum('*', d, h) <= bounds[i, 1]
                                      for d in day
                                      for h in home), "constr-(1,3), (0,)")
                elif constrList[i] == [(1, 3), (2, )]:
                    model.addConstrs((q.sum(d, '*', h) <= bounds[i, 1]
                                      for d in day
                                      for h in home), "constr-(1,3), (2,)")
                elif constrList[i] == [(1, 3), (0, 2)]:
                    model.addConstrs((x.sum('*', d, '*', h) <= bounds[i, 1]
                                      for d in day
                                      for h in home), "constr-(1,3), (0,2)")

                elif constrList[i] == [(2, 3), (0, )]:
                    model.addConstrs((p.sum('*', h, a) <= bounds[i, 1]
                                      for h in home
                                      for a in away), "constr-(2,3), (0,)")
                elif constrList[i] == [(2, 3), (1, )]:
                    model.addConstrs((q.sum('*', h, a) <= bounds[i, 1]
                                      for h in home
                                      for a in away), "constr-(2,3), (1,)")
                elif constrList[i] == [(2, 3), (0, 1)]:
                    model.addConstrs((x.sum('*', '*', h, a) <= bounds[i, 1]
                                      for h in home
                                      for a in away), "constr-(2,3), (0,1)")

                elif constrList[i] == [(0, 1, 2), (3, )]:
                    model.addConstrs((x.sum(c, d, a, '*') <= bounds[i, 1]
                                      for c in cycle for d in day
                                      for a in away), "constr-(0,1,2), (3,)")
                elif constrList[i] == [(0, 1, 3), (2, )]:
                    model.addConstrs((x.sum(c, d, '*', h) <= bounds[i, 1]
                                      for c in cycle for d in day
                                      for h in home), "constr-(0,1,3), (2,)")
                elif constrList[i] == [(0, 2, 3), (1, )]:
                    model.addConstrs((x.sum(c, '*', a, h) <= bounds[i, 1]
                                      for c in cycle for a in away
                                      for h in home), "constr-(0,2,3), (1,)")
                elif constrList[i] == [(1, 2, 3), (0, )]:
                    model.addConstrs((x.sum('*', d, a, h) <= bounds[i, 1]
                                      for d in day for a in away
                                      for h in home), "constr-(1,2,3), (0,)")

        if bounds[34, 5] + bounds[34, 4] > 0:
            # definition for the first day
            model.addConstrs((cNH[c, 0, 0, h] == n[c, 0, h] for c in cycle
                              for h in home), "cNH1")
            model.addConstrs((cNH[c, d1 + 1, 0, h] <= n[c, d1 + 1, h]
                              for c in cycle for h in home
                              for d1 in day if d1 < len(day) - 1), "cNA2")
            model.addConstrs((cNH[c, d1 + 1, 0, h] <= 1 - n[c, d1, h]
                              for c in cycle for h in home
                              for d1 in day if d1 < len(day) - 1), "cNA3")
            model.addConstrs(
                (cNH[c, d1 + 1, 0, h] >= n[c, d1 + 1, h] - n[c, d1, h]
                 for c in cycle for d1 in day
                 for h in home if d1 < len(day) - 1), "cNA4")

            # # definition for the second day and the third, fourth, etc...
            model.addConstrs((cNH[c, 0, d2, h] == 0 for c in cycle
                              for d2 in day for h in home if d2 > 0), "2cNA1")
            model.addConstrs((cNH[c, d1, d2, h] <= cNH[c, d1 - 1, d2 - 1, h]
                              for c in cycle for h in home for d1 in day
                              for d2 in day if d1 > 0 if d2 > 0))
            model.addConstrs((cNH[c, d1, d2, h] <= n[c, d1, h] for c in cycle
                              for d1 in day for d2 in day for h in home
                              if d1 > 0 if d2 > 0))
            model.addConstrs((cNH[c, d1, d2, h] >=
                              n[c, d1, h] + cNH[c, d1 - 1, d2 - 1, h] - 1
                              for c in cycle for d1 in day for d2 in day
                              for h in home if d1 > 0 if d2 > 0))
            if bounds[34, 5] > 0:
                model.addConstr((quicksum(
                    cNH[c, d1, d2, a] for c in cycle for d1 in day
                    for a in away
                    for d2 in range(bounds[34, 5].astype(int), len(day)))
                                 == 0), "cnASum")
            if bounds[34, 4] > 0:
                model.addConstrs((cNHs[c, 0, d2, h] == 0 for c in cycle
                                  for d2 in day for h in home), "minConsPlay")
                model.addConstrs((cNHs[c, d1, d2, h] <= cNH[c, d1 - 1, d2, h]
                                  for c in cycle for h in home for d1 in day
                                  for d2 in day if d1 > 0))
                model.addConstrs((cNHs[c, d1, d2, h] <= 1 - n[c, d1, h]
                                  for c in cycle for d1 in day for d2 in day
                                  for h in home if d1 > 0))
                model.addConstrs(
                    (cNHs[c, d1, d2, h] >= cNH[c, d1 - 1, d2, h] - n[c, d1, h]
                     for c in cycle for d1 in day for d2 in day for h in home
                     if d1 > 0))
                model.addConstrs((cNHs[c, num_md_per_cycle, d2, h] >=
                                  cNH[c, num_md_per_cycle - 1, d2, h]
                                  for c in cycle for d2 in day for h in home))
                model.addConstr((quicksum(
                    cNHs[c, d1, d2, a] * (bounds[34, 4] - 1 - d2)
                    for c in cycle for a in away for d1 in days
                    for d2 in range(bounds[34, 4].astype(int) - 1)) == 0))

        if bounds[31, 5] + bounds[31, 4] > 0:
            # definition for the first day
            model.addConstrs((cNA[c, 0, 0, a] == o[c, 0, a] for c in cycle
                              for a in away), "cNA1")
            model.addConstrs((cNA[c, d1 + 1, 0, a] <= o[c, d1 + 1, a]
                              for c in cycle for a in away
                              for d1 in day if d1 < len(day) - 1), "cNA2")
            model.addConstrs((cNA[c, d1 + 1, 0, a] <= 1 - o[c, d1, a]
                              for c in cycle for a in away
                              for d1 in day if d1 < len(day) - 1), "cNA3")
            model.addConstrs(
                (cNA[c, d1 + 1, 0, a] >= o[c, d1 + 1, a] - o[c, d1, a]
                 for c in cycle for d1 in day
                 for a in away if d1 < len(day) - 1), "cNA4")

            # # definition for the second day and the third, fourth, etc...
            model.addConstrs((cNA[c, 0, d2, a] == 0 for c in cycle
                              for d2 in day for a in away if d2 > 0), "2cNA1")
            model.addConstrs((cNA[c, d1, d2, a] <= cNA[c, d1 - 1, d2 - 1, a]
                              for c in cycle for a in away for d1 in day
                              for d2 in day if d1 > 0 if d2 > 0))
            model.addConstrs((cNA[c, d1, d2, a] <= o[c, d1, a] for c in cycle
                              for d1 in day for d2 in day for a in away
                              if d1 > 0 if d2 > 0))
            model.addConstrs((cNA[c, d1, d2, a] >=
                              o[c, d1, a] + cNA[c, d1 - 1, d2 - 1, a] - 1
                              for c in cycle for d1 in day for d2 in day
                              for a in away if d1 > 0 if d2 > 0))
            if bounds[31, 5] > 0:
                model.addConstr((quicksum(
                    cNA[c, d1, d2, a] for c in cycle for d1 in day
                    for a in away
                    for d2 in range(bounds[31, 5].astype(int), len(day)))
                                 == 0), "cnASum")
            if bounds[31, 4] > 0:
                model.addConstrs((cNAs[c, 0, d2, a] == 0 for c in cycle
                                  for d2 in day for a in away), "minConsPlay")
                model.addConstrs((cNAs[c, d1, d2, a] <= cNA[c, d1 - 1, d2, a]
                                  for c in cycle for a in away for d1 in day
                                  for d2 in day if d1 > 0))
                model.addConstrs((cNAs[c, d1, d2, a] <= 1 - o[c, d1, a]
                                  for c in cycle for d1 in day for d2 in day
                                  for a in away if d1 > 0))
                model.addConstrs(
                    (cNAs[c, d1, d2, a] >= cNA[c, d1 - 1, d2, a] - o[c, d1, a]
                     for c in cycle for d1 in day for d2 in day for a in away
                     if d1 > 0))
                model.addConstrs((cNAs[c, num_md_per_cycle, d2, a] >=
                                  cNA[c, num_md_per_cycle - 1, d2, a]
                                  for c in cycle for d2 in day for a in away))
                model.addConstr((quicksum(
                    cNAs[c, d1, d2, a] * (bounds[31, 4] - 1 - d2)
                    for c in cycle for a in away for d1 in days
                    for d2 in range(bounds[31, 4].astype(int) - 1)) == 0))

        # Sets the number of solutions to be generated
        model.setParam(GRB.Param.PoolSolutions, numSam)
        # grab the most optimal solutions
        model.setParam(GRB.Param.PoolSearchMode, 2)
        model.optimize()
        numSol = model.SolCount
        print("Number of solutions found for the model: " + str(numSol))

        if model.status == GRB.Status.INFEASIBLE:
            model.computeIIS()
            print("Following constraints are infeasible: ")
            for c in model.getConstrs():
                if c.IISConstr:
                    print(c.constrName)
        if model.status == GRB.Status.OPTIMAL:
            model.write('m.sol')
        for i in range(numSol):
            model.setParam(GRB.Param.SolutionNumber, i)
            # get value from subobtimal MIP sol (might change this in X if we dont do soft constraints)
            solution = model.getAttr('xn', x)

            tmp = np.zeros([numCycle, num_md_per_cycle, num_teams, num_teams])
            for key in solution:
                tmp[key] = round(solution[key])
            tmp_sol = tmp.astype(np.int64)
            with open(os.path.join(directory, "sol" + str(i) + ".csv"),
                      "w+",
                      newline='') as sol_csv:
                csv_writer = csv.writer(sol_csv, delimiter=',')

                # writes cycle row
                row = ['']
                for c in range(numCycle):
                    row.extend(['C' + str(c)] * num_md_per_cycle * num_teams)
                csv_writer.writerow(row)

                # writes round row
                row = ['']
                for c in range(numCycle):
                    for d in range(num_md_per_cycle):
                        row.extend(['R' + str(d)] * num_teams)
                csv_writer.writerow(row)

                # writes awayteam row
                row = ['']
                for c in range(numCycle):
                    for d in range(num_md_per_cycle):
                        for t in range(num_teams):
                            row.append('T' + str(t))
                csv_writer.writerow(row)

                # write the actual solution per team
                tmp_sol.astype(int)
                for t in range(num_teams):
                    row = ['T' + str(t)]
                    for c in range(numCycle):
                        for r in range(num_md_per_cycle):
                            for team in range(num_teams):
                                row.append(tmp_sol[c][r][t][team])
                    csv_writer.writerow(row)

    except GurobiError as e:
        raise e
예제 #4
0
class VRP:
    """ VRP class function

    Methods
    -------
    example2(bla=blabla)
        lorem ipsun
    example3()
        lorem ipsun

    """

    def __init__(self):

        self.Q = None
        self.n = None
        self.x_range = None
        self.y_range = None
        self.number_of_customers = None
        # CVRP
        self.demand_range = None
        # CVRPTW
        self.time_window = None
        self.opening_time = None
        self.closing_time = None
        self.processing_time = None

        self.K = None
        self.nodes = None
        self.N = None  # Customer Nodes
        self.V = None  # Depot and customer nodes
        self.A = None  # Arcs between depot and customer nodes
        self.c = None  # Cost associated with each arc
        self.q = None  # The weight that has to be delivered to each customer
        self.e = None  # Time window lower boundary
        self.l = None  # Time window upper boundary
        self.p = None  # Processing time

        self.model = None
        self.x = None
        self.u = None
        self.t = None

        self.subtour_type = 'DFJ'  # DFJ or MTZ
        self.gap_goal = 0.
        self.time_limit = 60*60
        self.M = None

        self.random_data_n_model_p = "random_datasets"

    def setup_random_data(self,
                          number_of_customers,
                          number_of_vehicles,
                          vehicle_capacity=5,
                          x_range=20, y_range=20,
                          demand_lower=1, demand_higher=10,
                          seed=420):

        """
        Random dataset generation 

        Parameters
        ----------
        number_of_customers : int
            Total number of customers in problem
        number_of_vehicles : int
            Number of vehicles
        Vehicle capacity : int, optional
            Vehicle capacity
        x_range : int, optional
            Horizontal Scaling for the random dataset
        y_range : int, optional
            Vertical Scaling for the random dataset
        demand_lower : int, optional
            Lower bound of customer demand range.
        demand_higher : int, optional
            Lower bound of customer demand range.
        seed : int, optional
            Seed for RNG
        """

        if seed is not None:
            np.random.seed(seed)

        self.Q = vehicle_capacity
        self.n = number_of_customers
        self.x_range = x_range
        self.y_range = y_range
        self.number_of_customers = number_of_customers
        self.demand_range = [demand_lower, demand_higher]
        self.K = np.arange(1, number_of_vehicles + 1)

        self.create_dataset()

        self.create_arcs()

    def setup_preset_data(self, file_name, number_of_vehicles, subtour_type='DFJ'):

        nb_customers, truck_capacity, n, v, demands, nodes = self.read_input_cvrp(file_name)

        self.read_input_cvrp(file_name)
        self.Q = truck_capacity
        self.n = nb_customers + 1

        self.number_of_customers = nb_customers
        self.K = np.arange(1, number_of_vehicles + 1)

        self.nodes = nodes
        self.N = n
        self.V = v
        self.q = demands

        self.subtour_type = subtour_type

        self.create_arcs()

        # Assign time windows to each customer
        if self.subtour_type == "TW":
            self.e = {}
            self.l = {}
            self.p = {}
            for i in self.N:
                self.e[i] = np.random.randint(self.opening_time, self.closing_time - self.time_window)
                self.l[i] = self.e[i] + self.time_window
                self.p[i] = self.processing_time

    def visualize(self,plot_sol='y'):


        cmap  = ['tab:blue','tab:orange','tab:green', 'tab:red','tab:purple','tab:brown','tab:pink','tab:gray','tab:olive','tab:cyan']

        fig, ax = plt.subplots(1, 1,figsize=(10,8))  # a figure with a 1x1 grid of Axes


        if plot_sol in ['y', 'yes']:

            plt.title( 'Vehicle Routing Problem solution (n={}, k={})'.format(self.n,len(self.K)) )

            self.find_active_arcs()
            tours = self.subtour(self.K, self.active_arcs)

            for k in tours.keys():
                vehicle_color = cmap[k-1]
                print(vehicle_color)
                vehicle_arcs = tours[k]

                ax.scatter([],[], c=vehicle_color, label='k='+str(k) )

                G = nx.DiGraph()
                for tour in vehicle_arcs:
                    idx = 0
                    for node in tour:
                        node_pos = (self.nodes.iloc[node][0],self.nodes.iloc[node][1])
                        G.add_node(node,pos=node_pos)

                        if idx < len(tour)-1:
                            node_i = tour[idx]
                            node_j = tour[idx+1]
                            edge_cost = round(self.c[(node_i,node_j)],2)
                            G.add_edge( node_i, node_j, weight=edge_cost )
                            idx += 1

                    node_pos = nx.get_node_attributes(G,'pos')
                    weights = nx.get_edge_attributes(G,'weight')

                    nx.draw(G,node_pos,ax=ax, node_size=400, node_color='w', edgecolors=vehicle_color, edge_color= vehicle_color )
                    nx.draw_networkx_edge_labels(G, node_pos, ax=ax, edge_labels=weights)

                    for node in node_pos.keys():
                        pos = node_pos[node]

                        if node == self.V[0]:
                            offset = -0.06
                            comma_on = ', '
                        elif node == self.V[-1]:
                            if self.V[-1] >= 10:
                                offset = 0.08
                            else:
                                offset = 0.06
                            comma_on = ''
                        else:
                            offset = 0
                            comma_on=''

                        ax.text(pos[0] + offset, pos[1], s=str(node)+comma_on, horizontalalignment='center',verticalalignment='center')


            # Recolor depot node to black
            G = nx.DiGraph()
            pos = (self.nodes.iloc[0][0],self.nodes.iloc[0][1])
            G.add_node(0,pos=pos)
            node_pos = nx.get_node_attributes(G,'pos')
            nx.draw_networkx_nodes(G,node_pos,ax=ax, node_size=400, node_shape='D',node_color='w', edgecolors='b')

            # Add axes
            xmin = self.nodes.min()['x_coord'] - 1
            ymin = self.nodes.min()['y_coord'] - 1

            xmax = self.nodes.max()['x_coord'] + 1
            ymax = self.nodes.max()['y_coord'] + 1

            plt.axis('on') # turns on axis
            ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
            ax.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)
            plt.legend(loc='lower right')

            print("------------------------------------ SOLUTION ------------------------------------")
            print("Subtours")
            print(tours)
            print("Runtime: ", self.model.Runtime)
            print("MIPGap: ",  self.model.MIPGap)
            print("Total vehicle capacity:", len(self.K)*self.Q)
            print("Total demand:", sum(self.q.values())  )
            print("Objective Value: ", self.model.objVal)

            plt.tight_layout()
            plt.savefig(f"solutions/n{self.n}k{len(self.K)}_{self.subtour_type}_sol.png")
            plt.show()


        elif plot_sol == 'n':
            plt.title('Vehicle Routing Problem scenario (n={}, k={})'.format(self.n,len(self.K)))

            G = nx.Graph()

            for node in self.V:
                node_pos = (self.nodes.iloc[node][0],self.nodes.iloc[node][1])
                G.add_node(node,pos=node_pos)

            for edge in self.c.keys():
                if edge != (self.V[0],self.V[-1]) and edge[1] != self.V[-1]:
                    G.add_edge(edge[0],edge[1],weight=round(self.c[edge],2))

            node_pos = nx.get_node_attributes(G, 'pos')
            weights = nx.get_edge_attributes(G , 'weight')

            offset = 0
            nx.draw(G, node_pos, ax=ax, node_color='w', edgecolors='k')

            comma_on=''

            for node in node_pos.keys():
                pos = node_pos[node]

                if node == self.V[0]:
                    offset = -0.06
                    comma_on = ', '
                elif node == self.V[-1]:
                    if self.V[-1] >= 10:
                        offset = 0.08
                    else:
                        offset = 0.06
                    comma_on = ''
                else:
                    offset = 0
                    comma_on=''

                ax.text(pos[0] + offset, pos[1], s=str(node)+comma_on, horizontalalignment='center',verticalalignment='center')

            nx.draw_networkx_edge_labels(G,node_pos,edge_labels=weights)

            # Recolor depot node to red
            G = nx.DiGraph()
            pos = (self.nodes.iloc[0][0],self.nodes.iloc[0][1])
            G.add_node(0,pos=pos)
            node_pos = nx.get_node_attributes(G,'pos')
            nx.draw_networkx_nodes(G,node_pos,ax=ax, node_size=400, node_shape='D',node_color='w', edgecolors='b')

            # Add axes
            xmin = self.nodes.min()['x_coord'] - 1
            ymin = self.nodes.min()['y_coord'] - 1

            xmax = self.nodes.max()['x_coord'] + 1
            ymax = self.nodes.max()['y_coord'] + 1

            plt.axis('on') # turns on axis
            ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
            ax.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)
            plt.legend(loc='lower right')

            plt.tight_layout()
            plt.savefig(f"models/n{self.n}k{len(self.K)}_{self.subtour_type}_nodes.png")
            plt.show()

        return

    ################################           DATASET        ######################################
    ################################################################################################

    def create_dataset(self):
        # If data sheet does not exist, generate one
        self.generate_node_table()
        self.create_customers()
        print("Node data generated")

    @staticmethod
    def calc_distance(p1, p2):
        """
        Distance between point p1 and point p2 in a 2-dimensional cartesian system.
        :param p1: 1D iterable of size of 2
        :param p2: 1D iterable of size of 2
        :return: Distance (float)
        """
        dist = np.linalg.norm( np.subtract(p1, p2) ) # Euclidian distance
        # dist = (((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5)
        return dist

    def generate_node_table(self, file_name="nodes.csv"):
        nodes_coord_x = np.random.rand(self.n + 1) * self.x_range
        nodes_coord_y = np.random.rand(self.n + 1) * self.y_range
        nodes_table = pd.DataFrame(np.transpose(np.array([nodes_coord_x, nodes_coord_y])),
                                   columns=['x_coord', 'y_coord'])

        if file_name == "nodes.csv":
            file_name = "n{}k{}_nodes.csv".format( self.n,len(self.K) )

        nodes_table.to_csv( os.path.join(self.random_data_n_model_p,file_name),index=False )
        self.nodes = nodes_table

        return nodes_table

    def read_node_table(self, file_name="nodes.csv"):
        if file_name == "nodes.csv":
            file_name = "n{}k{}_nodes.csv".format(self.n,self.k)

        nodes_table = pd.read_csv( os.path.join(self.random_data_n_model_p,file_name) )
        self.nodes = nodes_table
        return nodes_table

    def create_customers(self):
        """
        Select customer nodes, set customer demands, and customer time windows.
        :return:
        """
        random_selection = list(self.nodes.sample(n=self.n + 1).index)

        # Select the depot node
        # Depot node is randomly selected
        start_depot_idx = random_selection.pop(np.random.randint(0, len(random_selection) - 1, size=1)[0])
        # print("start_depot_idx", start_depot_idx)

        # Add a new end depot node to the end of nodes, which has the same coordinates as the start depot
        end_depot_coord = list(self.nodes.iloc[start_depot_idx])
        self.nodes = self.nodes.append(pd.DataFrame([end_depot_coord], columns=['x_coord', 'y_coord']),
                                       ignore_index=True)

        end_depot_idx = self.nodes.index[-1]
        # print("end_depot_idx", end_depot_idx)

        customer_nodes = self.nodes.to_numpy()[random_selection]
        depot_nodes = self.nodes.to_numpy()[[start_depot_idx, end_depot_idx]]

        self.nodes = pd.DataFrame(np.vstack((depot_nodes[0], customer_nodes, depot_nodes[1])),
                                  columns=['x_coord', 'y_coord'])

        self.N = np.arange(1, self.n + 1)
        self.V = np.hstack((0, self.N, self.n + 1))

        print(self.nodes)

        # Assign the customer demand to each customer
        self.q = {}
        for i in self.N:
            self.q[i] = np.random.randint(self.demand_range[0], self.demand_range[1], size=1)[0]

        assert sum(self.q.values()) <= self.Q * len(self.K), f"The total customer demand {sum(self.q.values())} " \
                                                             f"exceeds the total vehicle capacity: " \
                                                             f"{self.Q * len(self.K)} = {self.Q} * {len(self.K)}."

        # Assign time windows to each customer
        if self.subtour_type == 'TW':
            self.e = {}
            self.l = {}
            self.p = {}
            for i in self.N:
                self.e[i] = np.random.randint(self.opening_time, self.closing_time - self.time_window)
                self.l[i] = self.e[i] + self.time_window
                self.p[i] = self.processing_time
        return

    def create_arcs(self):
        """
        Create arcs between customer nodes
        :return:
        """
        self.A = [c for c in itertools.product(self.N, self.N)]

        # Add depot nodes
        # Make sure there are only leaving arcs from the depot start node...
        for j in self.V[1:]:
            self.A.append((self.V[0], j))
        # ...and there are only leading nodes to the depot end node.
        for i in self.V[1:-1]:
            self.A.append((i, self.V[-1]))

        # Remove the elements where i=j
        for i, tup in enumerate(self.A):
            if tup[0] == tup[1]:
                self.A.pop(i)

        # Make sure there are no duplicate arcs
        duplicates = [(k, v) for k, v in Counter(self.A).items() if v > 1]
        assert len(duplicates) == 0, f"Duplicate arc found: {duplicates}"

        # sort all the arcs
        A = np.array(self.A)

        sort_ = np.lexsort((A[:,1],A[:,0]),axis=0)

        self.A = A[sort_]

        # The cost to travel an arc equals its length
        self.c = {}
        for i, j in self.A:
            x_i = self.nodes.get('x_coord')[i]
            y_i = self.nodes.get('y_coord')[i]
            x_j = self.nodes.get('x_coord')[j]
            y_j = self.nodes.get('y_coord')[j]
            self.c[(i, j)] = self.calc_distance((x_i, y_i), (x_j, y_j))
        return

    ################################           READ CVRP        ####################################
    ################################################################################################

    ##################################################################################################
    # The reading functions are taken and modified from the original
    # https://www.localsolver.com/docs/last/exampletour/vrp.html#
    ##################################################################################################
    @staticmethod
    def read_elem(filename):
        with open(filename) as f:
            return [str(elem) for elem in f.read().split()]

    # The input files follow the "Augerat" format.
    def read_input_cvrp(self, filename):
        # file_it = iter(self.read_elem(sys.argv[1]))
        file_it = iter(self.read_elem(filename))

        nb_nodes = 0
        nb_customers = 0
        truck_capacity = 0
        while True:
            token = next(file_it)
            if token == "DIMENSION":
                next(file_it)  # Removes the ":"
                nb_nodes = int(next(file_it))
                nb_customers = nb_nodes - 1
            elif token == "CAPACITY":
                next(file_it)  # Removes the ":"
                truck_capacity = int(next(file_it))
            elif token == "EDGE_WEIGHT_TYPE":
                next(file_it)  # Removes the ":"
                token = next(file_it)
                if token != "EUC_2D":
                    print("Edge Weight Type " + token + " is not supported (only EUD_2D)")
                    sys.exit(1)
            elif token == "NODE_COORD_SECTION":
                break

        assert nb_customers != 0
        assert nb_nodes != 0
        assert truck_capacity != 0

        customers_x = [None] * nb_customers
        customers_y = [None] * nb_customers
        depot_x = 0
        depot_y = 0
        for n in range(nb_nodes):
            node_id = int(next(file_it))
            if node_id != n + 1:
                print("Unexpected index")
                sys.exit(1)
            if node_id == 1:
                depot_x = int(next(file_it))
                depot_y = int(next(file_it))
            else:
                # -2 because orginal customer indices are in 2..nbNodes
                customers_x[node_id - 2] = int(next(file_it))
                customers_y[node_id - 2] = int(next(file_it))

        nodes_coord_x = customers_x[:]
        nodes_coord_x.append(depot_x)
        nodes_coord_x.insert(0, depot_x)
        nodes_coord_y = customers_y[:]
        nodes_coord_y.append(depot_y)
        nodes_coord_y.insert(0, depot_y)
        nodes = pd.DataFrame(np.transpose(np.array([nodes_coord_x, nodes_coord_y])), columns=['x_coord', 'y_coord'])

        # Create customer index list N, and node index list V
        N = list(nodes.index[1:-1])
        assert len(N) == nb_customers
        V = list(nodes.index)
        assert len(V) == nb_nodes + 1

        token = next(file_it)
        if token != "DEMAND_SECTION":
            print("Expected token DEMAND_SECTION")
            sys.exit(1)

        demands = {}
        for n in N:
            demands[n] = None

        for n in range(nb_nodes):
            node_id = int(next(file_it))
            if node_id != n + 1:
                print("Unexpected index")
                sys.exit(1)
            if node_id == 1:
                if int(next(file_it)) != 0:
                    print("Demand for depot should be 0")
                    sys.exit(1)
            else:
                # First element in N is 1, but the first customer in the file is 2
                demands[node_id - 1] = int(next(file_it))

        token = next(file_it)
        if token != "DEPOT_SECTION":
            print("Expected token DEPOT_SECTION")
            sys.exit(1)

        warehouse_id = int(next(file_it))
        if warehouse_id != 1:
            print("Warehouse id is supposed to be 1")
            sys.exit(1)

        end_of_depot_section = int(next(file_it))
        if end_of_depot_section != -1:
            print("Expecting only one warehouse, more than one found")
            sys.exit(1)

        return nb_customers, truck_capacity, N, V, demands, nodes

    ################################           OPTIMIZATION        #################################
    ################################################################################################
    def CVRP_setup(self):
        """
        Additional constraints for subtour elimination from CVRP formulation
        :return:
        """
        self.general_setup()

        self.model.update()

        # Subtour elimination constraint (miller-tucker-zemlin)
        #
        # $$u_{j} - u_{i} \geq q_{j} - Q(1-x_{ijk}), i,j = \{1,....,n\}, i \neq j$$

        # Miller-Tucker-Zemlin formulation for subtour elimination

        if self.subtour_type == 'MTZ':
            for k in self.K:
                for i, j in self.A:
                    if i >= 1 and j >= 1:
                        if i != self.number_of_customers + 1 and j != self.number_of_customers + 1:
                            self.model.addConstr(
                                self.u[j, k] - self.u[i, k] >= self.q[j] - self.Q * (1 - self.x[i, j, k]))

            # Capacity constraint
            for i in self.N:
                for k in self.K:
                    self.model.addConstr(self.u[i, k] >= self.q[i])
                    self.model.addConstr(self.u[i, k] <= self.Q)

        # Subtour elimination constraint (Dantzig-Fulkerson Johnson)
        #
        # $$\sum_{i\in S}\sum_{j \in S,j \neq i} x_{ij} \leq |S| - 1$$
        elif self.subtour_type == 'DFJ':
            for k in self.K:
                self.model.addConstr(quicksum(
                    quicksum(self.q[j] * self.x[i, j, k] for j in self.N if j != i) for i in self.V if
                    i < self.number_of_customers + 1) <= self.Q)

        self.model.update()

        self.model.setParam("MIPGap", self.gap_goal)
        self.model.update()


        model_name = "n{}k{}_{}.lp".format( self.n,len(self.K),self.subtour_type )

        self.model.write( os.path.join("models",model_name) )

    def CVRPTW_setup(self):
        """
        Additional constraints from the time window formulation
        :return:
        """
        self.subtour_type = "TW"

        self.M = self.closing_time * 10  # Big-M formulation

        self.general_setup()
        self.model.update()

        # Continuous decision variable for arrival to customer i
        self.t = {}
        for i in self.N:
            self.t[i] = self.model.addVar(lb=self.opening_time, ub=self.closing_time,
                                          vtype=GRB.CONTINUOUS, name=f"t[{i}]")

        # Time window constraint
        print(self.t)
        print(self.e)
        print(self.l)
        for i in self.N:
            self.model.addConstr(self.t[i] >= self.e[i], name=f"Lower_time_{i}")
            self.model.addConstr(self.t[i] <= self.l[i], name=f"Upper_time_{i}")
            for j in self.N:
                if i != j:
                    self.model.addConstr(self.t[j] >=
                                         self.t[i] + self.processing_time + self.c[i, j] -
                                         (1 - quicksum(self.x[i, j, k] for k in self.K)) * self.M,
                                         name=f"Time_precedence_{i}_{j}")

        self.model.update()

        self.model.setParam("MIPGap", self.gap_goal)
        self.model.update()

        model_name = "n{}k{}_{}.lp".format( self.n,len(self.K),self.subtour_type )

        self.model.write( os.path.join("models",model_name) )

    def general_setup(self):
        """
        Basic model elements shared by both CVRP and CVRPTW
        :return:
        """

        self.model = Model()

        # Variables
        self.x = {}
        for i, j in self.A:
            for k in self.K:
                self.x[i, j, k] = self.model.addVar(lb=0, ub=1, vtype=GRB.BINARY, name=f"x[{i},{j},{k}]")


        if self.subtour_type == 'MTZ':
            self.u = {}
            for j in self.N:
                for k in self.K:
                    self.u[j, k] = self.model.addVar(vtype=GRB.CONTINUOUS, name=f"u[{j},{k}]")

        # Objective function
        # explanation for self.V[:-1] = [0, 1, ..., n]
        # explanation for self.V[1:] = [1, ..., n, n+1]
        self.model.setObjective(quicksum(self.x[i, j, k] * self.c[i, j] for i in self.V[:-1] for j in self.V[1:]
                                         for k in self.K if i != j),
                                sense=GRB.MINIMIZE)

        # Make sure there are no duplicates of i,j,k combination in x
        assert len(self.x) == len(set(self.x.keys()))

        self.model.update()

        # Constraints
        # Each vehicle must leave the depot
        for k in self.K:
            self.model.addConstr(quicksum(self.x[self.V[0], j, k] for j in self.V[1:]) == 1, name=f"Start_{k}")

        # Each vehicle must return to depot
        for k in self.K:
            self.model.addConstr(quicksum(self.x[j, self.V[-1], k] for j in self.V[:-1]) == 1, name=f"Finish_{k}")

        # Each customer must be visited by one vehicle
        for i in self.N:
            self.model.addConstr(quicksum(self.x[j, i, k] for k in self.K for j in self.V[:-1] if j != i) == 1,
                                 name=f"Visit_{i}")

        # If a vehicle visits a customer, it must also leave that customer
        for i in self.N:
            for k in self.K:
                self.model.addConstr(quicksum(self.x[j, i, k] for j in self.V[:-1] if j != i) ==  # What arrives to i
                                     quicksum(self.x[i, j, k] for j in self.V[1:] if j != i),  # Must leave from i
                                     name=f"Leave_{i}_{k}")

        # # Capacity constraint
        # for k in self.K:
        #     self.model.addConstr(
        #         quicksum(self.q[j] * self.x[i, j, k] for i in self.V[:-1] for j in self.V[1:-1] if i != j) <= self.Q,
        #         name=f"Capacity_{k}")
        self.model.setParam("TimeLimit", self.time_limit)
        return



    def add_model_vars_dfj(self):
            self.model._x = self.x # User made variables get passed along with underscore VarName
            self.model._A = self.A
            self.model._K = self.K
            self.model._V = self.V
            self.model._subtour = self.subtour

    def optimize(self):

        if self.subtour_type == 'MTZ':
            self.model.optimize()
        elif self.subtour_type == 'DFJ':
            self.add_model_vars_dfj()
            self.model.Params.lazyConstraints = 1
            self.model.optimize(self.subtourelim)
        elif self.subtour_type == '':
            if input("no subtour type chosen, continue?: ") in ['y', 'yes']:
                self.model.optimize()
        elif self.subtour_type == "TW":
            self.model.optimize()

        # The section below can be used to debug infeasibility
        if self.model.status == GRB.OPTIMAL:
            print(f"Optimal objective: {self.model.objVal}")
        elif self.model.status == GRB.INF_OR_UNBD:
            print("Model is infeasible or unbounded")
            sys.exit(0)
        elif self.model.status == GRB.INFEASIBLE:
            print("Model is infeasible")
            self.model.computeIIS()
            self.model.write("model.ilp")
            sys.exit(0)
        elif self.model.status == GRB.UNBOUNDED:
            print("Model is unbounded")
            sys.exit(0)
        else:
            print(f"Optimization ended with status {self.model.status}")

        if input("Save optimized result?:").lower() in ['y', 'yes']:
            solution_name = "n{}k{}_{}.sol".format(self.n,len(self.K),self.subtour_type)
            self.model.write( os.path.join("solutions",solution_name))

        return


    def find_active_arcs(self):

        active_arcs = []

        for i,j in self.A:
            for k in self.K:
                if round(self.x[i,j,k].x) == 1:
                    active_arcs.append([i,j,k])

        self.active_arcs = np.vstack(active_arcs)

    @staticmethod
    def subtourelim(mdl,where):
        if where == GRB.callback.MIPSOL:
            active_arcs = []
            solutions = mdl.cbGetSolution(mdl._x)

            for i, j in mdl._A:
                for k in mdl._K:
                    if round(solutions[i, j, k]) == 1:
                        active_arcs.append([i, j, k])

            active_arcs = np.vstack(active_arcs)

            tours = mdl._subtour(mdl._K, active_arcs)

            # add lazy constraints
            for k in tours.keys():
                if len(tours[k]) > 1:
                    for tour in tours[k]:
                        S = np.unique(tour)
                        expr = LinExpr()

                        for i in S:
                            if i != mdl._V[-1]:
                                for j in S:
                                    if j != i and j != mdl._V[0]:
                                            expr += mdl._x[i, j, k]

                        # expr = quicksum(mdl._x[i, j, k] for i in S for j in S if j != i if i != mdl._V[-1] or j != mdl._V[0])
                        mdl.cbLazy(expr <= len(S) - 1)

    @staticmethod
    def subtour(K, active_arcs):
        tours = {}

        for k in K:
            vehicle_tours = []
            vehicle_arcs = active_arcs[np.where(active_arcs[:, 2] == k)][:, 0:2]
            start_node, finish_node = vehicle_arcs[0]

            tour = [start_node, finish_node]
            vehicle_arcs = np.delete(vehicle_arcs, [0], axis=0)

            while True:
                while True:
                    next_node = np.where(vehicle_arcs[:, 0] == finish_node)

                    if next_node[0].size == 0:
                        vehicle_tours.append(tour)
                        break
                    else:
                        start_node, finish_node = vehicle_arcs[next_node][0]
                        vehicle_arcs = np.delete(vehicle_arcs, next_node[0], axis=0)

                        tour.append(finish_node)

                if vehicle_arcs.size != 0:
                    start_node, finish_node = vehicle_arcs[0]
                    vehicle_arcs = np.delete(vehicle_arcs, [0], axis=0)

                    tour = [start_node, finish_node]
                else:
                    tours[k] = vehicle_tours
                    break

        return tours
예제 #5
0
파일: e2.py 프로젝트: Opti-50/proyecto
m.addConstrs(
    (quicksum(A[i, j, k, h] for k in K for i in I) <= 1
     for h in H
     for j in J
     ),
    name="R25 - Para toda cama solo se permite máximo una instalación por hora"
)

m.addConstrs(
    (quicksum(A[i, j, k, h_] for k in K for h_ in range(1, h+1)) >= quicksum(B[i, j, h_] for h_ in range(1, h+1))
     for j in J
     for i in I
     for h in H),
    name="R26 - El paciente no puede desocupar la cama antes de ser instalado"
)

m.optimize()

sys.stdout = open('output-slack.txt', 'a', encoding='utf-8')
try:
    print('SOLUCIÓN')
    m.printAttr("X")

    # for c in m.getConstrs():
    #     print('%s : %g' % (c.constrName, c.slack))

except Exception as e:
    m.computeIIS()
sys.stdout = sys.__stdout__
예제 #6
0
class ILPSolver:
    def __init__(self, si: SolverInfo, budget, gurobi_params: Dict[str, Any] = None, ablation=False, overhead=False):
        self.gurobi_params = gurobi_params
        self.num_threads = self.gurobi_params.get('Threads', 1)
        self.budget = int(budget * MEM_GCD_MULTIPLIER * GB_TO_KB)
        self.si: SolverInfo = si
        self.solve_time = None
        self.ablation = ablation
        self.overhead = overhead

        V = self.si.loss + 1
        T = len(self.si.nodes) - self.si.loss
        Y = 3
        budget = self.budget

        self.m = Model("monet{}".format(self.budget))
        if gurobi_params is not None:
            for k, v in gurobi_params.items():
                setattr(self.m.Params, k, v)

        self.ram = np.array([math.ceil(self.si.nodes[i].mem*MEM_GCD_MULTIPLIER/1024) for i in self.si.nodes]) # Convert to KB
        self.cpu = dict(( i, [math.ceil(val*CPU_GCD_MULTIPLIER) for val in self.si.nodes[i].workspace_compute] ) for i in self.si.nodes)
        self.cpu_recompute = dict(( i, [math.ceil(val*CPU_GCD_MULTIPLIER) for val in self.si.nodes[i].recompute_workspace_compute] ) for i in self.si.nodes)
        self.cpu_inplace = dict(( i, [math.ceil(val*CPU_GCD_MULTIPLIER) for val in self.si.nodes[i].inplace_workspace_compute] ) for i in self.si.nodes)

        self.R = self.m.addVars(T, V, name="R", vtype=GRB.BINARY)   # Recomputation
        self.P = self.m.addVars(T, V, name="P", vtype=GRB.BINARY)   # In-memory
        self.S = self.m.addVars(T+1, V, name="S", vtype=GRB.BINARY) # Stored
        self.M = self.m.addVars(T, Y, name="M", vtype=GRB.BINARY)   # Backward operator implementation
        self.SM = self.m.addVars(T, V, Y, name="SM", vtype=GRB.BINARY)  # Linearization of S * M
        if self.si.select_conv_algo:
            self.RF = self.m.addVars(T, len(self.si.conv_list), self.si.num_conv_algos, name="RF", vtype=GRB.BINARY) # Conv operator implementations
        if self.si.do_inplace:
            self.IP = self.m.addVars(T, V, name="IP", vtype=GRB.BINARY) # In-place

    def cmem(self, j, recompute=False, inplace=False):
        if inplace:
            return [math.ceil(val * MEM_GCD_MULTIPLIER * 1024) for val in self.si.nodes[j].inplace_workspace_mem]
        else:
            if recompute:
                return [math.ceil(val * MEM_GCD_MULTIPLIER * 1024) for val in self.si.nodes[j].recompute_workspace_mem]
            else:
                return [math.ceil(val * MEM_GCD_MULTIPLIER * 1024) for val in self.si.nodes[j].workspace_mem]

    def local_memory(self, j):
        return math.ceil(self.si.nodes[j].local_memory * MEM_GCD_MULTIPLIER / 1024)

    def fixed_ram(self, j):
        return math.ceil(self.si.nodes[j].fixed_mem * MEM_GCD_MULTIPLIER / 1024)

    def build_model(self):
        V = self.si.loss + 1
        T = len(self.si.nodes) - self.si.loss
        Y = 3
        budget = self.budget

        # define objective function
        if self.si.select_conv_algo:
            fwd_compute = quicksum(self.R[0, i] * self.cpu[i][0] for i in range(V) if i not in self.si.conv_list)
            fwd_recompute = quicksum(quicksum(self.R[t, i]*self.cpu_recompute[i][0] for t in range(1,T)) for i in range(V) if i not in self.si.conv_list)
            conv_fwd_compute = quicksum(quicksum(self.RF[t,self.si.conv_list[i],c] * self.cpu[i][c] for t in range(T)) for i in self.si.conv_list if i<=self.si.loss for c in range(len(self.cpu[i]))) # conv's compute and recompute same
            if self.si.do_inplace:
                inplace_fwd_compute = quicksum(quicksum(self.IP[t,i] * (self.cpu_inplace[i][0]-self.cpu[i][0]) for t in range(T)) for i in self.si.inplace_list) # relu's compute and recompute same
            conv_bwd_compute = quicksum(quicksum(self.RF[t,self.si.conv_list[i],c] * self.cpu[i][c] for t in range(T)) for i in self.si.conv_list if i>self.si.loss for c in range(len(self.cpu[i]))) # conv's compute and recompute same
            bwd_compute = quicksum(self.M[t, p] * self.cpu[t+V-1][p] for t in range(T) for p in range(Y) if (p<len(self.cpu[t+V-1]) and (t+V-1) not in self.si.conv_list))
            if self.si.do_inplace:
                compute_fwd = fwd_compute + fwd_recompute + conv_fwd_compute + inplace_fwd_compute
            else:
                compute_fwd = fwd_compute + fwd_recompute + conv_fwd_compute
            compute_bwd = conv_bwd_compute + bwd_compute
            compute_cost = compute_fwd + compute_bwd
        else:
            fwd_compute = quicksum(self.R[0, i] * self.cpu[i][0] for i in range(V))
            fwd_recompute = quicksum(self.R[t, i] * self.cpu_recompute[i][0] for t in range(1,T) for i in range(V))
            if self.si.do_inplace:
                inplace_fwd_compute = quicksum(quicksum(self.IP[t,i] * (self.cpu_inplace[i][0]-self.cpu[i][0]) for t in range(T)) for i in self.si.inplace_list) # relu's compute and recompute same
            bwd_compute = quicksum(self.M[t, p] * self.cpu[t+V-1][p] for t in range(T) for p in range(Y) if p<len(self.cpu[t+V-1]))
            if self.si.do_inplace:
                compute_fwd = fwd_compute + fwd_recompute + inplace_fwd_compute
            else:
                compute_fwd = fwd_compute + fwd_recompute
            compute_bwd = bwd_compute
            compute_cost = compute_fwd + compute_bwd
        self.m.setObjective(compute_cost, GRB.MINIMIZE)
        self.compute_cost = compute_cost
        self.compute_fwd = compute_fwd
        self.compute_bwd = compute_bwd

        # Add in-place constraints
        for t in range(T):
            for v in range(V):
                if self.si.do_inplace:
                    self.m.addLConstr(self.P[t,v], GRB.LESS_EQUAL, self.R[t,v])
                    self.m.addLConstr(self.IP[t,v], GRB.LESS_EQUAL, self.R[t,v])
                    # print(list(self.si.inplace_list.keys()))
                    if v not in self.si.inplace_list:
                        self.m.addLConstr(self.IP[t,v], GRB.EQUAL, 0)
                    elif not isinstance(self.si.nodes[v], IntNode):
                        # If IP[v], then P[u] = 0, else P[u] = R[u]
                        self.m.addLConstr(self.P[t,self.si.inplace_list[v]], GRB.GREATER_EQUAL, self.R[t,self.si.inplace_list[v]] - 2*self.IP[t,v])
                        self.m.addLConstr(self.P[t,self.si.inplace_list[v]], GRB.LESS_EQUAL, 2 - 2*self.IP[t,v])
                        self.m.addLConstr(self.S[t+1,self.si.inplace_list[v]], GRB.LESS_EQUAL, 2 - 2*self.IP[t,v])
                else:
                    self.m.addLConstr(self.P[t,v], GRB.EQUAL, self.R[t,v])
        # store nothing in the beginning
        for i in range(V):
            self.m.addLConstr(self.S[0,i] , GRB.EQUAL, 0)
        # Recompute full forward
        for i in range(V):
            if not isinstance(self.si.nodes[i], IntNode):
                self.m.addLConstr(self.R[0,i] , GRB.EQUAL, 1)
        # All M which have no paths are set as 0
        self.m.addLConstr(self.M[0,0], GRB.EQUAL, 1)
        for p in range(1,Y):
            self.m.addLConstr(self.M[0,p], GRB.EQUAL, 0)
        for t in range(1, T):
            bkwd_t = V + t - 1
            paths = len(self.si.nodes[bkwd_t].args)
            for p in range(paths, Y):
                self.m.addLConstr(self.M[t,p], GRB.EQUAL, 0)
        # Set failed conv's RF to be 0
        if self.si.select_conv_algo:
            for i in self.si.conv_list:
                for c in range(self.si.num_conv_algos):
                    if c >= len(self.cpu[i]) or self.cpu[i][c] < 0:
                        for t in range(T):
                            self.m.addLConstr(self.RF[t,self.si.conv_list[i],c], GRB.EQUAL, 0)

        # create constraints for boolean multiplication linearization
        for p in range(Y):
            for i in range(V):
                for t in range(T):
                    self.m.addLConstr(self.SM[t,i, p], GRB.GREATER_EQUAL, self.M[t,p] + self.S[t+1,i] - 1)
                    self.m.addLConstr(self.SM[t,i, p], GRB.LESS_EQUAL, self.M[t,p])
                    self.m.addLConstr(self.SM[t,i, p], GRB.LESS_EQUAL, self.S[t+1,i])

        if self.ablation:
            print("Doing ablation")
            # Disable all recomputation
            for t in range(1,T):
                for v in range(V):
                    self.m.addLConstr(self.R[t,v], GRB.EQUAL, 0)
            # Fix all operations to be inplace
            for t in range(T):
                for v in range(V):
                    if self.si.do_inplace and v in self.si.inplace_list:
                        self.m.addLConstr(self.IP[t,v], GRB.EQUAL, self.R[t,v])

        # Correctness constraints
        # Ensure all checkpoints are in memory
        for t in range(T):
            for i in range(V):
                self.m.addLConstr(self.S[t + 1, i], GRB.LESS_EQUAL, self.S[t, i] + self.P[t, i])
        # At least one path should be used
        for t in range(T):
            self.m.addLConstr(quicksum(self.M[t,p] for p in range(Y)), GRB.GREATER_EQUAL, 1)
        # Ensure all computations are possible
        for (u, v, p) in self.si.edge_list:
            if u < V and v <V:
                for t in range(T):
                    self.m.addLConstr(self.R[t, v], GRB.LESS_EQUAL, self.R[t, u] + self.S[t, u])
            if u < V and v >= V:
                t = v + 1 - V
                self.m.addLConstr(self.M[t,p], GRB.LESS_EQUAL, self.P[t, u] + self.S[t+1, u])
        # Ensure that new nodes only computed if main node computed
        for t in range(T):
            for i in range(V):
                if self.si.nodes[i].has_intermediates:
                    for (_,intid) in self.si.nodes[i].intermediates:
                        self.m.addLConstr(self.R[t, intid], GRB.LESS_EQUAL, self.R[t,i])
                        if self.si.do_inplace and i in self.si.inplace_list:
                            self.m.addLConstr(self.IP[:,intid], GRB.GREATER_EQUAL, self.IP[:,i] + self.R[:,intid] -1)
                            self.m.addLConstr(self.IP[:,intid], GRB.LESS_EQUAL, self.IP[:,i])

        # Constraints for conv selection
        if self.si.select_conv_algo:
            for i in self.si.conv_list:
                if i < self.si.loss:
                    for t in range(T):
                        self.m.addLConstr(quicksum(self.RF[t,self.si.conv_list[i],c] for c in range(self.si.num_conv_algos)), GRB.GREATER_EQUAL, self.R[t,i])
                else:
                    t_bwdi = i + 1 - V
                    self.m.addLConstr(quicksum(self.RF[t_bwdi,self.si.conv_list[i],c] for c in range(self.si.num_conv_algos)), GRB.GREATER_EQUAL, 1)
                    for c in range(self.si.num_conv_algos):
                        for t in range(t_bwdi):
                            self.m.addLConstr(self.RF[t,self.si.conv_list[i],c], GRB.EQUAL, 0)
                        for t in range(t_bwdi+1, T):
                            self.m.addLConstr(self.RF[t,self.si.conv_list[i],c], GRB.EQUAL, 0)

        # Memory constraints
        for t in range(T):
            bkwd_t = V + t - 1
            bwd_node = self.si.nodes[bkwd_t]
        for t in range(T):
            bkwd_t = V + t - 1
            bwd_node = self.si.nodes[bkwd_t]
            bwd_local_memory = self.local_memory(bkwd_t) if t!=0 else 0
            gradm = bwd_local_memory + self.fixed_ram(bkwd_t-1)
            for j in range(V):
                keep_tensors = [[fwdin for fwdin in bwd_node.args[p] if fwdin <= self.si.loss] for p in range(len(bwd_node.args))]
                # Forward checkpoint constraint
                self.m.addLConstr(  gradm +
                                    quicksum(self.S[t,i]*self.ram[i] for i in range(j, V)) +
                                    quicksum(self.S[t+1,i]*self.ram[i] for i in range(j) if t<T-1) +
                                    quicksum( quicksum( self.M[t,p]*self.ram[i] for i in range(j) if i in keep_tensors[p]) for p in range(len(bwd_node.args)) ) -
                                    quicksum( quicksum( self.SM[t,i, p]*self.ram[i] for i in range(j) if i in keep_tensors[p]) for p in range(len(bwd_node.args)) ),
                                    GRB.LESS_EQUAL, budget)

                workspace_mem = self.cmem(j) if t==0 else self.cmem(j, recompute=True, inplace=False)
                if self.si.select_conv_algo and j in self.si.conv_list:
                    wm = quicksum(self.RF[t,self.si.conv_list[j],c] * workspace_mem[c] for c in range(len(workspace_mem)))
                elif self.si.do_inplace and j in self.si.inplace_list:
                    inplace_workspace_mem = self.cmem(j, recompute=False, inplace=True)
                    wm = self.IP[t,j]*(inplace_workspace_mem[0] - workspace_mem[0]) + self.R[t,j] * workspace_mem[0]
                else:
                    wm = self.R[t,j] * workspace_mem[0]
                # Forward recomputation constraint
                self.m.addLConstr(  gradm + wm + self.R[t,j]*self.ram[j] + self.R[t,j]*self.local_memory(j) +
                                    quicksum(self.S[t,i]*self.ram[i] for i in range(j+1, V)) +
                                    quicksum(self.S[t+ 1,i]*self.ram[i] for i in range(j) if i not in self.si.nodes[j].local_tensors) +
                                    quicksum( quicksum( self.M[t,p]*self.ram[i] for i in range(j) if i in keep_tensors[p]) for p in range(len(bwd_node.args)) ) -
                                    quicksum( quicksum( self.SM[t,i, p]*self.ram[i] for i in range(j) if i in keep_tensors[p]) for p in range(len(bwd_node.args)) ),
                                    GRB.LESS_EQUAL, budget)
                if t>0:
                    if self.si.select_conv_algo and bkwd_t in self.si.conv_list:
                        wmem = self.cmem(bkwd_t)
                        bwd_wm = quicksum(self.RF[t,self.si.conv_list[bkwd_t],c] * wmem[c] for c in range(len(wmem)))
                    else:
                        wmem = self.cmem(bkwd_t)
                        bwd_wm = quicksum(self.M[t,p]*wmem[p] for p in range(len(bwd_node.args)))
                    rammem = [sum([self.ram[i] for i in bwd_node.args[p] if i<V]) for p in range(len(bwd_node.args))]
                    # Backward constraint
                    self.m.addLConstr(  bwd_wm +
                                        bwd_local_memory + self.fixed_ram(bkwd_t) + self.ram[bkwd_t] +
                                        quicksum(self.M[t,p]*rammem[p] for p in range(len(bwd_node.args))) +
                                        quicksum(quicksum(self.SM[t,i,p]*self.ram[i]  for i in range(V) if i not in bwd_node.args[p]) for p in range(len(bwd_node.args))),
                                        GRB.LESS_EQUAL, budget)
        return None

    def solve(self):
        from time import time
        V = self.si.loss + 1
        T = len(self.si.nodes) - self.si.loss
        Y = 3
        budget = self.budget
        self.m.Params.TimeLimit = self.time_limit
        t0 = time()
        self.m.optimize()
        self.solve_time = time() - t0

        infeasible = (self.m.status == GRB.INFEASIBLE)
        if infeasible:
            self.m.computeIIS()
            self.m.write("model.ilp")
            raise ValueError("Infeasible model, check constraints carefully. Insufficient memory?")

        if self.m.solCount < 1:
            raise ValueError(f"Model status is {self.m.status} (not infeasible), but solCount is {self.m.solCount}")

        Rout = np.zeros((T, V), dtype=bool)
        Pout = np.zeros((T, V), dtype=bool)
        Sout = np.zeros((T+1, V), dtype=bool)
        Mout = np.zeros((T, Y), dtype=bool)
        SMout = np.zeros((T, V, Y), dtype=bool)
        RFout = np.zeros((T, len(self.si.conv_list), self.si.num_conv_algos), dtype=bool)
        IPout = np.zeros((T, V), dtype=bool)

        try:
            for t in range(T):
                for i in range(V):
                    Rout[t][i] = round(self.R[t, i].X)
                    Pout[t][i] = round(self.P[t, i].X)
                    Sout[t][i] = round(self.S[t, i].X)
                    if self.si.do_inplace:
                        IPout[t][i] = round(self.IP[t, i].X)
                    for p in range(Y):
                        SMout[t][i][p] = round(self.SM[t, i, p].X)
                for p in range(Y):
                    Mout[t][p] = round(self.M[t, p].X)
                if self.si.select_conv_algo:
                    for i in range(len(self.si.conv_list)):
                        for c in range(self.si.num_conv_algos):
                            RFout[t][i][c] = round(self.RF[t,i,c].X)
            for i in range(V):
                Sout[T][i] = round(self.S[T, i].X)

        except AttributeError as e:
            logging.exception(e)
            return None, None, None, None, None, None, None

        solution = Solution(Rout, Sout, Mout, RFout, IPout, Pout, self.solve_time, -1, -1, -1)
        return solution
예제 #7
0
def generatesSample(
    num_days,
    num_shifts,
    num_nurses,
    numSam,
    bounds,
    constrList,
    partial_sol,
    x_mapping,
    y_mapping,
    gid,
):

    N = list(range(num_nurses))
    D = list(range(num_days))
    Ds = list(range(num_days + 1))
    S = list(range(num_shifts))
    Ss = list(range(num_shifts + 1))
    #    Sk=list(range(2))

    #    constrList=[[(0,),(1,)],[(0,),(2,)],[(0,),(1,2)],[(1,),(0,)],[(1,),(2,)],[(1,),(0,2)],[(2,),(0,)],[(2,),(1,)],[(2,),(0,1)],[(0,1),(2,)],[(0,2),(1,)],[(1,2),(0,)]]

    try:
        sys.stdout = open(os.devnull, "w")
        m = Model("nspSolver")
        sys.stdout = sys.__stdout__
        m.setParam(GRB.Param.OutputFlag, 0)
        ########### Decision Variables #############
        #        x = m.addVars(N,D,S,Sk, vtype=GRB.BINARY, name="x")
        o = m.addVars(N, D, S, vtype=GRB.BINARY, name="o")
        p = m.addVars(N, D, vtype=GRB.BINARY, name="p")
        q = m.addVars(N, S, vtype=GRB.BINARY, name="q")
        r = m.addVars(S, D, vtype=GRB.BINARY, name="r")

        tw = m.addVars(N, D, D, vtype=GRB.BINARY, name="tw")
        sw = m.addVars(N, Ds, D, vtype=GRB.BINARY, name="sw")
        tw1 = m.addVars(N, D, S, D, vtype=GRB.BINARY, name="tw1")
        sw1 = m.addVars(N, Ds, S, D, vtype=GRB.BINARY, name="sw1")

        tws = m.addVars(N, S, S, vtype=GRB.BINARY, name="tws")
        sws = m.addVars(N, Ss, S, vtype=GRB.BINARY, name="sws")
        tfs = m.addVars(N, S, S, vtype=GRB.BINARY, name="tfs")
        sfs = m.addVars(N, Ss, S, vtype=GRB.BINARY, name="sfs")

        tf = m.addVars(N, D, D, vtype=GRB.BINARY, name="tf")
        sf = m.addVars(N, Ds, D, vtype=GRB.BINARY, name="sf")
        tw1f = m.addVars(N, D, S, D, vtype=GRB.BINARY, name="tw1f")
        sw1f = m.addVars(N, Ds, S, D, vtype=GRB.BINARY, name="sw1f")

        ########### Required Constraints #############
        for n in N:
            for d in D:
                for s in S:
                    if not np.isnan(partial_sol[n, d, s]):
                        m.addConstr((o[n, d, s] == partial_sol[n, d, s]),
                                    "partial solution")

        m.addConstrs((o.sum(n, d, "*") == p[n, d] for n in N for d in D), "po")

        #        m.addConstrs((x.sum(n,d,s,'*')==o[n,d,s] for n in N for d in D for s in S),"xo")
        #        m.addConstrs((x[n,d,s,sk]==o[n,d,s] for n in N for d in D for s in S for sk in Sk if nurse_skill[n]==sk),"xo")
        #        m.addConstrs((x[n,d,s,sk]==0 for n in N for d in D for s in S for sk in Sk if nurse_skill[n]!=sk),"xo")
        m.addConstrs((q[n, s] <= o.sum(n, "*", s) for n in N for s in S), "qo")
        m.addConstrs((q[n, s] * o.sum(n, "*", s) == o.sum(n, "*", s) for n in N
                      for s in S), "qo")
        m.addConstrs((r[s, d] <= o.sum("*", d, s) for d in D for s in S), "ro")
        m.addConstrs((r[s, d] * o.sum("*", d, s) == o.sum("*", d, s) for d in D
                      for s in S), "ro")

        ########### Hard Constraints #############
        #        print(bounds)
        for i in range(len(bounds)):
            if bounds[i, 0] > 0:
                #                print(bounds[i,0])
                if constrList[i] == "0:1":
                    m.addConstrs((r.sum("*", d) >= bounds[i, 0] for d in D),
                                 "constr")

                elif constrList[i] == "0:2":
                    m.addConstrs((p.sum("*", d) >= bounds[i, 0] for d in D),
                                 "constr")

                elif constrList[i] == "0:1,2":
                    m.addConstrs((o.sum("*", d, "*") >= bounds[i, 0]
                                  for d in D), "constr")

                elif constrList[i] == "1:0":
                    m.addConstrs((r.sum(s, "*") >= bounds[i, 0] for s in S),
                                 "constr")

                elif constrList[i] == "1:2":
                    m.addConstrs((q.sum("*", s) >= bounds[i, 0] for s in S),
                                 "constr")

                elif constrList[i] == "1:0,2":
                    m.addConstrs((o.sum("*", "*", s) >= bounds[i, 0]
                                  for s in S), "constr")

                elif constrList[i] == "2:0":
                    m.addConstrs((p.sum(n, "*") >= bounds[i, 0] for n in N),
                                 "constr")

                elif constrList[i] == "2:1":
                    m.addConstrs((q.sum(n, "*") >= bounds[i, 0] for n in N),
                                 "constr")

                elif constrList[i] == "2:0,1":
                    m.addConstrs((o.sum(n, "*", "*") >= bounds[i, 0]
                                  for n in N), "constr")

                elif constrList[i] == "0,1:2":
                    m.addConstrs(
                        (o.sum("*", d, s) >= bounds[i, 0] for d in D
                         for s in S),
                        "constr",
                    )

                elif constrList[i] == "0,2:1":
                    m.addConstrs(
                        (o.sum(n, d, "*") >= bounds[i, 0] for d in D
                         for n in N),
                        "constr",
                    )

                elif constrList[i] == "1,2:0":
                    m.addConstrs(
                        (o.sum(n, "*", s) >= bounds[i, 0] for n in N
                         for s in S),
                        "constr",
                    )

            if bounds[i, 1] > 0:
                #                print(bounds[i,1])
                if constrList[i] == "0:1":
                    m.addConstrs((r.sum("*", d) <= bounds[i, 1] for d in D),
                                 "constr")

                elif constrList[i] == "0:2":
                    m.addConstrs((p.sum("*", d) <= bounds[i, 1] for d in D),
                                 "constr")

                elif constrList[i] == "0:1,2":
                    m.addConstrs((o.sum("*", d, "*") <= bounds[i, 1]
                                  for d in D), "constr")

                elif constrList[i] == "1:0":
                    m.addConstrs((r.sum(s, "*") <= bounds[i, 1] for s in S),
                                 "constr")

                elif constrList[i] == "1:2":
                    m.addConstrs((q.sum("*", s) <= bounds[i, 1] for s in S),
                                 "constr")

                elif constrList[i] == "1:0,2":
                    m.addConstrs((o.sum("*", "*", s) <= bounds[i, 1]
                                  for s in S), "constr")

                elif constrList[i] == "2:0":
                    m.addConstrs((p.sum(n, "*") <= bounds[i, 1] for n in N),
                                 "constr")

                elif constrList[i] == "2:1":
                    m.addConstrs((q.sum(n, "*") <= bounds[i, 1] for n in N),
                                 "constr")

                elif constrList[i] == "2:0,1":
                    m.addConstrs((o.sum(n, "*", "*") <= bounds[i, 1]
                                  for n in N), "constr")

                elif constrList[i] == "0,1:2":
                    m.addConstrs(
                        (o.sum("*", d, s) <= bounds[i, 1] for d in D
                         for s in S),
                        "constr",
                    )

                elif constrList[i] == "0,2:1":
                    m.addConstrs(
                        (o.sum(n, d, "*") <= bounds[i, 1] for d in D
                         for n in N),
                        "constr",
                    )

                elif constrList[i] == "1,2:0":
                    m.addConstrs(
                        (o.sum(n, "*", s) <= bounds[i, 1] for n in N
                         for s in S),
                        "constr",
                    )

            #        if mt==1:
            #            for i in range(len(nurse_preference)):
            #                m.addConstr((o[i,nurse_preference[i][0],nurse_preference[i][1]] == 0),"nursePref")

            if constrList[i] == "2:0" and bounds[i, 5] + bounds[i, 4] > 0:
                m.addConstrs((tw[n, 0, 0] == p[n, 0] for n in N),
                             "MaxConsWork")
                m.addConstrs(
                    (tw[n, d1 + 1, 0] <= p[n, d1 + 1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tw[n, d1 + 1, 0] <= 1 - p[n, d1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tw[n, d1 + 1, 0] >= p[n, d1 + 1] - p[n, d1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsWork",
                )

                m.addConstrs((tw[n, 0, d2] == 0 for n in N
                              for d2 in D if d2 > 0), "MaxConsWork")
                m.addConstrs(
                    (tw[n, d1, d2] <= tw[n, d1 - 1, d2 - 1] for n in N
                     for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tw[n, d1, d2] <= p[n, d1] for n in N for d1 in D
                     for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tw[n, d1, d2] >= p[n, d1] + tw[n, d1 - 1, d2 - 1] - 1
                     for n in N for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsWork",
                )
                if bounds[i, 5] > 0:
                    m.addConstr(
                        (quicksum(tw[n, d1, d2] for n in N for d1 in D
                                  for d2 in range(bounds[i, 5], len(D))) == 0),
                        "maxconswork",
                    )
                if bounds[i, 4] > 0:
                    m.addConstrs((sw[n, 0, d2] == 0 for n in N for d2 in D),
                                 "MinConsWork")
                    m.addConstrs(
                        (sw[n, d1, d2] <= tw[n, d1 - 1, d2] for n in N
                         for d1 in D for d2 in D if d1 > 0),
                        "MinConsWork",
                    )
                    m.addConstrs(
                        (sw[n, d1, d2] <= 1 - p[n, d1] for n in N for d1 in D
                         for d2 in D if d1 > 0),
                        "MinConsWork",
                    )
                    m.addConstrs(
                        (sw[n, d1, d2] >= tw[n, d1 - 1, d2] - p[n, d1]
                         for n in N for d1 in D for d2 in D if d1 > 0),
                        "MinConsWork",
                    )

                    m.addConstrs(
                        (sw[n, num_days, d2] == tw[n, num_days - 1, d2]
                         for n in N for d2 in D),
                        "MinConsWork",
                    )

                    m.addConstr(
                        (quicksum(sw[n, d1, d2] * (bounds[i, 4] - 1 - d2)
                                  for n in N for d1 in Ds
                                  for d2 in range(bounds[i, 4] - 1)) == 0),
                        "minconswork",
                    )

            if constrList[i] == [(2, ),
                                 (0, )] and bounds[i, 3] + bounds[i, 2] > 0:
                m.addConstrs((tf[n, 0, 0] == 1 - p[n, 0] for n in N),
                             "MaxConsFree")
                m.addConstrs(
                    (tf[n, d1 + 1, 0] <= p[n, d1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tf[n, d1 + 1, 0] <= 1 - p[n, d1 + 1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tf[n, d1 + 1, 0] >= p[n, d1] - p[n, d1 + 1] for n in N
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )

                m.addConstrs((tf[n, 0, d2] == 0 for n in N
                              for d2 in D if d2 > 0), "MaxConsFree")
                m.addConstrs(
                    (tf[n, d1, d2] <= tf[n, d1 - 1, d2 - 1] for n in N
                     for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tf[n, d1, d2] <= 1 - p[n, d1] for n in N for d1 in D
                     for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tf[n, d1, d2] >= tf[n, d1 - 1, d2 - 1] - p[n, d1]
                     for n in N for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                if bounds[i, 3] > 0:
                    m.addConstr(
                        (quicksum(tf[n, d1, d2] for n in N for d1 in D
                                  for d2 in range(bounds[i, 3], len(D))) == 0),
                        "maxconsfree",
                    )

                if bounds[i, 2] > 0:
                    m.addConstrs((sf[n, 0, d2] == 0 for n in N for d2 in D),
                                 "MinConsFree")
                    m.addConstrs(
                        (sf[n, d1, d2] <= tf[n, d1 - 1, d2] for n in N
                         for d1 in D for d2 in D if d1 > 0),
                        "MinConsFree",
                    )
                    m.addConstrs(
                        (sf[n, d1, d2] <= p[n, d1] for n in N for d1 in D
                         for d2 in D if d1 > 0),
                        "MinConsFree",
                    )
                    m.addConstrs(
                        (sf[n, d1, d2] >= tf[n, d1 - 1, d2] + p[n, d1] - 1
                         for n in N for d1 in D for d2 in D if d1 > 0),
                        "MinConsFree",
                    )

                    m.addConstrs(
                        (sf[n, num_days, d2] == tf[n, num_days - 1, d2]
                         for n in N for d2 in D),
                        "MinConsFree",
                    )

                    m.addConstr(
                        (quicksum(sf[n, d1, d2] * (bounds[i, 2] - 1 - d2)
                                  for n in N for d1 in Ds
                                  for d2 in range(bounds[i, 2] - 1)) == 0),
                        "minconsfree",
                    )

            if constrList[i] == [(2, ),
                                 (1, )] and bounds[i, 5] + bounds[i, 4] > 0:
                m.addConstrs((tws[n, 0, 0] == q[n, 0] for n in N),
                             "MaxConsWork")
                m.addConstrs(
                    (tws[n, s1 + 1, 0] <= q[n, s1 + 1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tws[n, s1 + 1, 0] <= 1 - q[n, s1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tws[n, s1 + 1, 0] >= q[n, s1 + 1] - q[n, s1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsWork",
                )

                m.addConstrs((tws[n, 0, s2] == 0 for n in N
                              for s2 in S if s2 > 0), "MaxConsWork")
                m.addConstrs(
                    (tws[n, s1, s2] <= tws[n, s1 - 1, s2 - 1] for n in N
                     for s1 in S for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tws[n, s1, s2] <= q[n, s1] for n in N for s1 in S
                     for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsWork",
                )
                m.addConstrs(
                    (tws[n, s1, s2] >= q[n, s1] + tws[n, s1 - 1, s2 - 1] - 1
                     for n in N for s1 in S for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsWork",
                )

                if bounds[i, 5] > 0:
                    m.addConstr(
                        (quicksum(tws[n, s1, s2] for n in N for s1 in S
                                  for s2 in range(bounds[i, 5], len(S))) == 0),
                        "maxconswork",
                    )

                if bounds[i, 4] > 0:
                    m.addConstrs((sws[n, 0, s2] == 0 for n in N for s2 in S),
                                 "MinConsWork")
                    m.addConstrs(
                        (sws[n, s1, s2] <= tws[n, s1 - 1, s2] for n in N
                         for s1 in S for s2 in S if s1 > 0),
                        "MinConsWork",
                    )
                    m.addConstrs(
                        (sws[n, s1, s2] <= 1 - q[n, s1] for n in N for s1 in S
                         for s2 in S if s1 > 0),
                        "MinConsWork",
                    )
                    m.addConstrs(
                        (sws[n, s1, s2] >= tws[n, s1 - 1, s2] - q[n, s1]
                         for n in N for s1 in S for s2 in S if s1 > 0),
                        "MinConsWork",
                    )

                    m.addConstrs(
                        (sws[n, num_shifts, s2] == tws[n, num_shifts - 1, s2]
                         for n in N for s2 in S),
                        "MinConsWork",
                    )

                    m.addConstr(
                        (quicksum(sws[n, s1, s2] * (bounds[i, 4] - 1 - s2)
                                  for n in N for s1 in Ss
                                  for s2 in range(bounds[i, 4] - 1)) == 0),
                        "minconswork",
                    )

            if constrList[i] == [(2, ),
                                 (1, )] and bounds[i, 3] + bounds[i, 2] > 0:
                m.addConstrs((tfs[n, 0, 0] == 1 - q[n, 0] for n in N),
                             "MaxConsFree")
                m.addConstrs(
                    (tfs[n, s1 + 1, 0] <= q[n, s1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tfs[n, s1 + 1, 0] <= 1 - q[n, s1 + 1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tfs[n, s1 + 1, 0] >= q[n, s1] - q[n, s1 + 1] for n in N
                     for s1 in S if s1 < len(S) - 1),
                    "MaxConsFree",
                )

                m.addConstrs((tfs[n, 0, s2] == 0 for n in N
                              for s2 in S if s2 > 0), "MaxConsFree")
                m.addConstrs(
                    (tfs[n, s1, s2] <= tfs[n, s1 - 1, s2 - 1] for n in N
                     for s1 in S for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tfs[n, s1, s2] <= 1 - q[n, s1] for n in N for s1 in S
                     for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tfs[n, s1, s2] >= tfs[n, s1 - 1, s2 - 1] - q[n, s1]
                     for n in N for s1 in S for s2 in S if s1 > 0 if s2 > 0),
                    "MaxConsFree",
                )
                if bounds[i, 3] > 0:
                    m.addConstr(
                        (quicksum(tfs[n, s1, s2] for n in N for s1 in S
                                  for s2 in range(bounds[i, 3], len(S))) == 0),
                        "maxconsfree",
                    )

                if bounds[i, 2] > 0:
                    m.addConstrs((sfs[n, 0, s2] == 0 for n in N for s2 in S),
                                 "MinConsFree")
                    m.addConstrs(
                        (sfs[n, s1, s2] <= tfs[n, s1 - 1, s2] for n in N
                         for s1 in S for s2 in S if s1 > 0),
                        "MinConsFree",
                    )
                    m.addConstrs(
                        (sfs[n, s1, s2] <= q[n, s1] for n in N for s1 in S
                         for s2 in S if s1 > 0),
                        "MinConsFree",
                    )
                    m.addConstrs(
                        (sfs[n, s1, s2] >= tfs[n, s1 - 1, s2] + q[n, s1] - 1
                         for n in N for s1 in S for s2 in S if s1 > 0),
                        "MinConsFree",
                    )

                    m.addConstrs(
                        (sfs[n, num_shifts, s2] == tfs[n, num_shifts - 1, s2]
                         for n in N for s2 in S),
                        "MinConsWork",
                    )

                    m.addConstr(
                        (quicksum(sfs[n, s1, s2] * (bounds[i, 2] - 1 - s2)
                                  for n in N for s1 in Ss
                                  for s2 in range(bounds[i, 2] - 1)) == 0),
                        "minconsfree",
                    )

            if constrList[i] == [(1, 2),
                                 (0, )] and bounds[i, 5] + bounds[i, 4] > 0:
                m.addConstrs(
                    (tw1[n, 0, s, 0] == o[n, 0, s] for n in N for s in S),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1 + 1, s, 0] <= o[n, d1 + 1, s] for s in S
                     for n in N for d1 in D if d1 < len(D) - 1),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1 + 1, s, 0] <= 1 - o[n, d1, s] for s in S
                     for n in N for d1 in D if d1 < len(D) - 1),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1 + 1, s, 0] >= o[n, d1 + 1, s] - o[n, d1, s]
                     for s in S for n in N for d1 in D if d1 < len(D) - 1),
                    "MaxConsSameShift",
                )

                m.addConstrs(
                    (tw1[n, 0, s, d2] == 0 for s in S for n in N
                     for d2 in D if d2 > 0),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1, s, d2] <= tw1[n, d1 - 1, s, d2 - 1] for s in S
                     for n in N for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1, s, d2] <= o[n, d1, s] for s in S for n in N
                     for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsSameShift",
                )
                m.addConstrs(
                    (tw1[n, d1, s, d2] >=
                     o[n, d1, s] + tw1[n, d1 - 1, s, d2 - 1] - 1 for s in S
                     for n in N for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsSameShift",
                )
                if bounds[i, 5] > 0:
                    m.addConstr(
                        (quicksum(tw1[n, d1, s, d2] for s in S for n in N
                                  for d1 in D
                                  for d2 in range(bounds[i, 5], len(D))) == 0),
                        "maxconssameshift",
                    )

                if bounds[i, 4] > 0:
                    m.addConstrs(
                        (sw1[n, 0, s, d2] == 0 for s in S for n in N
                         for d2 in D),
                        "MinConsSameShift",
                    )
                    m.addConstrs(
                        (sw1[n, d1, s, d2] <= tw1[n, d1 - 1, s, d2] for s in S
                         for n in N for d1 in D for d2 in D if d1 > 0),
                        "MinConsSameShift",
                    )
                    m.addConstrs(
                        (sw1[n, d1, s, d2] <= 1 - o[n, d1, s] for s in S
                         for n in N for d1 in D for d2 in D if d1 > 0),
                        "MinConsSameShift",
                    )
                    m.addConstrs(
                        (sw1[n, d1, s, d2] >=
                         tw1[n, d1 - 1, s, d2] - o[n, d1, s] for s in S
                         for n in N for d1 in D for d2 in D if d1 > 0),
                        "MinConsSameShift",
                    )

                    m.addConstrs(
                        (sw1[n, num_days, s, d2] == tw1[n, num_days - 1, s, d2]
                         for n in N for s in S for d2 in D),
                        "MinConsWork",
                    )

                    m.addConstr(
                        (quicksum(sw1[n, d1, s, d2] * (bounds[i, 4] - 1 - d2)
                                  for s in S for n in N for d1 in Ds
                                  for d2 in range(bounds[i, 4] - 1)) == 0),
                        "minconssameshift",
                    )

            if constrList[i] == [(1, 2),
                                 (0, )] and bounds[i, 3] + bounds[i, 2] > 0:
                m.addConstrs(
                    (tw1f[n, 0, s, 0] == 1 - o[n, 0, s] for n in N for s in S),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1 + 1, s, 0] <= o[n, d1, s] for n in N for s in S
                     for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1 + 1, s, 0] <= 1 - o[n, d1 + 1, s] for n in N
                     for s in S for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1 + 1, s, 0] >= o[n, d1, s] - o[n, d1 + 1, s]
                     for n in N for s in S for d1 in D if d1 < len(D) - 1),
                    "MaxConsFree",
                )

                m.addConstrs(
                    (tw1f[n, 0, s, d2] == 0 for n in N for s in S
                     for d2 in D if d2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1, s, d2] <= tw1f[n, d1 - 1, s, d2 - 1]
                     for n in N for s in S for d1 in D
                     for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1, s, d2] <= 1 - o[n, d1, s] for n in N
                     for s in S for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                m.addConstrs(
                    (tw1f[n, d1, s, d2] >=
                     tw1f[n, d1 - 1, s, d2 - 1] - o[n, d1, s] for n in N
                     for s in S for d1 in D for d2 in D if d1 > 0 if d2 > 0),
                    "MaxConsFree",
                )
                if bounds[i, 3] > 0:
                    m.addConstr(
                        (quicksum(tw1f[n, d1, s, d2] for n in N for s in S
                                  for d1 in D
                                  for d2 in range(bounds[i, 3], len(D))) == 0),
                        "maxconsfree",
                    )

                if bounds[i, 2] > 0:
                    m.addConstrs(
                        (sw1f[n, 0, s, d2] == 0 for n in N for s in S
                         for d2 in D),
                        "MinConsw1free",
                    )
                    m.addConstrs(
                        (sw1f[n, d1, s, d2] <= tw1f[n, d1 - 1, s, d2]
                         for n in N for s in S for d1 in D
                         for d2 in D if d1 > 0),
                        "MinConsw1free",
                    )
                    m.addConstrs(
                        (sw1f[n, d1, s, d2] <= o[n, d1, s] for n in N
                         for s in S for d1 in D for d2 in D if d1 > 0),
                        "MinConsw1free",
                    )
                    m.addConstrs(
                        (sw1f[n, d1, s, d2] >=
                         tw1f[n, d1 - 1, s, d2] + o[n, d1, s] - 1 for n in N
                         for s in S for d1 in D for d2 in D if d1 > 0),
                        "MinConsw1free",
                    )

                    m.addConstrs(
                        (sw1f[n, num_days, s, d2]
                         == tw1f[n, num_days - 1, s, d2] for n in N for s in S
                         for d2 in D),
                        "MinConsWork",
                    )

                    m.addConstr(
                        (quicksum(sw1f[n, d1, s, d2] * (bounds[i, 2] - 1 - d2)
                                  for n in N for s in S for d1 in Ds
                                  for d2 in range(bounds[i, 2] - 1)) == 0),
                        "minconsw1free",
                    )

        m.setParam(GRB.Param.PoolSolutions, numSam)
        m.setParam(GRB.Param.PoolSearchMode, 2)
        m.optimize()
        nSolutions = m.SolCount
        # print("Number of solutions found: " + str(nSolutions))
        if m.status == GRB.Status.INFEASIBLE:
            m.computeIIS()
            # print("\nThe following constraint(s) cannot be satisfied:")
            for c in m.getConstrs():
                if c.IISConstr:
                    # print("%s" % c.constrName)
                    pass
        # if m.status == GRB.Status.OPTIMAL:
        #     m.write("m.sol")
        #        print(nSolutions)
        #        print(partial_sol)
        multi_predictions = []
        for i in range(nSolutions):
            provenance = ("countor", gid + "-" + str(uuid4()))
            m.setParam(GRB.Param.SolutionNumber, i)
            solution = m.getAttr("xn", o)
            #             print(m.getAttr("xn", p))
            tmp = np.zeros([num_nurses, num_days, num_shifts])
            for key in solution:
                tmp[key] = round(solution[key])
            #            tSample=np.swapaxes(np.swapaxes(tmp,0,1),1,2)
            tmp_sol = tmp.astype(int)

            #            print(partial_sol)
            for n in N:
                for d in D:
                    for s in S:
                        #                        print(x_mapping[n,d,s],y_mapping[n,d,s])
                        if np.isnan(partial_sol[n, d, s]):
                            #                            print("geer")
                            multi_predictions.append(
                                Prediction(
                                    Coordinate(
                                        y_mapping[n, d, s].item(),
                                        x_mapping[n, d, s].item(),
                                    ),
                                    tmp_sol[n, d, s].item(),
                                    1,
                                    provenance,
                                ))
        return multi_predictions

    except GurobiError as e:
        print("Error code " + str(e.errno) + ": " + str(e))

    except AttributeError:
        print("Encountered an attribute error")
def solve_cont_wasserstain(N, xi_n, K, r, c, b, L, C, d, dro_r, norm='inf'):
    """
        Defines a DRO version of the news vendor problem where
        the cost function is piece-wise convex in x (first stage)
        and the demand (i.e., max of affine funcions as in the paper).
        The cost function for a given value of x and demand realization
        xi is given by
        f(x,xi) = max{(c-r[0])x, cx - r[0]xi} #Ignorin other pieces
        Args:
            N (list): support indices
            xi_n (list): empirical support
            K (list): pieces indices
            r (list): coeff of xi on every piece (neg. of the sell cost)
            c (float): coeff of x on every piece (buy cost)
            b (list): intercepts of the pieces
            L (list): support constraint indices
            C (list): lhs coefficients of the constraints defining the
                support of the one-dimensional random variable
            d (list): rhs of the constraints for the support
            dro_r (float): radius of the dro model
            norm (str or int): Dual norm to be used. If inf, then
                the Wassersteain metric is defined under the L1-norm.
                If 2, then the metric is defined under the L2-norm.
        Returns:
            x_star (double): optimal solution to the NV problem
            new_support (ndarray): support of the worst-case distribution
            new_pmf (ndarray): pmf of the worst-case distribution
    """
    model = Model('ContWassersteinNewsVendor')
    model.params.OutputFlag = 0
    model.params.NumericFocus = 3
    model.params.QCPDual = 1
    x = model.addVar(lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS, name='x')
    s = model.addVars(N, lb=-GRB.INFINITY, ub=GRB.INFINITY, obj=[1 / len(N) for _ in N], vtype=GRB.CONTINUOUS, name='s')
    lam = model.addVar(lb=-GRB.INFINITY, ub=GRB.INFINITY, obj=dro_r, vtype=GRB.CONTINUOUS, name='lambda_var')
    gam = model.addVars(K, N, L, lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS, name='gamma_var')
    model.update()
    piece_ctrs = {}
    for i in N:
        for k in K:
            piece_ctrs[(k, i)] = model.addConstr(rhs=((c[k] * x + r[k] * xi_n[i] + b[k]) + quicksum(
                (d[l] - C[l] * xi_n[i]) * gam[k, i, l] for l in L)),
                                                 sense=GRB.GREATER_EQUAL,
                                                 lhs=s[i],
                                                 name=f'piece[{k},{i}]')
    
    norm_ctrs = {}
    norm_aux_ctrs = {}
    for k in K:
        for i in N:
            aux_var_k_i = model.addVar(lb=-GRB.INFINITY,
                                       ub=GRB.INFINITY,
                                       obj=0,
                                       vtype=GRB.CONTINUOUS,
                                       name=f'norm_aux[{k},{i}')
            # Extra constraint modeling the linear expresion inside the norm
            # dual variable of this constraint is q_ik
            norm_aux_ctrs[(k, i)] = model.addConstr(lhs=aux_var_k_i,
                                                    sense=GRB.EQUAL,
                                                    rhs=quicksum(C[l] * gam[k, i, l] for l in L) - r[k],
                                                    name=f'lin_norm_dual_q[{k},{i}]')
            
            if norm == 'inf':
                norm_ctrs[(k, i, -1)] = model.addConstr(lhs=lam - aux_var_k_i,
                                                        sense=GRB.GREATER_EQUAL,
                                                        rhs=0,
                                                        name='L_inf_norm_n[%i,%i]' % (k, i))
                norm_ctrs[(k, i, 1)] = model.addConstr(lhs=lam + aux_var_k_i,
                                                       sense=GRB.GREATER_EQUAL,
                                                       rhs=0,
                                                       name='L_inf_norm_p[%i,%i]' % (k, i))
            elif norm == 2:
                norm_ctrs[(k, i, 1)] = model.addConstr(lhs=lam - aux_var_k_i * aux_var_k_i,
                                                       sense=GRB.GREATER_EQUAL,
                                                       rhs=0,
                                                       name='L2Norm[%i,%i]' % (k, i))
            else:
                raise f'L-{norm} is not defined as a valid dual norm.'
    
    model.update()
    model.write('news_vendor_dro.lp')
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        #print(model.ObjVal, x.X, lam.X, [s[i].X for i in N])
        print(f"f* = {model.ObjVal}")
        # for c in model.getConstrs():
        #     if c.Pi > 1E-9:
        #         #pass
        #         print(c.ConstrName, c.Pi)
        # try:
        #     for c in model.getQConstrs():
        #         print(c.QCName, c.QCPi)
        # except:
        #     pass
        # for v in model.getVars():
        #     if v.X > 1E-6:
        #         print(v.VarName, v.X)
        #print('orig supp', xi_n)
        new_support = []
        pmf = []
        for (k, i) in piece_ctrs:
            if piece_ctrs[(k, i)].Pi > 1E-7:
                new_atom = xi_n[i]
                new_atom = xi_n[i] - (norm_aux_ctrs[(k, i)].Pi) / piece_ctrs[(k, i)].Pi
                new_support.append(new_atom)
                pmf.append(piece_ctrs[(k, i)].Pi)
        new_support = np.array(new_support)
        pmf = np.array(pmf)
        supp_argsort = np.argsort(new_support)
        pmf = pmf[supp_argsort]
        pmf = pmf / pmf.sum()  # In the QP case, we might have numerical error
        new_support.sort()
        
        for i in range(len(new_support) - 1, 0, -1):
            if np.abs(new_support[i] - new_support[i - 1]) < 1E-8:
                new_support = np.delete(new_support, obj=i)
                rep_prob = pmf[i]
                pmf = np.delete(pmf, obj=i)
                pmf[i - 1] += rep_prob
        # print('new supp ', new_support)
        # print('new pmf', pmf)
        return x.X, new_support, pmf
    else:
        model.computeIIS()
        model.write("NewsVendorInf.ilp")
        os.system("open -a TextEdit filename Users/dduque/Desktop/NewsVendorInf.ilp")