pyAgrum-nightly 2.3.1.9.dev202512261765915415__cp310-abi3-macosx_10_15_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. pyagrum/__init__.py +165 -0
  2. pyagrum/_pyagrum.so +0 -0
  3. pyagrum/bnmixture/BNMInference.py +268 -0
  4. pyagrum/bnmixture/BNMLearning.py +376 -0
  5. pyagrum/bnmixture/BNMixture.py +464 -0
  6. pyagrum/bnmixture/__init__.py +60 -0
  7. pyagrum/bnmixture/notebook.py +1058 -0
  8. pyagrum/causal/_CausalFormula.py +280 -0
  9. pyagrum/causal/_CausalModel.py +436 -0
  10. pyagrum/causal/__init__.py +81 -0
  11. pyagrum/causal/_causalImpact.py +356 -0
  12. pyagrum/causal/_dSeparation.py +598 -0
  13. pyagrum/causal/_doAST.py +761 -0
  14. pyagrum/causal/_doCalculus.py +361 -0
  15. pyagrum/causal/_doorCriteria.py +374 -0
  16. pyagrum/causal/_exceptions.py +95 -0
  17. pyagrum/causal/_types.py +61 -0
  18. pyagrum/causal/causalEffectEstimation/_CausalEffectEstimation.py +1175 -0
  19. pyagrum/causal/causalEffectEstimation/_IVEstimators.py +718 -0
  20. pyagrum/causal/causalEffectEstimation/_RCTEstimators.py +132 -0
  21. pyagrum/causal/causalEffectEstimation/__init__.py +46 -0
  22. pyagrum/causal/causalEffectEstimation/_backdoorEstimators.py +774 -0
  23. pyagrum/causal/causalEffectEstimation/_causalBNEstimator.py +324 -0
  24. pyagrum/causal/causalEffectEstimation/_frontdoorEstimators.py +396 -0
  25. pyagrum/causal/causalEffectEstimation/_learners.py +118 -0
  26. pyagrum/causal/causalEffectEstimation/_utils.py +466 -0
  27. pyagrum/causal/notebook.py +172 -0
  28. pyagrum/clg/CLG.py +658 -0
  29. pyagrum/clg/GaussianVariable.py +111 -0
  30. pyagrum/clg/SEM.py +312 -0
  31. pyagrum/clg/__init__.py +63 -0
  32. pyagrum/clg/canonicalForm.py +408 -0
  33. pyagrum/clg/constants.py +54 -0
  34. pyagrum/clg/forwardSampling.py +202 -0
  35. pyagrum/clg/learning.py +776 -0
  36. pyagrum/clg/notebook.py +480 -0
  37. pyagrum/clg/variableElimination.py +271 -0
  38. pyagrum/common.py +60 -0
  39. pyagrum/config.py +319 -0
  40. pyagrum/ctbn/CIM.py +513 -0
  41. pyagrum/ctbn/CTBN.py +573 -0
  42. pyagrum/ctbn/CTBNGenerator.py +216 -0
  43. pyagrum/ctbn/CTBNInference.py +459 -0
  44. pyagrum/ctbn/CTBNLearner.py +161 -0
  45. pyagrum/ctbn/SamplesStats.py +671 -0
  46. pyagrum/ctbn/StatsIndepTest.py +355 -0
  47. pyagrum/ctbn/__init__.py +79 -0
  48. pyagrum/ctbn/constants.py +54 -0
  49. pyagrum/ctbn/notebook.py +264 -0
  50. pyagrum/defaults.ini +199 -0
  51. pyagrum/deprecated.py +95 -0
  52. pyagrum/explain/_ComputationCausal.py +75 -0
  53. pyagrum/explain/_ComputationConditional.py +48 -0
  54. pyagrum/explain/_ComputationMarginal.py +48 -0
  55. pyagrum/explain/_CustomShapleyCache.py +110 -0
  56. pyagrum/explain/_Explainer.py +176 -0
  57. pyagrum/explain/_Explanation.py +70 -0
  58. pyagrum/explain/_FIFOCache.py +54 -0
  59. pyagrum/explain/_ShallCausalValues.py +204 -0
  60. pyagrum/explain/_ShallConditionalValues.py +155 -0
  61. pyagrum/explain/_ShallMarginalValues.py +155 -0
  62. pyagrum/explain/_ShallValues.py +296 -0
  63. pyagrum/explain/_ShapCausalValues.py +208 -0
  64. pyagrum/explain/_ShapConditionalValues.py +126 -0
  65. pyagrum/explain/_ShapMarginalValues.py +191 -0
  66. pyagrum/explain/_ShapleyValues.py +298 -0
  67. pyagrum/explain/__init__.py +81 -0
  68. pyagrum/explain/_explGeneralizedMarkovBlanket.py +152 -0
  69. pyagrum/explain/_explIndependenceListForPairs.py +146 -0
  70. pyagrum/explain/_explInformationGraph.py +264 -0
  71. pyagrum/explain/notebook/__init__.py +54 -0
  72. pyagrum/explain/notebook/_bar.py +142 -0
  73. pyagrum/explain/notebook/_beeswarm.py +174 -0
  74. pyagrum/explain/notebook/_showShapValues.py +97 -0
  75. pyagrum/explain/notebook/_waterfall.py +220 -0
  76. pyagrum/explain/shapley.py +225 -0
  77. pyagrum/lib/__init__.py +46 -0
  78. pyagrum/lib/_colors.py +390 -0
  79. pyagrum/lib/bn2graph.py +299 -0
  80. pyagrum/lib/bn2roc.py +1026 -0
  81. pyagrum/lib/bn2scores.py +217 -0
  82. pyagrum/lib/bn_vs_bn.py +605 -0
  83. pyagrum/lib/cn2graph.py +305 -0
  84. pyagrum/lib/discreteTypeProcessor.py +1102 -0
  85. pyagrum/lib/discretizer.py +58 -0
  86. pyagrum/lib/dynamicBN.py +390 -0
  87. pyagrum/lib/explain.py +57 -0
  88. pyagrum/lib/export.py +84 -0
  89. pyagrum/lib/id2graph.py +258 -0
  90. pyagrum/lib/image.py +387 -0
  91. pyagrum/lib/ipython.py +307 -0
  92. pyagrum/lib/mrf2graph.py +471 -0
  93. pyagrum/lib/notebook.py +1821 -0
  94. pyagrum/lib/proba_histogram.py +552 -0
  95. pyagrum/lib/utils.py +138 -0
  96. pyagrum/pyagrum.py +31495 -0
  97. pyagrum/skbn/_MBCalcul.py +242 -0
  98. pyagrum/skbn/__init__.py +49 -0
  99. pyagrum/skbn/_learningMethods.py +282 -0
  100. pyagrum/skbn/_utils.py +297 -0
  101. pyagrum/skbn/bnclassifier.py +1014 -0
  102. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSE.md +12 -0
  103. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
  104. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/LICENSES/MIT.txt +18 -0
  105. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/METADATA +145 -0
  106. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/RECORD +107 -0
  107. pyagrum_nightly-2.3.1.9.dev202512261765915415.dist-info/WHEEL +4 -0
