InfluenceDiffusion 0.0.18__tar.gz → 0.0.19__tar.gz
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-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Graph.py +22 -26
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Inference.py +2 -2
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Trace.py +5 -5
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/BaseWeightEstimator.py +65 -63
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/EMEstimation.py +13 -13
- influencediffusion-0.0.19/InfluenceDiffusion/estimation_models/OptimEstimation.py +563 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/__init__.py +1 -1
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/influence_models.py +29 -23
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/PKG-INFO +4 -1
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/SOURCES.txt +0 -1
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/requires.txt +3 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/PKG-INFO +4 -1
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/setup.py +5 -2
- influencediffusion-0.0.18/InfluenceDiffusion/estimation_models/CDFEstimation.py +0 -177
- influencediffusion-0.0.18/InfluenceDiffusion/estimation_models/OptimEstimation.py +0 -397
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/__init__.py +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/plot_utils.py +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/utils.py +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/weight_samplers.py +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/dependency_links.txt +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/top_level.txt +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/LICENSE +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/README.md +0 -0
- {influencediffusion-0.0.18 → influencediffusion-0.0.19}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from typing import List, Tuple, Union, Dict
|
|
2
|
+
from typing import List, Tuple, Union, Dict, Optional
|
|
3
3
|
import networkx as nx
|
|
4
4
|
|
|
5
5
|
__all__ = ["Graph"]
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Graph(nx.DiGraph):
|
|
9
|
-
def __init__(self, incoming_graph_data=None, weights: Union[List, np.
|
|
9
|
+
def __init__(self, incoming_graph_data=None, weights: Optional[Union[List, np.ndarray]] = None, **attr):
|
|
10
10
|
super().__init__(incoming_graph_data, **attr)
|
|
11
11
|
self.edge_array = np.array(self.edges)
|
|
12
12
|
if weights is None:
|
|
@@ -34,7 +34,7 @@ class Graph(nx.DiGraph):
|
|
|
34
34
|
def _get_edge_weight_attributes(self):
|
|
35
35
|
return np.array([edge[2] for edge in self.edges.data("weight", default=1)])
|
|
36
36
|
|
|
37
|
-
def set_weights(self, weights: Union[List, np.
|
|
37
|
+
def set_weights(self, weights: Union[List, np.ndarray]) -> "Graph":
|
|
38
38
|
"""
|
|
39
39
|
Set weights for the edges in the graph.
|
|
40
40
|
|
|
@@ -58,7 +58,7 @@ class Graph(nx.DiGraph):
|
|
|
58
58
|
self.weights = self._get_edge_weight_attributes()
|
|
59
59
|
return self
|
|
60
60
|
|
|
61
|
-
def get_edges(self, as_array: bool = False) -> Union[np.
|
|
61
|
+
def get_edges(self, as_array: bool = False) -> Union[np.ndarray, List[Tuple[int, int]]]:
|
|
62
62
|
"""
|
|
63
63
|
Retrieve the edges of the graph.
|
|
64
64
|
|
|
@@ -131,7 +131,7 @@ class Graph(nx.DiGraph):
|
|
|
131
131
|
"""
|
|
132
132
|
return len(self.nodes)
|
|
133
133
|
|
|
134
|
-
def get_children_mask(self, vertex: int) -> np.
|
|
134
|
+
def get_children_mask(self, vertex: int) -> np.ndarray:
|
|
135
135
|
"""
|
|
136
136
|
Create a mask for edges originating from a specified vertex.
|
|
137
137
|
|
|
@@ -147,7 +147,7 @@ class Graph(nx.DiGraph):
|
|
|
147
147
|
"""
|
|
148
148
|
return self.edge_array[:, 0] == vertex
|
|
149
149
|
|
|
150
|
-
def get_children(self, vertex: int
|
|
150
|
+
def get_children(self, vertex: int) -> np.ndarray:
|
|
151
151
|
"""
|
|
152
152
|
Get the children (outgoing edges) of a specified vertex.
|
|
153
153
|
|
|
@@ -155,17 +155,15 @@ class Graph(nx.DiGraph):
|
|
|
155
155
|
----------
|
|
156
156
|
vertex : int
|
|
157
157
|
The vertex for which to find children.
|
|
158
|
-
out_type : Union[set, list, np.array], optional
|
|
159
|
-
The type of output to return (default is set).
|
|
160
158
|
|
|
161
159
|
Returns
|
|
162
160
|
-------
|
|
163
161
|
Union[set, list, np.array]
|
|
164
162
|
The children of the vertex in the specified output type.
|
|
165
163
|
"""
|
|
166
|
-
return
|
|
164
|
+
return self.edge_array[:, 1][self.get_children_mask(vertex)]
|
|
167
165
|
|
|
168
|
-
def get_parents_mask(self, vertex: int) -> np.
|
|
166
|
+
def get_parents_mask(self, vertex: int) -> np.ndarray:
|
|
169
167
|
"""
|
|
170
168
|
Create a mask for edges leading to a specified vertex.
|
|
171
169
|
|
|
@@ -181,7 +179,7 @@ class Graph(nx.DiGraph):
|
|
|
181
179
|
"""
|
|
182
180
|
return self.edge_array[:, 1] == vertex
|
|
183
181
|
|
|
184
|
-
def get_parents(self, vertex: int
|
|
182
|
+
def get_parents(self, vertex: int) -> np.ndarray:
|
|
185
183
|
"""
|
|
186
184
|
Get the parents (incoming edges) of a specified vertex.
|
|
187
185
|
|
|
@@ -189,15 +187,13 @@ class Graph(nx.DiGraph):
|
|
|
189
187
|
----------
|
|
190
188
|
vertex : int
|
|
191
189
|
The vertex for which to find parents.
|
|
192
|
-
out_type : Union[set, list, np.array], optional
|
|
193
|
-
The type of output to return (default is set).
|
|
194
190
|
|
|
195
191
|
Returns
|
|
196
192
|
-------
|
|
197
193
|
Union[set, list, np.array]
|
|
198
194
|
The parents of the vertex in the specified output type.
|
|
199
195
|
"""
|
|
200
|
-
return
|
|
196
|
+
return self.edge_array[:, 0][self.get_parents_mask(vertex)]
|
|
201
197
|
|
|
202
198
|
def get_vertex_2_indegree_dict(self, weighted: bool = False) -> Dict[int, Union[float, int]]:
|
|
203
199
|
"""
|
|
@@ -215,7 +211,7 @@ class Graph(nx.DiGraph):
|
|
|
215
211
|
"""
|
|
216
212
|
return {vertex: self.get_indegree(vertex, weighted=weighted) for vertex in self.get_vertices()}
|
|
217
213
|
|
|
218
|
-
def get_outdegree(self, vertex: int, weighted: bool = False) ->
|
|
214
|
+
def get_outdegree(self, vertex: int, weighted: bool = False) -> float:
|
|
219
215
|
"""
|
|
220
216
|
Get the outdegree of a specified vertex.
|
|
221
217
|
|
|
@@ -228,13 +224,13 @@ class Graph(nx.DiGraph):
|
|
|
228
224
|
|
|
229
225
|
Returns
|
|
230
226
|
-------
|
|
231
|
-
|
|
227
|
+
float
|
|
232
228
|
The outdegree of the vertex.
|
|
233
229
|
"""
|
|
234
230
|
children_weights = self.weights[self.edge_array[:, 0] == vertex]
|
|
235
|
-
return np.sum(children_weights) if weighted else len(children_weights)
|
|
231
|
+
return np.sum(children_weights, dtype=float) if weighted else len(children_weights)
|
|
236
232
|
|
|
237
|
-
def get_all_outdegrees(self, weighted: bool = False) -> np.
|
|
233
|
+
def get_all_outdegrees(self, weighted: bool = False) -> np.ndarray:
|
|
238
234
|
"""
|
|
239
235
|
Get outdegrees for all vertices in the graph.
|
|
240
236
|
|
|
@@ -250,7 +246,7 @@ class Graph(nx.DiGraph):
|
|
|
250
246
|
"""
|
|
251
247
|
return np.array([self.get_outdegree(v, weighted=weighted) for v in self.get_vertices()])
|
|
252
248
|
|
|
253
|
-
def get_avg_outdegree(self, weighted: bool = False) ->
|
|
249
|
+
def get_avg_outdegree(self, weighted: bool = False) -> float:
|
|
254
250
|
"""
|
|
255
251
|
Calculate the average outdegree of all vertices.
|
|
256
252
|
|
|
@@ -261,10 +257,10 @@ class Graph(nx.DiGraph):
|
|
|
261
257
|
|
|
262
258
|
Returns
|
|
263
259
|
-------
|
|
264
|
-
|
|
260
|
+
float
|
|
265
261
|
The average outdegree of the vertices.
|
|
266
262
|
"""
|
|
267
|
-
return np.mean(self.get_all_outdegrees(weighted=weighted))
|
|
263
|
+
return np.mean(self.get_all_outdegrees(weighted=weighted), dtype=float)
|
|
268
264
|
|
|
269
265
|
def get_max_outdegree(self, weighted: bool = False) -> Union[float, int]:
|
|
270
266
|
"""
|
|
@@ -282,7 +278,7 @@ class Graph(nx.DiGraph):
|
|
|
282
278
|
"""
|
|
283
279
|
return np.max(self.get_all_outdegrees(weighted))
|
|
284
280
|
|
|
285
|
-
def get_indegree(self, vertex: int, weighted: bool = False) ->
|
|
281
|
+
def get_indegree(self, vertex: int, weighted: bool = False) -> float:
|
|
286
282
|
"""
|
|
287
283
|
Get the indegree of a specified vertex.
|
|
288
284
|
|
|
@@ -299,7 +295,7 @@ class Graph(nx.DiGraph):
|
|
|
299
295
|
The indegree of the vertex.
|
|
300
296
|
"""
|
|
301
297
|
parent_weights = self.weights[self.edge_array[:, 1] == vertex]
|
|
302
|
-
return np.sum(parent_weights) if weighted else len(parent_weights)
|
|
298
|
+
return np.sum(parent_weights, dtype=float) if weighted else len(parent_weights)
|
|
303
299
|
|
|
304
300
|
def get_indegrees_dict(self, weighted: bool = False) -> Dict[int, Union[int, float]]:
|
|
305
301
|
"""
|
|
@@ -317,7 +313,7 @@ class Graph(nx.DiGraph):
|
|
|
317
313
|
"""
|
|
318
314
|
return {v: self.get_indegree(v, weighted=weighted) for v in self.get_vertices()}
|
|
319
315
|
|
|
320
|
-
def get_all_indegrees(self, weighted: bool = False) -> np.
|
|
316
|
+
def get_all_indegrees(self, weighted: bool = False) -> np.ndarray:
|
|
321
317
|
"""
|
|
322
318
|
Get indegrees for all vertices in the graph.
|
|
323
319
|
|
|
@@ -364,9 +360,9 @@ class Graph(nx.DiGraph):
|
|
|
364
360
|
float
|
|
365
361
|
The average indegree of the vertices.
|
|
366
362
|
"""
|
|
367
|
-
return np.mean(self.get_all_indegrees(weighted))
|
|
363
|
+
return np.mean(self.get_all_indegrees(weighted), dtype=float)
|
|
368
364
|
|
|
369
|
-
def get_edges_mask_from_set_to_vertex(self, vertex_set: set, vertex: int) -> np.
|
|
365
|
+
def get_edges_mask_from_set_to_vertex(self, vertex_set: set, vertex: int) -> np.ndarray:
|
|
370
366
|
"""
|
|
371
367
|
Create a mask for edges leading from a set of vertices to a specified vertex.
|
|
372
368
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import jax
|
|
2
2
|
import jax.numpy as jnp
|
|
3
3
|
import numpy as np
|
|
4
|
-
from typing import Dict, Callable
|
|
4
|
+
from typing import Dict, Callable, Optional
|
|
5
5
|
from functools import partial
|
|
6
6
|
from scipy.stats import norm
|
|
7
7
|
from scipy.stats._distn_infrastructure import rv_frozen
|
|
@@ -12,7 +12,7 @@ __all__ = ["GLTInferenceModule"]
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class GLTInferenceModule:
|
|
15
|
-
def __init__(self, estimator: GLTWeightEstimator, vertex_2_jax_cdf: Dict[int, Callable] = None):
|
|
15
|
+
def __init__(self, estimator: GLTWeightEstimator, vertex_2_jax_cdf: Optional[Dict[int, Callable]] = None):
|
|
16
16
|
self.estimator = estimator
|
|
17
17
|
if vertex_2_jax_cdf is None:
|
|
18
18
|
self.vertex_2_jax_cdf = {vertex: self._make_jax_cdf(distrib)
|
|
@@ -34,7 +34,7 @@ class Traces(list):
|
|
|
34
34
|
The graph associated with the traces.
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
def __new__(cls, graph: Graph, traces: List[Union[Trace, Tuple[Set]]]) -> Traces:
|
|
37
|
+
def __new__(cls, graph: Graph, traces: List[Union[Trace, Tuple[Set, ...]]]) -> Traces:
|
|
38
38
|
return super(Traces, cls).__new__(cls, traces)
|
|
39
39
|
|
|
40
40
|
def __init__(self, graph: Graph, traces: List[Union[Trace, Tuple[Set]]]) -> None:
|
|
@@ -42,7 +42,7 @@ class Traces(list):
|
|
|
42
42
|
super().__init__(traces)
|
|
43
43
|
self.graph = graph
|
|
44
44
|
|
|
45
|
-
def append(self, obj: Union[Trace, Tuple[Set]]) -> None:
|
|
45
|
+
def append(self, obj: Union[Trace, Tuple[Set, ...]]) -> None:
|
|
46
46
|
"""Append a new trace or tuple of sets to the list of traces.
|
|
47
47
|
|
|
48
48
|
Parameters
|
|
@@ -68,10 +68,10 @@ class Trace(tuple):
|
|
|
68
68
|
Whether to check the feasibility of the trace during initialization.
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
|
-
def __new__(cls, graph: Graph, trace: Tuple[Set], check_feasibility: bool = True) -> Trace:
|
|
71
|
+
def __new__(cls, graph: Graph, trace: Tuple[Set, ...], check_feasibility: bool = True) -> Trace:
|
|
72
72
|
return super(Trace, cls).__new__(cls, trace)
|
|
73
73
|
|
|
74
|
-
def __init__(self, graph: Graph, trace: Tuple[Set], check_feasibility: bool = True) -> None:
|
|
74
|
+
def __init__(self, graph: Graph, trace: Tuple[Set, ...], check_feasibility: bool = True) -> None:
|
|
75
75
|
self.graph = graph
|
|
76
76
|
self.check_feasibility = check_feasibility
|
|
77
77
|
|
|
@@ -407,7 +407,7 @@ class Trace(tuple):
|
|
|
407
407
|
A set of edges where activation attempts were made.
|
|
408
408
|
"""
|
|
409
409
|
attempt_edges_mask = np.array([self.was_attempt_through_edge(edge) for edge in self.graph.edge_array])
|
|
410
|
-
return self.graph.edge_array[attempt_edges_mask]
|
|
410
|
+
return set(self.graph.edge_array[attempt_edges_mask])
|
|
411
411
|
|
|
412
412
|
def get_edges_between_subseq_active_nodes(self) -> Set[Tuple[int, int]]:
|
|
413
413
|
"""Get edges that connect subsequent active nodes.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from typing import List, Union, Dict
|
|
1
|
+
from typing import Iterable, List, Optional, Tuple, Union, Dict
|
|
2
2
|
import os
|
|
3
3
|
import pickle
|
|
4
4
|
import numpy as np
|
|
5
5
|
from joblib import Parallel, delayed
|
|
6
6
|
from scipy.stats import uniform
|
|
7
7
|
from scipy.stats._distn_infrastructure import rv_frozen
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
8
9
|
|
|
9
10
|
from ..Trace import Traces, PseudoTraces
|
|
10
11
|
from ..Graph import Graph
|
|
@@ -13,14 +14,14 @@ from ..utils import multiple_union, random_vector_inside_simplex
|
|
|
13
14
|
__all__ = ["BaseWeightEstimator", "BaseGLTWEightEstimator", "BaseICWEightEstimator"]
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
class BaseWeightEstimator:
|
|
17
|
+
class BaseWeightEstimator(ABC):
|
|
17
18
|
"""Base class for weight estimation in influence models.
|
|
18
19
|
|
|
19
20
|
Attributes
|
|
20
21
|
----------
|
|
21
22
|
graph : Graph
|
|
22
23
|
The graph representing the network.
|
|
23
|
-
n_jobs :
|
|
24
|
+
n_jobs : int, optional
|
|
24
25
|
Number of jobs for parallel processing.
|
|
25
26
|
informative_vertices : set
|
|
26
27
|
Set of informative vertices in the graph.
|
|
@@ -36,7 +37,7 @@ class BaseWeightEstimator:
|
|
|
36
37
|
Masks for active parents at time t.
|
|
37
38
|
"""
|
|
38
39
|
|
|
39
|
-
def __init__(self, graph: Graph, n_jobs: int = None):
|
|
40
|
+
def __init__(self, graph: Graph, n_jobs: Optional[int] = None):
|
|
40
41
|
self.graph = graph
|
|
41
42
|
self.n_jobs = n_jobs
|
|
42
43
|
self.informative_vertices = set()
|
|
@@ -65,7 +66,7 @@ class BaseWeightEstimator:
|
|
|
65
66
|
active_vertices = np.array(list(trace.get_all_activated_vertices()))
|
|
66
67
|
failed_vertices = np.array(list(trace.get_all_failed_vertices()))
|
|
67
68
|
for vertex in failed_vertices:
|
|
68
|
-
parents = self.graph.get_parents(vertex
|
|
69
|
+
parents = self.graph.get_parents(vertex)
|
|
69
70
|
active_parents_mask = np.in1d(parents, active_vertices)
|
|
70
71
|
failed_vertices_masks[vertex].append(active_parents_mask)
|
|
71
72
|
|
|
@@ -87,7 +88,7 @@ class BaseWeightEstimator:
|
|
|
87
88
|
cum_trace_list = [set()] + list(trace.cum_union())
|
|
88
89
|
for new_vertices_tp1, vertices_t, vertices_tm1 in zip(trace[1:], cum_trace_list[1:-1], cum_trace_list[:-2]):
|
|
89
90
|
for vertex in new_vertices_tp1:
|
|
90
|
-
parents = self.graph.get_parents(vertex
|
|
91
|
+
parents = self.graph.get_parents(vertex)
|
|
91
92
|
active_parents_mask_t = np.in1d(parents, np.array(list(vertices_t)))
|
|
92
93
|
active_parents_mask_tm1 = np.in1d(parents, np.array(list(vertices_tm1)))
|
|
93
94
|
vertex_2_active_parent_mask_tm1[vertex].append(active_parents_mask_tm1)
|
|
@@ -145,7 +146,7 @@ class BaseWeightEstimator:
|
|
|
145
146
|
|
|
146
147
|
for vertex, vertex_pseudo_traces in traces.items():
|
|
147
148
|
for vertices_tm1, new_vertices_t in vertex_pseudo_traces:
|
|
148
|
-
parents = self.graph.get_parents(vertex
|
|
149
|
+
parents = self.graph.get_parents(vertex)
|
|
149
150
|
active_parents_mask_tm1 = np.isin(parents, np.array(list(vertices_tm1)))
|
|
150
151
|
if new_vertices_t:
|
|
151
152
|
vertices_t = vertices_tm1 | new_vertices_t
|
|
@@ -180,14 +181,14 @@ class BaseWeightEstimator:
|
|
|
180
181
|
else:
|
|
181
182
|
raise NotImplementedError("traces can only be of type Traces or PseudoTraces")
|
|
182
183
|
|
|
183
|
-
def _preprocess_traces(self, traces: Union[Traces, PseudoTraces], masks_path: str = None) -> None:
|
|
184
|
+
def _preprocess_traces(self, traces: Union[Traces, PseudoTraces], masks_path: Optional[str] = None) -> None:
|
|
184
185
|
"""Preprocess traces by validating and computing masks.
|
|
185
186
|
|
|
186
187
|
Parameters
|
|
187
188
|
----------
|
|
188
189
|
traces : Union[Traces, PseudoTraces]
|
|
189
190
|
The traces to preprocess.
|
|
190
|
-
masks_path :
|
|
191
|
+
masks_path : str, optional
|
|
191
192
|
Path to load masks from, if applicable.
|
|
192
193
|
"""
|
|
193
194
|
traces = self._validate_traces(traces)
|
|
@@ -231,12 +232,12 @@ class BaseWeightEstimator:
|
|
|
231
232
|
"""
|
|
232
233
|
assert len(init_weights) == self.graph.count_edges()
|
|
233
234
|
|
|
234
|
-
def _init_weights(self, init_weights: Union[List, np.ndarray]) -> None:
|
|
235
|
+
def _init_weights(self, init_weights: Optional[Union[List, np.ndarray]]) -> None:
|
|
235
236
|
"""Initialize weights based on provided initial weights or generate random weights.
|
|
236
237
|
|
|
237
238
|
Parameters
|
|
238
239
|
----------
|
|
239
|
-
init_weights :
|
|
240
|
+
init_weights : Union[List, np.ndarray], optional
|
|
240
241
|
Initial weights to set. If None, random weights are generated.
|
|
241
242
|
"""
|
|
242
243
|
if init_weights is not None:
|
|
@@ -245,6 +246,7 @@ class BaseWeightEstimator:
|
|
|
245
246
|
else:
|
|
246
247
|
self.weights_ = self._generate_random_weights()
|
|
247
248
|
|
|
249
|
+
@abstractmethod
|
|
248
250
|
def _generate_random_weights(self) -> np.ndarray:
|
|
249
251
|
"""Generate random weights for edges.
|
|
250
252
|
|
|
@@ -253,46 +255,25 @@ class BaseWeightEstimator:
|
|
|
253
255
|
np.ndarray
|
|
254
256
|
Randomly generated weights.
|
|
255
257
|
"""
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
@abstractmethod
|
|
261
|
+
def _fit_vertex_parent_params(self, vertex: int, **optim_kwargs) -> Tuple:
|
|
262
|
+
"""Fit parameters for a specific vertex.
|
|
260
263
|
|
|
261
264
|
Parameters
|
|
262
265
|
----------
|
|
263
266
|
vertex : int
|
|
264
|
-
The vertex to
|
|
265
|
-
optim_kwargs : dict
|
|
266
|
-
Additional optimization parameters.
|
|
267
|
+
The vertex to fit parameters for.
|
|
267
268
|
|
|
268
269
|
Returns
|
|
269
270
|
-------
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
Tuple
|
|
272
|
+
Fitted parameters for the vertex.
|
|
272
273
|
"""
|
|
273
|
-
|
|
274
|
+
pass
|
|
274
275
|
|
|
275
|
-
def
|
|
276
|
-
"""Optimize parameters for all informative vertices in parallel.
|
|
277
|
-
|
|
278
|
-
Parameters
|
|
279
|
-
----------
|
|
280
|
-
verbose : bool, optional
|
|
281
|
-
If True, progress messages are printed.
|
|
282
|
-
optim_kwargs : dict
|
|
283
|
-
Additional optimization parameters.
|
|
284
|
-
|
|
285
|
-
Returns
|
|
286
|
-
-------
|
|
287
|
-
List[np.ndarray]
|
|
288
|
-
List of optimized parameters for each informative vertex.
|
|
289
|
-
"""
|
|
290
|
-
informative_vertices_params = Parallel(n_jobs=self.n_jobs, verbose=verbose)(
|
|
291
|
-
delayed(self._optimize_vertex_parent_params)(vertex, **optim_kwargs)
|
|
292
|
-
for vertex in self.informative_vertices)
|
|
293
|
-
return informative_vertices_params
|
|
294
|
-
|
|
295
|
-
def _set_informative_vertices_parent_params(self, informative_vertices_params: List[np.ndarray]) -> None:
|
|
276
|
+
def _set_informative_vertices_parent_params(self, informative_vertices_params: Iterable) -> None:
|
|
296
277
|
"""Set the optimized parameters for informative vertices.
|
|
297
278
|
|
|
298
279
|
Parameters
|
|
@@ -304,51 +285,51 @@ class BaseWeightEstimator:
|
|
|
304
285
|
self.weights_[self.graph.get_parents_mask(vertex)] = parent_weights
|
|
305
286
|
|
|
306
287
|
def _pre_fit(self, traces: Union[Traces, PseudoTraces],
|
|
307
|
-
init_weights: Union[List, np.ndarray] = None,
|
|
308
|
-
masks_path: str = None) -> None:
|
|
288
|
+
init_weights: Optional[Union[List, np.ndarray]] = None,
|
|
289
|
+
masks_path: Optional[str] = None) -> None:
|
|
309
290
|
"""Prepare for fitting by preprocessing traces and initializing weights.
|
|
310
291
|
|
|
311
292
|
Parameters
|
|
312
293
|
----------
|
|
313
294
|
traces : Union[Traces, PseudoTraces]
|
|
314
295
|
The traces to analyze.
|
|
315
|
-
init_weights :
|
|
296
|
+
init_weights : Union[List, np.ndarray], optional
|
|
316
297
|
Initial weights to set.
|
|
317
|
-
masks_path :
|
|
298
|
+
masks_path : str, optional
|
|
318
299
|
Path to load masks from.
|
|
319
300
|
"""
|
|
320
301
|
self._preprocess_traces(traces, masks_path=masks_path)
|
|
321
302
|
self._init_weights(init_weights)
|
|
322
303
|
|
|
323
304
|
def fit(self, traces: Union[Traces, PseudoTraces],
|
|
324
|
-
init_weights: Union[List, np.ndarray] = None,
|
|
305
|
+
init_weights: Optional[Union[List, np.ndarray]] = None,
|
|
325
306
|
verbose: bool = False,
|
|
326
|
-
masks_path: str = None,
|
|
327
|
-
**optim_kwargs) ->
|
|
307
|
+
masks_path: Optional[str] = None,
|
|
308
|
+
**optim_kwargs) -> "BaseWeightEstimator":
|
|
328
309
|
"""Fit the model to the given traces.
|
|
329
310
|
|
|
330
311
|
Parameters
|
|
331
312
|
----------
|
|
332
313
|
traces : Union[Traces, PseudoTraces]
|
|
333
314
|
The traces to analyze.
|
|
334
|
-
init_weights :
|
|
315
|
+
init_weights : Union[List, np.ndarray], optional
|
|
335
316
|
Initial weights to set.
|
|
336
317
|
verbose : bool, optional
|
|
337
318
|
If True, progress messages are printed.
|
|
338
|
-
masks_path :
|
|
319
|
+
masks_path : str, optional
|
|
339
320
|
Path to load masks from.
|
|
340
|
-
optim_kwargs : dict
|
|
341
|
-
Additional optimization parameters.
|
|
342
321
|
|
|
343
322
|
Returns
|
|
344
323
|
-------
|
|
345
|
-
|
|
346
|
-
The
|
|
324
|
+
self : object
|
|
325
|
+
The fitted estimator instance.
|
|
347
326
|
"""
|
|
348
327
|
self._pre_fit(traces, init_weights=init_weights, masks_path=masks_path)
|
|
349
|
-
informative_vertices_params = self.
|
|
328
|
+
informative_vertices_params = Parallel(n_jobs=self.n_jobs, verbose=verbose)(
|
|
329
|
+
delayed(self._fit_vertex_parent_params)(vertex, **optim_kwargs)
|
|
330
|
+
for vertex in self.informative_vertices)
|
|
350
331
|
self._set_informative_vertices_parent_params(informative_vertices_params)
|
|
351
|
-
return self
|
|
332
|
+
return self
|
|
352
333
|
|
|
353
334
|
|
|
354
335
|
class BaseGLTWEightEstimator(BaseWeightEstimator):
|
|
@@ -360,13 +341,32 @@ class BaseGLTWEightEstimator(BaseWeightEstimator):
|
|
|
360
341
|
Distribution mapping for vertices.
|
|
361
342
|
"""
|
|
362
343
|
|
|
363
|
-
def __init__(self, graph: Graph, n_jobs: int = None,
|
|
364
|
-
vertex_2_distrib: Union[Dict[int, rv_frozen], rv_frozen] = None):
|
|
344
|
+
def __init__(self, graph: Graph, n_jobs: Optional[int] = None,
|
|
345
|
+
vertex_2_distrib: Optional[Union[Dict[int, rv_frozen], rv_frozen]] = None):
|
|
365
346
|
super().__init__(graph, n_jobs)
|
|
347
|
+
self._validate_vertex_distributions(vertex_2_distrib)
|
|
348
|
+
|
|
349
|
+
def _validate_vertex_distributions(self, vertex_2_distrib: Optional[Union[Dict[int, rv_frozen], rv_frozen]] = None,
|
|
350
|
+
) -> None:
|
|
351
|
+
"""Initialize vertex distributions.
|
|
352
|
+
If vertex_2_distrib is None, a uniform distribution is assigned to each vertex.
|
|
353
|
+
If vertex_2_distrib is a single distribution, it is assigned to all vertices.
|
|
354
|
+
If vertex_2_distrib is a dictionary, it must have an entry for each vertex
|
|
355
|
+
|
|
356
|
+
Parameters
|
|
357
|
+
----------
|
|
358
|
+
vertex_2_distrib : Union[Dict[int, rv_frozen], rv_frozen], optional
|
|
359
|
+
Distribution mapping for vertices.
|
|
360
|
+
"""
|
|
366
361
|
if vertex_2_distrib is None:
|
|
367
|
-
vertex_2_distrib = {v: uniform(0, 1) for v in graph.get_vertices()}
|
|
362
|
+
vertex_2_distrib = {v: uniform(0, 1) for v in self.graph.get_vertices()}
|
|
368
363
|
elif isinstance(vertex_2_distrib, rv_frozen):
|
|
369
|
-
vertex_2_distrib = {v: vertex_2_distrib for v in graph.get_vertices()}
|
|
364
|
+
vertex_2_distrib = {v: vertex_2_distrib for v in self.graph.get_vertices()}
|
|
365
|
+
elif isinstance(vertex_2_distrib, Dict):
|
|
366
|
+
assert len(vertex_2_distrib) == self.graph.number_of_nodes(), \
|
|
367
|
+
"vertex_2_distrib must have an entry for each vertex in the graph."
|
|
368
|
+
else:
|
|
369
|
+
raise ValueError("vertex_2_distrib must be either a rv_frozen distribution or a dictionary")
|
|
370
370
|
self.vertex_2_distrib = vertex_2_distrib
|
|
371
371
|
|
|
372
372
|
def _generate_random_weights(self) -> np.ndarray:
|
|
@@ -380,7 +380,7 @@ class BaseGLTWEightEstimator(BaseWeightEstimator):
|
|
|
380
380
|
weights = np.zeros(self.graph.count_edges())
|
|
381
381
|
for vertex in self.informative_vertices:
|
|
382
382
|
parent_mask = self.graph.get_parents_mask(vertex)
|
|
383
|
-
num_parents = self.graph.get_indegree(vertex, weighted=False)
|
|
383
|
+
num_parents = int(self.graph.get_indegree(vertex, weighted=False))
|
|
384
384
|
support_ub = self.vertex_2_distrib[vertex].support()[1]
|
|
385
385
|
support_ub = 1 if support_ub == np.inf else support_ub
|
|
386
386
|
weights[parent_mask] = random_vector_inside_simplex(num_parents, ub=support_ub)
|
|
@@ -434,4 +434,6 @@ class BaseICWEightEstimator(BaseWeightEstimator):
|
|
|
434
434
|
AssertionError
|
|
435
435
|
If initial weights are not within the range [0, 1].
|
|
436
436
|
"""
|
|
437
|
-
|
|
437
|
+
if init_weights is not None:
|
|
438
|
+
init_weights = np.array(init_weights)
|
|
439
|
+
assert np.all((init_weights >= 0) & (init_weights <= 1))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import Optional, Union
|
|
3
3
|
|
|
4
4
|
from .BaseWeightEstimator import BaseWeightEstimator, BaseGLTWEightEstimator, BaseICWEightEstimator
|
|
5
5
|
from ..Trace import Traces, PseudoTraces
|
|
@@ -22,14 +22,14 @@ class BaseWeightEstimatorEM(BaseWeightEstimator):
|
|
|
22
22
|
Optimizes the parent weights for the specified vertex using the EM algorithm.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
def _em_step(self, vertex: int, vertex_parent_weights: np.
|
|
25
|
+
def _em_step(self, vertex: int, vertex_parent_weights: np.ndarray) -> np.ndarray:
|
|
26
26
|
"""Perform a single EM step for the specified vertex.
|
|
27
27
|
|
|
28
28
|
Parameters
|
|
29
29
|
----------
|
|
30
30
|
vertex : int
|
|
31
31
|
The vertex for which to perform the EM step.
|
|
32
|
-
vertex_parent_weights : np.
|
|
32
|
+
vertex_parent_weights : np.ndarray
|
|
33
33
|
The current weights of the parent vertices.
|
|
34
34
|
|
|
35
35
|
Returns
|
|
@@ -39,7 +39,7 @@ class BaseWeightEstimatorEM(BaseWeightEstimator):
|
|
|
39
39
|
"""
|
|
40
40
|
raise NotImplementedError()
|
|
41
41
|
|
|
42
|
-
def _optimize_vertex_parent_params(self, vertex: int, max_iter: int = 50, tol: float = 1e-3) -> np.
|
|
42
|
+
def _optimize_vertex_parent_params(self, vertex: int, max_iter: int = 50, tol: float = 1e-3) -> np.ndarray:
|
|
43
43
|
"""Optimize the parent weights for the specified vertex using the EM algorithm.
|
|
44
44
|
|
|
45
45
|
Parameters
|
|
@@ -99,7 +99,7 @@ class ICWeightEstimatorEM(BaseICWEightEstimator, BaseWeightEstimatorEM):
|
|
|
99
99
|
n_traces_parents_activated += self._failed_vertices_masks[vertex].sum(0)
|
|
100
100
|
self._vertex_2_num_traces_parents_activated[vertex] = n_traces_parents_activated
|
|
101
101
|
|
|
102
|
-
def _em_step(self, vertex: int, vertex_parent_weights: np.
|
|
102
|
+
def _em_step(self, vertex: int, vertex_parent_weights: np.ndarray) -> np.ndarray:
|
|
103
103
|
"""Perform a single EM step for the specified vertex.
|
|
104
104
|
|
|
105
105
|
This method computes the updated weights based on the current parent weights.
|
|
@@ -133,7 +133,7 @@ class ICWeightEstimatorEM(BaseICWEightEstimator, BaseWeightEstimatorEM):
|
|
|
133
133
|
upd_weights[seen_parents_mask] /= n_traces_parents_activated[seen_parents_mask]
|
|
134
134
|
return np.clip(upd_weights, 0, 1)
|
|
135
135
|
|
|
136
|
-
def _preprocess_traces(self, traces: Union[Traces, PseudoTraces], masks_path: str = None) -> None:
|
|
136
|
+
def _preprocess_traces(self, traces: Union[Traces, PseudoTraces], masks_path: Optional[str] = None) -> None:
|
|
137
137
|
"""Preprocesses the input traces and prepares necessary data for EM steps.
|
|
138
138
|
|
|
139
139
|
Parameters
|
|
@@ -146,7 +146,7 @@ class ICWeightEstimatorEM(BaseICWEightEstimator, BaseWeightEstimatorEM):
|
|
|
146
146
|
super()._preprocess_traces(traces=traces, masks_path=masks_path)
|
|
147
147
|
self._precompute_num_traces_parents_activated()
|
|
148
148
|
|
|
149
|
-
def _generate_random_weights(self) -> np.
|
|
149
|
+
def _generate_random_weights(self) -> np.ndarray:
|
|
150
150
|
"""Generates random initial weights for the parent vertices.
|
|
151
151
|
|
|
152
152
|
Returns
|
|
@@ -173,7 +173,7 @@ class LTWeightEstimatorEM(BaseGLTWEightEstimator, BaseWeightEstimatorEM):
|
|
|
173
173
|
Generates random initial weights for the parent vertices.
|
|
174
174
|
"""
|
|
175
175
|
|
|
176
|
-
def __init__(self, graph: Graph, n_jobs: int = None):
|
|
176
|
+
def __init__(self, graph: Graph, n_jobs: Optional[int] = None):
|
|
177
177
|
"""Initialize the Linear Threshold Weight Estimator.
|
|
178
178
|
|
|
179
179
|
Parameters
|
|
@@ -185,19 +185,19 @@ class LTWeightEstimatorEM(BaseGLTWEightEstimator, BaseWeightEstimatorEM):
|
|
|
185
185
|
"""
|
|
186
186
|
super().__init__(graph, n_jobs=n_jobs)
|
|
187
187
|
|
|
188
|
-
def _em_step(self, vertex: int, vertex_parent_weights: np.
|
|
188
|
+
def _em_step(self, vertex: int, vertex_parent_weights: np.ndarray) -> np.ndarray:
|
|
189
189
|
"""Perform a single EM step for the specified vertex.
|
|
190
190
|
|
|
191
191
|
Parameters
|
|
192
192
|
----------
|
|
193
193
|
vertex : int
|
|
194
194
|
The vertex for which to perform the EM step.
|
|
195
|
-
vertex_parent_weights : np.
|
|
195
|
+
vertex_parent_weights : np.ndarray
|
|
196
196
|
The current weights of the parent vertices.
|
|
197
197
|
|
|
198
198
|
Returns
|
|
199
199
|
-------
|
|
200
|
-
np.
|
|
200
|
+
np.ndarray
|
|
201
201
|
The updated weights for the parent vertices.
|
|
202
202
|
"""
|
|
203
203
|
if vertex in self._vertex_2_active_parent_mask_t:
|
|
@@ -222,12 +222,12 @@ class LTWeightEstimatorEM(BaseGLTWEightEstimator, BaseWeightEstimatorEM):
|
|
|
222
222
|
upd_weights = H_uvs / (H_empty_v + H_uvs.sum())
|
|
223
223
|
return upd_weights
|
|
224
224
|
|
|
225
|
-
def _generate_random_weights(self) -> np.
|
|
225
|
+
def _generate_random_weights(self) -> np.ndarray:
|
|
226
226
|
"""Generates random initial weights for the parent vertices.
|
|
227
227
|
|
|
228
228
|
Returns
|
|
229
229
|
-------
|
|
230
|
-
np.
|
|
230
|
+
np.ndarray
|
|
231
231
|
An array of random weights for the parent vertices.
|
|
232
232
|
"""
|
|
233
233
|
weights = np.zeros(self.graph.count_edges())
|