Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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,
    )
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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,
    )
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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,
    )
Ejemplo n.º 17
0
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,
    )