pyAgrum-nightly 2.3.1.9.dev202512261765915415__cp310-abi3-macosx_10_15_x86_64.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.
Files changed (107) hide show
  1. pyagrum/__init__.py +165 -0
  2. pyagrum/_pyagrum.so +0 -0
  3. pyagrum/bnmixture/BNMInference.py +268 -0
  4. pyagrum/bnmixture/BNMLearning.py +376 -0
  5. pyagrum/bnmixture/BNMixture.py +464 -0
  6. pyagrum/bnmixture/__init__.py +60 -0
  7. pyagrum/bnmixture/notebook.py +1058 -0
  8. pyagrum/causal/_CausalFormula.py +280 -0
  9. pyagrum/causal/_CausalModel.py +436 -0
  10. pyagrum/causal/__init__.py +81 -0
  11. pyagrum/causal/_causalImpact.py +356 -0
  12. pyagrum/causal/_dSeparation.py +598 -0
  13. pyagrum/causal/_doAST.py +761 -0
  14. pyagrum/causal/_doCalculus.py +361 -0
  15. pyagrum/causal/_doorCriteria.py +374 -0
  16. pyagrum/causal/_exceptions.py +95 -0
  17. pyagrum/causal/_types.py +61 -0
  18. pyagrum/causal/causalEffectEstimation/_CausalEffectEstimation.py +1175 -0
  19. pyagrum/causal/causalEffectEstimation/_IVEstimators.py +718 -0
  20. pyagrum/causal/causalEffectEstimation/_RCTEstimators.py +132 -0
  21. pyagrum/causal/causalEffectEstimation/__init__.py +46 -0
  22. pyagrum/causal/causalEffectEstimation/_backdoorEstimators.py +774 -0
  23. pyagrum/causal/causalEffectEstimation/_causalBNEstimator.py +324 -0
  24. pyagrum/causal/causalEffectEstimation/_frontdoorEstimators.py +396 -0
  25. pyagrum/causal/causalEffectEstimation/_learners.py +118 -0
  26. pyagrum/causal/causalEffectEstimation/_utils.py +466 -0
  27. pyagrum/causal/notebook.py +172 -0
  28. pyagrum/clg/CLG.py +658 -0
  29. pyagrum/clg/GaussianVariable.py +111 -0
  30. pyagrum/clg/SEM.py +312 -0
  31. pyagrum/clg/__init__.py +63 -0
  32. pyagrum/clg/canonicalForm.py +408 -0
  33. pyagrum/clg/constants.py +54 -0
  34. pyagrum/clg/forwardSampling.py +202 -0
  35. pyagrum/clg/learning.py +776 -0
  36. pyagrum/clg/notebook.py +480 -0
  37. pyagrum/clg/variableElimination.py +271 -0
  38. pyagrum/common.py +60 -0
  39. pyagrum/config.py +319 -0
  40. pyagrum/ctbn/CIM.py +513 -0
  41. pyagrum/ctbn/CTBN.py +573 -0
  42. pyagrum/ctbn/CTBNGenerator.py +216 -0
  43. pyagrum/ctbn/CTBNInference.py +459 -0
  44. pyagrum/ctbn/CTBNLearner.py +161 -0
  45. pyagrum/ctbn/SamplesStats.py +671 -0
  46. pyagrum/ctbn/StatsIndepTest.py +355 -0
  47. pyagrum/ctbn/__init__.py +79 -0
  48. pyagrum/ctbn/constants.py +54 -0
  49. pyagrum/ctbn/notebook.py +264 -0
  50. pyagrum/defaults.ini +199 -0
  51. pyagrum/deprecated.py +95 -0
  52. pyagrum/explain/_ComputationCausal.py +75 -0
  53. pyagrum/explain/_ComputationConditional.py +48 -0
  54. pyagrum/explain/_ComputationMarginal.py +48 -0
  55. pyagrum/explain/_CustomShapleyCache.py +110 -0
  56. pyagrum/explain/_Explainer.py +176 -0
  57. pyagrum/explain/_Explanation.py +70 -0
  58. pyagrum/explain/_FIFOCache.py +54 -0
  59. pyagrum/explain/_ShallCausalValues.py +204 -0
  60. pyagrum/explain/_ShallConditionalValues.py +155 -0
  61. pyagrum/explain/_ShallMarginalValues.py +155 -0
  62. pyagrum/explain/_ShallValues.py +296 -0
  63. pyagrum/explain/_ShapCausalValues.py +208 -0
  64. pyagrum/explain/_ShapConditionalValues.py +126 -0
  65. pyagrum/explain/_ShapMarginalValues.py +191 -0
  66. pyagrum/explain/_ShapleyValues.py +298 -0
  67. pyagrum/explain/__init__.py +81 -0
  68. pyagrum/explain/_explGeneralizedMarkovBlanket.py +152 -0
  69. pyagrum/explain/_explIndependenceListForPairs.py +146 -0
  70. pyagrum/explain/_explInformationGraph.py +264 -0
  71. pyagrum/explain/notebook/__init__.py +54 -0
  72. pyagrum/explain/notebook/_bar.py +142 -0
  73. pyagrum/explain/notebook/_beeswarm.py +174 -0
  74. pyagrum/explain/notebook/_showShapValues.py +97 -0
  75. pyagrum/explain/notebook/_waterfall.py +220 -0
  76. pyagrum/explain/shapley.py +225 -0
  77. pyagrum/lib/__init__.py +46 -0
  78. pyagrum/lib/_colors.py +390 -0
  79. pyagrum/lib/bn2graph.py +299 -0
  80. pyagrum/lib/bn2roc.py +1026 -0
  81. pyagrum/lib/bn2scores.py +217 -0
  82. pyagrum/lib/bn_vs_bn.py +605 -0
  83. pyagrum/lib/cn2graph.py +305 -0
  84. pyagrum/lib/discreteTypeProcessor.py +1102 -0
  85. pyagrum/lib/discretizer.py +58 -0
  86. pyagrum/lib/dynamicBN.py +390 -0
  87. pyagrum/lib/explain.py +57 -0
  88. pyagrum/lib/export.py +84 -0
  89. pyagrum/lib/id2graph.py +258 -0
  90. pyagrum/lib/image.py +387 -0
  91. pyagrum/lib/ipython.py +307 -0
  92. pyagrum/lib/mrf2graph.py +471 -0
  93. pyagrum/lib/notebook.py +1821 -0
  94. pyagrum/lib/proba_histogram.py +552 -0
  95. pyagrum/lib/utils.py +138 -0
  96. pyagrum/pyagrum.py +31495 -0
  97. pyagrum/skbn/_MBCalcul.py +242 -0
  98. pyagrum/skbn/__init__.py +49 -0
  99. pyagrum/skbn/_learningMethods.py +282 -0
  100. pyagrum/skbn/_utils.py +297 -0
  101. pyagrum/skbn/bnclassifier.py +1014 -0
  102. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSE.md +12 -0
  103. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
  104. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/MIT.txt +18 -0
  105. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/METADATA +145 -0
  106. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/RECORD +107 -0
  107. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/WHEEL +4 -0
