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,598 @@
|
|
|
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 file defines functions for dSeparation crtieria
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from typing import Set
|
|
46
|
+
|
|
47
|
+
import pyagrum
|
|
48
|
+
|
|
49
|
+
from pyagrum.causal._types import NodeId, DirectedModel, NodeSet
|
|
50
|
+
|
|
51
|
+
# pylint: disable=unused-import
|
|
52
|
+
import pyagrum.causal # for annotations
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def isParent(a: NodeId, b: NodeId, g: "pyagrum.BayesNet") -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Predicate on whether ``a`` is parent of ``b`` in the graph ``g``, the graph must be a DAG
|
|
58
|
+
"""
|
|
59
|
+
return g.existsArc(a, b)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ancester(x: NodeId, dm: DirectedModel, anc: NodeSet):
|
|
63
|
+
"""
|
|
64
|
+
Adds the ancestors of ``x`` in the Bayesian network ``bn`` to the set ``P``
|
|
65
|
+
"""
|
|
66
|
+
for parent in dm.parents(x):
|
|
67
|
+
if parent not in anc:
|
|
68
|
+
anc.add(parent)
|
|
69
|
+
ancester(parent, dm, anc)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _reduce_moralize(bn: "pyagrum.BayesNet", x: NodeSet, y: NodeSet, zset: NodeSet) -> "pyagrum.UndiGraph":
|
|
73
|
+
"""
|
|
74
|
+
Returns the undirected graph obtained by reducing (ancestor graph) and moralizing the Bayesian network ``bn``
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
bn: pyagrum.BayesNet
|
|
79
|
+
the BayesNet
|
|
80
|
+
x: Set[int|str]
|
|
81
|
+
NodeSet generating the ancestor graph
|
|
82
|
+
y: Set[int|str]
|
|
83
|
+
Second NodeSet generating the ancestor graph
|
|
84
|
+
zset: Set[int|str]
|
|
85
|
+
Third NodeSet generating the ancestor graph
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
pyagrum.UndiGraph
|
|
90
|
+
The reduced moralized graph
|
|
91
|
+
"""
|
|
92
|
+
G = pyagrum.UndiGraph()
|
|
93
|
+
|
|
94
|
+
Ancetre = x | y
|
|
95
|
+
anc = frozenset(Ancetre)
|
|
96
|
+
for i in anc:
|
|
97
|
+
ancester(i, bn, Ancetre)
|
|
98
|
+
|
|
99
|
+
for i in zset:
|
|
100
|
+
Ancetre.add(i)
|
|
101
|
+
ancester(i, bn, Ancetre)
|
|
102
|
+
for i in Ancetre:
|
|
103
|
+
G.addNodeWithId(i)
|
|
104
|
+
|
|
105
|
+
for b in G.nodes():
|
|
106
|
+
for a in bn.parents(b):
|
|
107
|
+
G.addEdge(a, b)
|
|
108
|
+
|
|
109
|
+
for nod in G.nodes():
|
|
110
|
+
parents_nod = bn.parents(nod)
|
|
111
|
+
for par in parents_nod:
|
|
112
|
+
for par2 in parents_nod:
|
|
113
|
+
if par2 != par:
|
|
114
|
+
G.addEdge(par, par2)
|
|
115
|
+
|
|
116
|
+
return G
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _removeZ(g_undi: "pyagrum.UndiGraph", zset: NodeSet):
|
|
120
|
+
for node in g_undi.nodes():
|
|
121
|
+
if node in zset:
|
|
122
|
+
g_undi.eraseNode(node)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _is_path_x_y(g_undi: "pyagrum.UndiGraph", sx: NodeSet, sy: NodeSet, marked: NodeSet = None) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
Predicate asserting the existence of a path between ``x`` and ``y`` in the non-oriented graph
|
|
128
|
+
``g_undi``, without going through the optional marking set ``mark``
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
g_undi: PyAgrum.UndiGraph
|
|
133
|
+
The graph
|
|
134
|
+
x: int
|
|
135
|
+
first node
|
|
136
|
+
y: int
|
|
137
|
+
second node
|
|
138
|
+
|
|
139
|
+
marked: Set[int]
|
|
140
|
+
forbidden nodes
|
|
141
|
+
|
|
142
|
+
:return:
|
|
143
|
+
bool
|
|
144
|
+
True if a path has been found
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def inner_ec(g_und: "pyagrum.UndiGraph", a: NodeId, b: NodeSet, m: NodeSet) -> bool:
|
|
148
|
+
if a in b:
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
m.add(a)
|
|
152
|
+
|
|
153
|
+
for n in g_und.neighbours(a):
|
|
154
|
+
if n not in m:
|
|
155
|
+
if inner_ec(g_und, n, b, m):
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
if len(sx) > len(sy):
|
|
161
|
+
ssx, ssy = sy, sx
|
|
162
|
+
else:
|
|
163
|
+
ssx, ssy = sx, sy
|
|
164
|
+
|
|
165
|
+
if marked is None:
|
|
166
|
+
marked = set()
|
|
167
|
+
|
|
168
|
+
ma = set(marked)
|
|
169
|
+
for i in ssx:
|
|
170
|
+
ma.add(i)
|
|
171
|
+
if inner_ec(g_undi, i, ssy, ma):
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def isDSep_tech2(bn: "pyagrum.BayesNet", sx: NodeSet, sy: NodeSet, zset: NodeSet) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Test of d-separation for ``x`` and ``y``, given ``zset`` using the graph-moralization method
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
bn: pyagrum.BayesNet
|
|
184
|
+
the bayesian network
|
|
185
|
+
sx: Set[int]
|
|
186
|
+
source nodes
|
|
187
|
+
sy: Set[int]
|
|
188
|
+
destinantion nodes
|
|
189
|
+
zset: Set[int]
|
|
190
|
+
blocking set
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
bool
|
|
195
|
+
True if ``Z`` d-separates ``x`` and ``y``
|
|
196
|
+
"""
|
|
197
|
+
g_undi = _reduce_moralize(bn, sx, sy, zset)
|
|
198
|
+
|
|
199
|
+
_removeZ(g_undi, zset)
|
|
200
|
+
|
|
201
|
+
if _is_path_x_y(g_undi, sx, sy):
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def isDSep_parents(bn: "pyagrum.BayesNet", sx: NodeSet, sy: NodeSet, zset: NodeSet) -> bool:
|
|
208
|
+
"""Test of d-separation of ``sx`` and ``sy`` given ``Z``, considering only the paths with an arc coming into ``x``
|
|
209
|
+
using the graph-moralization method
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
bn: pyagrum.BayesNet
|
|
215
|
+
the bayesian network
|
|
216
|
+
sx: Set[int]
|
|
217
|
+
source nodes
|
|
218
|
+
sy: Set[int]
|
|
219
|
+
destinantion nodes
|
|
220
|
+
zset: Set[int]
|
|
221
|
+
blocking set
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
bool
|
|
226
|
+
True if ``Z`` d-separates ``x`` and ``y``
|
|
227
|
+
"""
|
|
228
|
+
return _isDSep_tech2_parents(bn, sx, sy, zset)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _isDSep_tech2_parents(bn: "pyagrum.BayesNet", sx: NodeSet, sy: NodeSet, zset: NodeSet) -> bool:
|
|
232
|
+
"""Test of d-separation of ``sx`` and ``sy`` given ``Z``, considering only the paths with an arc coming into ``x``
|
|
233
|
+
using the graph-moralization method
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
bn: pyagrum.BayesNet
|
|
238
|
+
the bayesian network
|
|
239
|
+
sx: Set[int]
|
|
240
|
+
source nodes
|
|
241
|
+
sy: Set[int]
|
|
242
|
+
destinantion nodes
|
|
243
|
+
zset: Set[int]
|
|
244
|
+
blocking set
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
bool
|
|
249
|
+
"""
|
|
250
|
+
G = pyagrum.UndiGraph()
|
|
251
|
+
ancesters = sx | sy
|
|
252
|
+
anc = frozenset(ancesters)
|
|
253
|
+
for i in anc:
|
|
254
|
+
ancester(i, bn, ancesters)
|
|
255
|
+
|
|
256
|
+
for i in zset:
|
|
257
|
+
ancesters.add(i)
|
|
258
|
+
ancester(i, bn, ancesters)
|
|
259
|
+
for i in ancesters:
|
|
260
|
+
G.addNodeWithId(i)
|
|
261
|
+
|
|
262
|
+
for b in G.nodes():
|
|
263
|
+
for a in set(bn.parents(b)) - sx:
|
|
264
|
+
G.addEdge(a, b)
|
|
265
|
+
|
|
266
|
+
for nod in G.nodes():
|
|
267
|
+
parents_nod = set(bn.parents(nod)) - sx
|
|
268
|
+
for par in parents_nod:
|
|
269
|
+
for par2 in parents_nod:
|
|
270
|
+
if par2 != par:
|
|
271
|
+
G.addEdge(par, par2)
|
|
272
|
+
|
|
273
|
+
_removeZ(G, zset)
|
|
274
|
+
|
|
275
|
+
if _is_path_x_y(G, sx, sy):
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _isDSep_tech2_children(bn: "pyagrum.BayesNet", sx: NodeSet, sy: NodeSet, zset: NodeSet) -> bool:
|
|
282
|
+
"""Test of d-separation of ``x`` and ``y`` given ``zset``, considering only the paths with an arc coming from ``x``
|
|
283
|
+
using the graph-moralization method
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
bn: pyagrum.BayesNet
|
|
288
|
+
the bayesian network
|
|
289
|
+
sx: Set[int]
|
|
290
|
+
source nodes
|
|
291
|
+
sy: Set[int]
|
|
292
|
+
destinantion nodes
|
|
293
|
+
zset: Set[int]
|
|
294
|
+
blocking set
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
bool
|
|
299
|
+
"""
|
|
300
|
+
G = pyagrum.UndiGraph()
|
|
301
|
+
ancesters = sx | sy
|
|
302
|
+
for i in sy:
|
|
303
|
+
ancester(i, bn, ancesters)
|
|
304
|
+
# sx's ancesters will not be added since sx already is in ancesters
|
|
305
|
+
for i in zset:
|
|
306
|
+
ancesters.add(i)
|
|
307
|
+
ancester(i, bn, ancesters)
|
|
308
|
+
for i in ancesters:
|
|
309
|
+
G.addNodeWithId(i)
|
|
310
|
+
se = set(G.nodes()) - sx
|
|
311
|
+
for b in se:
|
|
312
|
+
for a in bn.parents(b):
|
|
313
|
+
G.addEdge(a, b)
|
|
314
|
+
|
|
315
|
+
for nod in se:
|
|
316
|
+
parents_nod = bn.parents(nod)
|
|
317
|
+
for par in parents_nod:
|
|
318
|
+
for par2 in parents_nod:
|
|
319
|
+
if par2 != par:
|
|
320
|
+
G.addEdge(par, par2)
|
|
321
|
+
_removeZ(G, zset)
|
|
322
|
+
|
|
323
|
+
if _is_path_x_y(G, sx, sy):
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _is_descendant(bn: "pyagrum.BayesNet", x: NodeId, y: NodeId, marked: NodeSet = None) -> bool:
|
|
330
|
+
"""Asserts whether or not ``x`` is a descendant of ``y`` in ``bn``"""
|
|
331
|
+
|
|
332
|
+
if marked is None:
|
|
333
|
+
marked = set()
|
|
334
|
+
|
|
335
|
+
if isParent(y, x, bn):
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
for c in bn.children(y):
|
|
339
|
+
if c not in marked:
|
|
340
|
+
marked.add(c)
|
|
341
|
+
if _is_descendant(bn, x, c, marked):
|
|
342
|
+
return True
|
|
343
|
+
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _is_ascendant(bn: "pyagrum.BayesNet", x: NodeId, y: NodeId, marquage: Set[int] = None) -> bool:
|
|
348
|
+
"""Predicate on whether ``x`` is an ancestor of ``y`` in the Bayesian network ``bn``"""
|
|
349
|
+
|
|
350
|
+
if isParent(x, y, bn):
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
if marquage is None:
|
|
354
|
+
marquage = set()
|
|
355
|
+
|
|
356
|
+
for p in bn.parents(y):
|
|
357
|
+
if p not in marquage:
|
|
358
|
+
marquage.add(p)
|
|
359
|
+
if _is_ascendant(bn, x, p, marquage):
|
|
360
|
+
return True
|
|
361
|
+
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def descendants(bn: "pyagrum.BayesNet", x: NodeId, marked: NodeSet = None, ensdesc: NodeSet = None) -> NodeSet:
|
|
366
|
+
"""Returns a set composed by all the descendents of ``x`` in ``bn``"""
|
|
367
|
+
if marked is None:
|
|
368
|
+
marked = set()
|
|
369
|
+
if ensdesc is None:
|
|
370
|
+
ensdesc = set()
|
|
371
|
+
|
|
372
|
+
ensdesc = ensdesc | set(bn.children(x))
|
|
373
|
+
|
|
374
|
+
for c in bn.children(x):
|
|
375
|
+
if c not in marked:
|
|
376
|
+
marked.add(c)
|
|
377
|
+
ensdesc = ensdesc | descendants(bn, c, marked)
|
|
378
|
+
|
|
379
|
+
return ensdesc
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _filaires(bn: DirectedModel, interest: NodeSet = None, inf: bool = True) -> NodeSet:
|
|
383
|
+
s = set()
|
|
384
|
+
|
|
385
|
+
if interest is None:
|
|
386
|
+
interest = set()
|
|
387
|
+
|
|
388
|
+
for x in bn.nodes():
|
|
389
|
+
if len(set(bn.parents(x)) - s) == 0 and len(bn.children(x)) == 1 and x not in interest:
|
|
390
|
+
a = x
|
|
391
|
+
while True:
|
|
392
|
+
s.add(a)
|
|
393
|
+
for a in bn.children(a):
|
|
394
|
+
break # take the first elt
|
|
395
|
+
if len(bn.children(a)) != 1 or len(set(bn.parents(a)) - s) != 0 or a in interest:
|
|
396
|
+
break
|
|
397
|
+
|
|
398
|
+
if inf and len(bn.children(x)) == 0 and len(bn.parents(x)) == 1 and x not in interest:
|
|
399
|
+
a = x
|
|
400
|
+
while True:
|
|
401
|
+
s.add(a)
|
|
402
|
+
for a in bn.parents(a):
|
|
403
|
+
break # take the first elt
|
|
404
|
+
if len(bn.children(a)) != 1 or a in interest:
|
|
405
|
+
break
|
|
406
|
+
if len(bn.parents(a)) != 1:
|
|
407
|
+
s.add(a)
|
|
408
|
+
break
|
|
409
|
+
return s
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _barren_nodes(bn: "pyagrum.BayesNet", interest: NodeSet = None) -> NodeSet:
|
|
413
|
+
"""Returns the set of recursively determined barren nodes in ``bn`` relatively to the set of nodes ``interest`` (if
|
|
414
|
+
``interest`` is void, then the whole set of nodes in the graph will be returned)"""
|
|
415
|
+
s = set()
|
|
416
|
+
|
|
417
|
+
if interest is None:
|
|
418
|
+
interest = set()
|
|
419
|
+
|
|
420
|
+
def inner_rec(a):
|
|
421
|
+
if a in interest | s:
|
|
422
|
+
return
|
|
423
|
+
s.add(a)
|
|
424
|
+
for b in bn.parents(a):
|
|
425
|
+
if len(set(bn.children(b)) - s) == 0 and (b not in s):
|
|
426
|
+
inner_rec(b)
|
|
427
|
+
|
|
428
|
+
for x in bn.nodes():
|
|
429
|
+
if len(bn.children(x)) == 0:
|
|
430
|
+
inner_rec(x)
|
|
431
|
+
|
|
432
|
+
return s
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def partialDAGFromBN(bn: "pyagrum.BayesNet", Nexcl: NodeSet = None) -> "pyagrum.DAG":
|
|
436
|
+
"""
|
|
437
|
+
Creates and returns a duplicate DAG of the given Bayesian network
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
bn : pyagrum.BayesNet
|
|
442
|
+
the source
|
|
443
|
+
Nexcl : NodeSet
|
|
444
|
+
the nodes
|
|
445
|
+
|
|
446
|
+
Returns
|
|
447
|
+
-------
|
|
448
|
+
pyagrum.DAG
|
|
449
|
+
"""
|
|
450
|
+
if Nexcl is None:
|
|
451
|
+
Nexcl = set()
|
|
452
|
+
d = pyagrum.DAG()
|
|
453
|
+
|
|
454
|
+
nodes = set(bn.nodes()) - (Nexcl)
|
|
455
|
+
for n in nodes:
|
|
456
|
+
d.addNodeWithId(n)
|
|
457
|
+
|
|
458
|
+
for x, y in bn.arcs():
|
|
459
|
+
if x in nodes and y in nodes:
|
|
460
|
+
d.addArc(x, y)
|
|
461
|
+
|
|
462
|
+
return d
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def dSep_reduce(g: "pyagrum.BayesNet", interest: NodeSet = None) -> "pyagrum.DAG":
|
|
466
|
+
"""
|
|
467
|
+
Reduce a BN by removing barren nodes w.r.t a set of nodes.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
g : pyagrum.BayesNet
|
|
472
|
+
the source
|
|
473
|
+
interest: NodeSet
|
|
474
|
+
the nodes of interest
|
|
475
|
+
|
|
476
|
+
Returns
|
|
477
|
+
-------
|
|
478
|
+
pyagrum.DAG
|
|
479
|
+
the reduced DAG
|
|
480
|
+
"""
|
|
481
|
+
if interest is None:
|
|
482
|
+
interest = set()
|
|
483
|
+
|
|
484
|
+
barren = _barren_nodes(g, interest)
|
|
485
|
+
|
|
486
|
+
reduced_g = partialDAGFromBN(g, barren)
|
|
487
|
+
|
|
488
|
+
for f in _filaires(reduced_g, interest, False):
|
|
489
|
+
reduced_g.eraseNode(f)
|
|
490
|
+
|
|
491
|
+
return reduced_g
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _blocked(
|
|
495
|
+
bn: "pyagrum.BayesNet", pht: bool, x: NodeSet, y: NodeSet, setz: NodeSet, marquage0: Set[int], marquage1: Set[int]
|
|
496
|
+
) -> bool:
|
|
497
|
+
"""
|
|
498
|
+
internal method to check if a path is blocked
|
|
499
|
+
"""
|
|
500
|
+
if x in y:
|
|
501
|
+
return False
|
|
502
|
+
|
|
503
|
+
isInxZ = x in setz
|
|
504
|
+
wasIn = x in marquage0 or x in marquage1
|
|
505
|
+
|
|
506
|
+
if pht:
|
|
507
|
+
marquage1.add(x)
|
|
508
|
+
else:
|
|
509
|
+
marquage0.add(x)
|
|
510
|
+
|
|
511
|
+
if not isInxZ and not wasIn:
|
|
512
|
+
for c in bn.children(x):
|
|
513
|
+
if c not in marquage1 and not _blocked(bn, True, c, y, setz, marquage0, marquage1):
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
if pht:
|
|
517
|
+
if isInxZ or len(setz & descendants(bn, x)) != 0:
|
|
518
|
+
for p in bn.parents(x):
|
|
519
|
+
if p not in marquage0 and not _blocked(bn, False, p, y, setz, marquage0, marquage1):
|
|
520
|
+
return False
|
|
521
|
+
|
|
522
|
+
else:
|
|
523
|
+
if not isInxZ:
|
|
524
|
+
for p in bn.parents(x):
|
|
525
|
+
if p not in marquage0 and not _blocked(bn, False, p, y, setz, marquage0, marquage1):
|
|
526
|
+
return False
|
|
527
|
+
|
|
528
|
+
return True
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _isDSep_tech1_parents(bn: "pyagrum.BayesNet", x: NodeId, sy: NodeSet, zset: NodeSet, reduced: bool = False) -> bool:
|
|
532
|
+
"""Test of d-separation of ``x`` and ``y`` given ``Z``, considering only the paths with an arc coming into ``x``
|
|
533
|
+
using the usual paths method"""
|
|
534
|
+
|
|
535
|
+
if not reduced and len(bn.nodes()) > 170:
|
|
536
|
+
g = dSep_reduce(bn, sy | zset | {x})
|
|
537
|
+
else:
|
|
538
|
+
g = bn
|
|
539
|
+
|
|
540
|
+
for p in g.parents(x):
|
|
541
|
+
if not _blocked(g, False, p, sy, zset, {x}, {x}):
|
|
542
|
+
return False
|
|
543
|
+
return True
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _isDSep_tech1_children(bn: "pyagrum.BayesNet", x: NodeId, sy: NodeSet, setz: NodeSet, reduced=False) -> bool:
|
|
547
|
+
"""Test of d-separation of ``x`` and ``y`` given ``Z``, considering only the paths with an arc coming from ``x``
|
|
548
|
+
using the usual paths method"""
|
|
549
|
+
|
|
550
|
+
if not reduced and len(bn.nodes()) > 170:
|
|
551
|
+
g = dSep_reduce(bn, sy | setz | {x})
|
|
552
|
+
else:
|
|
553
|
+
g = bn
|
|
554
|
+
|
|
555
|
+
for c in g.children(x):
|
|
556
|
+
if not _blocked(g, True, c, sy, setz, {x}, {x}):
|
|
557
|
+
return False
|
|
558
|
+
return True
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def isDSep_tech1(bn: "pyagrum.BayesNet", sx: NodeSet, sy: NodeSet, setz: NodeSet, reduced=False) -> bool:
|
|
562
|
+
"""Test of d-separation for ``x`` and ``y``, given ``Z`` using the usual paths method"""
|
|
563
|
+
|
|
564
|
+
if len(sx) > len(sy):
|
|
565
|
+
sx, sy = sy, sx
|
|
566
|
+
|
|
567
|
+
if not reduced and len(bn.nodes()) > 170:
|
|
568
|
+
g = dSep_reduce(bn, sx | sy | setz)
|
|
569
|
+
else:
|
|
570
|
+
g = bn
|
|
571
|
+
for i in sx:
|
|
572
|
+
if not _isDSep_tech1_parents(g, i, sy, setz, True):
|
|
573
|
+
return False
|
|
574
|
+
if not _isDSep_tech1_children(g, i, sy, setz, True):
|
|
575
|
+
return False
|
|
576
|
+
return True
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def isDSep(bn: "pyagrum.BayesNet", x: NodeSet, y: NodeSet, z: NodeSet) -> bool:
|
|
580
|
+
"""
|
|
581
|
+
Check if x and y are d-separated by z in the BN
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
bn: pyagrum.BayesNet
|
|
586
|
+
|
|
587
|
+
x : NodeSet
|
|
588
|
+
|
|
589
|
+
y : NodeSet
|
|
590
|
+
|
|
591
|
+
z : NodeSet
|
|
592
|
+
|
|
593
|
+
Returns
|
|
594
|
+
-------
|
|
595
|
+
bool
|
|
596
|
+
whether x and y are d-separated by z
|
|
597
|
+
"""
|
|
598
|
+
return isDSep_tech2(bn, x, y, z)
|