Ejemplo n.º 1
0
def eigen_drop(matrix, sol, lambd):
    """ Fitness function """
    matrix = np.array(matrix)
    matrix[sol, :] = 0
    matrix[:, sol] = 0

    eigval_pert = utils.get_max_eigenvalue(matrix)

    return lambd - eigval_pert
Ejemplo n.º 2
0
def netshield(A, k, al_out=None):
    """
    Perform the NetShield algorithm

    Parameters
    ----------
    A: 2D numpy array
        Adjancency matrix of graph to be immunised
    k: Integer
        Number of nodes to remove from graph
    al_out: Integer
        Optional, already removed nodes or nodes that should be ignored. Not intended for direct callers
    
        
    Returns
    --------
    Tuple of 1D numpy array and float
        First is indices of selected nodes
        Second is eigendrop
    """

    n = A.shape[0]

    shieldvalue = 0

    if (k < 1):
        return np.array([], dtype=np.int64), 0

    lambd, u = utils.get_max_eigen(A)

    # Compute individual Shield score of each node
    v = (2 * lambd - A.diagonal()) * np.square(u)

    n = A.shape[0]
    S = np.zeros(n, dtype=bool)

    # Add vertices greedily
    for _ in range(k):
        B = A[:, S]
        b = B.dot(u[S])
        score = v - (2 * b * u)
        score[S] = -1

        if al_out is not None:
            score[al_out] = -1

        i = np.argmax(score)
        S[i] = True
        shieldvalue += score[i]

    A_pert = np.array(A)
    A_pert[S, :] = 0
    A_pert[:, S] = 0

    eigdrop = lambd - utils.get_max_eigenvalue(A_pert)

    return np.where(S)[0], eigdrop
def _netshield_plus_mo_qp(adj, b, epsilon, context):
    """ Peforms actual NetShield algorithm """

    adj = np.array(adj)
    n = adj.shape[0]
    selected = np.zeros(n, dtype=bool)
    degrees = adj.sum(axis=0)
    m = context['model']
    m.setParam('OutputFlag', False)

    variables = context['variables']

    m.addConstr(context['degree_term'] <= epsilon)

    for i in range(1, int(np.ceil(n / b)) + 1):
        max_select = min(i * b, n)

        try:
            eigval, eigvec = context['computed_eigen'][selected.tobytes()]
        except KeyError:
            eigval, eigvec = utils.get_max_eigen(adj)
            eigvec[selected] = 0
            context['computed_eigen'][selected.tobytes()] = eigval, eigvec

        obj = context['obj_func'](adj, variables, eigval, eigvec)
        m.setObjective(obj, GRB.MAXIMIZE)

        add_variables = quicksum(variables)
        constr_add = m.addConstr(add_variables >= selected.sum() + 1)
        constr_max = m.addConstr(add_variables <= max_select)

        m.optimize()

        if m.Status == GRB.INFEASIBLE:
            break

        for i, v in enumerate(m.getVars()):
            if v.x == 1 and not selected[i]:
                selected[i] = True
                m.addConstr(variables[i] == 1)

        m.remove(constr_add)
        m.remove(constr_max)

        adj[selected, :] = 0
        adj[:, selected] = 0

    m.remove(m.getConstrs())
    if selected.sum() == 0:
        return np.where(selected)[0], 0, 0

    eigval_pert = utils.get_max_eigenvalue(adj)
    drop = context['eigenvalue_ori'] - eigval_pert
    cost = degrees[selected].sum()

    return np.where(selected)[0], drop, cost
def netshield(adj, k, al_out=None):
    """
    Perform the NetShield algorithm with QP solver

    Parameters
    ----------
    A: 2D numpy array
        Adjancency matrix of graph to be immunised
    k: Integer
        Number of nodes to remove from graph
    al_out: Integer
        Optional, already removed nodes or nodes that should be ignored. Not intended for direct callers


    Returns
    --------
    Tuple of 1D numpy array and float
        First is indices of selected nodes
        Second is eigendrop
    """

    eigval, eigvec = utils.get_max_eigen(adj)
    n = adj.shape[0]

    if k == 1:
        m = Model("lp")
    else:
        m = Model("qp")
    m.setParam('OutputFlag', False)

    variables = [
        m.addVar(name="x_{}".format(i), vtype=GRB.BINARY) for i in range(n)
    ]

    obj = _build_objective_qd(adj, variables, eigval, eigvec)

    const = quicksum(variables) == k

    m.setObjective(obj, GRB.MAXIMIZE)
    m.addConstr(const, "c1")

    if al_out is not None:
        for c in np.where(al_out)[0]:
            m.addConstr(variables[c] == 0)

    m.optimize()

    out = np.array([i for i, v in enumerate(m.getVars()) if v.x == 1])

    adj_pert = np.array(adj)
    adj_pert[out, :] = 0
    adj_pert[:, out] = 0

    eig_drop = eigval - utils.get_max_eigenvalue(adj_pert)

    return out, eig_drop
