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.
@@ -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)}
@@ -0,0 +1,4 @@
1
+ from Graph import *
2
+ from influence_models import *
3
+ from Trace import *
4
+ from utils import *