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,291 @@
1
+ import numpy as np
2
+ from typing import List, Union, Dict, Tuple
3
+ from scipy.optimize import LinearConstraint, minimize
4
+ from scipy.stats._distn_infrastructure import rv_frozen
5
+ from ..Graph import Graph
6
+ from BaseWeightEstimator import BaseGLTWEightEstimator
7
+ from ..Trace import Traces, PseudoTraces
8
+
9
+
10
+ class GLTWeightEstimator(BaseGLTWEightEstimator):
11
+ """GLTW Weight Estimator for modeling influence in networks.
12
+
13
+ Attributes
14
+ ----------
15
+ vertex_2_distrib : Dict[int, rv_frozen]
16
+ Distribution mapping for vertices.
17
+ """
18
+
19
+ def _compute_failed_vertex_ll(self, weights: np.ndarray, vertex: int) -> float:
20
+ """Compute the log-likelihood for failed vertices.
21
+
22
+ Parameters
23
+ ----------
24
+ weights : np.ndarray
25
+ The weights for the graph edges.
26
+ vertex : int
27
+ The vertex index.
28
+
29
+ Returns
30
+ -------
31
+ float
32
+ The log-likelihood for the failed vertex.
33
+ """
34
+ if vertex not in self._failed_vertices_masks:
35
+ return 0.0
36
+ cdf = self.vertex_2_distrib[vertex].cdf
37
+ failed_vertices_indeg = self._failed_vertices_masks[vertex] @ weights
38
+ failure_probs = 1.0 - cdf(failed_vertices_indeg)
39
+ ll = np.log(np.clip(failure_probs, 1e-8, None)).sum()
40
+ return ll
41
+
42
+ def _compute_activated_vertex_ll(self, weights: np.ndarray, vertex: int) -> float:
43
+ """Compute the log-likelihood for activated vertices.
44
+
45
+ Parameters
46
+ ----------
47
+ weights : np.ndarray
48
+ The weights for the graph edges.
49
+ vertex : int
50
+ The vertex index.
51
+
52
+ Returns
53
+ -------
54
+ float
55
+ The log-likelihood for the activated vertex.
56
+ """
57
+ if vertex not in self._vertex_2_active_parent_mask_tm1:
58
+ return 0.0
59
+ cdf = self.vertex_2_distrib[vertex].cdf
60
+ activated_vertices_indeg_tm1 = self._vertex_2_active_parent_mask_tm1[vertex] @ weights
61
+ activated_vertices_indeg_t = self._vertex_2_active_parent_mask_t[vertex] @ weights
62
+ activation_probs = cdf(activated_vertices_indeg_t) - cdf(activated_vertices_indeg_tm1)
63
+ ll = np.log(np.clip(activation_probs, 1e-8, None)).sum()
64
+ return ll
65
+
66
+ def _compute_vertex_nll(self, weights: np.ndarray, vertex: int) -> float:
67
+ """Compute the negative log-likelihood for a vertex.
68
+
69
+ Parameters
70
+ ----------
71
+ weights : np.ndarray
72
+ The weights for the graph edges.
73
+ vertex : int
74
+ The vertex index.
75
+
76
+ Returns
77
+ -------
78
+ float
79
+ The negative log-likelihood for the vertex.
80
+ """
81
+ ll = self._compute_activated_vertex_ll(weights, vertex) + self._compute_failed_vertex_ll(weights, vertex)
82
+ return -ll
83
+
84
+ def _compute_total_nll(self, weights: np.ndarray) -> float:
85
+ """Compute the total negative log-likelihood for informative vertices.
86
+
87
+ Parameters
88
+ ----------
89
+ weights : np.ndarray
90
+ The weights for the graph edges.
91
+
92
+ Returns
93
+ -------
94
+ float
95
+ The total negative log-likelihood.
96
+ """
97
+ return np.sum([
98
+ self._compute_vertex_nll(weights[self.graph.get_parents_mask(vertex)], vertex)
99
+ for vertex in self.informative_vertices
100
+ ])
101
+
102
+ def _compute_normalized_vertex_nll(self, weights: np.ndarray, vertex: int) -> float:
103
+ """Compute the normalized negative log-likelihood for a vertex.
104
+
105
+ Parameters
106
+ ----------
107
+ weights : np.ndarray
108
+ The weights for the graph edges.
109
+ vertex : int
110
+ The vertex index.
111
+
112
+ Returns
113
+ -------
114
+ float
115
+ The normalized negative log-likelihood for the vertex.
116
+ """
117
+ return self._compute_vertex_nll(weights, vertex) / self._vertex_2_num_traces_was_informative[vertex]
118
+
119
+ def _make_parent_weight_constraints(self, vertex: int, eps: float = 1e-8) -> LinearConstraint:
120
+ """Create constraints for parent weights of a vertex.
121
+
122
+ Parameters
123
+ ----------
124
+ vertex : int
125
+ The vertex index.
126
+ eps : float, optional
127
+ Small value for numerical stability.
128
+
129
+ Returns
130
+ -------
131
+ LinearConstraint
132
+ The linear constraints for optimization.
133
+ """
134
+ indeg_max = self.vertex_2_distrib[vertex].support()[1]
135
+ num_parents = self.graph.get_indegree(vertex, weighted=False)
136
+
137
+ mat_constrain = np.vstack([np.eye(num_parents), np.ones(num_parents)])
138
+ lb = eps * np.ones(num_parents + 1)
139
+ ub = (indeg_max - eps) * np.ones(num_parents + 1)
140
+ return LinearConstraint(A=mat_constrain, lb=lb, ub=ub)
141
+
142
+ def _precompute_all_constraints(self) -> None:
143
+ """Precompute weight constraints for all informative vertices."""
144
+ self._weight_constraints = {
145
+ vertex: self._make_parent_weight_constraints(vertex)
146
+ for vertex in self.informative_vertices
147
+ }
148
+
149
+ def _optimize_vertex_parent_params(self, vertex: int, optimization_kwargs: Dict = None) -> Tuple[np.ndarray, rv_frozen]:
150
+ """Optimize parameters for a specific vertex.
151
+
152
+ Parameters
153
+ ----------
154
+ vertex : int
155
+ The vertex to optimize.
156
+ optimization_kwargs : Dict, optional
157
+ Additional optimization parameters.
158
+
159
+ Returns
160
+ -------
161
+ Tuple[np.ndarray, rv_frozen]
162
+ Optimized weights and the vertex distribution.
163
+ """
164
+ optimizer_output = minimize(
165
+ lambda weights: self._compute_normalized_vertex_nll(weights, vertex=vertex),
166
+ x0=self.weights_[self.graph.get_parents_mask(vertex)],
167
+ method='SLSQP',
168
+ constraints=self._weight_constraints[vertex],
169
+ options=optimization_kwargs
170
+ )
171
+ return optimizer_output.x, self.vertex_2_distrib[vertex]
172
+
173
+ def _set_informative_vertices_parent_params(self, informative_vertices_params: List[Tuple[np.ndarray, rv_frozen]]) -> None:
174
+ """Set the optimized parameters for informative vertices.
175
+
176
+ Parameters
177
+ ----------
178
+ informative_vertices_params : List[Tuple[np.ndarray, rv_frozen]]
179
+ List of optimized parameters for each informative vertex.
180
+ """
181
+ for vertex, (parent_weights, vertex_distrib) in zip(self.informative_vertices, informative_vertices_params):
182
+ self.weights_[self.graph.get_parents_mask(vertex)] = parent_weights
183
+ self.vertex_2_distrib[vertex] = vertex_distrib
184
+
185
+ def _pre_fit(self, traces: Union[Traces, PseudoTraces],
186
+ init_weights: Union[List[float], np.array] = None,
187
+ masks_path: str = None) -> None:
188
+ """Prepare for fitting by preprocessing traces and initializing constraints.
189
+
190
+ Parameters
191
+ ----------
192
+ traces : Union[Traces, PseudoTraces]
193
+ The traces to analyze.
194
+ init_weights : Optional[List[float]]
195
+ Initial weights to set.
196
+ masks_path : Optional[str]
197
+ Path to load masks from.
198
+ """
199
+ self._preprocess_traces(traces, masks_path=masks_path)
200
+ self._precompute_all_constraints()
201
+ self._init_weights(init_weights)
202
+
203
+
204
+ class GLTGridSearchEstimator(GLTWeightEstimator):
205
+ """GLT Grid Search Estimator for hyperparameter tuning.
206
+
207
+ Attributes
208
+ ----------
209
+ vertex_2_distrib_grid : Dict[int, List[rv_frozen]]
210
+ Distribution grid for each vertex.
211
+ """
212
+
213
+ def __init__(self, graph: Graph,
214
+ distribs_grid: Union[Dict[int, List[rv_frozen]], List[rv_frozen]],
215
+ n_jobs: int = 1) -> None:
216
+ """Initialize the GLT Grid Search Estimator.
217
+
218
+ Parameters
219
+ ----------
220
+ graph : Graph
221
+ The graph structure.
222
+ distribs_grid : Union[Dict[int, List[rv_frozen]], List[rv_frozen]]
223
+ The grid of distributions for each vertex.
224
+ n_jobs : int, optional
225
+ Number of jobs for parallel processing.
226
+ """
227
+ super().__init__(graph=graph, vertex_2_distrib=None, n_jobs=n_jobs)
228
+ self._validate_grid(distribs_grid)
229
+
230
+ def _validate_grid(self, distribs_grid: Union[Dict[int, List[rv_frozen]], List[rv_frozen]]) -> None:
231
+ """Validate the distribution grid.
232
+
233
+ Parameters
234
+ ----------
235
+ distribs_grid : Union[Dict[int, List[rv_frozen]], List[rv_frozen]]
236
+ The grid of distributions to validate.
237
+
238
+ Raises
239
+ ------
240
+ AssertionError
241
+ If the distribution grid does not match the graph vertices.
242
+ """
243
+ if isinstance(distribs_grid, (List, Tuple)):
244
+ self.vertex_2_distrib_grid = {vertex: distribs_grid for vertex in self.graph.get_vertices()}
245
+ elif isinstance(distribs_grid, Dict):
246
+ assert set(distribs_grid.keys()) == self.graph.get_vertices(), \
247
+ "If `distribs_grid` is a dict, its keys should be the vertices of the graph."
248
+ self.vertex_2_distrib_grid = distribs_grid
249
+ else:
250
+ raise NotImplementedError("`distribs_grid` should be either a list, tuple, or a dict.")
251
+
252
+ assert all([
253
+ all([isinstance(distrib, rv_frozen) for distrib in vertex_distribs])
254
+ for vertex_distribs in self.vertex_2_distrib_grid.values()
255
+ ]), "All elements of the grid should be `rv_frozen`."
256
+
257
+ def _optimize_vertex_parent_params(self, vertex: int, optimization_kwargs: Dict = None) -> Tuple[np.ndarray, rv_frozen]:
258
+ """Optimize parameters for a specific vertex using grid search.
259
+
260
+ Parameters
261
+ ----------
262
+ vertex : int
263
+ The vertex to optimize.
264
+ optimization_kwargs : Dict, optional
265
+ Additional optimization parameters.
266
+
267
+ Returns
268
+ -------
269
+ Tuple[np.ndarray, rv_frozen]
270
+ Best weights and the best distribution for the vertex.
271
+ """
272
+ best_weights = None
273
+ best_nll = np.inf
274
+ best_distrib = None
275
+
276
+ for distrib in self.vertex_2_distrib_grid[vertex]:
277
+ self.vertex_2_distrib[vertex] = distrib
278
+ optimizer_output = minimize(
279
+ lambda weights: self._compute_normalized_vertex_nll(weights, vertex=vertex),
280
+ x0=self.weights_[self.graph.get_parents_mask(vertex)],
281
+ method='SLSQP',
282
+ constraints=self._weight_constraints[vertex],
283
+ options=optimization_kwargs
284
+ )
285
+ nll = optimizer_output.fun
286
+ if nll < best_nll:
287
+ best_weights = optimizer_output.x
288
+ best_distrib = distrib
289
+ best_nll = nll
290
+
291
+ return best_weights, best_distrib
@@ -0,0 +1,3 @@
1
+ from BaseWeightEstimator import *
2
+ from EMEstimation import *
3
+ from OptimEstimation import *