def netshield_mo(adj, e_delta):
    """
    Perform the NetShield multiobjective algorithm via the epsilon constraint method.
    Epsilon values used range from 0 to sum of all degrees of the vertices in the input graph

    Parameters
    ----------
    A: 2D numpy array
        Adjancency matrix of graph to be immunised.
    e_delta: Integer
        By how much to increase the epsilon value after each step.

    Returns
    --------
    List of dictionaries that form the approximated Pareto front. Dictionaries have the following keys:
        solution: 1D numpy array
            indices of selectec vertices
        evaluation: tuple of (float,int)
            eigendrop, cost.

    """

    eigval, eigvec = utils.get_max_eigen(adj)
    degrees = adj.sum(axis=0)
    max_cost = degrees.sum()
    n = adj.shape[0]

    e_delta = min(max(e_delta, 1), max_cost)

    m = Model("qp")
    m.setParam('OutputFlag', False)

    variables = [
        m.addVar(name="x_{}".format(i), vtype=GRB.BINARY) for i in range(n)
    ]

    obj = _build_objective_qd(adj, variables, eigval, eigvec)

    constr = LinExpr()
    constr.addTerms(degrees, variables)
    m.setObjective(obj, GRB.MAXIMIZE)

    solutions = [{'solution': np.array([]), 'evaluation': (0, 0)}]
    unique = set()

    for i in range(int(np.ceil(max_cost / e_delta)) + 1):
        epsilon = min(i * e_delta, max_cost)
        print(epsilon)
        epsilon_constr = m.addConstr(constr <= epsilon, "c1")

        m.optimize()
        out = np.array([i for i, v in enumerate(m.getVars()) if v.x == 1])

        if out.shape[0] > 0 and out.tobytes() not in unique:
            adj_pert = np.array(adj)
            adj_pert[out, :] = 0
            adj_pert[:, out] = 0

            eig_drop = eigval - utils.get_max_eigenvalue(adj_pert)
            cost = degrees[out].sum()

            solution = {'solution': out, 'evaluation': (eig_drop, cost)}

            solutions.append(solution)
            unique.add(out.tobytes())

        m.remove(epsilon_constr)

    return _get_non_dominated(solutions)
def netshield_plus_mo(adj, b, e_delta):
    """
    Perform the NetShield+ multiobjective algorithm via the epsilon constraint method.
    Epsilon values used range from 0 to sum of all degrees of the vertices in the input graph

    Parameters
    ----------
    A: 2D numpy array
        Adjancency matrix of graph to be immunised.
    b: Integer
        Batch size. How many nodes are removed in one step.
    e_delta: Integer
        By how much to increase the epsilon value after each step.

    Returns
    --------
    List of dictionaries that form the approximated Pareto front. Dictionaries have the following keys:
        solution: 1D numpy array
            indices of selectec vertices
        evaluation: tuple of (float,int)
            eigendrop, cost.

    """

    max_cost = adj.sum()
    solutions = []
    unique = set()
    n = adj.shape[0]
    degrees = adj.sum(axis=0)

    # Dict that contains needed that can be reused over all calls to netshield information
    # model: Gurobi QP model
    # obj_func: Gurobi objective function
    # variables: Gurobi variable objects
    # degree_term: Constraint of cost <= epsilon
    # computed_eigen: Contains all unique eigendecompositions for found solutions.
    #   Often the same (intermediate) solutions are found during the whole process
    #   This caches the result to avoid many same eigendecompositions that are computationally expensive.
    # eigenvalue_ori: Original eigenvalue of input graph

    context = {}

    if b == 1:
        context['model'] = Model('lp')
        context['obj_func'] = _build_objective_linear
    else:
        context['model'] = Model('qp')
        context['obj_func'] = _build_objective_qd

    context['variables'] = [
        context['model'].addVar(name="x_{}".format(i), vtype=GRB.BINARY)
        for i in range(n)
    ]

    context['degree_term'] = LinExpr()
    context['degree_term'].addTerms(degrees, context['variables'])
    context['computed_eigen'] = dict()
    context['eigenvalue_ori'] = utils.get_max_eigenvalue(adj)

    # Loop over all epsilon values
    for i in range(int(np.ceil(max_cost / e_delta)) + 1):
        epsilon = min(i * e_delta, max_cost)
        print('epsilon: ', epsilon)
        solution, drop, cost = _netshield_plus_mo_qp(adj, b, epsilon, context)

        if solution.tobytes() not in unique:
            solutions.append({
                'solution': solution,
                'evaluation': (drop, cost)
            })
            unique.add(solution.tobytes())

    return _get_non_dominated(solutions)