-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathattacker.py
129 lines (100 loc) · 3.64 KB
/
attacker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Network attacks following various strategies."""
import random
class NetworkAttacker:
def __init__(self, network, n, metric=None):
"""Initialize a network attacker.
Parameters
----------
network : networkx.Graph (or similar)
Network to be attacked.
n : int
Number of nodes to be removed per step.
metric : function, optional
Node metric to measure which node to target
in certain attacks (nodes with higher values
for the metric are better targets). Defaults
to a degree-based metric.
"""
self._network = network.copy()
self._n = n
if metric is None:
self._metric = type(self._network).degree
else:
self._metric = metric
def _random_node(self, nodes=None):
"""Choose a node at random.
Parameters
----------
nodes : iterable, optional
Nodes to choose from. If not provided, the entire
network is assumed.
Returns
-------
Random node from the graph.
"""
return random.choice(list(
nodes if nodes is not None else self._network.nodes()))
def _best_node(self, nodes=None):
"""Choose the node with the highest value for the metric.
Parameters
----------
nodes : iterable, optional
Nodes to choose from. If not provided, the entire
network is assumed.
Returns
-------
Node with the maximum value of the metric.
"""
ranks = self._metric(self._network, nodes)
node, _ = max(ranks, key=lambda item: item[1])
return node
def _attack(self, choose_target, n=None):
if n is None:
n = self._n
for _ in range(n):
self._network.remove_node(choose_target())
def _path_chooser(self, neighbor_discriminator):
target = self._random_node()
neighbors = None
def choose_target():
nonlocal target
nonlocal neighbors
if neighbors is not None:
if not neighbors:
target = self._random_node()
else:
target = neighbor_discriminator(neighbors)
neighbors = list(self._network.neighbors(target))
return target
return choose_target
def random(self, n=None):
"""Attack nodes randomly."""
self._attack(self._random_node, n)
def targeted(self, n=None):
"""Attack nodes as ordered by the metric."""
self._attack(self._best_node, n)
def random_path(self, n=None):
"""Attack nodes following a random path.
The first node is chosen at random. Afterwards, a random
neighbor is attacked. If the previously attacked node has no
neighbors, the procedure is restarted.
"""
self._attack(self._path_chooser(self._random_node), n)
def targeted_path(self, n=None):
"""Attack nodes using a "mixed" approach.
The first node is chosen at random. Afterwards, the "fittest"
(as gauged by the metric) neighbor is attacked. If the
previously attacked node has no neighbors, the procedure is
restarted.
"""
self._attack(self._path_chooser(self._best_node), n)
@property
def n(self):
"""int: Number of nodes that are attacked at each step."""
return self._n
@property
def network(self):
"""networkx.Graph (or similar): Network being attacked."""
return self._network