@@ -0,0 +1,408 @@
1
+ ############################################################################
2
+ # This file is part of the aGrUM/pyAgrum library. #
3
+ # #
4
+ # Copyright (c) 2005-2025 by #
5
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
6
+ # - Christophe GONZALES(_at_AMU) #
7
+ # #
8
+ # The aGrUM/pyAgrum library is free software; you can redistribute it #
9
+ # and/or modify it under the terms of either : #
10
+ # #
11
+ # - the GNU Lesser General Public License as published by #
12
+ # the Free Software Foundation, either version 3 of the License, #
13
+ # or (at your option) any later version, #
14
+ # - the MIT license (MIT), #
15
+ # - or both in dual license, as here. #
16
+ # #
17
+ # (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
18
+ # #
19
+ # This aGrUM/pyAgrum library is distributed in the hope that it will be #
20
+ # useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
21
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
25
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
26
+ # OTHER DEALINGS IN THE SOFTWARE. #
27
+ # #
28
+ # See LICENCES for more details. #
29
+ # #
30
+ # SPDX-FileCopyrightText: Copyright 2005-2025 #
31
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
32
+ # - Christophe GONZALES(_at_AMU) #
33
+ # SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
34
+ # #
35
+ # Contact : info_at_agrum_dot_org #
36
+ # homepage : http://agrum.gitlab.io #
37
+ # gitlab : https://gitlab.com/agrumery/agrum #
38
+ # #
39
+ ############################################################################
40
+
41
+ """
42
+ This module implements the canonical form of a CLG density.
43
+ """
44
+
45
+ import numpy as np
46
+ from numpy.linalg import inv
47
+ from numpy.linalg import det
48
+
49
+
50
+ class CanonicalForm:
51
+ def __init__(self, scope=[], K=[], h=[], g=0):
52
+ # Expecting a list of int corresponding to the id of the variables in the CLG.
53
+ self._scope = scope
54
+ self._size = len(self._scope)
55
+ self._K = np.reshape(K, (len(K), len(K)))
56
+ self._h = np.reshape(h, (len(h), 1))
57
+ self._g = g
58
+
59
+ # Canonical forms are sorted by increasing
60
+ # variable index (in the CLG or with a user defined order).
61
+ # This order gives the order of matrix and vector
62
+ # row/columns which is important for the different
63
+ # operations on canonical forms.
64
+ self._sort()
65
+
66
+ def __str__(self):
67
+ return "scope = {0}, K = {1}, h = {2}, g = {3}".format(self._scope, self._K.tolist(), self._h.tolist(), self._g)
68
+
69
+ def __repr__(self):
70
+ return str(self)
71
+
72
+ def __contains__(self, item):
73
+ return item in self._scope
74
+
75
+ def scope(self):
76
+ """
77
+ Return the scope of the canonical form.
78
+
79
+ Returns
80
+ ------
81
+ List[int]
82
+ The scope of the canonical form.
83
+ """
84
+ return self._scope
85
+
86
+ @classmethod
87
+ def fromCLG(cls, variable, parents, mu, sigma, B):
88
+ """
89
+ Factory to construct a canonical form from a conditional linear gaussian.
90
+
91
+ Parameters
92
+ ----------
93
+ variable : int
94
+ The variable to which the clg density is associated.
95
+ parents : List[int]
96
+ Parents of the variable in the graph.
97
+ mu : float
98
+ The intercept of the linear regression.
99
+ sigma : float
100
+ The variance of the clg density.
101
+ B : List[float]
102
+ The weights of the linear regression.
103
+
104
+ Returns
105
+ -------
106
+ CanonicalForm
107
+ The canonical form corresponding to the clg density.
108
+
109
+ References
110
+ ----------
111
+ The formulas are given in "Propagation of Probabilities, Means, and
112
+ Variances in Mixed Graphical Association Models, S. Lauritzen (1992)".
113
+ """
114
+ gamma = sigma**2 # The variance
115
+ if len(parents) == 0: # Unconditional case (no parents)
116
+ scope = [variable]
117
+ h = [mu / gamma]
118
+ K = [[1 / gamma]]
119
+ else: # General case
120
+ scope = [variable] + parents
121
+
122
+ B = np.reshape(B, (len(B), 1))
123
+ h = mu / gamma * np.insert(-B, 0, 1, axis=0)
124
+ K = 1 / gamma * np.block([[1, -B.T], [-B, np.dot(B, B.T)]])
125
+
126
+ g = -(mu**2 / gamma + np.log(2 * np.pi * gamma)) / 2
127
+
128
+ cf = cls(scope, K, h, g)
129
+ # Canonical form should always be sorted when created !
130
+ cf._sort()
131
+
132
+ return cf
133
+
134
+ def toGaussian(self):
135
+ """
136
+ Gives the parameters of the gaussian associated to the canonical form.
137
+
138
+ Warnings
139
+ --------
140
+ This operation is only well defined if K is invertible.
141
+
142
+ Returns
143
+ -------
144
+ List[int]
145
+ Parematers of the Gaussian associated to the canonical form.
146
+ """
147
+ Sigma = inv(self._K)
148
+ mu = np.dot(Sigma, self._h)
149
+ return self._scope, mu, Sigma
150
+
151
+ @staticmethod
152
+ def _permute_matrix(matrix, permutation):
153
+ """
154
+ Permutes a matrix given a permutation.
155
+
156
+ Parameters
157
+ ----------
158
+ matrix : np.array
159
+ The matrix to permute.
160
+ permutation : List[int]
161
+ The permutation to use.
162
+
163
+ Returns
164
+ -------
165
+ np.array
166
+ The permuted matrix.
167
+ """
168
+ matrix[:, :] = matrix[permutation, :] # Permutation of rows
169
+ matrix[:, :] = matrix[:, permutation] # Permutation of columns
170
+
171
+ return matrix
172
+
173
+ @staticmethod
174
+ def _permute_vector(vector, permutation):
175
+ """
176
+ Permutes a vector given a permutation.
177
+
178
+ Parameters
179
+ ----------
180
+ vector : np.array
181
+ The vector to permute.
182
+ permutation : List[int]
183
+ The permutation to use.
184
+
185
+ Returns
186
+ -------
187
+ np.array
188
+ The permuted vector.
189
+ """
190
+ vector[:, :] = vector[permutation, :]
191
+
192
+ return vector
193
+
194
+ def _sort(self):
195
+ """
196
+ Sort the variable of the scope according to their indices in the CLG.
197
+ """
198
+ sorted_scope = sorted(self._scope)
199
+ if self._scope != sorted_scope:
200
+ permutation = [self._scope.index(e) for e in sorted_scope]
201
+ self._K = CanonicalForm._permute_matrix(self._K, permutation)
202
+ self._h = CanonicalForm._permute_vector(self._h, permutation)
203
+ self._scope = sorted_scope
204
+ return
205
+
206
+ def augment(self, variables):
207
+ """
208
+ Augment the scope of the canonical form with variables.
209
+
210
+ Parameters
211
+ ----------
212
+ variables : List[int]
213
+ The variables to add to the scope.
214
+
215
+ Returns
216
+ -------
217
+ CanonicalForm
218
+ The augmented canonical form.
219
+ """
220
+ new_scope = list(set(self._scope + variables))
221
+ new_scope.sort()
222
+
223
+ new_variables = list(set(new_scope) - set(self._scope))
224
+ new_variables.sort()
225
+
226
+ if len(self._scope) == 0: # Case where the canonical form is empty
227
+ augmented_K = np.zeros((len(new_variables), len(new_variables)))
228
+ augmented_h = np.zeros((len(new_variables), 1))
229
+ else:
230
+ augmented_K = self._K
231
+ augmented_h = self._h
232
+ positions = [new_scope.index(v) for v in new_variables]
233
+ # Position of first variable in new scope
234
+ fv_new_position = new_scope.index(self._scope[0])
235
+ for position in positions:
236
+ # Avoid using insert when not necessary
237
+ if position < fv_new_position:
238
+ augmented_K = np.concatenate((np.zeros((len(augmented_K), 1)), augmented_K), axis=1)
239
+ augmented_K = np.concatenate((np.zeros((1, len(augmented_K) + 1)), augmented_K), axis=0)
240
+ augmented_h = np.concatenate(([[0]], augmented_h), axis=0)
241
+ elif position < len(augmented_K):
242
+ # Adding a new column of zeros at position in K
243
+ augmented_K = np.insert(augmented_K, [position], np.zeros((len(augmented_K), 1)), axis=1)
244
+ # Adding a new row of zeros at position in K
245
+ augmented_K = np.insert(augmented_K, [position], np.zeros((1, len(augmented_K) + 1)), axis=0)
246
+ # Adding a new zero at position in h
247
+ augmented_h = np.insert(augmented_h, position, 0, axis=0)
248
+ else:
249
+ augmented_K = np.concatenate((augmented_K, np.zeros((len(augmented_K), 1))), axis=1)
250
+ augmented_K = np.concatenate((augmented_K, np.zeros((1, len(augmented_K) + 1))), axis=0)
251
+ augmented_h = np.concatenate((augmented_h, [[0]]), axis=0)
252
+
253
+ return CanonicalForm(new_scope, augmented_K, augmented_h, self._g)
254
+
255
+ def __mul__(self, other):
256
+ """
257
+ Multiplies two canonical forms.
258
+
259
+ Parameters
260
+ ----------
261
+ other : CanonicalForm
262
+ The canonical form to multiply with the current canonical form.
263
+
264
+ Returns
265
+ -------
266
+ CanonicalForm
267
+ The product.
268
+ """
269
+ scope = list(set(self._scope + other._scope))
270
+ scope.sort()
271
+
272
+ # Each canonical form is augmented with the scope of the other
273
+ augmented_self = self.augment(other._scope)
274
+ augmented_other = other.augment(self._scope)
275
+
276
+ # Definition of the multiplication of two canonical forms
277
+ K = augmented_self._K + augmented_other._K
278
+ h = augmented_self._h + augmented_other._h
279
+ g = augmented_self._g + augmented_other._g
280
+
281
+ return CanonicalForm(scope, K, h, g)
282
+
283
+ def __truediv__(self, other):
284
+ """
285
+ Divides two canonical forms.
286
+
287
+ Parameters
288
+ ----------
289
+ other : CanonicalForm
290
+ The canonical form to divide the current canonical form by.
291
+
292
+ Returns
293
+ -------
294
+ CanonicalForm
295
+ The ratio.
296
+ """
297
+ scope = list(set(self._scope + other._scope))
298
+ scope.sort()
299
+
300
+ # Each canonical form is augmented with the scope of the other
301
+ augmented_self = self.augment(other._scope)
302
+ augmented_other = other.augment(self._scope)
303
+
304
+ K = augmented_self._K - augmented_other._K
305
+ h = augmented_self._h - augmented_other._h
306
+ g = augmented_self._g - augmented_other._g
307
+
308
+ return CanonicalForm(scope, K, h, g)
309
+
310
+ def __eq__(self, other):
311
+ if isinstance(other, CanonicalForm):
312
+ return (
313
+ self._scope == other._scope
314
+ and np.allclose(self._K, other._K, 1e-7)
315
+ and np.allclose(self._h, other._h, 1e-7)
316
+ and np.allclose(self._g, other._g, 1e-7)
317
+ )
318
+
319
+ return False
320
+
321
+ def marginalize(self, variables):
322
+ """
323
+ Marginalize the variables from the canonical form.
324
+
325
+ Parameters
326
+ ----------
327
+ variables : List[int]
328
+ The variables to marginalize.
329
+
330
+ Returns
331
+ -------
332
+ CanonicalForm
333
+ The marginalized canonical form.
334
+ """
335
+ if len(variables) == 0:
336
+ return self
337
+
338
+ if not all(variable in self._scope for variable in variables):
339
+ raise ValueError("All the variables to marginalize are not in the scope of the canonical form")
340
+
341
+ new_scope = list(set(self._scope) - set(variables))
342
+ variables.sort()
343
+ new_scope.sort()
344
+
345
+ ns_positions = [self._scope.index(v) for v in new_scope]
346
+ v_positions = [self._scope.index(v) for v in variables]
347
+
348
+ K_xx = self._K[np.ix_(ns_positions, ns_positions)]
349
+ K_yy = self._K[np.ix_(v_positions, v_positions)]
350
+ K_xy = self._K[np.ix_(ns_positions, v_positions)]
351
+ K_yx = self._K[np.ix_(v_positions, ns_positions)]
352
+
353
+ h_x = self._h[ns_positions]
354
+ h_y = self._h[v_positions]
355
+
356
+ K = K_xx - np.dot(np.dot(K_xy, inv(K_yy)), K_yx)
357
+ h = h_x - np.dot(np.dot(K_xy, inv(K_yy)), h_y)
358
+ g = self._g + 0.5 * (
359
+ len(variables) * np.log(2 * np.pi) - np.log(det(K_yy)) + np.dot(np.dot(np.transpose(h_y), inv(K_yy)), h_y)
360
+ )
361
+
362
+ return CanonicalForm(new_scope, K, h, g[0][0])
363
+
364
+ def reduce(self, evidences):
365
+ """
366
+ Reduce the canonical form given a set of evidences.
367
+
368
+ Parameters
369
+ ----------
370
+ evidences : Dict[int, float]
371
+ The evidences.
372
+
373
+ Returns
374
+ -------
375
+ CanonicalForm
376
+ The reduced canonical form.
377
+ """
378
+ # Removing observed variables that are not in the scope of the CF
379
+ evidences = {var: evidences[var] for var in self._scope if var in evidences}
380
+
381
+ if len(evidences) == 0:
382
+ return self
383
+
384
+ variables = list(evidences.keys())
385
+ values = list(evidences.values())
386
+
387
+ # One liner to sort variables and values according to variables
388
+ variables, values = map(list, zip(*sorted(zip(variables, values))))
389
+ values = np.reshape(values, (len(values), 1))
390
+
391
+ new_scope = list(set(self._scope) - set(variables))
392
+ new_scope.sort()
393
+
394
+ ns_positions = [self._scope.index(v) for v in new_scope]
395
+ v_positions = [self._scope.index(v) for v in variables]
396
+
397
+ K_xx = self._K[np.ix_(ns_positions, ns_positions)]
398
+ K_yy = self._K[np.ix_(v_positions, v_positions)]
399
+ K_xy = self._K[np.ix_(ns_positions, v_positions)]
400
+
401
+ h_x = self._h[ns_positions]
402
+ h_y = self._h[v_positions]
403
+
404
+ h = h_x - np.dot(K_xy, values)
405
+
406
+ g = self._g + np.dot(np.transpose(h_y), values) - 0.5 * np.dot(np.dot(np.transpose(values), K_yy), values)
407
+
408
+ return CanonicalForm(new_scope, K_xx, h, g[0][0])
@@ -0,0 +1,54 @@
1
+ ############################################################################
2
+ # This file is part of the aGrUM/pyAgrum library. #
3
+ # #
4
+ # Copyright (c) 2005-2025 by #
5
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
6
+ # - Christophe GONZALES(_at_AMU) #
7
+ # #
8
+ # The aGrUM/pyAgrum library is free software; you can redistribute it #
9
+ # and/or modify it under the terms of either : #
10
+ # #
11
+ # - the GNU Lesser General Public License as published by #
12
+ # the Free Software Foundation, either version 3 of the License, #
13
+ # or (at your option) any later version, #
14
+ # - the MIT license (MIT), #
15
+ # - or both in dual license, as here. #
16
+ # #
17
+ # (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
18
+ # #
19
+ # This aGrUM/pyAgrum library is distributed in the hope that it will be #
20
+ # useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
21
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
25
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
26
+ # OTHER DEALINGS IN THE SOFTWARE. #
27
+ # #
28
+ # See LICENCES for more details. #
29
+ # #
30
+ # SPDX-FileCopyrightText: Copyright 2005-2025 #
31
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
32
+ # - Christophe GONZALES(_at_AMU) #
33
+ # SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
34
+ # #
35
+ # Contact : info_at_agrum_dot_org #
36
+ # homepage : http://agrum.gitlab.io #
37
+ # gitlab : https://gitlab.com/agrumery/agrum #
38
+ # #
39
+ ############################################################################
40
+
41
+ """
42
+ The purpose of this module is to define the constants used in the package.
43
+ """
44
+
45
+ from typing import List, Union, NewType
46
+
47
+ ALPHA: float = 0.95
48
+ INFINITY: float = 10e9
49
+ REJECT = True
50
+ FAIL_TO_REJECT = False
51
+
52
+ Domain: type = List[str]
53
+ NodeId: type = NewType("NodeId", int)
54
+ NameOrId: type = Union[str, NodeId]
@@ -0,0 +1,202 @@
1
+ ############################################################################
2
+ # This file is part of the aGrUM/pyAgrum library. #
3
+ # #
4
+ # Copyright (c) 2005-2025 by #
5
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
6
+ # - Christophe GONZALES(_at_AMU) #
7
+ # #
8
+ # The aGrUM/pyAgrum library is free software; you can redistribute it #
9
+ # and/or modify it under the terms of either : #
10
+ # #
11
+ # - the GNU Lesser General Public License as published by #
12
+ # the Free Software Foundation, either version 3 of the License, #
13
+ # or (at your option) any later version, #
14
+ # - the MIT license (MIT), #
15
+ # - or both in dual license, as here. #
16
+ # #
17
+ # (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
18
+ # #
19
+ # This aGrUM/pyAgrum library is distributed in the hope that it will be #
20
+ # useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
21
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
25
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
26
+ # OTHER DEALINGS IN THE SOFTWARE. #
27
+ # #
28
+ # See LICENCES for more details. #
29
+ # #
30
+ # SPDX-FileCopyrightText: Copyright 2005-2025 #
31
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
32
+ # - Christophe GONZALES(_at_AMU) #
33
+ # SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
34
+ # #
35
+ # Contact : info_at_agrum_dot_org #
36
+ # homepage : http://agrum.gitlab.io #
37
+ # gitlab : https://gitlab.com/agrumery/agrum #
38
+ # #
39
+ ############################################################################
40
+
41
+ """
42
+ This module implements the forward sampling algorithm for CLG
43
+ """
44
+
45
+ import numpy as np
46
+
47
+ import pandas as pd
48
+
49
+ from .CLG import CLG
50
+
51
+
52
+ class ForwardSampling:
53
+ def __init__(self, model: CLG):
54
+ self._model = model
55
+ self._id2samples = {}
56
+
57
+ def makeSample(self, N, seed: int = None):
58
+ """
59
+ Make N samples using forward sampling.
60
+
61
+ Parameters
62
+ ----------
63
+ N : int
64
+ Amount of samples.
65
+ seed : int
66
+ Seed for the random number generator
67
+
68
+ Returns
69
+ -------
70
+ ForwardSampling
71
+ the object itself.
72
+ """
73
+ nprng = np.random.default_rng(seed)
74
+ self._id2samples = {}
75
+
76
+ for node in self._model.topologicalOrder():
77
+ v = self._model.variable(node)
78
+ mu = v.mu()
79
+ for parent in self._model.parents(node):
80
+ mu += self._model.coefArc(parent, node) * self._id2samples[parent]
81
+ self._id2samples[node] = nprng.normal(mu, v.sigma(), N)
82
+
83
+ return self
84
+
85
+ def toarray(self, val=None):
86
+ """
87
+ Return the samples of variable <val>.
88
+
89
+ Parameters
90
+ ----------
91
+ val : NameOrId or None
92
+ Name or id of the variable.
93
+
94
+ Returns
95
+ -------
96
+ np.ndarray
97
+ The samples of variable <val> or all the samples if <val> is None.
98
+ """
99
+ if val is None:
100
+ return np.stack([self._id2samples[node] for node in self._model.nodes()]).T
101
+ else:
102
+ return self._id2samples[self._model.nameOrId(val)]
103
+
104
+ def mean_sample(self, val):
105
+ """
106
+ Figure out the mean of variable <val> in the samples
107
+
108
+ Parameters
109
+ ----------
110
+ val : NameOrId or None
111
+ Name or id of the variable.
112
+
113
+ Returns
114
+ -------
115
+ float
116
+ The mean of variable <val>'s samples or all the means as a dict if <val> is None.
117
+ """
118
+ if val is None:
119
+ return {self._model.variable(k).name(): self._id2samples[k].mean() for k in self._model.topologicalOrder()}
120
+ else:
121
+ return self._id2samples[self._model.nameOrId(val)].mean()
122
+
123
+ def variance_sample(self, val):
124
+ """
125
+ Figure out the variance of variable <val> in the samples.
126
+
127
+ Parameters
128
+ ----------
129
+ val : NameOrId or None
130
+ Name or id of the variable.
131
+
132
+ Returns
133
+ -------
134
+ float
135
+ The variance of variable <val>'s samples or all the variances as a dict if <val> is None.
136
+ """
137
+ if val is None:
138
+ return {self._model.variable(k).name(): self._id2samples[k].var() for k in self._model.topologicalOrder()}
139
+ else:
140
+ return self._id2samples[self._model.nameOrId(val)].var()
141
+
142
+ def stddev_sample(self, val):
143
+ """
144
+ Figure out the standard deviation of variable <val> in the samples.
145
+
146
+ Parameters
147
+ ----------
148
+ val : NameOrId
149
+ Name or id of the variable.
150
+
151
+ Returns
152
+ -------
153
+ float
154
+ The standard deviation of variable <val>'s samples or all the standard deviations as a dict if <val> is None.
155
+ """
156
+ if val is None:
157
+ return {self._model.variable(k).name(): self._id2samples[k].std() for k in self._model.topologicalOrder()}
158
+ else:
159
+ return self._id2samples[self._model.nameOrId(val)].std()
160
+
161
+ def covariance_sample(self, vals=None):
162
+ """
163
+ Computes the covariance between variables in <vals>.
164
+
165
+ Parameters
166
+ ----------
167
+ vals : List[NameOrId] option
168
+ List of names or ids of the variables.
169
+
170
+ Returns
171
+ -------
172
+ np.ndarray
173
+ The covariance between variables in <vals> or all the covariances if <vals> is None.
174
+ """
175
+ if vals is None:
176
+ samples = [self._id2samples[k] for k in self._model.nodes()]
177
+ else:
178
+ samples = [self._id2samples[self._model.nameOrId(k)] for k in vals]
179
+
180
+ return np.cov(samples)
181
+
182
+ def topandas(self):
183
+ """
184
+ Convert the samples to pandas dataframe.
185
+
186
+ Parameters
187
+ ----------
188
+ sample_name : str
189
+ Name of the new csv file.
190
+ """
191
+ return pd.DataFrame({self._model.name(node): self._id2samples[node] for node in self._model.nodes()})
192
+
193
+ def tocsv(self, sample_name: str):
194
+ """
195
+ Convert the samples to csv.
196
+
197
+ Parameters
198
+ ----------
199
+ sample_name : str
200
+ Name of the new csv file.
201
+ """
202
+ self.topandas().to_csv(sample_name, index=False, sep=",")