def optimise_robust_picef(cfg): m, cycles, cycle_vars, num_edges_var = create_picef_model(cfg) # for use later floor_gamma = np.floor(cfg.gamma) ceil_gamma = np.ceil(cfg.gamma) gamma_frac = cfg.gamma - floor_gamma # add cycle vars cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = cycle_weight(c_obj.vs, cfg.digraph) c_obj.grb_var = var cycle_list.append(c_obj) m.update() # gamma is integer if gamma_frac == 0: if cfg.use_chains: for ndd in cfg.ndds: for e in ndd.edges: g_var = m.addVar(vtype=GRB.BINARY) d_var = m.addVar(vtype=GRB.BINARY) e.g_var = g_var e.d_var = d_var m.addGenConstrAnd(e.d_var, [e.g_var, e.edge_var]) m.update() # add g and d variables for pair-pair edges for e in cfg.digraph.es: g_var = m.addVar(vtype=GRB.BINARY) d_var = m.addVar(vtype=GRB.BINARY) e.g_var = g_var e.d_var = d_var m.addGenConstrAnd(e.d_var, [e.g_var, e.used_var]) m.update() # gamma is not integer else: if cfg.use_chains: # use both gf (full discount if gf=1, gp=0) and gp (partial discount, if gf=gp=1) for ndd in cfg.ndds: for e in ndd.edges: gf_var = m.addVar(vtype=GRB.BINARY) df_var = m.addVar(vtype=GRB.BINARY) e.gf_var = gf_var e.df_var = df_var m.addGenConstrAnd(e.df_var, [e.gf_var, e.edge_var]) gp_var = m.addVar(vtype=GRB.BINARY) dp_var = m.addVar(vtype=GRB.BINARY) e.gp_var = gp_var e.dp_var = dp_var m.addGenConstrAnd(e.dp_var, [e.gp_var, e.edge_var]) m.update() for e in cfg.digraph.es: gf_var = m.addVar(vtype=GRB.BINARY) df_var = m.addVar(vtype=GRB.BINARY) e.gf_var = gf_var e.df_var = df_var m.addGenConstrAnd(e.df_var, [e.gf_var, e.used_var]) gp_var = m.addVar(vtype=GRB.BINARY) dp_var = m.addVar(vtype=GRB.BINARY) e.gp_var = gp_var e.dp_var = dp_var m.addGenConstrAnd(e.dp_var, [e.gp_var, e.used_var]) m.update() # discount indicators g follow same ordering as the edge discount values (sort in increasing order) if cfg.use_chains: ndd_e = [e for ndd in cfg.ndds for e in ndd.edges] else: ndd_e = [] all_edges = cfg.digraph.es + ndd_e e_sorted = sorted(all_edges, key=lambda x: x.discount, reverse=False) # ordering constraints over g # gamma is integer if gamma_frac == 0: for i in range(len(e_sorted) - 1): m.addConstr(e_sorted[i].g_var <= e_sorted[i + 1].g_var) # gamma is not integer else: for i in range(len(e_sorted) - 1): m.addConstr(e_sorted[i].gf_var <= e_sorted[i + 1].gf_var) m.addConstr(e_sorted[i].gp_var <= e_sorted[i + 1].gp_var) # number of edges used in matching (include all position-indexed vars) # uncertainty budget (number of discounted edges) gamma_var = m.addVar(vtype=GRB.CONTINUOUS) m.addGenConstrMin(gamma_var, [num_edges_var, cfg.gamma]) # add a cardinality restriction if necessary if cfg.cardinality_restriction is not None: m.addConstr(num_edges_var <= cfg.cardinality_restriction) m.update() # limit number of discounted variables # gamma is integer if gamma_frac == 0: m.addConstr(quicksum(e.d_var for e in all_edges) == gamma_var) # gamma is not integer else: h_var = m.addVar(vtype=GRB.BINARY) m.addConstr(cfg.gamma - num_edges_var <= W_small * h_var) m.addConstr(num_edges_var - cfg.gamma <= W_small * (1 - h_var)) m.addConstr( quicksum(e.dp_var for e in all_edges) == h_var * num_edges_var + (1 - h_var) * ceil_gamma) m.addConstr( quicksum(e.df_var for e in all_edges) == h_var * num_edges_var + (1 - h_var) * floor_gamma) # total discount (by edge) # gamma is integer if gamma_frac == 0: total_discount = quicksum(e.discount * e.d_var for e in all_edges) # gamma is not integer else: total_discount = quicksum((1 - gamma_frac) * e.discount * e.df_var for e in all_edges) + \ quicksum(gamma_frac * e.discount * e.dp_var for e in all_edges) # set a variable for the total (optimistic matching weight) total_weight = m.addVar(vtype=GRB.CONTINUOUS) m.update() if not cfg.use_chains: m.addConstr(total_weight == quicksum( failure_aware_cycle_weight(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars))) obj_expr = total_weight - total_discount elif cfg.edge_success_prob == 1: m.addConstr(total_weight == (quicksum( cycle_weight(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.weight * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.weight * var for e in cfg.digraph.es for var in e.grb_vars))) obj_expr = total_weight - total_discount else: raise Warning("not implemented") m.setObjective(obj_expr, GRB.MAXIMIZE) optimize(m) if gamma_frac == 0: # gamma is integer discounted_pair_edges = [e for e in cfg.digraph.es if e.d_var.x > 0] for e in discounted_pair_edges: e.discount_frac = e.d_var.x if cfg.use_chains: discounted_ndd_edges = [(i_ndd, e) for i_ndd, ndd in enumerate(cfg.ndds) for e in ndd.edges if e.d_var.x > 0.0] for _, e in discounted_ndd_edges: e.discount_frac = e.d_var.x else: # gamma is not integer discounted_pair_edges = [e for e in cfg.digraph.es \ if ((e.df_var.x > 0.0) or (e.dp_var.x > 0.0))] for e in discounted_pair_edges: e.discount_frac = ( 1 - gamma_frac) * e.df_var.x + gamma_frac * e.dp_var.x if cfg.use_chains: discounted_ndd_edges = [(i_ndd, e) for i_ndd, ndd in enumerate(cfg.ndds) for e in ndd.edges \ if ((e.df_var.x > 0.0) or (e.dp_var.x > 0.0))] for _, e in discounted_ndd_edges: e.discount_frac = ( 1 - gamma_frac) * e.df_var.x + gamma_frac * e.dp_var.x if cfg.use_chains: ndd_matching_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_matching_edges = [] used_matching_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] matching_edges = ndd_matching_edges + used_matching_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges))) chains_used = kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds, cfg.edge_success_prob) if cfg.use_chains \ else [] cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution(ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=chains_used, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, gamma=cfg.gamma, robust_weight=m.objVal, optimistic_weight=total_weight.x, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity(sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain) return sol, matching_edges
def optimize_SAA_picef(cfg, num_weight_measurements, gamma, alpha): m, cycles, cycle_vars, _ = create_picef_model(cfg) # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = failure_aware_cycle_weight(c_obj.vs, cfg.digraph, cfg.edge_success_prob) c_obj.grb_var = var cycle_list.append(c_obj) # add variables for each edge weight measurement weight_vars = m.addVars(num_weight_measurements, vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY) for i in range(num_weight_measurements): m.addConstr(weight_vars[i] == -(quicksum(e.used_var * e.weight_list[i] for e in cfg.digraph.es) + quicksum(e.weight_list[i] * e.edge_var for ndd in cfg.ndds for e in ndd.edges))) # auxiliary variable d_var = m.addVar(vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY) # add pi variables & constraints for SAA pi_vars = m.addVars(num_weight_measurements, vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY) for i in range(num_weight_measurements): m.addConstr(pi_vars[i] >= weight_vars[i]) m.addConstr(pi_vars[i] >= (1 + gamma / alpha) * weight_vars[i] - (d_var * gamma) / alpha) # objective obj = (1.0 / float(num_weight_measurements)) * quicksum(pi_vars) + gamma * d_var m.setObjective(obj, sense=GRB.MINIMIZE) if not cfg.use_chains: raise Exception("not implemented") elif cfg.edge_success_prob == 1: pass else: raise Exception("not implemented") optimize(m) pair_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob) ndd_chain_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_chain_edges = [] matching_chains = [] matching_edges = pair_edges + ndd_chain_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges))) cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution(ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity(sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain) return sol, matching_edges
def optimize_picef(cfg): m, cycles, cycle_vars, _ = create_picef_model(cfg) # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = failure_aware_cycle_weight(c_obj.vs, cfg.digraph, cfg.edge_success_prob) c_obj.grb_var = var cycle_list.append(c_obj) if not cfg.use_chains: obj_expr = quicksum( failure_aware_cycle_weight(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) elif cfg.edge_success_prob == 1: obj_expr = (quicksum( cycle_weight(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.weight * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.weight * var for e in cfg.digraph.es for var in e.grb_vars)) else: obj_expr = (quicksum( failure_aware_cycle_weight(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.weight * cfg.edge_success_prob * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum( e.weight * cfg.edge_success_prob**(pos + 1) * var for e in cfg.digraph.es for var, pos in zip(e.grb_vars, e.grb_var_positions))) m.setObjective(obj_expr, GRB.MAXIMIZE) optimize(m) pair_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob) ndd_chain_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_chain_edges = [] matching_chains = [] matching_edges = pair_edges + ndd_chain_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges))) cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution(ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity(sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain) return sol, matching_edges
def optimize_DRO_SAA_picef(cfg, num_weight_measurements, gamma, alpha, theta, w_min, w_max): """Solve the DRO-SAA formulation of (Ren, 2020) Arguments: cfg: (OptConfig object) num_weight_measurements: (int). number of weight measurements associated with each edge gamma: (float). parameter balancing between a pure CVar objective (gamma->infinity) and a pure max-expectation objective (gamma=0) alpha: (float). CVar protection level, should be on [0, 1] theta: prediction of distance between assumed distribution and true distribution w_min: (float). assumed minimum edge weight of unknown distribution w_max: (float). assumed maximum edge weight of unknown distribution """ m, cycles, cycle_vars, _ = create_picef_model(cfg) # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = failure_aware_cycle_weight(c_obj.vs, cfg.digraph, cfg.edge_success_prob) c_obj.grb_var = var cycle_list.append(c_obj) # add variables for each edge weight measurement weight_vars = m.addVars(num_weight_measurements, vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY) for i in range(num_weight_measurements): m.addConstr(weight_vars[i] == -(quicksum(e.used_var * e.weight_list[i] for e in cfg.digraph.es) + quicksum(e.weight_list[i] * e.edge_var for ndd in cfg.ndds for e in ndd.edges))) # auxiliary variables d_var = m.addVar(vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY, name='d') lam_var = m.addVar(vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY, name='lambda') s_vars = m.addVars(num_weight_measurements, vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY, name='s') # add eta and mu vars for each edge for e in cfg.digraph.es: e.eta_vars = m.addVars(num_weight_measurements, 2, vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name='eta') e.mu_vars = m.addVars(num_weight_measurements, 2, vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name='mu') for n in cfg.ndds: for e in n.edges: # also add the used_var here, for convenience e.used_var = e.edge_var e.eta_vars = m.addVars(num_weight_measurements, 2, vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name='eta') e.mu_vars = m.addVars(num_weight_measurements, 2, vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name='mu') # add variables for each edge weight measurement weight_vars = m.addVars(num_weight_measurements, vtype=GRB.CONTINUOUS, lb=-GRB.INFINITY, ub=GRB.INFINITY) for i in range(num_weight_measurements): m.addConstr(weight_vars[i] == -(quicksum(e.used_var * e.weight_list[i] for e in cfg.digraph.es) + quicksum(e.weight_list[i] * e.edge_var for ndd in cfg.ndds for e in ndd.edges))) b1 = 0 b2 = -d_var * gamma / alpha # construct a list of all edges, for convenience e_list = cfg.digraph.es + [e for n in cfg.ndds for e in n.edges] # add main constraints for i_measurement in range(num_weight_measurements): # k = 1 edge_sum_1a = quicksum([ e.eta_vars[i_measurement, 0] * (w_max - e.weight_list[i_measurement]) for e in e_list ]) edge_sum_1b = quicksum([ e.mu_vars[i_measurement, 0] * (e.weight_list[i_measurement] - w_min) for e in e_list ]) m.addConstr(b1 + weight_vars[i_measurement] + edge_sum_1a + edge_sum_1b <= s_vars[i_measurement], name=("s_constr_k1_i%d" % i_measurement)) # k = 2 edge_sum_2a = quicksum([ e.eta_vars[i_measurement, 1] * (w_max - e.weight_list[i_measurement]) for e in e_list ]) edge_sum_2b = quicksum([ e.mu_vars[i_measurement, 1] * (e.weight_list[i_measurement] - w_min) for e in e_list ]) m.addConstr(b2 + (1 + gamma / alpha) * weight_vars[i_measurement] + edge_sum_2a + edge_sum_2b <= s_vars[i_measurement], name=("s_constr__k2_i%d" % i_measurement)) # now for the norm sum (using the 1-norm) # for each edge, get |\eta_ik - \mu_ik - a_k|. then sum all of these to obtain the 1-norm e_norm_plus_vars_k1 = m.addVars(len(e_list), vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name=("e_norm_plus_k1_i%d" % i_measurement)) e_norm_minus_vars_k1 = m.addVars(len(e_list), vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name=("e_norm_minus_k1_i%d" % i_measurement)) e_norm_plus_vars_k2 = m.addVars(len(e_list), vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name=("e_norm_plus_k2_i%d" % i_measurement)) e_norm_minus_vars_k2 = m.addVars(len(e_list), vtype=GRB.CONTINUOUS, lb=0.0, ub=GRB.INFINITY, name=("e_norm_minus_k2_i%d" % i_measurement)) for i_e, e in enumerate(e_list): # k = 1 m.addConstr( e_norm_plus_vars_k1[i_e] - e_norm_minus_vars_k1[i_e] == e.eta_vars[i_measurement, 0] - e.mu_vars[i_measurement, 0] + e.used_var) # k = 2 m.addConstr( e_norm_plus_vars_k2[i_e] - e_norm_minus_vars_k2[i_e] == e.eta_vars[i_measurement, 1] - e.mu_vars[i_measurement, 1] + (1 + gamma / alpha) * e.used_var) # the 1-norm must be bounded by lambda, for each measurement and for each k m.addConstr( quicksum(e_norm_plus_vars_k2) + quicksum(e_norm_minus_vars_k2) <= lam_var) m.addConstr( quicksum(e_norm_plus_vars_k1) + quicksum(e_norm_minus_vars_k1) <= lam_var) # objective obj = lam_var * theta + (1.0 / float(num_weight_measurements) ) * quicksum(s_vars) + gamma * d_var m.setObjective(obj, sense=GRB.MINIMIZE) if not cfg.use_chains: raise Exception("not implemented") elif cfg.edge_success_prob == 1: pass else: raise Exception("not implemented") optimize(m) pair_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob) # matching_chains = [] ndd_chain_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_chain_edges = [] matching_chains = [] matching_edges = pair_edges + ndd_chain_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges))) cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution(ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity(sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain) return sol, matching_edges
def create_picef_model(cfg, check_edge_success=False): """Optimise using the PICEF formulation. Args: cfg: an OptConfig object check_edge_success: (bool). if True, check if each edge has e.success = False. if e.success=False, the edge cannot be used. Returns: an OptSolution object """ cycles = cfg.digraph.find_cycles(cfg.max_cycle) m = create_mip_model(time_lim=cfg.timelimit, verbose=cfg.verbose) m.params.method = -1 cycle_vars = [m.addVar(vtype=GRB.BINARY) for __ in cycles] vtx_to_vars = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints( cfg.digraph, cfg.ndds, cfg.use_chains, cfg.max_chain, m, vtx_to_vars, store_edge_positions=True, check_edge_success=check_edge_success, ) for i, c in enumerate(cycles): for v in c: vtx_to_vars[v.id].append(cycle_vars[i]) for l in vtx_to_vars: if len(l) > 0: m.addConstr(quicksum(l) <= 1) # add variables for each pair-pair edge indicating whether it is used in a cycle or chain for e in cfg.digraph.es: used_in_cycle = [] for var, c in zip(cycle_vars, cycles): if kidney_utils.cycle_contains_edge(c, e): used_in_cycle.append(var) used_var = m.addVar(vtype=GRB.INTEGER) if check_edge_success: if not e.success: m.addConstr(used_var == 0) if cfg.use_chains: m.addConstr(used_var == quicksum(used_in_cycle) + quicksum(e.grb_vars)) else: m.addConstr(used_var == quicksum(used_in_cycle)) e.used_var = used_var # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = failure_aware_cycle_weight(c_obj.vs, cfg.digraph, cfg.edge_success_prob) c_obj.grb_var = var cycle_list.append(c_obj) # add objective if not cfg.use_chains: obj_expr = quicksum( failure_aware_cycle_weight(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) elif cfg.edge_success_prob == 1: obj_expr = (quicksum( cycle_weight(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.weight * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.weight * var for e in cfg.digraph.es for var in e.grb_vars)) else: obj_expr = (quicksum( failure_aware_cycle_weight(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.weight * cfg.edge_success_prob * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum( e.weight * cfg.edge_success_prob**(pos + 1) * var for e in cfg.digraph.es for var, pos in zip(e.grb_vars, e.grb_var_positions))) m.setObjective(obj_expr, GRB.MAXIMIZE) m.update() # attach the necessary objects to the optconfig cfg.m = m cfg.cycles = cycles cfg.cycle_vars = cycle_vars cfg.cycle_list = cycle_list
def optimize_dro_saa_edge_existence(cfg, num_measurements, gamma, alpha): """ solve the PICEF model with DRO-SAA objective for edge existence uncertainty this requires that each edge has the property e.realizations: a binary vector of length num_measurements """ m, cycles, cycle_vars, _ = create_picef_model( cfg, add_o_vars=True, num_o_vars=num_measurements ) # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = expected_cycle_weight(c_obj) c_obj.grb_var = var # for DRO-SAA - keep track of cycle realizations c_obj.realizations = [ min(e.realizations[n] for e in c_obj.edges) for n in range(num_measurements) ] cycle_list.append(c_obj) # add weight variables for each realization w_vars = m.addVars(num_measurements, lb=-GRB.INFINITY, ub=GRB.INFINITY) for n in range(num_measurements): # objective for the n^th realization m.addConstr( w_vars[n] == ( -quicksum( e.weight * big_o_var for i in range(cfg.max_chain - 1) for v in cfg.digraph.vs for e, big_o_var in zip(v.edges_in[i], v.big_o_vars_in[i][n]) ) - quicksum(c.weight * c.realizations[n] * c.grb_var for c in cycle_list) ) ) d_var = m.addVar(lb=-GRB.INFINITY, ub=GRB.INFINITY) # define pi variables pi_vars = m.addVars(num_measurements, lb=0, ub=GRB.INFINITY) for n in range(num_measurements): m.addConstr(pi_vars[n] >= w_vars[n] - d_var) # define objective obj_expr = (1.0 / float(num_measurements)) * quicksum(w_vars) + gamma * ( d_var + (1.0 / (alpha * float(num_measurements))) * quicksum(pi_vars) ) m.setObjective(obj_expr, GRB.MINIMIZE) optimize(m) pair_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob ) ndd_chain_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_chain_edges = [] matching_chains = [] matching_edges = pair_edges + ndd_chain_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges)) ) cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution( ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction, ) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity( sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain ) return cycle_obj, matching_chains
def optimize_picef_heterogeneous_edge_prob(cfg): """ solve the PICEF model with heterogeneous edge success probabilities of Ren, McElfresh, Bidkhori, Dickerson (2020) this requires that each edge has the property edge.success_prob """ m, cycles, cycle_vars, _ = create_picef_model(cfg, add_o_vars=True) # add cycle objects cycle_list = [] for c, var in zip(cycles, cycle_vars): c_obj = Cycle(c) c_obj.add_edges(cfg.digraph.es) c_obj.weight = expected_cycle_weight(c_obj) c_obj.grb_var = var cycle_list.append(c_obj) # add objective chain_weight = quicksum( e.weight * big_o_var for i in range(cfg.max_chain - 1) for v in cfg.digraph.vs for e, big_o_var in zip(v.edges_in[i], v.big_o_vars_in[i]) ) obj_expr = chain_weight + quicksum(c.weight * c.grb_var for c in cycle_list) m.setObjective(obj_expr, GRB.MAXIMIZE) optimize(m) pair_edges = [e for e in cfg.digraph.es if e.used_var.x > 0.5] if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob ) ndd_chain_edges = [ e for ndd in cfg.ndds for e in ndd.edges if e.edge_var.x > 0.5 ] else: ndd_chain_edges = [] matching_chains = [] matching_edges = pair_edges + ndd_chain_edges if cfg.cardinality_restriction is not None: if len(matching_edges) > cfg.cardinality_restriction: raise Warning( "cardinality restriction is violated: restriction = %d edges, matching uses %d edges" % (cfg.cardinality_restriction, len(matching_edges)) ) cycles_used = [c for c, v in zip(cycles, cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cycle_list if c.grb_var.x > 0.5] sol = OptSolution( ip_model=m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, chain_restriction=cfg.chain_restriction, cycle_restriction=cfg.cycle_restriction, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, cardinality_restriction=cfg.cardinality_restriction, ) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity( sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain ) return cycle_obj, matching_chains