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.
- pyagrum/__init__.py +165 -0
- pyagrum/_pyagrum.so +0 -0
- pyagrum/bnmixture/BNMInference.py +268 -0
- pyagrum/bnmixture/BNMLearning.py +376 -0
- pyagrum/bnmixture/BNMixture.py +464 -0
- pyagrum/bnmixture/__init__.py +60 -0
- pyagrum/bnmixture/notebook.py +1058 -0
- pyagrum/causal/_CausalFormula.py +280 -0
- pyagrum/causal/_CausalModel.py +436 -0
- pyagrum/causal/__init__.py +81 -0
- pyagrum/causal/_causalImpact.py +356 -0
- pyagrum/causal/_dSeparation.py +598 -0
- pyagrum/causal/_doAST.py +761 -0
- pyagrum/causal/_doCalculus.py +361 -0
- pyagrum/causal/_doorCriteria.py +374 -0
- pyagrum/causal/_exceptions.py +95 -0
- pyagrum/causal/_types.py +61 -0
- pyagrum/causal/causalEffectEstimation/_CausalEffectEstimation.py +1175 -0
- pyagrum/causal/causalEffectEstimation/_IVEstimators.py +718 -0
- pyagrum/causal/causalEffectEstimation/_RCTEstimators.py +132 -0
- pyagrum/causal/causalEffectEstimation/__init__.py +46 -0
- pyagrum/causal/causalEffectEstimation/_backdoorEstimators.py +774 -0
- pyagrum/causal/causalEffectEstimation/_causalBNEstimator.py +324 -0
- pyagrum/causal/causalEffectEstimation/_frontdoorEstimators.py +396 -0
- pyagrum/causal/causalEffectEstimation/_learners.py +118 -0
- pyagrum/causal/causalEffectEstimation/_utils.py +466 -0
- pyagrum/causal/notebook.py +172 -0
- pyagrum/clg/CLG.py +658 -0
- pyagrum/clg/GaussianVariable.py +111 -0
- pyagrum/clg/SEM.py +312 -0
- pyagrum/clg/__init__.py +63 -0
- pyagrum/clg/canonicalForm.py +408 -0
- pyagrum/clg/constants.py +54 -0
- pyagrum/clg/forwardSampling.py +202 -0
- pyagrum/clg/learning.py +776 -0
- pyagrum/clg/notebook.py +480 -0
- pyagrum/clg/variableElimination.py +271 -0
- pyagrum/common.py +60 -0
- pyagrum/config.py +319 -0
- pyagrum/ctbn/CIM.py +513 -0
- pyagrum/ctbn/CTBN.py +573 -0
- pyagrum/ctbn/CTBNGenerator.py +216 -0
- pyagrum/ctbn/CTBNInference.py +459 -0
- pyagrum/ctbn/CTBNLearner.py +161 -0
- pyagrum/ctbn/SamplesStats.py +671 -0
- pyagrum/ctbn/StatsIndepTest.py +355 -0
- pyagrum/ctbn/__init__.py +79 -0
- pyagrum/ctbn/constants.py +54 -0
- pyagrum/ctbn/notebook.py +264 -0
- pyagrum/defaults.ini +199 -0
- pyagrum/deprecated.py +95 -0
- pyagrum/explain/_ComputationCausal.py +75 -0
- pyagrum/explain/_ComputationConditional.py +48 -0
- pyagrum/explain/_ComputationMarginal.py +48 -0
- pyagrum/explain/_CustomShapleyCache.py +110 -0
- pyagrum/explain/_Explainer.py +176 -0
- pyagrum/explain/_Explanation.py +70 -0
- pyagrum/explain/_FIFOCache.py +54 -0
- pyagrum/explain/_ShallCausalValues.py +204 -0
- pyagrum/explain/_ShallConditionalValues.py +155 -0
- pyagrum/explain/_ShallMarginalValues.py +155 -0
- pyagrum/explain/_ShallValues.py +296 -0
- pyagrum/explain/_ShapCausalValues.py +208 -0
- pyagrum/explain/_ShapConditionalValues.py +126 -0
- pyagrum/explain/_ShapMarginalValues.py +191 -0
- pyagrum/explain/_ShapleyValues.py +298 -0
- pyagrum/explain/__init__.py +81 -0
- pyagrum/explain/_explGeneralizedMarkovBlanket.py +152 -0
- pyagrum/explain/_explIndependenceListForPairs.py +146 -0
- pyagrum/explain/_explInformationGraph.py +264 -0
- pyagrum/explain/notebook/__init__.py +54 -0
- pyagrum/explain/notebook/_bar.py +142 -0
- pyagrum/explain/notebook/_beeswarm.py +174 -0
- pyagrum/explain/notebook/_showShapValues.py +97 -0
- pyagrum/explain/notebook/_waterfall.py +220 -0
- pyagrum/explain/shapley.py +225 -0
- pyagrum/lib/__init__.py +46 -0
- pyagrum/lib/_colors.py +390 -0
- pyagrum/lib/bn2graph.py +299 -0
- pyagrum/lib/bn2roc.py +1026 -0
- pyagrum/lib/bn2scores.py +217 -0
- pyagrum/lib/bn_vs_bn.py +605 -0
- pyagrum/lib/cn2graph.py +305 -0
- pyagrum/lib/discreteTypeProcessor.py +1102 -0
- pyagrum/lib/discretizer.py +58 -0
- pyagrum/lib/dynamicBN.py +390 -0
- pyagrum/lib/explain.py +57 -0
- pyagrum/lib/export.py +84 -0
- pyagrum/lib/id2graph.py +258 -0
- pyagrum/lib/image.py +387 -0
- pyagrum/lib/ipython.py +307 -0
- pyagrum/lib/mrf2graph.py +471 -0
- pyagrum/lib/notebook.py +1821 -0
- pyagrum/lib/proba_histogram.py +552 -0
- pyagrum/lib/utils.py +138 -0
- pyagrum/pyagrum.py +31495 -0
- pyagrum/skbn/_MBCalcul.py +242 -0
- pyagrum/skbn/__init__.py +49 -0
- pyagrum/skbn/_learningMethods.py +282 -0
- pyagrum/skbn/_utils.py +297 -0
- pyagrum/skbn/bnclassifier.py +1014 -0
- pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSE.md +12 -0
- pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
- pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/MIT.txt +18 -0
- pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/METADATA +145 -0
- pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/RECORD +107 -0
- 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])
|
pyagrum/clg/constants.py
ADDED
|
@@ -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=",")
|