def optimize_picef(cfg, check_edge_success=False): """create and solve a picef model, and return the solution""" if cfg.m is None: create_picef_model(cfg, check_edge_success=check_edge_success) optimize(cfg.m) if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob) else: matching_chains = [] cycles_used = [c for c, v in zip(cfg.cycles, cfg.cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cfg.cycle_list if c.grb_var.x > 0.5] sol = OptSolution( ip_model=cfg.m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, ) 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, sol
def optimise_eef(cfg, full_red=False): """Optimise using the reduced extended edge formulation (Constantino et al., EJOR, 2013). Note that this implementation does not yet include chains, and throws an exception if a chain cap greater than zero is used. Args: cfg: an OptConfig object full_red: True if cycles should be generated in order to reduce number of variables further Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError( "This formulation does not support failure-aware matching.") m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 m.params.presolve = 0 # For each vertex v, a list of variables corresponding to in-edges to v vtx_to_in_edges = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints(cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_in_edges) vars_and_edges = add_eef_vars_and_constraints(cfg.max_cycle, cfg.digraph, m, full_red, cfg.eef_alt_constraints, vtx_to_in_edges) obj_expr = quicksum(edge.score * var for var, edge, low_v_id in vars_and_edges) if cfg.max_chain > 0: obj_expr += quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) obj_expr += quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) cycle_start_vv = [] cycle_next_vv = {} for var, edge, low_v_id in vars_and_edges: if var.x > 0.1: cycle_next_vv[edge.src.id] = edge.tgt.id cycle_start_vv.append(edge.src.id) return OptSolution(ip_model=m, cycles=kidney_utils.selected_edges_to_cycles( cfg.digraph, cycle_start_vv, cycle_next_vv), chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph)
def optimise_eef(cfg, full_red=False): """Optimise using the reduced extended edge formulation (Constantino et al., EJOR, 2013). Note that this implementation does not yet include chains, and throws an exception if a chain cap greater than zero is used. Args: cfg: an OptConfig object full_red: True if cycles should be generated in order to reduce number of variables further Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError("This formulation does not support failure-aware matching.") m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 m.params.presolve = 0 # For each vertex v, a list of variables corresponding to in-edges to v vtx_to_in_edges = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints(cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_in_edges) vars_and_edges = add_eef_vars_and_constraints( cfg.max_cycle, cfg.digraph, m, full_red, cfg.eef_alt_constraints, vtx_to_in_edges ) obj_expr = quicksum(edge.score * var for var, edge, low_v_id in vars_and_edges) if cfg.max_chain > 0: obj_expr += quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) obj_expr += quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) cycle_start_vv = [] cycle_next_vv = {} for var, edge, low_v_id in vars_and_edges: if var.x > 0.1: cycle_next_vv[edge.src.id] = edge.tgt.id cycle_start_vv.append(edge.src.id) return OptSolution( ip_model=m, cycles=kidney_utils.selected_edges_to_cycles(cfg.digraph, cycle_start_vv, cycle_next_vv), chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph, )
def optimise_uuef(cfg): """Optimise using the uncapped edge formulation. Args: cfg: an OptConfig object Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError( "This formulation does not support failure-aware matching.") m = create_ip_model(cfg.timelimit, cfg.verbose) add_unlimited_vars_and_constraints(cfg.digraph, cfg.ndds, m) obj_expr = (quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars)) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) # Try all possible cycle start positions cycle_start_vv = range(cfg.digraph.n) cycle_next_vv = {} for e in cfg.digraph.es: for var in e.grb_vars: if var.x > 0.1: cycle_next_vv[e.src.id] = e.tgt.id return OptSolution( ip_model=m, cycles=kidney_utils.selected_edges_to_cycles(cfg.digraph, cycle_start_vv, cycle_next_vv), chains=kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph)
def solve_picef_model(cfg, remove_edges=[]): """ solve a picef model using a config object, and return the solution if remove_edges is provided, disallow these edges from being used. """ for e in remove_edges: e.used_var.setAttr(GRB.Attr.UB, 0.0) optimize(cfg.m) if cfg.use_chains: matching_chains = kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob) else: matching_chains = [] cycles_used = [c for c, v in zip(cfg.cycles, cfg.cycle_vars) if v.x > 0.5] cycle_obj = [c for c in cfg.cycle_list if c.grb_var.x > 0.5] sol = OptSolution( ip_model=cfg.m, cycles=cycles_used, cycle_obj=cycle_obj, chains=matching_chains, digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, cycle_cap=cfg.max_chain, chain_cap=cfg.max_cycle, ) sol.add_matching_edges(cfg.ndds) kidney_utils.check_validity(sol, cfg.digraph, cfg.ndds, cfg.max_cycle, cfg.max_chain) # allow removed edges to be used again for e in remove_edges: e.used_var.setAttr(GRB.Attr.UB, 1.0) return sol
def optimise_uuef(cfg): """Optimise using the uncapped edge formulation. Args: cfg: an OptConfig object Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError("This formulation does not support failure-aware matching.") m = create_ip_model(cfg.timelimit, cfg.verbose) add_unlimited_vars_and_constraints(cfg.digraph, cfg.ndds, m) obj_expr = quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum( e.score * var for e in cfg.digraph.es for var in e.grb_vars ) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) # Try all possible cycle start positions cycle_start_vv = range(cfg.digraph.n) cycle_next_vv = {} for e in cfg.digraph.es: for var in e.grb_vars: if var.x > 0.1: cycle_next_vv[e.src.id] = e.tgt.id return OptSolution( ip_model=m, cycles=kidney_utils.selected_edges_to_cycles(cfg.digraph, cycle_start_vv, cycle_next_vv), chains=kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph, )
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
def optimise_picef(cfg): """Optimise using the PICEF formulation. Args: cfg: an OptConfig object Returns: an OptSolution object """ cycles = cfg.digraph.find_cycles(cfg.max_cycle) m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 cycle_vars = [m.addVar(vtype=GRB.BINARY) for __ in cycles] m.update() vtx_to_vars = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints( cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_vars, store_edge_positions=cfg.edge_success_prob != 1) 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) if cfg.max_chain == 0: obj_expr = quicksum( failure_aware_cycle_score(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_score(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars)) else: obj_expr = (quicksum( failure_aware_cycle_score(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score * cfg.edge_success_prob * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum( e.score * 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) optimise(m, cfg) return OptSolution( ip_model=m, cycles=[c for c, v in zip(cycles, cycle_vars) if v.x > 0.5], chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob), digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob)
def optimise_hpief_prime(cfg, full_red=False, hpief_2_prime=False): """Optimise using the HPIEF' or HPIEF'' formulation. The HPIEF' model is based on HPIEF, but does not include cycle-edge variables at position zero. HPIEF'' also removes variables corresponding to edges at the last possible position of a cycle. Args: cfg: an OptConfig object full_red: True if cycles should be generated in order to reduce number of variables further hpief_2_prime: Use HPIEF''? Default: HPIEF' Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError( "This formulation does not support failure-aware matching.") if cfg.max_cycle < 3: hpief_2_prime = False m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 m.params.presolve = 0 # For each vertex v, a list of variables corresponding to in-edges to v vtx_to_in_edges = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints(cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_in_edges) vars_and_edges = add_hpief_prime_vars_and_constraints( cfg.max_cycle, cfg.digraph, vtx_to_in_edges, m, full_red, hpief_2_prime) obj_terms = [] for var, pos, edge, low_v_id in vars_and_edges: score = edge.score if pos == 1: score += cfg.digraph.adj_mat[low_v_id][edge.src.id].score if hpief_2_prime and pos == cfg.max_cycle - 2 and edge.tgt.id != low_v_id: score += cfg.digraph.adj_mat[edge.tgt.id][low_v_id].score obj_terms.append(score * var) obj_expr = quicksum(obj_terms) if cfg.max_chain > 0: obj_expr += quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) obj_expr += quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) cycle_start_vv = [] cycle_next_vv = {} for var, pos, edge, low_v_id in vars_and_edges: if var.x > 0.1: cycle_next_vv[edge.src.id] = edge.tgt.id if pos == 1: cycle_start_vv.append(low_v_id) cycle_next_vv[low_v_id] = edge.src.id if hpief_2_prime and pos == cfg.max_cycle - 2 and edge.tgt.id != low_v_id: cycle_next_vv[edge.tgt.id] = low_v_id return OptSolution(ip_model=m, cycles=kidney_utils.selected_edges_to_cycles( cfg.digraph, cycle_start_vv, cycle_next_vv), chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph)
def optimise_picef(cfg): """Optimise using the PICEF formulation. Args: cfg: an OptConfig object Returns: an OptSolution object """ cycles = cfg.digraph.find_cycles(cfg.max_cycle) # m = create_ip_model(cfg.timelimit, cfg.verbose) m = create_ip_model(cfg.timelimit, cfg.verbose, multi=cfg.multi, gap=cfg.gap) # changed by Duncan m.params.method = 2 cycle_vars = [m.addVar(vtype=GRB.BINARY) for __ in cycles] m.update() vtx_to_vars = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints(cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_vars, store_edge_positions=cfg.edge_success_prob!=1) for i, c in enumerate(cycles): for v in c: vtx_to_vars[v.id].append(cycle_vars[i]) # added by Duncan #min_fair_score = sens.count(1) #pct = 0 #min_sensitized = pct * min_fair_score # print "len dot: {}".format(len(np.dot(sens,v_used))) #print "# sensitized = {}".format(cfg.min_fair_score) # m.addConstr(np.dot(sens,v_used) >= cfg.min_fair_score) #print "in IP: num sens: {}".format(cfg.min_fair_score) if cfg.min_fair_score > 0: # sens = [1 if v.sensitized else 0 for v in cfg.digraph.vs] # v_used = [ quicksum(l) if len(l)>0 else 0 for l in vtx_to_vars ] # if cfg.edge_success_prob == 1: # m.addConstr(quicksum(sens[i]*v_used[i] for i in range(cfg.digraph.n)) >= cfg.min_fair_score) # below is for failure-aware... the fair score (from only highly sensitized patients) must be above the threshold # WARNING: this adds extra variables to the model... should not do this. # else: d_fair = cfg.digraph.fair_copy() # ndd_fair = [n.fair_copy() for n in cfg.ndds] # m_copy = m.copy() # vtx_to_vars_fair = [[] for __ in d_fair.vs] # add_chain_vars_and_constraints(d_fair, ndd_fair, cfg.max_chain, m, # vtx_to_vars_fair, store_edge_positions=cfg.edge_success_prob != 1) if cfg.max_chain == 0: fair_score = quicksum(failure_aware_cycle_score(c, d_fair, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) elif cfg.edge_success_prob == 1: fair_score = (quicksum(cycle_score(c, d_fair) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score * e.edge_var * e.target_v.sensitized for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score * var * e.tgt.sensitized for e in cfg.digraph.es for var in e.grb_vars)) else: fair_score = ( quicksum(failure_aware_cycle_score(c, d_fair, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score*cfg.edge_success_prob * e.edge_var * e.target_v.sensitized for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score*cfg.edge_success_prob**(pos+1) * var * e.tgt.sensitized for e in cfg.digraph.es for var, pos in zip(e.grb_vars, e.grb_var_positions))) m.addConstr(fair_score >= cfg.min_fair_score) for l in vtx_to_vars: if len(l) > 0: m.addConstr(quicksum(l) <= 1) if cfg.max_chain==0: obj_expr = quicksum(failure_aware_cycle_score(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_score(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) ) else: obj_expr = ( quicksum(failure_aware_cycle_score(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score*cfg.edge_success_prob * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score*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) optimise(m, cfg) if cfg.multi > 1: if m.status == GRB.status.INFEASIBLE: return OptCore(m, cfg.digraph, cfg.multi, [], True) else: nSolutions = m.SolCount solutions = [None] * nSolutions for n_sol in range(nSolutions): m.setParam(GRB.Param.SolutionNumber, n_sol) solutions[n_sol] = OptSolution(ip_model=m, cycles=[c for c, v in zip(cycles, cycle_vars) if v.Xn > 0.5], chains=[] if cfg.max_chain==0 else kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob), digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob) # Print objective values of solutions # for e in range(nSolutions): # m.setParam(GRB.Param.SolutionNumber, e) # print '%g ' % m.PoolObjVal ## if e % 15 == 14: ## print('') # print '' return OptCore(m, cfg.digraph, cfg.multi, solutions) else: if m.status == GRB.status.INFEASIBLE: return OptSolution(ip_model=m, cycles=[], chains=[], digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, infeasible = True) else: return OptSolution(ip_model=m, cycles=[c for c, v in zip(cycles, cycle_vars) if v.x > 0.5], chains=[] if cfg.max_chain==0 else kidney_utils.get_optimal_chains( cfg.digraph, cfg.ndds, cfg.edge_success_prob), digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob)
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 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_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 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 optimise_hpief_prime(cfg, full_red=False, hpief_2_prime=False): """Optimise using the HPIEF' or HPIEF'' formulation. The HPIEF' model is based on HPIEF, but does not include cycle-edge variables at position zero. HPIEF'' also removes variables corresponding to edges at the last possible position of a cycle. Args: cfg: an OptConfig object full_red: True if cycles should be generated in order to reduce number of variables further hpief_2_prime: Use HPIEF''? Default: HPIEF' Returns: an OptSolution object """ if cfg.edge_success_prob != 1: raise ValueError("This formulation does not support failure-aware matching.") if cfg.max_cycle < 3: hpief_2_prime = False m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 m.params.presolve = 0 # For each vertex v, a list of variables corresponding to in-edges to v vtx_to_in_edges = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints(cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_in_edges) vars_and_edges = add_hpief_prime_vars_and_constraints( cfg.max_cycle, cfg.digraph, vtx_to_in_edges, m, full_red, hpief_2_prime ) obj_terms = [] for var, pos, edge, low_v_id in vars_and_edges: score = edge.score if pos == 1: score += cfg.digraph.adj_mat[low_v_id][edge.src.id].score if hpief_2_prime and pos == cfg.max_cycle - 2 and edge.tgt.id != low_v_id: score += cfg.digraph.adj_mat[edge.tgt.id][low_v_id].score obj_terms.append(score * var) obj_expr = quicksum(obj_terms) if cfg.max_chain > 0: obj_expr += quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) obj_expr += quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) m.setObjective(obj_expr, GRB.MAXIMIZE) optimise(m, cfg) cycle_start_vv = [] cycle_next_vv = {} for var, pos, edge, low_v_id in vars_and_edges: if var.x > 0.1: cycle_next_vv[edge.src.id] = edge.tgt.id if pos == 1: cycle_start_vv.append(low_v_id) cycle_next_vv[low_v_id] = edge.src.id if hpief_2_prime and pos == cfg.max_cycle - 2 and edge.tgt.id != low_v_id: cycle_next_vv[edge.tgt.id] = low_v_id return OptSolution( ip_model=m, cycles=kidney_utils.selected_edges_to_cycles(cfg.digraph, cycle_start_vv, cycle_next_vv), chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds), digraph=cfg.digraph, )
def optimise_picef(cfg): """Optimise using the PICEF formulation. Args: cfg: an OptConfig object Returns: an OptSolution object """ cycles = cfg.digraph.find_cycles(cfg.max_cycle) m = create_ip_model(cfg.timelimit, cfg.verbose) m.params.method = 2 cycle_vars = [m.addVar(vtype=GRB.BINARY) for __ in cycles] m.update() vtx_to_vars = [[] for __ in cfg.digraph.vs] add_chain_vars_and_constraints( cfg.digraph, cfg.ndds, cfg.max_chain, m, vtx_to_vars, store_edge_positions=cfg.edge_success_prob != 1 ) 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) if cfg.max_chain == 0: obj_expr = quicksum( failure_aware_cycle_score(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_score(c, cfg.digraph) * var for c, var in zip(cycles, cycle_vars)) + quicksum(e.score * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum(e.score * var for e in cfg.digraph.es for var in e.grb_vars) ) else: obj_expr = ( quicksum( failure_aware_cycle_score(c, cfg.digraph, cfg.edge_success_prob) * var for c, var in zip(cycles, cycle_vars) ) + quicksum(e.score * cfg.edge_success_prob * e.edge_var for ndd in cfg.ndds for e in ndd.edges) + quicksum( e.score * 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) optimise(m, cfg) return OptSolution( ip_model=m, cycles=[c for c, v in zip(cycles, cycle_vars) if v.x > 0.5], chains=[] if cfg.max_chain == 0 else kidney_utils.get_optimal_chains(cfg.digraph, cfg.ndds, cfg.edge_success_prob), digraph=cfg.digraph, edge_success_prob=cfg.edge_success_prob, )