@@ -0,0 +1,280 @@
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 a representation of a causal query in a causal model
43
+ """
44
+
45
+ from collections import defaultdict
46
+ from typing import Union, Optional, Dict
47
+
48
+ import pyagrum
49
+
50
+ from pyagrum.causal._types import NameSet
51
+ from pyagrum.causal._doAST import ASTtree
52
+
53
+ # pylint: disable=unused-import
54
+ import pyagrum.causal # for annotations
55
+
56
+
57
+ class CausalFormula:
58
+ """
59
+ Represents a causal query in a causal model. The query is encoded as an CausalFormula that can be evaluated in the
60
+ causal model : $P(on|knowing, \\overhook (doing))$
61
+
62
+ Parameters
63
+ ----------
64
+ cm : CausalModel
65
+ the causal model
66
+ root : ASTtree
67
+ the syntax tree
68
+ on : str|Set[str]
69
+ the variable or the set of variables of interest
70
+ doing : str|Set[str]
71
+ the intervention variable(s)
72
+ knowing: None|str|Set[str]
73
+ the observation variable(s)
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ cm: "pyagrum.causal.CausalModel",
79
+ root: ASTtree,
80
+ on: Union[str, NameSet],
81
+ doing: Union[str, NameSet],
82
+ knowing: Optional[NameSet] = None,
83
+ ):
84
+ """
85
+ Parameters
86
+ ----------
87
+ cm : CausalModel
88
+ the causal model
89
+ root : ASTtree
90
+ the syntax tree
91
+ on : str|Set[str]
92
+ the variable or the set of variables of interest
93
+ doing : str|Set[str]
94
+ the intervention variable(s)
95
+ knowing: None|str|Set[str]
96
+ the observation variable(s)
97
+ """
98
+ self._cm = cm
99
+ self._root = root
100
+
101
+ if isinstance(on, str):
102
+ self._on = {on}
103
+ else:
104
+ self._on = on
105
+
106
+ if isinstance(doing, str):
107
+ self._doing = {doing}
108
+ else:
109
+ self._doing = doing
110
+
111
+ if knowing is None:
112
+ self._knowing = set()
113
+ elif isinstance(knowing, str):
114
+ self._knowing = {knowing}
115
+ else:
116
+ self._knowing = knowing
117
+
118
+ def _setDoing(self, doing: Union[str, NameSet]):
119
+ if isinstance(doing, str):
120
+ self._doing = {doing}
121
+ else:
122
+ self._doing = doing
123
+
124
+ def _setKnowing(self, knowing: Union[str, NameSet]):
125
+ if isinstance(knowing, str):
126
+ self._knowing = {knowing}
127
+ else:
128
+ self._knowing = knowing
129
+
130
+ def __str__(self, prefix: str = "") -> str:
131
+ """
132
+
133
+ Parameters
134
+ ----------
135
+ prefix :
136
+ a prefix for each line of the string representation
137
+
138
+ Returns
139
+ -------
140
+ str
141
+ the string version of the CausalFormula
142
+ """
143
+ return self.root.__str__(prefix)
144
+
145
+ def latexQuery(self, values: Optional[Dict[str, str]] = None) -> str:
146
+ """
147
+ Returns a string representing the query compiled by this Formula. If values, the query is annotated with the
148
+ values in the dictionary.
149
+
150
+ Parameters
151
+ ----------
152
+ values : None|Dict[str,str]
153
+ the values to add in the query representation
154
+
155
+ Returns
156
+ -------
157
+ str
158
+ the LaTeX representation of the causal query for this CausalFormula
159
+ """
160
+ if values is None:
161
+ values = {}
162
+
163
+ def _getVarRepresentation(v: str) -> str:
164
+ if v not in values:
165
+ return v
166
+
167
+ bn = self.cm.observationalBN()
168
+ label = bn.variable(self.cm.idFromName(v)).label(_getLabelIdx(bn, v, values[v]))
169
+ return v + "=" + label
170
+
171
+ # adding values when necessary
172
+ on = [_getVarRepresentation(k) for k in self._on]
173
+ doing = [_getVarRepresentation(k) for k in self._doing]
174
+ knowing = [_getVarRepresentation(k) for k in self._knowing]
175
+
176
+ latexOn = ",".join(on)
177
+
178
+ doOpPref = pyagrum.config["causal", "latex_do_prefix"]
179
+ doOpSuff = pyagrum.config["causal", "latex_do_suffix"]
180
+ latexDo = ""
181
+ if len(doing) > 0:
182
+ latexDo = ",".join([doOpPref + d + doOpSuff for d in doing])
183
+
184
+ latexKnw = ""
185
+ if len(knowing) > 0:
186
+ if latexDo != "":
187
+ latexKnw = ", "
188
+ latexKnw += ",".join(knowing)
189
+
190
+ return "P( " + latexOn + " \\mid " + latexDo + latexKnw + ")"
191
+
192
+ def toLatex(self) -> str:
193
+ """
194
+
195
+ Returns
196
+ -------
197
+ str
198
+ a LaTeX representation of the CausalFormula
199
+ """
200
+ occur = defaultdict(int)
201
+ for n in self._cm.observationalBN().nodes():
202
+ occur[self._cm.observationalBN().variable(n).name()] = 0
203
+ for n in self._doing:
204
+ occur[n] = 1
205
+ for n in self._knowing:
206
+ occur[n] = 1
207
+ for n in self._on:
208
+ occur[n] = 1
209
+
210
+ return self.latexQuery() + " = " + self._root.toLatex(occur)
211
+
212
+ def copy(self) -> "CausalFormula":
213
+ """
214
+ Copy theAST. Note that the causal model is just referenced. The tree is copied.
215
+
216
+ Returns
217
+ -------
218
+ CausalFormula
219
+ the copu
220
+ """
221
+ return CausalFormula(self.cm, self.root.copy(), self._on, self._doing, self._knowing)
222
+
223
+ @property
224
+ def cm(self) -> "pyagrum.causal.CausalModel":
225
+ """
226
+
227
+ Returns
228
+ -------
229
+ CausalModel
230
+ the causal model
231
+ """
232
+ return self._cm
233
+
234
+ @property
235
+ def root(self) -> ASTtree:
236
+ """
237
+
238
+ Returns
239
+ -------
240
+ ASTtree
241
+ the causalFormula as an ASTtree
242
+ """
243
+ return self._root
244
+
245
+ def eval(self) -> "pyagrum.Tensor":
246
+ """
247
+ Compute the Tensor from the CausalFormula over vars using cond as value for others variables
248
+
249
+ Returns
250
+ -------
251
+ pyagrum.Tensor
252
+ The resulting distribution
253
+ """
254
+ return self.root.eval(self.cm.observationalBN())
255
+
256
+
257
+ def _getLabelIdx(bn: "pyagrum.BayesNet", varname: str, val: Union[int, str]) -> int:
258
+ """
259
+ Find the index of a label in a discrete variable from a BN.
260
+
261
+ If val is an int, we keep is as is. If it is a str, we try to find the correct index in the variable
262
+
263
+ Parameters
264
+ ----------
265
+ bn: pyagrum.BayesNet
266
+ the BN where to find the variable
267
+ varname : str
268
+ the name of the variable
269
+ val : int|str
270
+ the index or the name of the label
271
+
272
+ Returns
273
+ -------
274
+ int
275
+ the index of the label
276
+ """
277
+ if not isinstance(val, str):
278
+ return val
279
+
280
+ return bn.variableFromName(varname).index(val)
@@ -0,0 +1,436 @@
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 a representation for causal model
43
+ """
44
+
45
+ import itertools as it
46
+ from typing import Union, Dict, Tuple
47
+
48
+ import pyagrum
49
+
50
+ from pyagrum.causal._types import LatentDescriptorList, NodeSet, NodeId, ArcSet, NameSet
51
+ from pyagrum.causal._doorCriteria import backdoor_generator, frontdoor_generator
52
+
53
+ # pylint: disable=unused-import
54
+ import pyagrum.causal # for annotations
55
+
56
+
57
+ class CausalModel:
58
+ """
59
+ From an observational BNs and the description of latent variables, this class represent a complet causal model
60
+ obtained by adding the latent variables specified in ``latentVarsDescriptor`` to the Bayesian network ``bn``.
61
+
62
+ Parameters
63
+ ----------
64
+ bn: pyagrum.BayesNet
65
+ an observational Bayesian network
66
+ latentVarsDescriptor: List[(str,List[int])]
67
+ list of couples (<latent variable name>, <list of affected variables' ids>).
68
+ keepArcs: bool
69
+ By default, the arcs between variables affected by a common latent variable will be removed but this can be avoided by setting ``keepArcs`` to ``True``
70
+ """
71
+
72
+ def __init__(self, bn: "pyagrum.BayesNet", latentVarsDescriptor: LatentDescriptorList = None, keepArcs: bool = False):
73
+ self.__observationalBN = bn
74
+ self.__latentVarsDescriptor = latentVarsDescriptor
75
+ self.__keepArcs = keepArcs
76
+
77
+ if latentVarsDescriptor is None:
78
+ latentVarsDescriptor = []
79
+
80
+ # we have to redefine those attributes since the __observationalBN may be augmented by latent variables
81
+ self.__causalBN = pyagrum.BayesNet()
82
+
83
+ # nodes of BN
84
+ for n in bn.nodes():
85
+ self.__causalBN.add(bn.variable(n), n)
86
+
87
+ # arcs on BN
88
+ for x, y in bn.arcs():
89
+ self.__causalBN.addArc(x, y)
90
+
91
+ # latent variables and arcs from latent variables
92
+ self.__lat: NodeSet = set()
93
+
94
+ self.__names = {nId: self.__causalBN.variable(nId).name() for nId in self.__causalBN.nodes()}
95
+
96
+ for n, ls in latentVarsDescriptor:
97
+ self.addLatentVariable(n, ls, keepArcs)
98
+
99
+ def clone(self) -> "pyagrum.causal.CausalModel":
100
+ """
101
+ Copy a causal model
102
+
103
+ :return: the copy
104
+ """
105
+ return CausalModel(pyagrum.BayesNet(self.__observationalBN), self.__latentVarsDescriptor, self.__keepArcs)
106
+
107
+ def addLatentVariable(self, name: str, lchild: Tuple[str, str], keepArcs: bool = False) -> None:
108
+ """
109
+ Add a new latent variable with a name, a tuple of children and replacing (or not) correlations between children.
110
+
111
+ Parameters
112
+ ----------
113
+ name: str
114
+ the name of the latent variable
115
+ lchild: Tuple[str,str]
116
+ the tuple of (2) children
117
+ keepArcs: bool
118
+ do wee keep (or not) the arc between the children ?
119
+ """
120
+ # simplest variable to add : only 2 modalities for latent variables
121
+ id_latent = self.__causalBN.add(name, 2)
122
+ self.__lat.add(id_latent)
123
+ self.__names[id_latent] = name
124
+
125
+ for item in lchild:
126
+ j = self.__observationalBN.idFromName(item) if isinstance(item, str) else item
127
+ self.addCausalArc(id_latent, j)
128
+
129
+ if not keepArcs:
130
+ ils = {self.__observationalBN.idFromName(x) for x in lchild}
131
+ for ix, iy in it.combinations(ils, 2):
132
+ if ix in self.__causalBN.parents(iy):
133
+ self.eraseCausalArc(ix, iy)
134
+ elif iy in self.__causalBN.parents(ix):
135
+ self.eraseCausalArc(iy, ix)
136
+
137
+ def toDot(self) -> str:
138
+ """
139
+ Create a dot representation of the causal model
140
+
141
+ :return: the dot representation in a string
142
+ """
143
+ res = "digraph {"
144
+
145
+ # latent variables
146
+ if pyagrum.config.asBool["causal", "show_latent_names"]:
147
+ shap = "ellipse"
148
+ else:
149
+ shap = "point"
150
+
151
+ for n in self.nodes():
152
+ if n in self.latentVariablesIds():
153
+ res += f'''
154
+ "{self.names()[n]}" [fillcolor="{pyagrum.config["causal", "default_node_bgcolor"]}",
155
+ fontcolor="{pyagrum.config["causal", "default_node_fgcolor"]}",
156
+ style=filled,shape={shap}];
157
+
158
+ '''
159
+
160
+ # not latent variables
161
+ for n in self.nodes():
162
+ if n not in self.latentVariablesIds():
163
+ res += f'''
164
+ "{self.names()[n]}" [fillcolor="{pyagrum.config["causal", "default_node_bgcolor"]}",
165
+ fontcolor="{pyagrum.config["causal", "default_node_fgcolor"]}",
166
+ style=filled,shape="ellipse"];
167
+
168
+ '''
169
+
170
+ for a, b in self.arcs():
171
+ res += ' "' + self.names()[a] + '"->"' + self.names()[b] + '" '
172
+ if a in self.latentVariablesIds() or b in self.latentVariablesIds():
173
+ res += ' [style="dashed"];'
174
+ else:
175
+ black_color = pyagrum.config["notebook", "default_arc_color"]
176
+ res += ' [color="' + black_color + ":" + black_color + '"];'
177
+ res += "\n"
178
+
179
+ res += "\n};"
180
+ return res
181
+
182
+ def causalBN(self) -> "pyagrum.BayesNet":
183
+ """
184
+ :return: the causal Bayesian network
185
+
186
+ :warning: do not infer any computations in this model. It is strictly a structural model
187
+ """
188
+ return self.__causalBN
189
+
190
+ def observationalBN(self) -> "pyagrum.BayesNet":
191
+ """
192
+ :return: the observational Bayesian network
193
+ """
194
+ return self.__observationalBN
195
+
196
+ def connectedComponents(self) -> Dict[int, NodeSet]:
197
+ """
198
+ Return a map of connected components and their nodes.
199
+
200
+ Returns
201
+ -------
202
+ Dict[int,NodeSet]:
203
+ thedisc of connected components
204
+ """
205
+ return self.__causalBN.connectedComponents()
206
+
207
+ def parents(self, x: Union[NodeId, str]) -> NodeSet:
208
+ """
209
+ From a NodeId, returns its parent (as a set of NodeId)
210
+
211
+ Parameters
212
+ ----------
213
+ x : int
214
+ the node
215
+
216
+ Returns
217
+ -------
218
+ Set[int]
219
+ the set of parents
220
+ """
221
+ return self.__causalBN.parents(self.__causalBN.idFromName(x) if isinstance(x, str) else x)
222
+
223
+ def children(self, x: Union[NodeId, str]) -> NodeSet:
224
+ """
225
+ From a NodeId, returns its children (as a set of NodeId)
226
+
227
+ Parameters
228
+ ----------
229
+ x : int
230
+ the node
231
+
232
+ Returns
233
+ -------
234
+ Set[int]
235
+ the set of children
236
+ """
237
+ return self.__causalBN.children(self.__causalBN.idFromName(x) if isinstance(x, str) else x)
238
+
239
+ def names(self) -> Dict[NodeId, str]:
240
+ """
241
+ Returns
242
+ -------
243
+ Dict[int,str]
244
+ the map NodeId,Name
245
+ """
246
+ return self.__names
247
+
248
+ def idFromName(self, name: str) -> NodeId:
249
+ """
250
+
251
+ Parameters
252
+ ----------
253
+ name: str
254
+ the name of the variable
255
+
256
+ Returns
257
+ -------
258
+ int
259
+ the id of the variable
260
+ """
261
+ return self.__causalBN.idFromName(name)
262
+
263
+ def latentVariablesIds(self) -> NodeSet:
264
+ """
265
+ Returns
266
+ -------
267
+ NodeSet
268
+ the set of ids of latent variables in the causal model
269
+ """
270
+ return self.__lat
271
+
272
+ def eraseCausalArc(self, x: Union[NodeId, str], y: Union[NodeId, str]) -> None:
273
+ """
274
+ Erase the arc x->y
275
+
276
+ Parameters
277
+ ----------
278
+ x : int|str
279
+ the nodeId or the name of the first node
280
+ y : int|str
281
+ the nodeId or the name of the second node
282
+ """
283
+ ix = self.__observationalBN.idFromName(x) if isinstance(x, str) else x
284
+ iy = self.__observationalBN.idFromName(y) if isinstance(y, str) else y
285
+ self.__causalBN.eraseArc(pyagrum.Arc(ix, iy))
286
+
287
+ def addCausalArc(self, x: Union[NodeId, str], y: Union[NodeId, str]) -> None:
288
+ """
289
+ Add an arc x->y
290
+
291
+ Parameters
292
+ ----------
293
+ x : int|str
294
+ the nodeId or the name of the first node
295
+ y : int|str
296
+ the nodeId or the name of the second node
297
+ """
298
+ ix = self.__observationalBN.idFromName(x) if isinstance(x, str) else x
299
+ iy = self.__observationalBN.idFromName(y) if isinstance(y, str) else y
300
+ self.__causalBN.addArc(ix, iy)
301
+
302
+ def existsArc(self, x: Union[NodeId, str], y: Union[NodeId, str]) -> bool:
303
+ """
304
+ Does the arc x->y exist ?
305
+
306
+ Parameters
307
+ ----------
308
+ x : int|str
309
+ the nodeId or the name of the first node
310
+ y : int|str
311
+ the nodeId or the name of the second node
312
+
313
+ Returns
314
+ -------
315
+ bool
316
+ True if the arc exists.
317
+ """
318
+ ix = self.__observationalBN.idFromName(x) if isinstance(x, str) else x
319
+ iy = self.__observationalBN.idFromName(y) if isinstance(y, str) else y
320
+ return self.__causalBN.dag().existsArc(ix, iy)
321
+
322
+ def nodes(self) -> NodeSet:
323
+ """
324
+ :return: the set of nodes
325
+ """
326
+
327
+ return self.__causalBN.nodes()
328
+
329
+ def arcs(self) -> ArcSet:
330
+ """
331
+ :return: the set of arcs
332
+ """
333
+ return self.__causalBN.arcs()
334
+
335
+ def backDoor(
336
+ self, cause: Union[NodeId, str], effect: Union[NodeId, str], withNames: bool = True
337
+ ) -> Union[None, NameSet, NodeSet]:
338
+ """
339
+ Check if a backdoor exists between `cause` and `effect`
340
+
341
+ Parameters
342
+ ----------
343
+ cause: int|str
344
+ the nodeId or the name of the cause
345
+ effect: int|str
346
+ the nodeId or the name of the effect
347
+ withNames: bool
348
+ wether we use ids (int) or names (str)
349
+
350
+ Returns
351
+ -------
352
+ None|Set[str]|Set[int]
353
+ None if no found backdoor. Otherwise return the found backdoors as set of ids or set of names.
354
+ """
355
+ icause = self.__observationalBN.idFromName(cause) if isinstance(cause, str) else cause
356
+ ieffect = self.__observationalBN.idFromName(effect) if isinstance(effect, str) else effect
357
+
358
+ for bd in backdoor_generator(self, icause, ieffect, self.latentVariablesIds()):
359
+ if withNames:
360
+ return {self.__observationalBN.variable(i).name() for i in bd}
361
+
362
+ return bd
363
+
364
+ return None
365
+
366
+ def frontDoor(
367
+ self, cause: Union[NodeId, str], effect: Union[NodeId, str], withNames: bool = True
368
+ ) -> Union[None, NameSet, NodeSet]:
369
+ """
370
+ Check if a frontdoor exists between cause and effet
371
+
372
+ Parameters
373
+ ----------
374
+ cause: int|str
375
+ the nodeId or the name of the cause
376
+ effect: int|str
377
+ the nodeId or the name of the effect
378
+ withNames: bool
379
+ wether we use ids (int) or names (str)
380
+
381
+ Returns
382
+ -------
383
+ None|Set[str]|Set[int]
384
+ None if no found frontdoot. Otherwise return the found frontdoors as set of ids or set of names.
385
+ """
386
+ icause = self.__observationalBN.idFromName(cause) if isinstance(cause, str) else cause
387
+ ieffect = self.__observationalBN.idFromName(effect) if isinstance(effect, str) else effect
388
+
389
+ for fd in frontdoor_generator(self, icause, ieffect, self.latentVariablesIds()):
390
+ if withNames:
391
+ return {self.__observationalBN.variable(i).name() for i in fd}
392
+
393
+ return fd
394
+
395
+ return None
396
+
397
+
398
+ def inducedCausalSubModel(cm: CausalModel, sns: NodeSet = None) -> CausalModel:
399
+ """
400
+ Create an causal model induced by a subset of nodes.
401
+
402
+ Parameters
403
+ ----------
404
+ cm: CausalModel
405
+ the causal model
406
+ sns: Set[int]
407
+ the set of nodes
408
+
409
+ Returns
410
+ -------
411
+ CausalModel
412
+ the induced sub-causal model
413
+ """
414
+ if sns is None:
415
+ sns = cm.nodes()
416
+ nodes = sns - cm.latentVariablesIds()
417
+
418
+ bn = pyagrum.BayesNet()
419
+
420
+ for n in nodes:
421
+ bn.add(cm.observationalBN().variable(n), n)
422
+
423
+ for x, y in cm.arcs():
424
+ if y in nodes:
425
+ if x in nodes:
426
+ bn.addArc(x, y)
427
+
428
+ names = cm.names()
429
+ latentVarsDescriptor = []
430
+ lats = cm.latentVariablesIds()
431
+ for latentVar in lats:
432
+ inters = cm.children(latentVar) & nodes
433
+ if len(inters) > 0:
434
+ latentVarsDescriptor.append((names[latentVar], list(inters)))
435
+
436
+ return CausalModel(bn, latentVarsDescriptor, True)