def init_matchable(self): """initialize the property matchable for each edge in the graph. matchable = True means the edge can be included in any matching (i.e., it's in at least one cycle or chain)""" # initialize all edges to be not-matchable for e in self.all_edge_list: e.matchable = False cycles = self.graph.find_cycles(self.cycle_cap) cycle_list = [] # all edges in a cycle are matchable for c in cycles: c_obj = Cycle(c) c_obj.add_edges(self.graph.es) cycle_list.append(c_obj) for e in c_obj.edges: e.matchable = True # all NDDEdges are matchable for n in self.altruists: for e in n.edges: e.matchable = True # all pair-pair edges that are within chain distance are matchable dists_from_ndd = kidney_utils.get_dist_from_nearest_ndd( self.graph, self.altruists ) for e in self.all_edge_list: if dists_from_ndd[e.src.id] <= self.chain_cap - 1: e.matchable = True if self.logger is not None: self.logger.info( f"matchable edges: {len([e for e in self.all_edge_list if e.matchable])} (out of {len(self.all_edge_list)} total)" )
def add_chain_vars_and_constraints(digraph, ndds, max_chain, m, vtx_to_vars, store_edge_positions=False): """Add the IP variables and constraints for chains in PICEF and HPIEF'. Args: ndds: a list of NDDs in the instance max_chain: the chain cap m: The Gurobi model vtx_to_vars: A list such that for each Vertex v in the Digraph, vtx_to_vars[v.id] will contain the Gurobi variables representing edges pointing to v. store_edge_positions: if this is True, then an attribute grb_edge_positions will be added to edges that have associated Gurobi variables. edge.grb_edge_positions[i] will indicate the position of the edge respresented by edge.grb_vars[i]. (default: False) """ if max_chain > 0: for v in digraph.vs: v.grb_vars_in = [[] for i in range(max_chain - 1)] v.grb_vars_out = [[] for i in range(max_chain - 1)] for ndd in ndds: ndd_edge_vars = [] for e in ndd.edges: edge_var = m.addVar(vtype=GRB.BINARY) e.edge_var = edge_var ndd_edge_vars.append(edge_var) vtx_to_vars[e.target_v.id].append(edge_var) if max_chain > 1: e.target_v.grb_vars_in[0].append(edge_var) m.update() m.addConstr(quicksum(ndd_edge_vars) <= 1) dists_from_ndd = kidney_utils.get_dist_from_nearest_ndd(digraph, ndds) # Add pair->pair edge variables, indexed by position in chain for e in digraph.es: e.grb_vars = [] if store_edge_positions: e.grb_var_positions = [] for i in range(max_chain - 1): if dists_from_ndd[e.src.id] <= i + 1: edge_var = m.addVar(vtype=GRB.BINARY) e.grb_vars.append(edge_var) if store_edge_positions: e.grb_var_positions.append(i + 1) vtx_to_vars[e.tgt.id].append(edge_var) e.src.grb_vars_out[i].append(edge_var) if i < max_chain - 2: e.tgt.grb_vars_in[i + 1].append(edge_var) m.update() # At each chain position, sum of edges into a vertex must be >= sum of edges out for i in range(max_chain - 1): for v in digraph.vs: m.addConstr(quicksum(v.grb_vars_in[i]) >= quicksum(v.grb_vars_out[i]))
def add_chain_vars_and_constraints(digraph, ndds, max_chain, m, vtx_to_vars, store_edge_positions=False): """Add the IP variables and constraints for chains in PICEF and HPIEF'. Args: ndds: a list of NDDs in the instance max_chain: the chain cap m: The Gurobi model vtx_to_vars: A list such that for each Vertex v in the Digraph, vtx_to_vars[v.id] will contain the Gurobi variables representing edges pointing to v. store_edge_positions: if this is True, then an attribute grb_edge_positions will be added to edges that have associated Gurobi variables. edge.grb_edge_positions[i] will indicate the position of the edge respresented by edge.grb_vars[i]. (default: False) """ if max_chain > 0: for v in digraph.vs: v.grb_vars_in = [[] for i in range(max_chain-1)] v.grb_vars_out = [[] for i in range(max_chain-1)] for ndd in ndds: ndd_edge_vars = [] for e in ndd.edges: edge_var = m.addVar(vtype=GRB.BINARY) e.edge_var = edge_var ndd_edge_vars.append(edge_var) vtx_to_vars[e.target_v.id].append(edge_var) if max_chain>1: e.target_v.grb_vars_in[0].append(edge_var) m.update() m.addConstr(quicksum(ndd_edge_vars) <= 1) dists_from_ndd = kidney_utils.get_dist_from_nearest_ndd(digraph, ndds) # Add pair->pair edge variables, indexed by position in chain for e in digraph.es: e.grb_vars = [] if store_edge_positions: e.grb_var_positions = [] for i in range(max_chain-1): if dists_from_ndd[e.src.id] <= i+1: edge_var = m.addVar(vtype=GRB.BINARY) e.grb_vars.append(edge_var) if store_edge_positions: e.grb_var_positions.append(i+1) vtx_to_vars[e.tgt.id].append(edge_var) e.src.grb_vars_out[i].append(edge_var) if i < max_chain-2: e.tgt.grb_vars_in[i+1].append(edge_var) m.update() # At each chain position, sum of edges into a vertex must be >= sum of edges out for i in range(max_chain-1): for v in digraph.vs: m.addConstr(quicksum(v.grb_vars_in[i]) >= quicksum(v.grb_vars_out[i]))
def add_chain_vars_and_constraints( digraph, ndds, use_chains, max_chain, m, vtx_to_vars, store_edge_positions=False, add_o_vars=False, num_o_vars=0, check_edge_success=False, ): """Add the IP variables and constraints for chains in PICEF and HPIEF'. Args: ndds: a list of NDDs in the instance use_chains: boolean: True if chains should be used max_chain: the chain cap m: The Gurobi model vtx_to_vars: A list such that for each Vertex v in the Digraph, vtx_to_vars[v.id] will contain the Gurobi variables representing edges pointing to v. store_edge_positions: if this is True, then an attribute grb_var_positions will be added to edges that have associated Gurobi variables. edge.grb_var_positions[i] will indicate the position of the edge respresented by edge.grb_vars[i]. (default: False) add_o_vars: (bool). specific to the heterogeneous edge success probability formulation. add o_vars and big_o_vars properties to each edge, and add big_o_vars_in and big_o_vars_out properties to each vertex (analagous to v.edges_in and v.edges_out. this requires that e.success_prob is set for each edge num_o_vars: (int). if set to >1, then create this number of o and big_o variables, for the DRO-SAA formulation. if this is set, each edge needs the property e.realizations - a binary vector of length num_o_vars """ if use_chains: # max_chain > 0: for v in digraph.vs: v.grb_vars_in = [[] for i in range(max_chain - 1)] v.grb_vars_out = [[] for i in range(max_chain - 1)] v.edges_in = [[] for i in range(max_chain - 1)] v.edges_out = [[] for i in range(max_chain - 1)] if add_o_vars: if num_o_vars < 2: v.big_o_vars_in = [[] for i in range(max_chain - 1)] v.big_o_vars_out = [[] for i in range(max_chain - 1)] else: # v.big_o_vars[i][n] is the n^th instance of big_o_vars for the i^th edge position... v.big_o_vars_in = [ [[] for j in range(num_o_vars)] for i in range(max_chain - 1) ] v.big_o_vars_out = [ [[] for j in range(num_o_vars)] for i in range(max_chain - 1) ] for ndd in ndds: ndd_edge_vars = [] for e in ndd.edges: edge_var = m.addVar(vtype=GRB.BINARY) if check_edge_success: if not e.success: m.addConstr(edge_var == 0) e.edge_var = edge_var ndd_edge_vars.append(edge_var) if add_o_vars: if num_o_vars < 2: e.o_var = m.addVar(lb=0, ub=(1 - e.success_prob)) e.big_o_var = m.addVar(lb=0, ub=(1 - e.success_prob)) m.addConstr(e.big_o_var <= e.edge_var) m.addConstr(e.big_o_var <= e.o_var) else: e.o_vars = m.addVars(num_o_vars, lb=0, ub=1) e.big_o_vars = m.addVars(num_o_vars, lb=0, ub=1) for n in range(num_o_vars): m.addConstr(e.big_o_vars[n] <= e.edge_var) m.addConstr(e.big_o_vars[n] <= e.o_vars[n]) m.addConstr(e.o_vars[n] <= e.realizations[n]) vtx_to_vars[e.tgt.id].append(edge_var) if max_chain > 1: e.tgt.grb_vars_in[0].append(edge_var) e.tgt.edges_in[0].append(e) if add_o_vars: if num_o_vars < 2: e.tgt.big_o_vars_in[0].append(e.big_o_var) else: for n in range(num_o_vars): e.tgt.big_o_vars_in[0][n].append(e.big_o_vars[n]) m.update() m.addConstr(quicksum(ndd_edge_vars) <= 1) dists_from_ndd = kidney_utils.get_dist_from_nearest_ndd(digraph, ndds) # Add pair->pair edge variables, indexed by position in chain # e.grb_var are the chain variables for each edge. for e in digraph.es: e.grb_vars = [] if add_o_vars: if num_o_vars < 2: e.o_vars = [] e.big_o_vars = [] else: # e.o_vars[n][i] is the o-var for the n^th edge realization e.o_vars = [] e.big_o_vars = [] if store_edge_positions: e.grb_var_positions = [] for i in range(max_chain - 1): if dists_from_ndd[e.src.id] <= i + 1: edge_var = m.addVar(vtype=GRB.BINARY) if check_edge_success: if not e.success: m.addConstr(edge_var == 0) e.grb_vars.append(edge_var) if add_o_vars: if num_o_vars < 2: o_var = m.addVar(lb=0, ub=(1 - e.success_prob)) big_o_var = m.addVar(lb=0, ub=(1 - e.success_prob)) e.o_vars.append(o_var) e.big_o_vars.append(big_o_var) m.addConstr(big_o_var <= edge_var) m.addConstr(big_o_var <= o_var) else: o_vars = m.addVars(num_o_vars, lb=0, ub=1) big_o_vars = m.addVars(num_o_vars, lb=0, ub=1) e.o_vars.append(o_vars) e.big_o_vars.append(big_o_vars) for n in range(num_o_vars): m.addConstr(big_o_vars[n] <= edge_var) m.addConstr(big_o_vars[n] <= o_vars[n]) m.addConstr(o_vars[n] <= e.realizations[n]) if store_edge_positions: e.grb_var_positions.append(i + 1) vtx_to_vars[e.tgt.id].append(edge_var) e.src.grb_vars_out[i].append(edge_var) e.src.edges_out[i].append(e) if add_o_vars: if num_o_vars < 2: e.src.big_o_vars_out[i].append(big_o_var) else: for n in range(num_o_vars): e.src.big_o_vars_out[i][n].append(big_o_vars[n]) if i < max_chain - 2: e.tgt.grb_vars_in[i + 1].append(edge_var) e.tgt.edges_in[i + 1].append(e) if add_o_vars: if num_o_vars < 2: e.tgt.big_o_vars_in[i + 1].append(big_o_var) else: for n in range(num_o_vars): e.tgt.big_o_vars_in[i + 1][n].append(big_o_vars[n]) # m.update() # At each chain position, sum of edges into a vertex must be >= sum of edges out for i in range(max_chain - 1): for v in digraph.vs: m.addConstr(quicksum(v.grb_vars_in[i]) >= quicksum(v.grb_vars_out[i])) if add_o_vars: if num_o_vars < 2: assert len(v.edges_out[i]) == len(v.big_o_vars_out[i]) m.addConstr( quicksum(v.big_o_vars_in[i]) >= quicksum( (1.0 / e.success_prob) * var for e, var in zip(v.edges_out[i], v.big_o_vars_out[i]) ) ) else: for n in range(num_o_vars): m.addConstr( quicksum(v.big_o_vars_in[i][n]) >= quicksum(v.big_o_vars_out[i][n]) ) m.update()