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
pyagrum/lib/id2graph.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
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 tools for mapping Influcence Diagram (and inference) in dot language in order to
|
|
43
|
+
be displayed/saved as image.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import time
|
|
47
|
+
import math
|
|
48
|
+
import hashlib
|
|
49
|
+
from tempfile import mkdtemp
|
|
50
|
+
|
|
51
|
+
import pydot as dot
|
|
52
|
+
|
|
53
|
+
import pyagrum as gum
|
|
54
|
+
import pyagrum.lib._colors as gumcols
|
|
55
|
+
from pyagrum.lib.proba_histogram import saveFigProba
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def ID2dot(diag, size=None):
|
|
59
|
+
"""
|
|
60
|
+
create a pydot representation of the influence diagram
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
diag: pyagrum.InfluenceDiagram
|
|
65
|
+
the model
|
|
66
|
+
size: int|str
|
|
67
|
+
the size of the visualization
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
pydot.Dot
|
|
72
|
+
the dot representation of the influence diagram
|
|
73
|
+
"""
|
|
74
|
+
res = "digraph {\n"
|
|
75
|
+
|
|
76
|
+
# chance node
|
|
77
|
+
res += f'''
|
|
78
|
+
node [fillcolor="{gum.config["influenceDiagram", "default_chance_bgcolor"]}",
|
|
79
|
+
fontcolor="{gum.config["influenceDiagram", "default_chance_fgcolor"]}",
|
|
80
|
+
style=filled,shape={gum.config["influenceDiagram", "chance_shape"]},
|
|
81
|
+
height=0,margin=0.1];
|
|
82
|
+
'''
|
|
83
|
+
for node in diag.nodes():
|
|
84
|
+
if diag.isChanceNode(node):
|
|
85
|
+
res += ' "' + diag.variable(node).name() + '";' + "\n"
|
|
86
|
+
|
|
87
|
+
# decision node
|
|
88
|
+
res += f'''
|
|
89
|
+
node [fillcolor="{gum.config["influenceDiagram", "default_decision_bgcolor"]}",
|
|
90
|
+
fontcolor="{gum.config["influenceDiagram", "default_decision_fgcolor"]}",
|
|
91
|
+
style=filled,shape={gum.config["influenceDiagram", "decision_shape"]},
|
|
92
|
+
height=0,margin=0.1];
|
|
93
|
+
'''
|
|
94
|
+
for node in diag.nodes():
|
|
95
|
+
if diag.isDecisionNode(node):
|
|
96
|
+
res += ' "' + diag.variable(node).name() + '";' + "\n"
|
|
97
|
+
|
|
98
|
+
# utility node
|
|
99
|
+
res += f'''
|
|
100
|
+
node [fillcolor="{gum.config["influenceDiagram", "default_utility_bgcolor"]}",
|
|
101
|
+
fontcolor="{gum.config["influenceDiagram", "default_utility_fgcolor"]}",
|
|
102
|
+
style=filled,shape={gum.config["influenceDiagram", "utility_shape"]}, height=0,margin=0.1];
|
|
103
|
+
'''
|
|
104
|
+
for node in diag.nodes():
|
|
105
|
+
if diag.isUtilityNode(node):
|
|
106
|
+
res += ' "' + diag.variable(node).name() + '";' + "\n"
|
|
107
|
+
|
|
108
|
+
# arcs
|
|
109
|
+
res += "\n"
|
|
110
|
+
for node in diag.nodes():
|
|
111
|
+
for chi in diag.children(node):
|
|
112
|
+
res += ' "' + diag.variable(node).name() + '"->"' + diag.variable(chi).name() + '"'
|
|
113
|
+
if diag.isDecisionNode(chi):
|
|
114
|
+
res += (
|
|
115
|
+
f' [style="{gum.config["influenceDiagram", "decision_arc_style"]}", color = "{gumcols.getBlackInTheme()}"]'
|
|
116
|
+
)
|
|
117
|
+
elif diag.isUtilityNode(chi):
|
|
118
|
+
res += (
|
|
119
|
+
f' [style="{gum.config["influenceDiagram", "utility_arc_style"]}", color = "{gumcols.getBlackInTheme()}"]'
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
res += f' [color = "{gumcols.getBlackInTheme()}"]'
|
|
123
|
+
res += ";\n"
|
|
124
|
+
res += "}"
|
|
125
|
+
|
|
126
|
+
g = dot.graph_from_dot_data(res)[0]
|
|
127
|
+
|
|
128
|
+
# workaround for some badly parsed graph (pyparsing>=3.03)
|
|
129
|
+
g.del_node('"\\n"')
|
|
130
|
+
|
|
131
|
+
if size is None:
|
|
132
|
+
size = gum.config["influenceDiagram", "default_id_size"]
|
|
133
|
+
# dynamic member makes pylink unhappy
|
|
134
|
+
# pylint: disable=no-member
|
|
135
|
+
g.set_size(size)
|
|
136
|
+
return g
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def LIMIDinference2dot(diag, size, engine, evs, targets):
|
|
140
|
+
"""
|
|
141
|
+
create a pydot representation of an inference in a influence diagram
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
diag: pyagrum.InfluenceDiagram
|
|
146
|
+
the model
|
|
147
|
+
size: float|str
|
|
148
|
+
the size of the rendered graph
|
|
149
|
+
engine: pyagrum.InfluenceDiagramInference
|
|
150
|
+
the inference algorithm used. If None, ShaferShenoyLIMIDInference will be used
|
|
151
|
+
evs: Dict[str,str|int|List[float]]
|
|
152
|
+
the evidence
|
|
153
|
+
targets: Set[str]
|
|
154
|
+
set of targetted variable. If targets={} then each node is a target
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
pydot.Dot
|
|
159
|
+
the representation of the inference
|
|
160
|
+
"""
|
|
161
|
+
startTime = time.time()
|
|
162
|
+
if engine is None:
|
|
163
|
+
ie = gum.ShaferShenoyLIMIDInference(diag)
|
|
164
|
+
else:
|
|
165
|
+
ie = engine
|
|
166
|
+
ie.setEvidence(evs)
|
|
167
|
+
ie.makeInference()
|
|
168
|
+
stopTime = time.time()
|
|
169
|
+
meu = ie.MEU()
|
|
170
|
+
|
|
171
|
+
temp_dir = mkdtemp("", "tmp", None) # with TemporaryDirectory() as temp_dir:
|
|
172
|
+
|
|
173
|
+
dotstr = 'digraph structs {\n fontcolor="' + gumcols.getBlackInTheme() + '";bgcolor="transparent";'
|
|
174
|
+
|
|
175
|
+
fontname, fontsize = gumcols.fontFromMatplotlib()
|
|
176
|
+
dotstr += f'node[fontname="{fontname}",fontsize="{fontsize}"];'
|
|
177
|
+
|
|
178
|
+
fmt = "." + gum.config["influenceDiagram", "utility_visible_digits"] + "f"
|
|
179
|
+
if gum.config.asBool["influenceDiagram", "utility_show_loss"]:
|
|
180
|
+
titut = f"mEL {-meu['mean']:{fmt}}"
|
|
181
|
+
else:
|
|
182
|
+
titut = f"MEU {meu['mean']:{fmt}}"
|
|
183
|
+
if gum.config.asBool["influenceDiagram", "utility_show_stdev"]:
|
|
184
|
+
titut += f" (stdev={math.sqrt(meu['variance']):{fmt}})"
|
|
185
|
+
|
|
186
|
+
slabel = f'label="{titut}'
|
|
187
|
+
|
|
188
|
+
if gum.config.asBool["notebook", "show_inference_time"]:
|
|
189
|
+
slabel += f"\nInference in {1000 * (stopTime - startTime):6.2f}ms"
|
|
190
|
+
dotstr += slabel + '";\n'
|
|
191
|
+
|
|
192
|
+
for nid in diag.nodes():
|
|
193
|
+
name = diag.variable(nid).name()
|
|
194
|
+
|
|
195
|
+
# defaults
|
|
196
|
+
if diag.isChanceNode(nid):
|
|
197
|
+
bgcolor = gum.config["influenceDiagram", "default_chance_bgcolor"]
|
|
198
|
+
fgcolor = gum.config["influenceDiagram", "default_chance_fgcolor"]
|
|
199
|
+
shape = gum.config["influenceDiagram", "chance_shape"]
|
|
200
|
+
elif diag.isDecisionNode(nid):
|
|
201
|
+
bgcolor = gum.config["influenceDiagram", "default_decision_bgcolor"]
|
|
202
|
+
fgcolor = gum.config["influenceDiagram", "default_decision_fgcolor"]
|
|
203
|
+
shape = gum.config["influenceDiagram", "decision_shape"]
|
|
204
|
+
else: # diag.isUtilityNode(nid):
|
|
205
|
+
bgcolor = gum.config["influenceDiagram", "default_utility_bgcolor"]
|
|
206
|
+
fgcolor = gum.config["influenceDiagram", "default_utility_fgcolor"]
|
|
207
|
+
shape = gum.config["influenceDiagram", "utility_shape"]
|
|
208
|
+
|
|
209
|
+
# 'hard' colour for evidence (?)
|
|
210
|
+
if nid in ie.hardEvidenceNodes() | ie.softEvidenceNodes():
|
|
211
|
+
bgcolor = gum.config["notebook", "evidence_bgcolor"]
|
|
212
|
+
fgcolor = gum.config["notebook", "evidence_fgcolor"]
|
|
213
|
+
|
|
214
|
+
styleattribute = "style=filled, height=0,margin=0.1"
|
|
215
|
+
colorattribute = f'fillcolor="{bgcolor}", fontcolor="{fgcolor}", color="#000000"'
|
|
216
|
+
|
|
217
|
+
if not diag.isUtilityNode(nid):
|
|
218
|
+
if len(targets) == 0 or name in targets or nid in targets:
|
|
219
|
+
filename = temp_dir + hashlib.md5(name.encode()).hexdigest() + "." + gum.config["notebook", "graph_format"]
|
|
220
|
+
saveFigProba(ie.posterior(name), filename, bgcolor=bgcolor, util=ie.posteriorUtility(nid), txtcolor=fgcolor)
|
|
221
|
+
dotstr += f' "{name}" [shape=rectangle,image="{filename}",label="", {colorattribute}];\n'
|
|
222
|
+
else:
|
|
223
|
+
dotstr += f' "{name}" [{colorattribute},shape={shape},{styleattribute}]'
|
|
224
|
+
else: # utility node
|
|
225
|
+
mv = ie.meanVar(name)
|
|
226
|
+
|
|
227
|
+
if gum.config.asBool["influenceDiagram", "utility_show_loss"]:
|
|
228
|
+
coef = -1
|
|
229
|
+
else:
|
|
230
|
+
coef = 1
|
|
231
|
+
|
|
232
|
+
fmt = f".{gum.config.asInt['influenceDiagram', 'utility_visible_digits']}f"
|
|
233
|
+
labut = f"{name} : {coef * mv['mean']:{fmt}}"
|
|
234
|
+
if gum.config.asBool["influenceDiagram", "utility_show_stdev"]:
|
|
235
|
+
labut += f" ({math.sqrt(mv['variance']):{fmt}})"
|
|
236
|
+
|
|
237
|
+
dotstr += f' "{name}" [label="{labut}",{colorattribute},{styleattribute},shape={shape}]'
|
|
238
|
+
|
|
239
|
+
# arcs
|
|
240
|
+
dotstr += "\n"
|
|
241
|
+
for node in diag.nodes():
|
|
242
|
+
for chi in diag.children(node):
|
|
243
|
+
dotstr += ' "' + diag.variable(node).name() + '"->"' + diag.variable(chi).name() + '"'
|
|
244
|
+
if diag.isDecisionNode(chi):
|
|
245
|
+
dotstr += f' [style="{gum.config["influenceDiagram", "decision_arc_style"]}"]'
|
|
246
|
+
elif diag.isUtilityNode(chi):
|
|
247
|
+
dotstr += f' [style="{gum.config["influenceDiagram", "utility_arc_style"]}"]'
|
|
248
|
+
dotstr += ";\n"
|
|
249
|
+
dotstr += "}"
|
|
250
|
+
|
|
251
|
+
g = dot.graph_from_dot_data(dotstr)[0]
|
|
252
|
+
|
|
253
|
+
if size is None:
|
|
254
|
+
size = gum.config["influenceDiagram", "default_id_inference_size"]
|
|
255
|
+
g.set_size(size)
|
|
256
|
+
g.temp_dir = temp_dir
|
|
257
|
+
|
|
258
|
+
return g
|
pyagrum/lib/image.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
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 model and inference as image
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
import os
|
|
46
|
+
import tempfile
|
|
47
|
+
import re
|
|
48
|
+
import matplotlib.image as mpimg
|
|
49
|
+
import pydot as dot
|
|
50
|
+
|
|
51
|
+
import pyagrum as gum
|
|
52
|
+
from pyagrum.lib.bn2graph import BN2dot, BNinference2dot
|
|
53
|
+
from pyagrum.lib.cn2graph import CN2dot, CNinference2dot
|
|
54
|
+
from pyagrum.lib.id2graph import ID2dot, LIMIDinference2dot
|
|
55
|
+
from pyagrum.lib.mrf2graph import MRF2UGdot, MRFinference2UGdot
|
|
56
|
+
from pyagrum.lib.mrf2graph import MRF2FactorGraphdot, MRFinference2FactorGraphdot
|
|
57
|
+
import pyagrum.lib._colors as gumcols
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def export(model, filename=None, **kwargs):
|
|
61
|
+
"""
|
|
62
|
+
export the graphical representation of the model in filename (png, pdf,etc.)
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
model: pyagrum.GraphicalModel
|
|
67
|
+
the model to show (pyagrum.BayesNet, pyagrum.MarkovRandomField, pyagrum.InfluenceDiagram or pyagrum.Tensor)
|
|
68
|
+
filename: str
|
|
69
|
+
the name of the resulting file (suffix in ['pdf', 'png', 'fig', 'jpg', 'svg', 'ps']). If filename is None, the result is a np.array ready to be used with imshow().
|
|
70
|
+
|
|
71
|
+
Note
|
|
72
|
+
----
|
|
73
|
+
Model can also just possess a method `toDot()` or even be a simple string in dot syntax.
|
|
74
|
+
"""
|
|
75
|
+
if filename is None:
|
|
76
|
+
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
|
|
77
|
+
export(model, tmp.name, **kwargs)
|
|
78
|
+
img = mpimg.imread(tmp.name)
|
|
79
|
+
try:
|
|
80
|
+
os.remove(tmp.name)
|
|
81
|
+
except PermissionError: # probably windows error : file still 'used' ... grrr...
|
|
82
|
+
pass
|
|
83
|
+
return img
|
|
84
|
+
|
|
85
|
+
fmt_image = filename.split(".")[-1]
|
|
86
|
+
if fmt_image not in ["pdf", "png", "fig", "jpg", "svg", "ps"]:
|
|
87
|
+
raise NameError(
|
|
88
|
+
f"{filename} in not a correct filename for export : extension '{fmt_image}' not in [pdf,png,fig,jpg,svg]."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if isinstance(model, gum.BayesNet):
|
|
92
|
+
fig = BN2dot(model, **kwargs)
|
|
93
|
+
elif isinstance(model, gum.MarkovRandomField):
|
|
94
|
+
if gum.config["notebook", "default_markovrandomfield_view"] == "graph":
|
|
95
|
+
fig = MRF2UGdot(model, **kwargs)
|
|
96
|
+
else:
|
|
97
|
+
fig = MRF2FactorGraphdot(model, **kwargs)
|
|
98
|
+
elif isinstance(model, gum.InfluenceDiagram):
|
|
99
|
+
fig = ID2dot(model, **kwargs)
|
|
100
|
+
elif isinstance(model, gum.CredalNet):
|
|
101
|
+
fig = CN2dot(model, **kwargs)
|
|
102
|
+
elif isinstance(model, dot.Dot):
|
|
103
|
+
fig = model
|
|
104
|
+
elif hasattr(model, "toDot"):
|
|
105
|
+
fig = dot.graph_from_dot_data(model.toDot())[0]
|
|
106
|
+
elif isinstance(model, str):
|
|
107
|
+
fig = dot.graph_from_dot_data(model)[0]
|
|
108
|
+
else:
|
|
109
|
+
raise gum.InvalidArgument(
|
|
110
|
+
"Argument model should be a PGM (BayesNet, MarkovRandomField or Influence Diagram) or has a method `toDot()` or is a string"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
gumcols.prepareDot(fig, **kwargs).write(filename, format=fmt_image)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def prepareShowInference(
|
|
117
|
+
model,
|
|
118
|
+
engine=None,
|
|
119
|
+
evs=None,
|
|
120
|
+
targets=None,
|
|
121
|
+
size=None,
|
|
122
|
+
nodeColor=None,
|
|
123
|
+
factorColor=None,
|
|
124
|
+
arcWidth=None,
|
|
125
|
+
arcColor=None,
|
|
126
|
+
cmapNode=None,
|
|
127
|
+
cmapArc=None,
|
|
128
|
+
graph=None,
|
|
129
|
+
view=None,
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
Transform an inference for a model in a dot representation
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
model: pyAgrum:GraphicalModel
|
|
137
|
+
the model in which to infer (pyagrum.BayesNet, pyagrum.MarkovRandomField or pyagrum.InfluenceDiagram)
|
|
138
|
+
filename: str
|
|
139
|
+
the name of the resulting file (suffix in ['pdf', 'png', 'ps']). If filename is None, the result is a np.array ready to be used with imshow().
|
|
140
|
+
engine: pyagrum.Inference
|
|
141
|
+
inference algorithm used. If None, gum.LazyPropagation will be used for BayesNet,gum.ShaferShenoy for gum.MarkovRandomField and gum.ShaferShenoyLIMIDInference for gum.InfluenceDiagram.
|
|
142
|
+
evs: Dict[str,str|int]
|
|
143
|
+
map of evidence
|
|
144
|
+
targets: Set[str|int]
|
|
145
|
+
set of targets
|
|
146
|
+
size: str
|
|
147
|
+
size of the rendered graph
|
|
148
|
+
nodeColor: Dict[int,float]
|
|
149
|
+
a nodeMap of values (between 0 and 1) to be shown as color of nodes (with special colors for 0 and 1)
|
|
150
|
+
factorColor: Dict[int,float]
|
|
151
|
+
a nodeMap of values (between 0 and 1) to be shown as color of factors (in MarkovRandomField representation)
|
|
152
|
+
arcWidth: Dict[(int,int),float]
|
|
153
|
+
a arcMap of values to be shown as width of arcs
|
|
154
|
+
arcColor: Dict[(int,int),float]
|
|
155
|
+
a arcMap of values (between 0 and 1) to be shown as color of arcs
|
|
156
|
+
cmapNode: matplotlib.colors.ColorMap
|
|
157
|
+
color map to show the color of nodes and arcs
|
|
158
|
+
cmapArc: matplotlib.colors.ColorMap
|
|
159
|
+
color map to show the vals of Arcs.
|
|
160
|
+
graph: pyagrum.Graph
|
|
161
|
+
only shows nodes that have their id in the graph (and not in the whole BN)
|
|
162
|
+
view: str
|
|
163
|
+
graph | factorgraph | None (default) for Markov random field
|
|
164
|
+
|
|
165
|
+
Raises
|
|
166
|
+
------
|
|
167
|
+
pyagrum.InvalidArgument:
|
|
168
|
+
if the arg is invalid
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
str
|
|
173
|
+
the obtained graph as a string
|
|
174
|
+
"""
|
|
175
|
+
if size is None:
|
|
176
|
+
size = gum.config["notebook", "default_graph_inference_size"]
|
|
177
|
+
|
|
178
|
+
if evs is None:
|
|
179
|
+
evs = {}
|
|
180
|
+
|
|
181
|
+
if targets is None:
|
|
182
|
+
targets = {}
|
|
183
|
+
|
|
184
|
+
if isinstance(model, gum.BayesNet):
|
|
185
|
+
if engine is None:
|
|
186
|
+
engine = gum.LazyPropagation(model)
|
|
187
|
+
return BNinference2dot(
|
|
188
|
+
model,
|
|
189
|
+
size=size,
|
|
190
|
+
engine=engine,
|
|
191
|
+
evs=evs,
|
|
192
|
+
targets=targets,
|
|
193
|
+
nodeColor=nodeColor,
|
|
194
|
+
arcWidth=arcWidth,
|
|
195
|
+
arcColor=arcColor,
|
|
196
|
+
cmapNode=cmapNode,
|
|
197
|
+
cmapArc=cmapArc,
|
|
198
|
+
)
|
|
199
|
+
if isinstance(model, gum.MarkovRandomField):
|
|
200
|
+
if view is None:
|
|
201
|
+
view = gum.config["notebook", "default_markovrandomfield_view"]
|
|
202
|
+
if engine is None:
|
|
203
|
+
engine = gum.ShaferShenoyMRFInference(model)
|
|
204
|
+
|
|
205
|
+
if view == "graph":
|
|
206
|
+
return MRFinference2UGdot(
|
|
207
|
+
model,
|
|
208
|
+
size=size,
|
|
209
|
+
engine=engine,
|
|
210
|
+
evs=evs,
|
|
211
|
+
targets=targets,
|
|
212
|
+
nodeColor=nodeColor,
|
|
213
|
+
factorColor=factorColor,
|
|
214
|
+
arcWidth=arcWidth,
|
|
215
|
+
arcColor=arcColor,
|
|
216
|
+
cmapNode=cmapNode,
|
|
217
|
+
cmapArc=cmapArc,
|
|
218
|
+
)
|
|
219
|
+
# view=factor graph
|
|
220
|
+
return MRFinference2FactorGraphdot(
|
|
221
|
+
model,
|
|
222
|
+
size=size,
|
|
223
|
+
engine=engine,
|
|
224
|
+
evs=evs,
|
|
225
|
+
targets=targets,
|
|
226
|
+
nodeColor=nodeColor,
|
|
227
|
+
factorColor=factorColor,
|
|
228
|
+
cmapNode=cmapNode,
|
|
229
|
+
)
|
|
230
|
+
if isinstance(model, gum.InfluenceDiagram):
|
|
231
|
+
if engine is None:
|
|
232
|
+
engine = gum.ShaferShenoyLIMIDInference(model)
|
|
233
|
+
return LIMIDinference2dot(model, size=size, engine=engine, evs=evs, targets=targets)
|
|
234
|
+
if isinstance(model, gum.CredalNet):
|
|
235
|
+
if engine is None:
|
|
236
|
+
engine = gum.CNMonteCarloSampling(model)
|
|
237
|
+
return CNinference2dot(
|
|
238
|
+
model,
|
|
239
|
+
size=size,
|
|
240
|
+
engine=engine,
|
|
241
|
+
evs=evs,
|
|
242
|
+
targets=targets,
|
|
243
|
+
nodeColor=nodeColor,
|
|
244
|
+
arcWidth=arcWidth,
|
|
245
|
+
arcColor=arcColor,
|
|
246
|
+
cmapNode=cmapNode,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
raise gum.InvalidArgument("Argument model should be a PGM (BayesNet, MarkovRandomField or Influence Diagram)")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def prepareLinksForSVG(mainSvg):
|
|
253
|
+
"""
|
|
254
|
+
Inlining links in svg
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
mainSvg: str
|
|
259
|
+
the main svg to be changed
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
------
|
|
263
|
+
str
|
|
264
|
+
the new version with inlined links
|
|
265
|
+
"""
|
|
266
|
+
re_images = re.compile(r"(<image [^>]*>)")
|
|
267
|
+
re_xlink = re.compile(r"xlink:href=\"([^\"]*)")
|
|
268
|
+
re_viewbox = re.compile(r"(viewBox=\"[^\"]*\")")
|
|
269
|
+
|
|
270
|
+
# analyze mainSvg (find the secondary svgs)
|
|
271
|
+
__fragments = {}
|
|
272
|
+
for img in re.finditer(re_images, mainSvg):
|
|
273
|
+
# print(img)
|
|
274
|
+
secondarySvg = re.findall(re_xlink, img.group(1))[0]
|
|
275
|
+
content = ""
|
|
276
|
+
with open(secondarySvg, encoding="utf8") as f:
|
|
277
|
+
inSvg = False
|
|
278
|
+
for line in f:
|
|
279
|
+
if line[0:4] == "<svg":
|
|
280
|
+
inSvg = True
|
|
281
|
+
viewBox = re.findall(re_viewbox, line)[0]
|
|
282
|
+
# print("VIEWBOX {}".format(viewBox))
|
|
283
|
+
elif inSvg:
|
|
284
|
+
content += line
|
|
285
|
+
__fragments[secondarySvg] = (viewBox, content)
|
|
286
|
+
|
|
287
|
+
if len(__fragments) > 0:
|
|
288
|
+
# replace image tags by svg tags
|
|
289
|
+
img2svg = re.sub(r"<image ([^>]*)/>", r"<svg \g<1>>", mainSvg)
|
|
290
|
+
|
|
291
|
+
# insert secondaries into main
|
|
292
|
+
def ___insertSecondarySvgs(matchObj):
|
|
293
|
+
vb, code = __fragments[matchObj.group(1)]
|
|
294
|
+
return vb + matchObj.group(2) + code
|
|
295
|
+
|
|
296
|
+
mainSvg = re.sub(r'xlink:href="([^"]*)"(.*>)', ___insertSecondarySvgs, img2svg)
|
|
297
|
+
|
|
298
|
+
# remove buggy white-space (for notebooks)
|
|
299
|
+
mainSvg = mainSvg.replace("white-space:pre;", "")
|
|
300
|
+
return mainSvg
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def dot_as_svg_string(gr, size):
|
|
304
|
+
"""
|
|
305
|
+
repr a pydot graph in a notebook
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
size : str
|
|
310
|
+
size of the rendered graph
|
|
311
|
+
"""
|
|
312
|
+
if size is not None:
|
|
313
|
+
gr.set_size(size)
|
|
314
|
+
|
|
315
|
+
gsvg = prepareLinksForSVG(gr.create_svg(encoding="utf-8").decode("utf-8"))
|
|
316
|
+
return gsvg
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def exportInference(model, filename=None, **kwargs):
|
|
320
|
+
"""
|
|
321
|
+
the graphical representation of an inference in a notebook
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
model: pyAgrum:GraphicalModel
|
|
326
|
+
the model in which to infer (pyagrum.BayesNet, pyagrum.MarkovRandomField or pyagrum.InfluenceDiagram)
|
|
327
|
+
filename: str
|
|
328
|
+
the name of the resulting file (suffix in ['pdf', 'png', 'ps']). If filename is None, the result is a np.array ready to be used with imshow().
|
|
329
|
+
engine: pyagrum.Inference
|
|
330
|
+
inference algorithm used. If None, gum.LazyPropagation will be used for BayesNet,gum.ShaferShenoy for gum.MarkovRandomField and gum.ShaferShenoyLIMIDInference for gum.InfluenceDiagram.
|
|
331
|
+
evs: Dict[str,str|int]
|
|
332
|
+
map of evidence
|
|
333
|
+
targets: Set[str|int]
|
|
334
|
+
set of targets
|
|
335
|
+
size: str
|
|
336
|
+
size of the rendered graph
|
|
337
|
+
nodeColor: Dict[int,float]
|
|
338
|
+
a nodeMap of values (between 0 and 1) to be shown as color of nodes (with special colors for 0 and 1)
|
|
339
|
+
factorColor: Dict[int,float]
|
|
340
|
+
a nodeMap of values (between 0 and 1) to be shown as color of factors (in MarkovRandomField representation)
|
|
341
|
+
arcWidth: Dict[(int,int),float]
|
|
342
|
+
a arcMap of values to be shown as width of arcs
|
|
343
|
+
arcColor: Dict[(int,int),float]
|
|
344
|
+
a arcMap of values (between 0 and 1) to be shown as color of arcs
|
|
345
|
+
cmap: matplotlib.colors.ColorMap
|
|
346
|
+
color map to show the color of nodes and arcs
|
|
347
|
+
cmapArc: matplotlib.colors.ColorMap
|
|
348
|
+
color map to show the vals of Arcs.
|
|
349
|
+
graph: pyagrum.Graph
|
|
350
|
+
only shows nodes that have their id in the graph (and not in the whole BN)
|
|
351
|
+
view: str
|
|
352
|
+
graph | factorgraph | None (default) for Markov random field
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
str|dot.Dot
|
|
357
|
+
the desired representation of the inference
|
|
358
|
+
"""
|
|
359
|
+
if filename is None:
|
|
360
|
+
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
|
|
361
|
+
exportInference(model, tmp.name, **kwargs)
|
|
362
|
+
img = mpimg.imread(tmp.name)
|
|
363
|
+
try:
|
|
364
|
+
os.remove(tmp.name)
|
|
365
|
+
except PermissionError: # probably windows error : file still 'used' ... grrr...
|
|
366
|
+
pass
|
|
367
|
+
return img
|
|
368
|
+
|
|
369
|
+
fmt_image = filename.split(".")[-1]
|
|
370
|
+
if fmt_image not in ["pdf", "png", "ps"]:
|
|
371
|
+
raise NameError(f"{filename} in not a correct filename for export : extension '{fmt_image}' not in [pdf,png,ps].")
|
|
372
|
+
|
|
373
|
+
import cairosvg
|
|
374
|
+
|
|
375
|
+
if "size" in kwargs:
|
|
376
|
+
size = kwargs["size"]
|
|
377
|
+
else:
|
|
378
|
+
size = gum.config["notebook", "default_graph_inference_size"]
|
|
379
|
+
|
|
380
|
+
svgtxt = dot_as_svg_string(gumcols.prepareDot(prepareShowInference(model, **kwargs), **kwargs), size=size)
|
|
381
|
+
|
|
382
|
+
if fmt_image == "pdf":
|
|
383
|
+
cairosvg.svg2pdf(bytestring=svgtxt, write_to=filename)
|
|
384
|
+
elif fmt_image == "png":
|
|
385
|
+
cairosvg.svg2png(bytestring=svgtxt, write_to=filename)
|
|
386
|
+
else: # format=="ps"
|
|
387
|
+
cairosvg.svg2ps(bytestring=svgtxt, write_to=filename)
|