pyAgrum-nightly 2.3.0.9.dev202512061764412981__cp310-abi3-macosx_11_0_arm64.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 +171 -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.0.9.dev202512061764412981.dist-info/LICENSE.md +12 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/MIT.txt +18 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/METADATA +145 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/RECORD +107 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,58 @@
|
|
|
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 contains the deprecated Discretizer class. See the new DiscreteTypeProcessor class.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
import warnings
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def Discretizer(*args, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
Warnings
|
|
51
|
+
------
|
|
52
|
+
DeprecationWarning
|
|
53
|
+
If the function is called it warns the deprecation of this class, creates a discreteTypeProcessor object and returns it.
|
|
54
|
+
"""
|
|
55
|
+
warnings.warn("Discretizer is deprecated since pyAgrum>=2.0.0. Use DiscreteTypeProcessor instead", DeprecationWarning)
|
|
56
|
+
from .discreteTypeProcessor import DiscreteTypeProcessor
|
|
57
|
+
|
|
58
|
+
return DiscreteTypeProcessor(*args, **kwargs)
|
pyagrum/lib/dynamicBN.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
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 provide basic tools for dealing with dynamic Bayesian Network (and inference) : modeling, visualisation, inference.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
import numpy as np
|
|
46
|
+
import pydot as dot
|
|
47
|
+
|
|
48
|
+
import matplotlib.pyplot as plt
|
|
49
|
+
from matplotlib.patches import Rectangle
|
|
50
|
+
|
|
51
|
+
import pyagrum as gum
|
|
52
|
+
|
|
53
|
+
noTimeCluster = "void"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _splitName(name):
|
|
57
|
+
"""
|
|
58
|
+
By convention, name of dynamic variable "X" in dBN may be
|
|
59
|
+
- "X0" for timeslice 0 both in unrolled BN and in 2TBN
|
|
60
|
+
- "Xt" for timeslice t in a 2TBN
|
|
61
|
+
- "X"+str(i) for timelice i with integer i>0 in unrolled BN
|
|
62
|
+
- other naes are not in a timeslice
|
|
63
|
+
@argument name : str (name of the dynamic variable)
|
|
64
|
+
@return static_name,timeslice with timeslice =noTimeCluster,"t" or str(i)
|
|
65
|
+
"""
|
|
66
|
+
if name[-1] == "t":
|
|
67
|
+
return name[:-1], "t"
|
|
68
|
+
i = len(name) - 1
|
|
69
|
+
if not name[i].isdigit():
|
|
70
|
+
return name, noTimeCluster
|
|
71
|
+
|
|
72
|
+
while name[i].isdigit():
|
|
73
|
+
if i == 0:
|
|
74
|
+
return name, noTimeCluster
|
|
75
|
+
i -= 1
|
|
76
|
+
|
|
77
|
+
return name[: i + 1], name[i + 1 :]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _isInFirstTimeSlice(name):
|
|
81
|
+
"""
|
|
82
|
+
@return true if there is a 0 at the end of name
|
|
83
|
+
"""
|
|
84
|
+
return name[-1] == "0"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _isInSecondTimeSlice(name):
|
|
88
|
+
"""
|
|
89
|
+
@return true if there is a t at the end of name
|
|
90
|
+
"""
|
|
91
|
+
return name[-1] == "t"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _isInNoTimeSlice(name):
|
|
95
|
+
return name[-1] not in ["0", "t"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def realNameFrom2TBNname(name, ts):
|
|
99
|
+
"""
|
|
100
|
+
@return dynamic name from static name and timeslice (no check)
|
|
101
|
+
"""
|
|
102
|
+
return f"{name[:-1]}{ts}" if not _isInNoTimeSlice(name) else name
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def getTimeSlicesRange(dbn):
|
|
106
|
+
"""
|
|
107
|
+
get the range and (name,radical) of each variables
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
dbn: gum.BayesNet
|
|
112
|
+
a 2TBN or an unrolled BN
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
------
|
|
117
|
+
Dict[str,List[T[str,str]]]
|
|
118
|
+
all the timeslice of a dbn : ['0','t'] for a classic 2TBN, range(T) for a classic unrolled BN
|
|
119
|
+
"""
|
|
120
|
+
timeslices = {}
|
|
121
|
+
|
|
122
|
+
for i in dbn.nodes():
|
|
123
|
+
n = dbn.variable(i).name()
|
|
124
|
+
label, ts = _splitName(n)
|
|
125
|
+
if ts in timeslices:
|
|
126
|
+
timeslices[ts].append((n, label))
|
|
127
|
+
else:
|
|
128
|
+
timeslices[ts] = [(n, label)]
|
|
129
|
+
|
|
130
|
+
return timeslices
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def is2TBN(bn):
|
|
134
|
+
"""
|
|
135
|
+
Check if bn is a 2 TimeSlice Bayesian network
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
bn: pyagrum.BayesNet
|
|
140
|
+
the Bayesian network
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
bool
|
|
145
|
+
True if the BN is syntaxically correct to be a 2TBN
|
|
146
|
+
"""
|
|
147
|
+
ts = getTimeSlicesRange(bn)
|
|
148
|
+
if not set(ts.keys()) <= {noTimeCluster, "0", "t"}:
|
|
149
|
+
return False, "Some variables are not correctly suffixed."
|
|
150
|
+
|
|
151
|
+
domainSizes = dict()
|
|
152
|
+
for name, radical in ts["t"]:
|
|
153
|
+
domainSizes[radical] = bn.variable(name).domainSize()
|
|
154
|
+
|
|
155
|
+
res = ""
|
|
156
|
+
for name, radical in ts["0"]:
|
|
157
|
+
if radical in domainSizes:
|
|
158
|
+
if domainSizes[radical] != bn.variable(name).domainSize():
|
|
159
|
+
res = f"\n - for variables {name}/{radical}t"
|
|
160
|
+
|
|
161
|
+
if res != "":
|
|
162
|
+
return False, "Domain size mismatch : " + res
|
|
163
|
+
|
|
164
|
+
return True, ""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _TimeSlicesToDot(dbn):
|
|
168
|
+
"""
|
|
169
|
+
Try to correctly represent dBN and 2TBN in dot format
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
dbn: pyagrum.BayesNet
|
|
174
|
+
a 2TBN or an unrolled BN
|
|
175
|
+
"""
|
|
176
|
+
timeslices = getTimeSlicesRange(dbn)
|
|
177
|
+
kts = sorted(timeslices.keys(), key=lambda x: -1 if x == noTimeCluster else 1e8 if x == "t" else int(x))
|
|
178
|
+
|
|
179
|
+
# dynamic member makes pylink unhappy
|
|
180
|
+
# pylint: disable=no-member
|
|
181
|
+
g = dot.Dot(graph_type="digraph")
|
|
182
|
+
g.set_rankdir("LR")
|
|
183
|
+
g.set_splines("ortho")
|
|
184
|
+
g.set_node_defaults(color="#000000", fillcolor="white", style="filled")
|
|
185
|
+
|
|
186
|
+
for k in kts:
|
|
187
|
+
if k != noTimeCluster:
|
|
188
|
+
cluster = dot.Cluster(k, label=f"Time slice {k}", bgcolor="#DDDDDD", rankdir="same")
|
|
189
|
+
g.add_subgraph(cluster)
|
|
190
|
+
else:
|
|
191
|
+
cluster = g # small trick to add in graph variable in no timeslice
|
|
192
|
+
for n, label in sorted(timeslices[k]):
|
|
193
|
+
cluster.add_node(dot.Node('"' + n + '"', label='"' + label + '"'))
|
|
194
|
+
|
|
195
|
+
g.set_edge_defaults(color="blue", constraint="False")
|
|
196
|
+
for tail, head in dbn.arcs():
|
|
197
|
+
g.add_edge(dot.Edge('"' + dbn.variable(tail).name() + '"', '"' + dbn.variable(head).name() + '"'))
|
|
198
|
+
|
|
199
|
+
g.set_edge_defaults(style="invis", constraint="True")
|
|
200
|
+
for x in timeslices["0"]:
|
|
201
|
+
name = x[1]
|
|
202
|
+
prec = None
|
|
203
|
+
for k in kts:
|
|
204
|
+
if k == noTimeCluster:
|
|
205
|
+
continue
|
|
206
|
+
if prec is not None:
|
|
207
|
+
g.add_edge(dot.Edge(f'"{name}{prec}"', f'"{name}{k}"'))
|
|
208
|
+
prec = k
|
|
209
|
+
|
|
210
|
+
return g
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def showTimeSlices(dbn, size=None):
|
|
214
|
+
"""
|
|
215
|
+
Try to correctly display dBN and 2TBN
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
dbn: pyagrum.BayesNet
|
|
220
|
+
a 2TBN or an unrolled BN
|
|
221
|
+
size: int or str
|
|
222
|
+
size of the fig
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
# jupyter notebooks is optional
|
|
226
|
+
# pylint: disable=import-outside-toplevel
|
|
227
|
+
from pyagrum.lib.notebook import showGraph
|
|
228
|
+
|
|
229
|
+
if size is None:
|
|
230
|
+
size = gum.config["dynamicBN", "default_graph_size"]
|
|
231
|
+
|
|
232
|
+
showGraph(_TimeSlicesToDot(dbn), size)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def getTimeSlices(dbn, size=None):
|
|
236
|
+
"""
|
|
237
|
+
Try to correctly represent dBN and 2TBN as an HTML string
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
dbn: pyagrum.BayesNet
|
|
242
|
+
a 2TBN or an unrolled BN
|
|
243
|
+
size: int or str
|
|
244
|
+
size of the fig
|
|
245
|
+
"""
|
|
246
|
+
# jupyter notebooks is optional
|
|
247
|
+
# pylint: disable=import-outside-toplevel
|
|
248
|
+
from pyagrum.lib.notebook import getGraph
|
|
249
|
+
|
|
250
|
+
if size is None:
|
|
251
|
+
size = gum.config["dynamicBN", "default_graph_size"]
|
|
252
|
+
|
|
253
|
+
return getGraph(_TimeSlicesToDot(dbn), size)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def unroll2TBN(dbn, nbr):
|
|
257
|
+
"""
|
|
258
|
+
unroll a 2TBN given the nbr of timeslices
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
dbn: pyagrum.BayesNet
|
|
263
|
+
a 2TBN or an unrolled BN
|
|
264
|
+
nbr: int
|
|
265
|
+
the number of timeslice
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
pyagrum.BayesNet
|
|
270
|
+
unrolled BN from a 2TBN and the nbr of timeslices
|
|
271
|
+
"""
|
|
272
|
+
ts = getTimeSlicesRange(dbn)
|
|
273
|
+
if not {noTimeCluster, "0", "t"}.issuperset(ts.keys()) and {"0", "t"}.issubset(ts.keys()):
|
|
274
|
+
raise TypeError("unroll2TBN needs a 2-TimeSlice BN")
|
|
275
|
+
|
|
276
|
+
bn = gum.BayesNet()
|
|
277
|
+
|
|
278
|
+
# variable creation
|
|
279
|
+
for dbn_id in dbn.nodes():
|
|
280
|
+
name = dbn.variable(dbn_id).name()
|
|
281
|
+
if _isInNoTimeSlice(name):
|
|
282
|
+
bn.add(dbn.variable(dbn_id))
|
|
283
|
+
elif _isInFirstTimeSlice(name):
|
|
284
|
+
# create a clone of the variable in the new bn
|
|
285
|
+
bn.add(dbn.variable(dbn_id))
|
|
286
|
+
else:
|
|
287
|
+
for ts in range(1, nbr):
|
|
288
|
+
# create a clone of the variable in the new bn
|
|
289
|
+
nid = bn.add(dbn.variable(dbn_id))
|
|
290
|
+
bn.changeVariableName(nid, realNameFrom2TBNname(name, ts)) # create the true name
|
|
291
|
+
|
|
292
|
+
# add parents
|
|
293
|
+
# the main pb : to have the same order for parents w.r.t the order in 2TBN
|
|
294
|
+
for dbn_id in dbn.nodes():
|
|
295
|
+
name = dbn.variable(dbn_id).name()
|
|
296
|
+
# right order for parents
|
|
297
|
+
lvarnames = list(reversed(dbn.cpt(dbn_id).names))
|
|
298
|
+
lvarnames.pop() # remove the node itself, parents remain
|
|
299
|
+
lvarnames.reverse()
|
|
300
|
+
|
|
301
|
+
for name_parent in lvarnames:
|
|
302
|
+
if not _isInSecondTimeSlice(name):
|
|
303
|
+
if not _isInSecondTimeSlice(name_parent):
|
|
304
|
+
bn.addArc(bn.idFromName(name_parent), bn.idFromName(name))
|
|
305
|
+
else:
|
|
306
|
+
if _isInFirstTimeSlice(name):
|
|
307
|
+
raise TypeError("An arc from timeslice t to timeslice is impossible in dBN")
|
|
308
|
+
for ts in range(1, nbr):
|
|
309
|
+
new_name_parent = realNameFrom2TBNname(name_parent, ts) # current TimeSlice
|
|
310
|
+
bn.addArc(bn.idFromName(new_name_parent), bn.idFromName(name))
|
|
311
|
+
else:
|
|
312
|
+
for ts in range(1, nbr):
|
|
313
|
+
if _isInFirstTimeSlice(name_parent):
|
|
314
|
+
new_name_parent = realNameFrom2TBNname(name_parent, ts - 1) # last TimeSlice
|
|
315
|
+
else:
|
|
316
|
+
new_name_parent = realNameFrom2TBNname(name_parent, ts) # current TimeSlice
|
|
317
|
+
new_name = realNameFrom2TBNname(name, ts) # necessary current TimeSlice
|
|
318
|
+
bn.addArc(bn.idFromName(new_name_parent), bn.idFromName(new_name))
|
|
319
|
+
|
|
320
|
+
# tensor creation
|
|
321
|
+
for dbn_id in dbn.nodes():
|
|
322
|
+
name = dbn.variable(dbn_id).name()
|
|
323
|
+
if not _isInSecondTimeSlice(name):
|
|
324
|
+
bn.cpt(bn.idFromName(name))[:] = dbn.cpt(dbn_id)[:]
|
|
325
|
+
else:
|
|
326
|
+
for ts in range(1, nbr):
|
|
327
|
+
bn.cpt(bn.idFromName(realNameFrom2TBNname(name, ts)))[:] = dbn.cpt(dbn_id)[:]
|
|
328
|
+
|
|
329
|
+
return bn
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def plotFollowUnrolled(lovars, dbn, T, evs, vars_title=None):
|
|
333
|
+
"""
|
|
334
|
+
plot the dynamic evolution of a list of vars with a dBN
|
|
335
|
+
|
|
336
|
+
:param lovars: list of variables to follow
|
|
337
|
+
:param dbn: the unrolled dbn
|
|
338
|
+
:param T: the time range
|
|
339
|
+
:param evs: observations
|
|
340
|
+
:param vars_title: string for default or a dictionary with the variable name as key and the respective title as value.
|
|
341
|
+
"""
|
|
342
|
+
ie = gum.LazyPropagation(dbn)
|
|
343
|
+
ie.setEvidence(evs)
|
|
344
|
+
ie.makeInference()
|
|
345
|
+
|
|
346
|
+
x = np.arange(T)
|
|
347
|
+
|
|
348
|
+
for var in lovars:
|
|
349
|
+
v0 = dbn.variableFromName(var + "0")
|
|
350
|
+
lpots = []
|
|
351
|
+
for i in range(v0.domainSize()):
|
|
352
|
+
serie = []
|
|
353
|
+
for t in range(T):
|
|
354
|
+
serie.append(ie.posterior(dbn.idFromName(var + str(t)))[i])
|
|
355
|
+
lpots.append(serie)
|
|
356
|
+
|
|
357
|
+
_, ax = plt.subplots()
|
|
358
|
+
plt.xlim(left=0, right=T - 1)
|
|
359
|
+
plt.ylim(top=1, bottom=0)
|
|
360
|
+
ax.xaxis.grid()
|
|
361
|
+
|
|
362
|
+
# Setting a customized title
|
|
363
|
+
if vars_title is None:
|
|
364
|
+
plt.title(f"Following variable {var}", fontsize=20)
|
|
365
|
+
elif len(vars_title) != 0:
|
|
366
|
+
plt.title(vars_title[var], fontsize=20)
|
|
367
|
+
else:
|
|
368
|
+
raise TypeError("Incorrect format of the plots title dictionary")
|
|
369
|
+
|
|
370
|
+
plt.xlabel("time")
|
|
371
|
+
|
|
372
|
+
stack = ax.stackplot(x, lpots)
|
|
373
|
+
|
|
374
|
+
proxy_rects = [Rectangle((0, 0), 1, 1, fc=pc.get_facecolor()[0]) for pc in stack]
|
|
375
|
+
labels = [v0.label(i) for i in range(v0.domainSize())]
|
|
376
|
+
plt.legend(proxy_rects, labels, loc="center left", bbox_to_anchor=(1, 0.5), ncol=1, fancybox=True, shadow=True)
|
|
377
|
+
|
|
378
|
+
plt.show()
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def plotFollow(lovars, twoTdbn, T, evs):
|
|
382
|
+
"""
|
|
383
|
+
plots modifications of variables in a 2TDN knowing the size of the time window (T) and the evidence on the sequence.
|
|
384
|
+
|
|
385
|
+
:param lovars: list of variables to follow
|
|
386
|
+
:param twoTdbn: the two-timeslice dbn
|
|
387
|
+
:param T: the time range
|
|
388
|
+
:param evs: observations
|
|
389
|
+
"""
|
|
390
|
+
plotFollowUnrolled(lovars, unroll2TBN(twoTdbn, T), T, evs)
|
pyagrum/lib/explain.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
tools for BN qualitative analysis and explainability
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
import warnings
|
|
46
|
+
|
|
47
|
+
warnings.warn(
|
|
48
|
+
"The module 'pyagrum.lib.explain' has been deprecated since version 2.2.2. "
|
|
49
|
+
"Please use the 'pyagrum.explain' module instead.",
|
|
50
|
+
DeprecationWarning,
|
|
51
|
+
stacklevel=2,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from pyagrum.explain.shapley import ShapValues # noqa: E402
|
|
55
|
+
from pyagrum.explain.notebook import showShapValues # noqa: E402
|
|
56
|
+
from pyagrum.explain import independenceListForPairs # noqa: E402
|
|
57
|
+
from pyagrum.explain import showInformation, getInformation, getInformationGraph # noqa: E402
|
pyagrum/lib/export.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
Tools for exporting graphical models and inference as other formats.
|
|
43
|
+
|
|
44
|
+
For each function `pyagrum.lib.export.to...(model,filename)`, it is assumed that the filename is complete (including the correct suffix).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
import sys
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def toGML(model, filename: str = None):
|
|
51
|
+
"""
|
|
52
|
+
Export directed graphical models as a graph to the graph GML format (https://gephi.org/users/supported-graph-formats/gml-format/)
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
model :
|
|
57
|
+
the directed graphical model to export
|
|
58
|
+
filename : Optional[str]
|
|
59
|
+
the name of the file (including the prefix), if None , use sys.stdout
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def _toGML(model, gmlfile):
|
|
63
|
+
print("graph", file=gmlfile)
|
|
64
|
+
print("[", file=gmlfile)
|
|
65
|
+
for i in model.nodes():
|
|
66
|
+
print(" node", file=gmlfile)
|
|
67
|
+
print(" [", file=gmlfile)
|
|
68
|
+
print(f" id X{i}", file=gmlfile)
|
|
69
|
+
print(f' label "{model.variable(i).name()}"', file=gmlfile)
|
|
70
|
+
print(" ]", file=gmlfile)
|
|
71
|
+
print("", file=gmlfile)
|
|
72
|
+
for i, j in model.arcs():
|
|
73
|
+
print(" edge", file=gmlfile)
|
|
74
|
+
print(" [", file=gmlfile)
|
|
75
|
+
print(f" source X{i}", file=gmlfile)
|
|
76
|
+
print(f" target X{j}", file=gmlfile)
|
|
77
|
+
print(" ]", file=gmlfile)
|
|
78
|
+
print("]", file=gmlfile)
|
|
79
|
+
|
|
80
|
+
if filename is None:
|
|
81
|
+
_toGML(model, sys.stdout)
|
|
82
|
+
else:
|
|
83
|
+
with open(filename, "w") as gmlfile:
|
|
84
|
+
_toGML(model, gmlfile)
|