InfluenceDiffusion 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- InfluenceDiffusion/Graph.py +478 -0
- InfluenceDiffusion/Trace.py +431 -0
- InfluenceDiffusion/__init__.py +4 -0
- InfluenceDiffusion/estimation_models/BaseWeightEstimator.py +436 -0
- InfluenceDiffusion/estimation_models/EMEstimation.py +236 -0
- InfluenceDiffusion/estimation_models/OptimEstimation.py +291 -0
- InfluenceDiffusion/estimation_models/__init__.py +3 -0
- InfluenceDiffusion/influence_models.py +614 -0
- InfluenceDiffusion/utils.py +29 -0
- InfluenceDiffusion-0.0.3.dist-info/METADATA +21 -0
- InfluenceDiffusion-0.0.3.dist-info/RECORD +13 -0
- InfluenceDiffusion-0.0.3.dist-info/WHEEL +5 -0
- InfluenceDiffusion-0.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from utils import multiple_union
|
|
3
|
+
from Graph import Graph
|
|
4
|
+
from typing import Union, Set, List, Tuple, Dict
|
|
5
|
+
import numpy as np
|
|
6
|
+
from itertools import chain
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PseudoTraces(dict):
|
|
10
|
+
"""A collection of pseudo traces.
|
|
11
|
+
|
|
12
|
+
Attributes
|
|
13
|
+
----------
|
|
14
|
+
pseudo_traces : Dict[int, List[Tuple[Set, Set]]]
|
|
15
|
+
A dictionary mapping trace indices to lists of pseudo traces.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, pseudo_traces: Dict[int, List[Tuple[Set, Set]]]) -> None:
|
|
19
|
+
for trace in chain.from_iterable(pseudo_traces.values()):
|
|
20
|
+
assert len(trace) == 2, "Each pseudo trace should have length 2"
|
|
21
|
+
assert isinstance(trace[0], set) and isinstance(trace[1], set), "Both elements of trace must be sets"
|
|
22
|
+
super().__init__(pseudo_traces)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Traces(list):
|
|
26
|
+
"""A list of traces associated with a graph.
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
graph : Graph
|
|
31
|
+
The graph associated with the traces.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __new__(cls, graph: Graph, traces: List[Union[Trace, Tuple[Set]]]) -> Traces:
|
|
35
|
+
return super(Traces, cls).__new__(cls, traces)
|
|
36
|
+
|
|
37
|
+
def __init__(self, graph: Graph, traces: List[Union[Trace, Tuple[Set]]]) -> None:
|
|
38
|
+
traces = [trace if isinstance(trace, Trace) else Trace(graph, trace) for trace in traces]
|
|
39
|
+
super().__init__(traces)
|
|
40
|
+
self.graph = graph
|
|
41
|
+
|
|
42
|
+
def append(self, obj: Union[Trace, Tuple[Set]]) -> None:
|
|
43
|
+
"""Append a new trace or tuple of sets to the list of traces.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
obj : Union[Trace, Tuple[Set]]
|
|
48
|
+
The trace or tuple of sets to append.
|
|
49
|
+
"""
|
|
50
|
+
if isinstance(obj, Trace):
|
|
51
|
+
super().append(obj)
|
|
52
|
+
else:
|
|
53
|
+
assert isinstance(obj, tuple) and all(isinstance(elem, set) for elem in obj)
|
|
54
|
+
super().append(Trace(self.graph, obj))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Trace(tuple):
|
|
58
|
+
"""A trace consisting of sets of activated vertices over time.
|
|
59
|
+
|
|
60
|
+
Attributes
|
|
61
|
+
----------
|
|
62
|
+
graph : Graph
|
|
63
|
+
The graph associated with the trace.
|
|
64
|
+
check_feasibility : bool
|
|
65
|
+
Whether to check the feasibility of the trace during initialization.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __new__(cls, graph: Graph, trace: Tuple[Set], check_feasibility: bool = True) -> Trace:
|
|
69
|
+
return super(Trace, cls).__new__(cls, trace)
|
|
70
|
+
|
|
71
|
+
def __init__(self, graph: Graph, trace: Tuple[Set], check_feasibility: bool = True) -> None:
|
|
72
|
+
self.graph = graph
|
|
73
|
+
self.check_feasibility = check_feasibility
|
|
74
|
+
|
|
75
|
+
if check_feasibility:
|
|
76
|
+
self.make_feasibility_check()
|
|
77
|
+
|
|
78
|
+
self.__cumulative_trace: Tuple[Set] = None
|
|
79
|
+
self.__failed_vertices: Set = None
|
|
80
|
+
self.__activated_vertices: Set = None
|
|
81
|
+
self.__activation_time_dict: Dict[int, int] = None
|
|
82
|
+
|
|
83
|
+
def make_feasibility_check(self) -> None:
|
|
84
|
+
"""Check if each vertex has at least one active parent at each time step."""
|
|
85
|
+
for t, (set_t, set_tp1) in enumerate(zip(self[:-1], self[1:])):
|
|
86
|
+
for vertex in set_tp1:
|
|
87
|
+
parents = self.graph.get_parents(vertex)
|
|
88
|
+
assert len(set_t.intersection(parents)) != 0, \
|
|
89
|
+
f"Vertex {vertex} has no parents in newly active set at time {t}"
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def length(self) -> int:
|
|
93
|
+
"""Return the number of time steps in the trace."""
|
|
94
|
+
return len(self) - 1
|
|
95
|
+
|
|
96
|
+
def cum_union(self) -> Tuple[Set]:
|
|
97
|
+
"""Calculate the cumulative union of activated vertices over time.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
Tuple[Set]
|
|
102
|
+
A tuple containing sets of activated vertices at each time step.
|
|
103
|
+
"""
|
|
104
|
+
if self.__cumulative_trace is None:
|
|
105
|
+
cumulative_trace = [self[0]]
|
|
106
|
+
for new_activated_vertices in self[1:]:
|
|
107
|
+
cur_activated_vertices = cumulative_trace[-1].union(new_activated_vertices)
|
|
108
|
+
cumulative_trace.append(cur_activated_vertices)
|
|
109
|
+
self.__cumulative_trace = tuple(cumulative_trace)
|
|
110
|
+
return self.__cumulative_trace
|
|
111
|
+
|
|
112
|
+
def get_num_activated_vertices(self) -> int:
|
|
113
|
+
"""Get the total number of activated vertices.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
int
|
|
118
|
+
The number of activated vertices.
|
|
119
|
+
"""
|
|
120
|
+
return len(self.get_all_activated_vertices())
|
|
121
|
+
|
|
122
|
+
def get_all_activated_vertices(self) -> Set:
|
|
123
|
+
"""Get all activated vertices throughout the trace.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
Set
|
|
128
|
+
A set of all activated vertices.
|
|
129
|
+
"""
|
|
130
|
+
if self.__activated_vertices is None:
|
|
131
|
+
if self.__cumulative_trace is not None:
|
|
132
|
+
self.__activated_vertices = self.__cumulative_trace[-1]
|
|
133
|
+
else:
|
|
134
|
+
self.__activated_vertices = multiple_union(self)
|
|
135
|
+
return self.__activated_vertices
|
|
136
|
+
|
|
137
|
+
def get_all_activated_vertices_no_seed(self) -> Set:
|
|
138
|
+
"""Get all activated vertices excluding the seed vertices.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
Set
|
|
143
|
+
A set of all activated vertices except the initial seed.
|
|
144
|
+
"""
|
|
145
|
+
return self.get_all_activated_vertices().difference(self[0])
|
|
146
|
+
|
|
147
|
+
def get_all_failed_vertices(self) -> Set:
|
|
148
|
+
"""Get all failed vertices that were activated.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
Set
|
|
153
|
+
A set of failed vertices.
|
|
154
|
+
"""
|
|
155
|
+
if self.__failed_vertices is None:
|
|
156
|
+
failed_vertices = set()
|
|
157
|
+
all_activated_vertices = self.get_all_activated_vertices()
|
|
158
|
+
for vertex in all_activated_vertices:
|
|
159
|
+
failed_vertices.update(self.graph.get_children(vertex))
|
|
160
|
+
self.__failed_vertices = failed_vertices.difference(all_activated_vertices)
|
|
161
|
+
return self.__failed_vertices
|
|
162
|
+
|
|
163
|
+
def get_all_failed_and_activated_vertices(self) -> Set:
|
|
164
|
+
"""Get all vertices that are either failed or activated.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
Set
|
|
169
|
+
A set of failed and activated vertices.
|
|
170
|
+
"""
|
|
171
|
+
return self.get_all_failed_vertices().union(self.get_all_activated_vertices())
|
|
172
|
+
|
|
173
|
+
def get_all_failed_and_activated_vertices_no_seed(self) -> Set:
|
|
174
|
+
"""Get all vertices that are failed or activated, excluding the seed.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
Set
|
|
179
|
+
A set of failed and activated vertices except the initial seed.
|
|
180
|
+
"""
|
|
181
|
+
return self.get_all_failed_vertices().union(self.get_all_activated_vertices_no_seed())
|
|
182
|
+
|
|
183
|
+
def get_active_parents_at_time(self, vertex: int, time: int) -> Set:
|
|
184
|
+
"""Get all active parents of a vertex after a certain time.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
vertex : int
|
|
189
|
+
The vertex to check.
|
|
190
|
+
time : int
|
|
191
|
+
The time step to check.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
Set
|
|
196
|
+
A set of active parents at the specified time.
|
|
197
|
+
"""
|
|
198
|
+
assert int(time) == time and time >= -1, "time should be an integer >= -1"
|
|
199
|
+
if time == -1:
|
|
200
|
+
return set()
|
|
201
|
+
return self.cum_union()[time].intersection(self.graph.get_parents(vertex))
|
|
202
|
+
|
|
203
|
+
def get_active_parents_mask_at_time(self, vertex: int, time: int) -> np.ndarray:
|
|
204
|
+
"""Get a mask of active parents for a vertex at a certain time.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
vertex : int
|
|
209
|
+
The vertex to check.
|
|
210
|
+
time : int
|
|
211
|
+
The time step to check.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
np.ndarray
|
|
216
|
+
A boolean mask of active parents.
|
|
217
|
+
"""
|
|
218
|
+
active_parents_at_time = self.get_active_parents_at_time(vertex, time)
|
|
219
|
+
return self.graph.get_edges_mask_from_set_to_vertex(active_parents_at_time, vertex)
|
|
220
|
+
|
|
221
|
+
def get_active_parents_edge_indices_at_time(self, vertex: int, time: int) -> np.ndarray:
|
|
222
|
+
"""Get the edge indices of active parents for a vertex at a certain time.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
vertex : int
|
|
227
|
+
The vertex to check.
|
|
228
|
+
time : int
|
|
229
|
+
The time step to check.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
np.ndarray
|
|
234
|
+
The indices of edges connected to active parents.
|
|
235
|
+
"""
|
|
236
|
+
return np.arange(self.graph.count_edges())[self.get_active_parents_mask_at_time(vertex, time)]
|
|
237
|
+
|
|
238
|
+
def get_newly_active_parents_at_time(self, vertex: int, time: int) -> Set:
|
|
239
|
+
"""Get newly activated parents of a vertex at a certain time.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
vertex : int
|
|
244
|
+
The vertex to check.
|
|
245
|
+
time : int
|
|
246
|
+
The time step to check.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
Set
|
|
251
|
+
A set of newly activated parents at the specified time.
|
|
252
|
+
"""
|
|
253
|
+
assert int(time) == time and time >= -1, "time should be an integer >= -1"
|
|
254
|
+
if time == -1:
|
|
255
|
+
return set()
|
|
256
|
+
return self[time].intersection(self.graph.get_parents(vertex))
|
|
257
|
+
|
|
258
|
+
def get_newly_active_parents_mask_at_time(self, vertex: int, time: int) -> np.ndarray:
|
|
259
|
+
"""Get a mask of newly active parents for a vertex at a certain time.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
vertex : int
|
|
264
|
+
The vertex to check.
|
|
265
|
+
time : int
|
|
266
|
+
The time step to check.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
np.ndarray
|
|
271
|
+
A boolean mask of newly active parents.
|
|
272
|
+
"""
|
|
273
|
+
newly_active_parents_at_time = self.get_newly_active_parents_at_time(vertex, time)
|
|
274
|
+
return self.graph.get_edges_mask_from_set_to_vertex(newly_active_parents_at_time, vertex)
|
|
275
|
+
|
|
276
|
+
def get_newly_active_parents_edge_indices_at_time(self, vertex: int, time: int) -> np.ndarray:
|
|
277
|
+
"""Get the edge indices of newly active parents for a vertex at a certain time.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
vertex : int
|
|
282
|
+
The vertex to check.
|
|
283
|
+
time : int
|
|
284
|
+
The time step to check.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
np.ndarray
|
|
289
|
+
The indices of edges connected to newly active parents.
|
|
290
|
+
"""
|
|
291
|
+
return np.arange(self.graph.count_edges())[self.get_newly_active_parents_mask_at_time(vertex, time)]
|
|
292
|
+
|
|
293
|
+
def get_vertex_activation_time(self, vertex: int) -> int:
|
|
294
|
+
"""Get the time step at which a vertex was activated.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
vertex : int
|
|
299
|
+
The vertex to check.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
int
|
|
304
|
+
The activation time of the vertex.
|
|
305
|
+
"""
|
|
306
|
+
assert vertex in self.get_all_activated_vertices(), "vertex not activated"
|
|
307
|
+
return self.get_activation_time_dict()[vertex]
|
|
308
|
+
|
|
309
|
+
def get_activation_time_dict(self) -> Dict[int, int]:
|
|
310
|
+
"""Get a dictionary mapping each activated vertex to its activation time.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
Dict[int, int]
|
|
315
|
+
A dictionary of vertices and their activation times.
|
|
316
|
+
"""
|
|
317
|
+
if self.__activation_time_dict is None:
|
|
318
|
+
time_array = np.empty((0, 2), dtype=int)
|
|
319
|
+
for time, node_set in enumerate(self):
|
|
320
|
+
new_nodes_time_array = np.vstack([list(node_set), time * np.ones(len(node_set), dtype=int)]).T
|
|
321
|
+
time_array = np.append(time_array, new_nodes_time_array, axis=0)
|
|
322
|
+
self.__activation_time_dict = dict(time_array)
|
|
323
|
+
return self.__activation_time_dict
|
|
324
|
+
|
|
325
|
+
def is_edge_between_subseq_active_nodes(self, edge: Tuple[int, int]) -> bool:
|
|
326
|
+
"""Check if an edge connects two subsequent active nodes.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
edge : Tuple[int, int]
|
|
331
|
+
The edge defined by a source and a sink vertex.
|
|
332
|
+
|
|
333
|
+
Returns
|
|
334
|
+
-------
|
|
335
|
+
bool
|
|
336
|
+
True if the edge connects active nodes at subsequent times, False otherwise.
|
|
337
|
+
"""
|
|
338
|
+
assert self.graph.is_edge_in_graph(edge), "no edge in graph"
|
|
339
|
+
if not set(edge).issubset(self.get_all_activated_vertices()):
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
activation_time_dict = self.get_activation_time_dict()
|
|
343
|
+
source, sink = edge
|
|
344
|
+
source_time, sink_time = activation_time_dict[source], activation_time_dict[sink]
|
|
345
|
+
return sink_time - source_time == 1
|
|
346
|
+
|
|
347
|
+
def is_edge_from_unseen_to_failed_node(self, edge: Tuple[int, int]) -> bool:
|
|
348
|
+
"""Check if an edge goes from an unseen node to a failed node.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
edge : Tuple[int, int]
|
|
353
|
+
The edge defined by a source and a sink vertex.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
bool
|
|
358
|
+
True if the edge goes from an unseen node to a failed node, False otherwise.
|
|
359
|
+
"""
|
|
360
|
+
assert self.graph.is_edge_in_graph(edge), "no edge in graph"
|
|
361
|
+
source, sink = edge
|
|
362
|
+
return sink in self.get_all_failed_vertices() and source not in self.get_all_activated_vertices()
|
|
363
|
+
|
|
364
|
+
def get_attempted_edges_mask(self) -> np.ndarray:
|
|
365
|
+
"""Get a mask indicating which edges were attempted for activation.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
np.ndarray
|
|
370
|
+
A boolean mask for attempted edges.
|
|
371
|
+
"""
|
|
372
|
+
attempted_edges = self.get_edges_with_activation_attempt()
|
|
373
|
+
return np.array([(edge in attempted_edges) for edge in self.graph.get_edges()], dtype=bool)
|
|
374
|
+
|
|
375
|
+
def was_attempt_through_edge(self, edge: Tuple[int, int]) -> bool:
|
|
376
|
+
"""Check if activation was attempted through a given edge.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
edge : Tuple[int, int]
|
|
381
|
+
The edge defined by a source and a sink vertex.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
bool
|
|
386
|
+
True if activation was attempted through the edge, False otherwise.
|
|
387
|
+
"""
|
|
388
|
+
assert self.graph.is_edge_in_graph(edge)
|
|
389
|
+
activation_time_dict = self.get_activation_time_dict()
|
|
390
|
+
source, sink = edge
|
|
391
|
+
if source not in activation_time_dict:
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
source_time = activation_time_dict[source]
|
|
395
|
+
return (source in self.get_active_parents_at_time(sink, source_time) and
|
|
396
|
+
sink not in self[source_time])
|
|
397
|
+
|
|
398
|
+
def get_edges_with_activation_attempt(self) -> Set[Tuple[int, int]]:
|
|
399
|
+
"""Get edges where activation attempts occurred.
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
Set[Tuple[int, int]]
|
|
404
|
+
A set of edges where activation attempts were made.
|
|
405
|
+
"""
|
|
406
|
+
attempt_edges_mask = np.array([self.was_attempt_through_edge(edge) for edge in self.graph.edge_array])
|
|
407
|
+
return self.graph.edge_array[attempt_edges_mask]
|
|
408
|
+
|
|
409
|
+
def get_edges_between_subseq_active_nodes(self) -> Set[Tuple[int, int]]:
|
|
410
|
+
"""Get edges that connect subsequent active nodes.
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
Set[Tuple[int, int]]
|
|
415
|
+
A set of edges between subsequent active nodes.
|
|
416
|
+
"""
|
|
417
|
+
active_sources_mask = np.isin(self.graph.edge_array[:, 0], list(self.get_all_activated_vertices()))
|
|
418
|
+
return {tuple(edge) for edge in self.graph.edge_array[active_sources_mask]
|
|
419
|
+
if self.is_edge_between_subseq_active_nodes(edge)}
|
|
420
|
+
|
|
421
|
+
def get_edges_from_unseen_to_failed_nodes(self) -> Set[Tuple[int, int]]:
|
|
422
|
+
"""Get edges that go from unseen nodes to failed nodes.
|
|
423
|
+
|
|
424
|
+
Returns
|
|
425
|
+
-------
|
|
426
|
+
Set[Tuple[int, int]]
|
|
427
|
+
A set of edges from unseen to failed nodes.
|
|
428
|
+
"""
|
|
429
|
+
failed_sink_mask = np.isin(self.graph.edge_array[:, 1], list(self.get_all_failed_vertices()))
|
|
430
|
+
return {tuple(edge) for edge in self.graph.edge_array[failed_sink_mask]
|
|
431
|
+
if self.is_edge_from_unseen_to_failed_node(edge)}
|