60 template < GUM_Numeric GUM_SCALAR >
66 template < GUM_Numeric GUM_SCALAR >
71 template < GUM_Numeric GUM_SCALAR >
77 for (
const auto& [latent,
children]: latentVarsDescriptor) {
84 template < GUM_Numeric GUM_SCALAR >
92 template < GUM_Numeric GUM_SCALAR >
103 template < GUM_Numeric GUM_SCALAR >
105 std::string_view latentName,
106 const std::vector< std::string >& childrenOfLatent,
108 std::vector< NodeId > childIds;
109 childIds.reserve(childrenOfLatent.size());
110 for (
const auto& nm: childrenOfLatent) {
116 template < GUM_Numeric GUM_SCALAR >
118 const std::vector< NodeId >& childrenOfLatent,
121 if (childrenOfLatent.size() < 2) {
128 "Latent variable name conflicts with an observed variable: "
129 + std::string(latentName));
131 if (_ids_names_.existsSecond(std::string(latentName))) {
133 "Latent variable name already used by another latent: " + std::string(latentName));
137 const NodeId id_latent = _causalDAG_.addNode();
140 _ids_names_.insert(id_latent, std::string(latentName));
143 for (
const auto& cid: childrenOfLatent) {
146 _causalDAG_.addArc(id_latent, cid);
150 if (!assumeNonSpurious) {
151 for (
auto i = childrenOfLatent.begin(); i != childrenOfLatent.end(); ++i) {
154 for (
auto jj = j; jj != childrenOfLatent.end(); ++jj) {
158 Arc ab(a, b), ba(b, a);
166 template < GUM_Numeric GUM_SCALAR >
171 template < GUM_Numeric GUM_SCALAR >
182 template < GUM_Numeric GUM_SCALAR >
186 latentIds.
insert(it.first());
191 template < GUM_Numeric GUM_SCALAR >
195 latentNames.
insert(it.second());
204 template < GUM_Numeric GUM_SCALAR >
209 "Arc(" + std::to_string(x) +
"," + std::to_string(y)
210 +
") not present in observationalBN");
216 template < GUM_Numeric GUM_SCALAR >
221 template < GUM_Numeric GUM_SCALAR >
226 "Arc(" + std::to_string(x) +
"," + std::to_string(y)
227 +
") not present in observationalBN");
230 if (!_causalDAG_.existsArc(
Arc(x, y))) { _causalDAG_.addArc(x, y); }
233 template < GUM_Numeric GUM_SCALAR >
238 template < GUM_Numeric GUM_SCALAR >
244 template < GUM_Numeric GUM_SCALAR >
260 template < GUM_Numeric GUM_SCALAR >
269 while (!remaining.
empty()) {
273 std::vector< NodeId > stack{root};
274 remaining.
erase(root);
276 while (!stack.empty()) {
277 const NodeId u = stack.back();
282 for (
auto v: _causalDAG_.parents(u)) {
288 for (
auto v: _causalDAG_.children(u)) {
303 template < GUM_Numeric GUM_SCALAR >
319 BayesNet< GUM_SCALAR > bn;
323 for (
auto n: nodes) {
325 const NodeId nn = bn.add(var, n);
331 for (
const auto& a: causalDag.arcs()) {
332 const NodeId x = a.tail();
333 const NodeId y = a.head();
334 if (nodes.contains(x) && nodes.contains(y)) { bn.addArc(idmap[x], idmap[y]); }
340 std::vector< NodeId > mapped_children;
341 mapped_children.reserve(4);
345 if (nodes.contains(c)) mapped_children.push_back(idmap[c]);
349 if (mapped_children.size() >= 2) {
350 latentDesc.emplace_back(cm.
nameFromId(L), std::move(mapped_children));
363 template < GUM_Numeric GUM_SCALAR >
369 template < GUM_Numeric GUM_SCALAR >
374 template < GUM_Numeric GUM_SCALAR >
379 template < GUM_Numeric GUM_SCALAR >
388 template < GUM_Numeric GUM_SCALAR >
394 "CausalModel::backDoor: 'cause' and 'effect' must be observed (non-latent).");
398 if (candidates.empty())
return std::nullopt;
399 return candidates.front();
402 template < GUM_Numeric GUM_SCALAR >
408 "CausalModel::frontDoor: 'cause' and 'effect' must be observed (non-latent).");
412 if (candidates.empty())
return std::nullopt;
413 return candidates.front();
419 template < GUM_Numeric GUM_SCALAR >
423 const char* EDGE_COL)
const {
429 std::stringstream out;
430 out <<
"digraph {\n";
438 }
catch (gum::NotFound&) { label =
"L" + std::to_string(n); }
439 out <<
" \"" << label <<
"\" "
440 <<
"[fillcolor=\"" << NODE_BG <<
"\", "
441 <<
"fontcolor=\"" << NODE_FG <<
"\", "
442 <<
"style=filled,shape=" << (SHOW_LATENT_NAMES ?
"ellipse" :
"point") <<
"];\n";
452 }
catch (gum::NotFound&) { label =
"L" + std::to_string(n); }
453 out <<
" \"" << label <<
"\" "
454 <<
"[fillcolor=\"" << NODE_BG <<
"\", "
455 <<
"fontcolor=\"" << NODE_FG <<
"\", "
456 <<
"style=filled,shape=ellipse];\n";
462 for (
const auto head:
_causalDAG_.children(tail)) {
466 }
catch (gum::NotFound&) { lt =
"L" + std::to_string(tail); }
469 }
catch (gum::NotFound&) { lh =
"L" + std::to_string(head); }
474 out <<
" \"" << lt <<
"\"->\"" << lh <<
"\" ";
476 out <<
"[style=dashed]";
479 out <<
"[color=\"" << EDGE_COL <<
":" << EDGE_COL <<
"\"]";
493 template < GUM_Numeric GUM_SCALAR >
501 names.insert(it.second());
506 template < GUM_Numeric GUM_SCALAR >
515 template < GUM_Numeric GUM_SCALAR >
524 template < GUM_Numeric GUM_SCALAR >
535 if (includeLatentVariables) {
537 result.
insert(iter.first(), iter.second());
548 template < GUM_Numeric GUM_SCALAR >
553 template < GUM_Numeric GUM_SCALAR >
561 template < GUM_Numeric GUM_SCALAR >
563 std::string_view effect)
const {
567 template < GUM_Numeric GUM_SCALAR >
569 std::string_view effect)
const {
573 template < GUM_Numeric GUM_SCALAR >
578 template < GUM_Numeric GUM_SCALAR >
The base class for all directed edges.
void insert(const T1 &first, const T2 &second)
A causal model pairing an observational BayesNet with a causal DAG.
DAG causalDAG() const
Causal DAG (observed + latent variables), with node names set.
std::string toDot(const bool SHOW_LATENT_NAMES=false, const char *NODE_BG="#404040", const char *NODE_FG="white", const char *EDGE_COL="#4A4A4A") const
DOT representation of the causal DAG (observed + latent).
void addLatentVariable(std::string_view latentName, const std::vector< std::string > &childrenOfLatent, bool assumeNonSpurious=false)
Add a latent variable by names of its observed children.
CausalModel< GUM_SCALAR > inducedCausalSubModel(const CausalModel< GUM_SCALAR > &cm, NodeSet subset) const
Induced causal submodel on a subset of nodes.
~CausalModel()
Destructor.
NodeSet latentVariablesIds() const
Node ids of all latent variables.
DAG _causalDAG_
The underlying DAG representing the causal structure (observed + latent).
HashTable< NodeId, NodeSet > connectedComponents() const
Weakly connected components of the causal DAG.
bool isAssumedSpurious(NodeId x, NodeId y) const
Returns true if arc is present in observationalBN and absent in causalDAG.
Set< std::string > latentVariablesNames() const
Names of all latent variables.
std::optional< NodeSet > frontDoor(NodeId cause, NodeId effect) const
Find a frontdoor adjustment set (Z) between cause and effect (IDs only).
Bijection< NodeId, std::string > id2name(bool includeLatentVariable=false) const
Bidirectional mapping between node ids and variable names.
Bijection< NodeId, std::string > _ids_names_
Bidirectional mapping between node ids and variable names (observed + latent).
NodeId idFromName(std::string_view name) const
Node id from variable name (observed or latent).
bool existsArc(NodeId x, NodeId y) const
Whether a causal arc x → y exists (by ids) in the causal DAG.
std::string nameFromId(NodeId id) const
Variable name from node id (observed or latent).
NodeSet children(NodeId x) const
Children of a node (by id) in the causal DAG (including latents).
const DiscreteVariable & variable(NodeId id) const
From id to variable (observed only, by id).
BayesNet< GUM_SCALAR > _observationalBN_
The underlying BayesNet representing the observed part of the model.
CausalModel()=delete
Default constructor disabled: a causal model must be built from a BN.
Set< std::string > names() const
All variable names appearing in the causal model (observed + latent).
void assumeNonSpurious(NodeId x, NodeId y)
Mark an arc as non-spurious: present in observationalBN, added to causalDAG.
std::optional< NodeSet > backDoor(NodeId cause, NodeId effect) const
Find a backdoor adjustment set (Z) between cause and effect (IDs only).
void assumeSpurious(NodeId x, NodeId y)
Mark an arc as spurious: present in observationalBN, removed from causalDAG.
NodeSet parents(NodeId x) const
Parents of a node (by id) in the causal DAG (including latents).
const BayesNet< GUM_SCALAR > & observationalBN() const
Observational BN (observed variables only).
Base class for discrete random variable.
static NodeSetVec enumerateBackdoorSets(const DAG &dag, NodeId X, NodeId Y, const NodeSet &excluded_nodes=NodeSet(), std::size_t max_cardinality=0, bool only_minimal=true, bool stopAtFirst=false)
Enumerate valid backdoor adjustment sets.
static NodeSetVec enumerateFrontdoorSets(const DAG &dag, NodeId X, NodeId Y, const NodeSet &excluded_nodes=NodeSet(), std::size_t max_cardinality=0, bool only_minimal=true, bool stopAtFirst=false)
Enumerate valid frontdoor adjustment sets.
The class for generic Hash Tables.
value_type & insert(const Key &key, const Val &val)
Adds a new element (actually a copy of this element) into the hash table.
Exception: at least one argument passed to a function is not what was expected.
void setName(NodeId id, const std::string &name)
sets the name of node id
bool contains(const Key &k) const
Indicates whether a given elements belong to the set.
bool empty() const noexcept
Indicates whether the set is the empty set.
iterator begin() const
The usual unsafe begin iterator to parse the set.
void insert(const Key &k)
Inserts a new element into the set.
void erase(const Key &k)
Erases an element from the set.
Door criteria (backdoor/frontdoor) utilities for a DAG.
#define GUM_ERROR(type, msg)
Size NodeId
Type for node ids.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
bool contains(std::string_view s, std::string_view needle)
true if needle in s
gum is the global namespace for all aGrUM entities
std::vector< LatentDescriptorIds > LatentDescriptorVector
Collection of latent descriptors (see LatentDescriptorIds).