aGrUM 3.0.0
a C++ library for (probabilistic) graphical models
causalFormula_tpl.h
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2026 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-2026 *
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
45#pragma once
46
47
48#include <algorithm>
49#include <vector>
50
52
53namespace gum {
54 // ---------- helpers ----------
55
56 template < GUM_Numeric GUM_SCALAR >
58 const Set< std::string >& names) {
59 NodeSet ids;
60 const auto& bn = cm.observationalBN();
61 for (const auto& n: names) {
62 ids.insert(bn.idFromName(n)); // throws NotFound if unknown
63 }
64 return ids;
65 }
66
67 template < GUM_Numeric GUM_SCALAR >
69 const auto& bn = _cm.observationalBN();
70 // Touch the variable object to ensure validity (throws NotFound if invalid)
71 for (const auto& id: _on)
72 (void)bn.variable(id);
73 for (const auto& id: _doing)
74 (void)bn.variable(id);
75 for (const auto& id: _knowing)
76 (void)bn.variable(id);
77 }
78
79 template < GUM_Numeric GUM_SCALAR >
81 // Ensure pairwise disjointness: on ∩ doing = ∅, on ∩ knowing = ∅, doing ∩ knowing = ∅
82 for (const auto& id: _on) {
83 if (_doing.contains(id))
84 GUM_ERROR(OperationNotAllowed, "Variable cannot be both in 'on' and 'doing'");
85 if (_knowing.contains(id))
86 GUM_ERROR(OperationNotAllowed, "Variable cannot be both in 'on' and 'knowing'");
87 }
88 for (const auto& id: _doing) {
89 if (_knowing.contains(id))
90 GUM_ERROR(OperationNotAllowed, "Variable cannot be both in 'doing' and 'knowing'");
91 }
92 }
93
94 // ---------- constructors ----------
95
96 template < GUM_Numeric GUM_SCALAR >
109
110 template < GUM_Numeric GUM_SCALAR >
112 std::unique_ptr< ASTtree< GUM_SCALAR > > root,
113 const NodeSet& on,
114 const NodeSet& doing,
115 const NodeSet& knowing,
116 std::string_view explanation) :
117 _cm(cm), _root(std::move(root)), _on(on), _doing(doing), _knowing(knowing),
121 }
122
123 template < GUM_Numeric GUM_SCALAR >
125 _cm(other._cm), _root(other._root ? other._root->copy() : nullptr), _on(other._on),
126 _doing(other._doing), _knowing(other._knowing), _explanation(other._explanation) {}
127
128 // ---------- core API ----------
129
130 // causalFormula_tpl.h (or inline in the header)
131 template < GUM_Numeric GUM_SCALAR >
133 return static_cast< bool >(_root);
134 }
135
136 template < GUM_Numeric GUM_SCALAR >
137 const ASTtree< GUM_SCALAR >& CausalFormula< GUM_SCALAR >::root() const {
138 if (!_root) GUM_ERROR(OperationNotAllowed, "No AST: effect not identified.");
139 return *_root;
140 }
141
142 template < GUM_Numeric GUM_SCALAR >
143 Tensor< GUM_SCALAR > CausalFormula< GUM_SCALAR >::eval() const {
144 if (!_root) {
145 GUM_ERROR(gum::OperationNotAllowed,
146 "CausalFormula::eval() called on a non-identified formula.");
147 }
148 return _root->eval(_cm.observationalBN());
149 }
150
151 template < GUM_Numeric GUM_SCALAR >
153 if (!_root) return "[unidentifiable]";
154 return _root->toString();
155 }
156
157 template < GUM_Numeric GUM_SCALAR >
158 std::string CausalFormula< GUM_SCALAR >::toLatex(std::string_view doOperatorPrefix,
159 std::string_view doOperatorSuffix) const {
160 if (!_root) return "[unidentifiable]";
161
162 // Track variable name occurrences for prime management in AST LaTeX
164
165 const auto& bn = _cm.observationalBN();
166 auto addNames = [&](const NodeSet& S) {
167 for (const auto& id: S) {
168 const auto& v = bn.variable(id);
169 const auto& name = v.name();
170 if (!nameOccur.exists(name)) nameOccur.insert(name, 1);
171 }
172 };
173 addNames(_on);
174 addNames(_doing);
175 addNames(_knowing);
176
177 return latexQuery(doOperatorPrefix, doOperatorSuffix) + " = " + _root->toLatex(nameOccur);
178 }
179
180 template < GUM_Numeric GUM_SCALAR >
181 std::string CausalFormula< GUM_SCALAR >::latexQuery(std::string_view doOperatorPrefix,
182 std::string_view doOperatorSuffix) const {
183 const auto& bn = _cm.observationalBN();
184
185 auto namesSorted = [&](const NodeSet& S) {
186 std::vector< std::string > res;
187 res.reserve(S.size());
188 for (const auto& id: S)
189 res.emplace_back(bn.variable(id).name());
190 std::sort(res.begin(), res.end());
191 return res;
192 };
193
194 const auto onNames = namesSorted(_on);
195 const auto doingNames = namesSorted(_doing);
196 const auto knowingNames = namesSorted(_knowing);
197
198 std::stringstream ss;
199 ss << "P\\left(";
200
201 for (size_t i = 0; i < onNames.size(); ++i) {
202 ss << onNames[i];
203 if (i + 1 < onNames.size()) ss << ",";
204 }
205
206 if (!doingNames.empty() || !knowingNames.empty()) {
207 ss << " \\mid ";
208
209 if (!doingNames.empty()) {
210 ss << doOperatorPrefix << doingNames.front();
211 for (size_t i = 1; i < doingNames.size(); ++i)
212 ss << "," << doingNames[i];
213 ss << doOperatorSuffix;
214 }
215
216 if (!knowingNames.empty()) {
217 if (!doingNames.empty()) ss << ",";
218 ss << knowingNames.front();
219 for (size_t i = 1; i < knowingNames.size(); ++i)
220 ss << "," << knowingNames[i];
221 }
222 }
223
224 ss << "\\right)";
225 return ss.str();
226 }
227
228 template < GUM_Numeric GUM_SCALAR >
229 std::unique_ptr< CausalFormula< GUM_SCALAR > > CausalFormula< GUM_SCALAR >::copy() const {
230 return std::make_unique< CausalFormula< GUM_SCALAR > >(_cm,
231 _root->copy(),
232 _on,
233 _doing,
234 _knowing);
235 }
236
237 // ---------- convenience name accessors ----------
238
239 template < GUM_Numeric GUM_SCALAR >
240 std::vector< std::string > CausalFormula< GUM_SCALAR >::onNames() const {
241 const auto& bn = _cm.observationalBN();
242 std::vector< std::string > out;
243 out.reserve(_on.size());
244 for (const auto& id: _on)
245 out.emplace_back(bn.variable(id).name());
246 std::sort(out.begin(), out.end());
247 return out;
248 }
249
250 template < GUM_Numeric GUM_SCALAR >
251 std::vector< std::string > CausalFormula< GUM_SCALAR >::doingNames() const {
252 const auto& bn = _cm.observationalBN();
253 std::vector< std::string > out;
254 out.reserve(_doing.size());
255 for (const auto& id: _doing)
256 out.emplace_back(bn.variable(id).name());
257 std::sort(out.begin(), out.end());
258 return out;
259 }
260
261 template < GUM_Numeric GUM_SCALAR >
262 std::vector< std::string > CausalFormula< GUM_SCALAR >::knowingNames() const {
263 const auto& bn = _cm.observationalBN();
264 std::vector< std::string > out;
265 out.reserve(_knowing.size());
266 for (const auto& id: _knowing)
267 out.emplace_back(bn.variable(id).name());
268 std::sort(out.begin(), out.end());
269 return out;
270 }
271
272 // ---------- accessors ----------
273
274 template < GUM_Numeric GUM_SCALAR >
278
279 template < GUM_Numeric GUM_SCALAR >
281 return _on;
282 }
283
284 template < GUM_Numeric GUM_SCALAR >
286 return _doing;
287 }
288
289 template < GUM_Numeric GUM_SCALAR >
291 return _knowing;
292 }
293
294 template < GUM_Numeric GUM_SCALAR >
295 const std::string& CausalFormula< GUM_SCALAR >::explanation() const {
296 return _explanation;
297 }
298
299} // namespace gum
Represents an identified causal formula and its query context.
Tensor< GUM_SCALAR > eval() const
Evaluates the formula's AST to compute the resulting probability distribution.
const NodeSet & on() const
std::vector< std::string > doingNames() const
const std::string _explanation
const NodeSet _knowing
static NodeSet _toNodeSetFromNames_(const CausalModel< GUM_SCALAR > &cm, const Set< std::string > &names)
Convert a set of names to a set of node ids (validates existence).
const NodeSet _on
std::vector< std::string > knowingNames() const
std::string toLatex(std::string_view doOperatorPrefix="do(", std::string_view doOperatorSuffix=")") const
Generates a full LaTeX equation: Query = Formula.
const CausalModel< GUM_SCALAR > & _cm
const NodeSet & knowing() const
std::vector< std::string > onNames() const
Convenience: return names corresponding to stored node ids (sorted).
const std::string & explanation() const
const CausalModel< GUM_SCALAR > & cm() const
CausalFormula(const CausalModel< GUM_SCALAR > &cm, std::unique_ptr< ASTtree< GUM_SCALAR > > root, const Set< std::string > &on, const Set< std::string > &doing, const Set< std::string > &knowing=Set< std::string >{}, std::string_view explanation="")
Constructs a CausalFormula object (variables given by names).
void _ensureNoVariablesOverlap() const
Verifies that all variables (_on, _knowing, _doing) have no intersection.
const NodeSet & doing() const
bool isIdentified() const noexcept
Whether the causal effect has been identified.
std::unique_ptr< ASTtree< GUM_SCALAR > > _root
std::string toString() const
Generates a string representation of the formula's AST.
void _ensureVariablesExist() const
Verifies that all variables (_on, _knowing, _doing) exist in the BN.
const ASTtree< GUM_SCALAR > & root() const
Access the root AST node of the identified formula.
const NodeSet _doing
std::unique_ptr< CausalFormula< GUM_SCALAR > > copy() const
Creates a deep copy of the CausalFormula (including its AST).
std::string latexQuery(std::string_view doOperatorPrefix="do(", std::string_view doOperatorSuffix=")") const
Generates a LaTeX representation of the original query, e.g., P(Y | do(X), Z).
A causal model pairing an observational BayesNet with a causal DAG.
Definition causalModel.h:84
The class for generic Hash Tables.
Definition hashTable.h:640
value_type & insert(const Key &key, const Val &val)
Adds a new element (actually a copy of this element) into the hash table.
bool exists(const Key &key) const
Checks whether there exists an element with a given key in the hashtable.
Exception : operation not allowed.
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:510
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
STL namespace.