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,271 @@
1
+ ############################################################################
2
+ # This file is part of the aGrUM/pyAgrum library. #
3
+ # #
4
+ # Copyright (c) 2005-2025 by #
5
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
6
+ # - Christophe GONZALES(_at_AMU) #
7
+ # #
8
+ # The aGrUM/pyAgrum library is free software; you can redistribute it #
9
+ # and/or modify it under the terms of either : #
10
+ # #
11
+ # - the GNU Lesser General Public License as published by #
12
+ # the Free Software Foundation, either version 3 of the License, #
13
+ # or (at your option) any later version, #
14
+ # - the MIT license (MIT), #
15
+ # - or both in dual license, as here. #
16
+ # #
17
+ # (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
18
+ # #
19
+ # This aGrUM/pyAgrum library is distributed in the hope that it will be #
20
+ # useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
21
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
25
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
26
+ # OTHER DEALINGS IN THE SOFTWARE. #
27
+ # #
28
+ # See LICENCES for more details. #
29
+ # #
30
+ # SPDX-FileCopyrightText: Copyright 2005-2025 #
31
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
32
+ # - Christophe GONZALES(_at_AMU) #
33
+ # SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
34
+ # #
35
+ # Contact : info_at_agrum_dot_org #
36
+ # homepage : http://agrum.gitlab.io #
37
+ # gitlab : https://gitlab.com/agrumery/agrum #
38
+ # #
39
+ ############################################################################
40
+
41
+ """
42
+ This module implements the inference algorithms for CLG.
43
+ """
44
+
45
+ import math
46
+
47
+ import numpy as np
48
+ from pyagrum import JunctionTreeGenerator
49
+ from .canonicalForm import CanonicalForm
50
+ from .GaussianVariable import GaussianVariable
51
+
52
+
53
+ class CLGVariableElimination:
54
+ def __init__(self, clg):
55
+ self._clg = clg
56
+ self._evidence = {}
57
+ self._cf_dict = self._constructCanonicalForms()
58
+
59
+ def updateEvidence(self, evidence):
60
+ """
61
+ Update evidences.
62
+
63
+ Parameters
64
+ ----------
65
+ evidence : dict
66
+ A dictionary of evidence.
67
+ """
68
+ self._evidence.update(evidence)
69
+
70
+ def hasEvidence(self, variable):
71
+ """
72
+ Check if a variable has an evidence.
73
+
74
+ Parameters
75
+ ----------
76
+ variable : str
77
+ The variable name.
78
+
79
+ Returns
80
+ -------
81
+ bool
82
+ True if the variable has an evidence, False otherwise.
83
+ """
84
+ return variable in self._evidence
85
+
86
+ def eraseEvidence(self, variable):
87
+ """
88
+ Remove the evidence corresponding to the variable name.
89
+
90
+ Parameters
91
+ ----------
92
+ variable : str
93
+ The variable name.
94
+ """
95
+ del self._evidence[variable]
96
+
97
+ def eraseAllEvidence(self):
98
+ """
99
+ Remove all the evidences.
100
+
101
+ Parameters
102
+ ----------
103
+ variable : str
104
+ The variable name.
105
+ """
106
+ self._evidence = {}
107
+
108
+ def nbrEvidence(self):
109
+ """
110
+ Returns the number of evidence.
111
+
112
+ Returns
113
+ -------
114
+ int
115
+ The number of evidence.
116
+ """
117
+ return len(self._evidence)
118
+
119
+ def canonicalPosterior(self, variables, normalized=True):
120
+ """
121
+ Returns the posterior density as a canonical form.
122
+
123
+ Parameters
124
+ ----------
125
+ variables : list
126
+ The list of target variable names.
127
+ normalized : bool, optional
128
+ Optional parameter to normalize the returned canonical form.
129
+
130
+ Returns
131
+ -------
132
+ CanonicalForm
133
+ The posterior canonical form.
134
+ """
135
+ if type(variables) is not list:
136
+ variables = [variables]
137
+
138
+ for l in variables:
139
+ if l in self._evidence:
140
+ raise ValueError(f"The variable {l} is observed.")
141
+
142
+ # Converting list of name to list of ids
143
+ variables = [self._clg._name2id[v] for v in variables]
144
+
145
+ # Finding the elimination order
146
+ jtg = JunctionTreeGenerator()
147
+ elimination_order = jtg.eliminationOrder(self._clg._graph)
148
+
149
+ # Removing posterior variables from elimination order
150
+ elimination_order_removed = []
151
+ elimination_order_kept = []
152
+ for var in elimination_order:
153
+ if var not in variables:
154
+ elimination_order_removed.append(var)
155
+ else:
156
+ elimination_order_kept.append(var)
157
+
158
+ cf_list = self._sum_product_ve(elimination_order_removed, list(self._cf_dict.values()), self._evidence)
159
+ posterior = np.prod(cf_list)
160
+ if normalized:
161
+ normalization_cf = self._sum_product_ve(elimination_order_kept, cf_list, {})
162
+ posterior = posterior / np.prod(normalization_cf)
163
+ return posterior
164
+
165
+ def posterior(self, variable: str) -> GaussianVariable:
166
+ """
167
+ Returns the posterior density as a Gaussian variable.
168
+
169
+ Parameters
170
+ ----------
171
+ variable : str
172
+ The target variable name.
173
+
174
+ Returns
175
+ -------
176
+ GaussianVariable
177
+ The posterior Gaussian variable.
178
+ """
179
+ if variable in self._evidence:
180
+ return GaussianVariable(variable, self._evidence[variable], 0)
181
+
182
+ posterior_cf = self.canonicalPosterior([variable])
183
+ _, t_mu, t_var = posterior_cf.toGaussian()
184
+
185
+ return GaussianVariable(variable, t_mu[0][0], math.sqrt(t_var[0][0]))
186
+
187
+ def _constructCanonicalForms(self):
188
+ """
189
+ Construct the canonical forms associated with the CLG.
190
+ """
191
+ cf_dict = {}
192
+ for node in self._clg._graph.nodes():
193
+ var = self._clg._id2var[node]
194
+ parents = list(self._clg.parents(node))
195
+ if len(parents) == 0:
196
+ cf = CanonicalForm.fromCLG(node, [], var.mu(), var.sigma(), [])
197
+ else:
198
+ B = []
199
+ for parent in parents:
200
+ B.append(self._clg._arc2coef[(parent, node)])
201
+
202
+ cf = CanonicalForm.fromCLG(node, parents, var.mu(), var.sigma(), B)
203
+ cf_dict[node] = cf
204
+ return cf_dict
205
+
206
+ def _sum_product_ve(self, elimination_order, cf_list, evidence):
207
+ """
208
+ The variable elimination algorithm for CLG.
209
+
210
+ Parameters
211
+ ----------
212
+ elimination_order : list
213
+ The elimination order.
214
+ cf_list : list
215
+ A list of canonical forms.
216
+ evidence : dict
217
+ A dictionary of evidences.
218
+
219
+ Returns
220
+ -------
221
+ list
222
+ A list of canonical forms with variables in elimination_order eliminated.
223
+ """
224
+
225
+ # Converting from dict[str, float] to dict[NodeId, float]
226
+ evidence = {self._clg._name2id[name]: evidence[name] for name in evidence.keys()}
227
+
228
+ # Reducing the canonical forms containing observed variables
229
+ if len(evidence) != 0:
230
+ for i, cf in enumerate(cf_list):
231
+ cf_list[i] = cf.reduce(evidence)
232
+
233
+ if len(elimination_order) != 0:
234
+ for variable in elimination_order:
235
+ cf_list = self._sum_product_eliminate_var(cf_list, variable)
236
+
237
+ return cf_list
238
+
239
+ def _sum_product_eliminate_var(self, cf_list, variable):
240
+ """
241
+ Remove a variable from a set of canonical forms.
242
+
243
+ Parameters
244
+ ----------
245
+ cf_list : list
246
+ A list of canonical form.
247
+ variable : int
248
+ The variable id to eliminate.
249
+
250
+ Returns
251
+ -------
252
+ list
253
+ The list of canonical form with the variable removed.
254
+ """
255
+ contain_var_cfs = [] # CF containing the variable
256
+ id_to_remove = []
257
+ for i, cf in enumerate(cf_list):
258
+ if variable in cf:
259
+ contain_var_cfs.append(cf)
260
+ id_to_remove.append(i)
261
+
262
+ if len(contain_var_cfs) == 0:
263
+ return cf_list
264
+
265
+ for i in reversed(id_to_remove):
266
+ cf_list.pop(i)
267
+ cf_product = np.prod(contain_var_cfs)
268
+ cf_marg = cf_product.marginalize([variable])
269
+ cf_list.append(cf_marg)
270
+
271
+ return cf_list
pyagrum/common.py ADDED
@@ -0,0 +1,60 @@
1
+ __version__ = '2.3.1.9'
2
+ __license__ = __doc__
3
+ __project_url__ = 'https://agrum.org'
4
+ __project_name__ = 'pyAgrum'
5
+ __project_description__ = __doc__
6
+ __project__ = __doc__
7
+
8
+
9
+ def about():
10
+ """
11
+ about() for pyAgrum
12
+
13
+ """
14
+ print(f"pyAgrum {__version__}")
15
+ print("(c) 2015-2024 Pierre-Henri Wuillemin, Christophe Gonzales")
16
+ print("""
17
+ This is free software; see the source code for copying conditions.
18
+ There is ABSOLUTELY NO WARRANTY; not even for MERCHANTABILITY or
19
+ FITNESS FOR A PARTICULAR PURPOSE. For details, see 'pyagrum.warranty'.
20
+ """)
21
+ ############################################################################
22
+ # This file is part of the aGrUM/pyAgrum library. #
23
+ # #
24
+ # Copyright (c) 2005-2025 by #
25
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
26
+ # - Christophe GONZALES(_at_AMU) #
27
+ # #
28
+ # The aGrUM/pyAgrum library is free software; you can redistribute it #
29
+ # and/or modify it under the terms of either : #
30
+ # #
31
+ # - the GNU Lesser General Public License as published by #
32
+ # the Free Software Foundation, either version 3 of the License, #
33
+ # or (at your option) any later version, #
34
+ # - the MIT license (MIT), #
35
+ # - or both in dual license, as here. #
36
+ # #
37
+ # (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
38
+ # #
39
+ # This aGrUM/pyAgrum library is distributed in the hope that it will be #
40
+ # useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
41
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
42
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
43
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
44
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
45
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
46
+ # OTHER DEALINGS IN THE SOFTWARE. #
47
+ # #
48
+ # See LICENCES for more details. #
49
+ # #
50
+ # SPDX-FileCopyrightText: Copyright 2005-2025 #
51
+ # - Pierre-Henri WUILLEMIN(_at_LIP6) #
52
+ # - Christophe GONZALES(_at_AMU) #
53
+ # SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
54
+ # #
55
+ # Contact : info_at_agrum_dot_org #
56
+ # homepage : http://agrum.gitlab.io #
57
+ # gitlab : https://gitlab.com/agrumery/agrum #
58
+ # #
59
+ ############################################################################
60
+
pyagrum/config.py ADDED
@@ -0,0 +1,319 @@
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
+ configuration tool for pyAgrum
43
+ """
44
+
45
+ from configparser import ConfigParser
46
+ import os
47
+
48
+
49
+ class GumSingleton(type):
50
+ _instances = {}
51
+
52
+ def __call__(cls, *args, **kwargs):
53
+ if cls not in cls._instances:
54
+ cls._instances[cls] = super(GumSingleton, cls).__call__(*args, **kwargs)
55
+ return cls._instances[cls]
56
+
57
+
58
+ class PyAgrumConfiguration(metaclass=GumSingleton):
59
+ """PyAgrumConfiguration is a the pyAgrum configuration singleton. The configuration is build
60
+ as a classical ConfigParser with read-only structure. Then a value is adressable using a double key: ``[section,key]``.
61
+
62
+ See `this notebook <https://lip6.fr/Pierre-Henri.Wuillemin/aGrUM/docs/last/notebooks/configForPyAgrum.ipynb.html>`_.
63
+
64
+ Examples
65
+ --------
66
+ >>> import pyagrum as gum
67
+ >>> gum.config["dynamicBN", "default_graph_size"] = 10
68
+ >>> gum.config["dynamicBN", "default_graph_size"]
69
+ "10"
70
+ """
71
+
72
+ def _check_int(self, s):
73
+ if s[0] in ("-", "+"):
74
+ return s[1:].isdigit()
75
+ return s.isdigit()
76
+
77
+ def _check_float(self, s):
78
+ t = s.split(".")
79
+ if len(t) == 1:
80
+ return self._check_int(t[0])
81
+ elif len(t) == 2:
82
+ return self._check_int(t[0]) and t[1].isdigit()
83
+ else:
84
+ return False
85
+
86
+ def _check_bool(self, s):
87
+ return self._check_bool_true(s) or self._check_bool_false(s)
88
+
89
+ def _check_bool_true(self, s):
90
+ return s.upper() in ["TRUE", "1", "ON", "YES"]
91
+
92
+ def _check_bool_false(self, s):
93
+ return s.upper() in ["FALSE", "0", "OFF", "NO"]
94
+
95
+ class _Casterization:
96
+ def __init__(self, container):
97
+ self.container = container
98
+
99
+ class _CastAsInt(_Casterization):
100
+ def __getitem__(self, x):
101
+ return int(self.container[x])
102
+
103
+ def __setitem__(self, x, v):
104
+ s = str(v)
105
+ if self.container._check_int(s):
106
+ self.container[x] = s
107
+ else:
108
+ raise ValueError(f"'{s}' must contain an int.")
109
+
110
+ class _CastAsFloat(_Casterization):
111
+ def __getitem__(self, x):
112
+ return float(self.container[x])
113
+
114
+ def __setitem__(self, x, v):
115
+ s = str(v)
116
+ if self.container._check_float(s):
117
+ self.container[x] = s
118
+ else:
119
+ raise ValueError(f"'{s}' must contain a float.")
120
+
121
+ class _CastAsBool(_Casterization):
122
+ def __getitem__(self, x):
123
+ return self.container._check_bool_true(self.container[x])
124
+
125
+ def __setitem__(self, x, v):
126
+ s = str(v)
127
+ if self.container._check_bool(s):
128
+ self.container[x] = s
129
+ else:
130
+ raise ValueError(f"'{s}' must contain a boolean (False/True, 0/1, Off/On).")
131
+
132
+ def __init__(self):
133
+ self.__parser = ConfigParser(allow_no_value=False)
134
+
135
+ defaultsfn = os.path.dirname(__file__) + "/defaults.ini"
136
+ self.__parser.read(defaultsfn)
137
+ self.__defaults = self.__str__()
138
+ self.__hooks = []
139
+ self.__stacks = []
140
+
141
+ self.asInt = self._CastAsInt(self)
142
+ self.asFloat = self._CastAsFloat(self)
143
+ self.asBool = self._CastAsBool(self)
144
+
145
+ def add_hook(self, fn):
146
+ self.__hooks.append(fn)
147
+
148
+ def run_hooks(self):
149
+ for fn in self.__hooks:
150
+ fn()
151
+
152
+ def set(self, section, option, value, no_hook=False):
153
+ """set a property in a section. Preferably use ``__getitem__`` and ``__setitem__``.
154
+
155
+ Examples
156
+ --------
157
+ >>> gum.config["dynamicBN", "default_graph_size"] = 10
158
+ >>> gum.config["dynamicBN", "default_graph_size"]
159
+ "10"
160
+
161
+ Arguments:
162
+ section {str} -- the section name (has to exist in defaults)
163
+ option {str} -- the option/property name (has to exist in defaults)
164
+ value {str} -- the value (will be store as string)
165
+ no_hook {bool} -- (optional) should this call trigger the hooks ?
166
+
167
+ Raises:
168
+ SyntaxError: if the secion name or the property name does not exist
169
+ """
170
+ if section in self.__parser.sections():
171
+ if option in self.__parser[section]:
172
+ self.__parser.set(section, option, str(value))
173
+ if not no_hook:
174
+ self.run_hooks()
175
+ else:
176
+ raise SyntaxError(f"Key '{section},{option}' unknown in pyAgrum configuration.")
177
+ else:
178
+ raise SyntaxError(f"Section '{section}' unknown in pyAgrum configuration.")
179
+
180
+ def get(self, section, option):
181
+ """Give the value associated to section.option. Preferably use ``__getitem__`` and ``__setitem__``.
182
+
183
+ Examples
184
+ --------
185
+ >>> gum.config["dynamicBN", "default_graph_size"] = 10
186
+ >>> gum.config["dynamicBN", "default_graph_size"]
187
+ "10"
188
+
189
+ Arguments:
190
+ section {str} -- the section
191
+ option {str} -- the property
192
+
193
+ Returns:
194
+ str -- the value (as string)
195
+ """
196
+ return self.__parser.get(section, option)
197
+
198
+ def __diff(self):
199
+ mine = self.__parser
200
+ c = ConfigParser()
201
+ c.read_string(self.__defaults)
202
+
203
+ def aff_sec(section):
204
+ return (
205
+ "["
206
+ + section
207
+ + "]\n"
208
+ + "\n".join(
209
+ [
210
+ f" {key} = {mine[section][key]}"
211
+ for key in mine[section].keys()
212
+ if mine.get(section, key) != c.get(section, key)
213
+ ]
214
+ )
215
+ )
216
+
217
+ return "\n".join([sec for sec in [aff_sec(section) for section in mine.sections()] if "=" in sec])
218
+
219
+ def save(self):
220
+ """Save the diff with the defaults in ``pyagrum.ini`` in the current directory"""
221
+ with open("pyagrum.ini", "w") as configfile:
222
+ print(self.__diff(), file=configfile)
223
+
224
+ def reset(self):
225
+ """back to defaults"""
226
+ self.__parser.read_string(self.__defaults)
227
+ self.run_hooks()
228
+
229
+ def load(self):
230
+ """load pyagrum.ini in the current directory, and change the properties if needed
231
+
232
+ Raises:
233
+ FileNotFoundError: if there is no pyagrum.ini in the current directory
234
+ """
235
+ if os.path.isfile("pyagrum.ini"):
236
+ # to force to use the protected set() method
237
+ c = ConfigParser()
238
+ c.read("pyagrum.ini")
239
+ error_found = False
240
+ for section in c.sections():
241
+ if section not in self.__parser.sections():
242
+ error_found = True
243
+ print(f"[pyagrum.ini] Section '{section}' does not exist.")
244
+ for option in c[section]:
245
+ try:
246
+ self.set(section, option, c[section][option], no_hook=True)
247
+ except SyntaxError:
248
+ error_found = True
249
+ print(f"[pyagrum.ini] Option '{section}.{option}' does not exist.")
250
+ self.run_hooks()
251
+ if error_found:
252
+ self.save()
253
+ else:
254
+ raise FileNotFoundError("No file 'pyagrum.ini' in current directory.")
255
+
256
+ def grep(self, search):
257
+ """grep in the configuration any section or properties matching the argument. If a section match the argume, all the section is displayed.
258
+
259
+ Arguments:
260
+ search {str} -- the string to find
261
+ """
262
+ mine = self.__parser
263
+ lowsearch = search.lower()
264
+
265
+ def aff_sec(section, all):
266
+ return (
267
+ "["
268
+ + section
269
+ + "]\n"
270
+ + "\n".join([f" {key} = {mine[section][key]}" for key in mine[section].keys() if all or lowsearch in key])
271
+ )
272
+
273
+ print(
274
+ "\n".join([sec for sec in [aff_sec(section, lowsearch in section) for section in mine.sections()] if "=" in sec])
275
+ )
276
+
277
+ def diff(self):
278
+ """print the diff between actual configuration and the defaults. This is what is saved in the file ``pyagrum.ini`` by the method `PyAgrumConfiguration.save()`"""
279
+ print(self.__diff())
280
+
281
+ def __str__(self):
282
+ mine = self.__parser
283
+
284
+ def aff_sec(section):
285
+ return "[" + section + "]\n" + "\n".join([f" {key} = {mine[section][key]}" for key in mine[section].keys()])
286
+
287
+ return "\n".join([aff_sec(section) for section in mine.sections()])
288
+
289
+ def __repr__(self):
290
+ res = self.__diff()
291
+ if "=" in res:
292
+ return res
293
+ else:
294
+ return "# no customized property\n" + self.__str__()
295
+
296
+ def __getitem__(self, key):
297
+ return self.get(key[0], key[1])
298
+
299
+ def __setitem__(self, key, value):
300
+ return self.set(key[0], key[1], value)
301
+
302
+ def __delitem__(self, key):
303
+ raise SyntaxError("No deletion of item in configuration")
304
+
305
+ def pop(self):
306
+ """
307
+ Pop the last config from the stack and set it as the current configuration
308
+ """
309
+ if len(self.__stacks) > 0:
310
+ self.__parser.read_string(self.__stacks.pop())
311
+ self.run_hooks()
312
+ else:
313
+ raise IndexError("[pyAgrum] No configuration to pop")
314
+
315
+ def push(self):
316
+ """
317
+ Push the current configuration in the stack
318
+ """
319
+ self.__stacks.append(str(self))