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.
Files changed (24) hide show
  1. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Graph.py +22 -26
  2. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Inference.py +2 -2
  3. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/Trace.py +5 -5
  4. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/BaseWeightEstimator.py +65 -63
  5. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/EMEstimation.py +13 -13
  6. influencediffusion-0.0.19/InfluenceDiffusion/estimation_models/OptimEstimation.py +563 -0
  7. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/estimation_models/__init__.py +1 -1
  8. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/influence_models.py +29 -23
  9. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/PKG-INFO +4 -1
  10. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/SOURCES.txt +0 -1
  11. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/requires.txt +3 -0
  12. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/PKG-INFO +4 -1
  13. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/setup.py +5 -2
  14. influencediffusion-0.0.18/InfluenceDiffusion/estimation_models/CDFEstimation.py +0 -177
  15. influencediffusion-0.0.18/InfluenceDiffusion/estimation_models/OptimEstimation.py +0 -397
  16. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/__init__.py +0 -0
  17. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/plot_utils.py +0 -0
  18. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/utils.py +0 -0
  19. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion/weight_samplers.py +0 -0
  20. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/dependency_links.txt +0 -0
  21. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/InfluenceDiffusion.egg-info/top_level.txt +0 -0
  22. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/LICENSE +0 -0
  23. {influencediffusion-0.0.18 → influencediffusion-0.0.19}/README.md +0 -0
  24. {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.array] = None, **attr):
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.array]) -> "Graph":
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.array, List[Tuple[int, int]]]:
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.array:
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, out_type: Union[set, list, np.array] = set) -> Union[set, list, np.array]:
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 out_type(self.edge_array[:, 1][self.get_children_mask(vertex)])
164
+ return self.edge_array[:, 1][self.get_children_mask(vertex)]
167
165
 
168
- def get_parents_mask(self, vertex: int) -> np.array:
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, out_type: Union[set, list, np.array] = set) -> Union[set, list, np.array]:
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 out_type(self.edge_array[:, 0][self.get_parents_mask(vertex)])
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) -> Union[float, int]:
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
- Union[float, int]
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.array:
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) -> Union[float, int]:
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
- Union[float, int]
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) -> Union[float, int]:
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.array:
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.array:
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 : Optional[int]
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, out_type=np.array)
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, out_type=np.array)
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, out_type=np.array)
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 : Optional[str]
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 : Optional[Union[List, np.ndarray]]
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
- raise NotImplementedError("Subclasses should implement this method.")
257
-
258
- def _optimize_vertex_parent_params(self, vertex: int, **optim_kwargs) -> np.ndarray:
259
- """Optimize parameters for a specific vertex.
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 optimize parameters for.
265
- optim_kwargs : dict
266
- Additional optimization parameters.
267
+ The vertex to fit parameters for.
267
268
 
268
269
  Returns
269
270
  -------
270
- np.ndarray
271
- Optimized parameters for the vertex.
271
+ Tuple
272
+ Fitted parameters for the vertex.
272
273
  """
273
- raise NotImplementedError("Subclasses should implement this method.")
274
+ pass
274
275
 
275
- def _optimize_informative_vertices_parent_params(self, verbose: bool = False, **optim_kwargs) -> List[np.ndarray]:
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 : Optional[Union[List, np.ndarray]]
296
+ init_weights : Union[List, np.ndarray], optional
316
297
  Initial weights to set.
317
- masks_path : Optional[str]
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) -> np.ndarray:
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 : Optional[Union[List, np.ndarray]]
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 : Optional[str]
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
- np.ndarray
346
- The final estimated weights.
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._optimize_informative_vertices_parent_params(verbose=verbose, **optim_kwargs)
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.weights_
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
- assert np.all((init_weights >= 0) & (init_weights <= 1))
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.array) -> np.array:
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.array
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.array:
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.array) -> np.array:
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.array:
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.array) -> np.array:
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.array
195
+ vertex_parent_weights : np.ndarray
196
196
  The current weights of the parent vertices.
197
197
 
198
198
  Returns
199
199
  -------
200
- np.array
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.array:
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.array
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())