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))
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
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
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__
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
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")