aGrUM 3.0.0
a C++ library for (probabilistic) graphical models
gum::DoCalculus< GUM_SCALAR > Class Template Reference

Instance-based do-calculus utilities bound to a single CausalModel. More...

#include <doCalculus.h>

Public Types

using FormulaPtr = std::unique_ptr< ASTtree< GUM_SCALAR > >
 Owned pointer to an AST representing a probability expression.
using NameList = std::vector< std::string >
 Convenience container for name-based overloads.

Public Member Functions

 DoCalculus (const CausalModel< GUM_SCALAR > &cm) noexcept
const CausalModel< GUM_SCALAR > & model () const noexcept
FormulaPtr getBackDoorTree (NodeId cause, NodeId effect, const NodeSet &zset) const
FormulaPtr getBackDoorTree (std::string_view causeName, std::string_view effectName, const NameList &zNames) const
FormulaPtr getFrontDoorTree (NodeId cause, NodeId effect, const NodeSet &zset) const
FormulaPtr getFrontDoorTree (std::string_view causeName, std::string_view effectName, const NameList &zNames) const
FormulaPtr identifyingIntervention (const NodeSet &Y, const NodeSet &X) const
 ID: identify P(Y | do(X)) (NodeId-based).
FormulaPtr identifyingIntervention (const NameList &Y, const NameList &X) const
 ID: identify P(Y | do(X)) (name-based).
FormulaPtr doCalculus (const NodeSet &on, const NodeSet &doing) const
 Thin wrapper around ID: doCalculus(on, doing).
FormulaPtr doCalculus (const NameList &on, const NameList &doing) const
FormulaPtr doCalculusWithObservation (const NodeSet &on, const NodeSet &doing, const NodeSet &knowing) const
 IDC: identify P(on | do(doing), knowing).
FormulaPtr doCalculusWithObservation (const NameList &on, const NameList &doing, const NameList &knowing) const

Private Member Functions

std::vector< NodeSet_cDecomposition_ (const CausalModel< GUM_SCALAR > &cm) const
 c-decomposition (confounding components) among observed nodes.
std::vector< NodeSet_cDecompositionOn_ (const CausalModel< GUM_SCALAR > &sub) const
 c-decomposition on an induced submodel (observed nodes only).
std::vector< NodeId_topoObserved_ (const CausalModel< GUM_SCALAR > &cm) const
 Topological order over observed nodes (ignoring latent parents).
FormulaPtr _ID_ (const CausalModel< GUM_SCALAR > &cm, const NodeSet &Y, const NodeSet &X, std::unique_ptr< ASTtree< GUM_SCALAR > > P) const
 ID core (recursive), with optional accumulated distribution P (AST) to carry in decompositions.

Static Private Member Functions

static NodeSet _ancestorsIn_ (const DAG &g, const NodeSet &T)
 Ancestors (in DAG g) of a set T (including T).
static DAG _removeIncomingInto_ (const DAG &dag, const NodeSet &X)
 DAG copy with all incoming arcs into X removed (G_{\overline X}).
static DAG _removeInIntoDoing_outOfKnowing_ (const DAG &dag, const NodeSet &doing, const NodeSet &knowing)
 IDC helper: DAG copy with incoming into doing removed AND outgoing from knowing removed.
static std::string _hedgeExceptionMsg (const CausalModel< GUM_SCALAR > &cm, const NodeSet &Y, const NodeSet &X, const NodeSet &V, const NodeSet &S)
 Format a hedge exception message.

Private Attributes

const CausalModel< GUM_SCALAR > & _cm

Detailed Description

template<GUM_Numeric GUM_SCALAR>
class gum::DoCalculus< GUM_SCALAR >

Instance-based do-calculus utilities bound to a single CausalModel.

This class provides:

  • AST builders for standard adjustment formulas:
    • Backdoor: \(\sum_{Z} P(y \mid x, z)\,P(z)\)
    • Frontdoor: \(\sum_{Z} P(z \mid x)\,\sum_{x} P(y \mid x, z)\,P(x)\)
  • Public entry points for general identification (ID/IDC):
    • identifyingIntervention(Y, X)
    • doCalculus(on, doing)
    • doCalculusWithObservation(on, doing, knowing)

Returned ASTs are evaluated against the observational BN of the bound causal model.

Definition at line 78 of file doCalculus.h.

Member Typedef Documentation

◆ FormulaPtr

template<GUM_Numeric GUM_SCALAR>
using gum::DoCalculus< GUM_SCALAR >::FormulaPtr = std::unique_ptr< ASTtree< GUM_SCALAR > >

Owned pointer to an AST representing a probability expression.

Definition at line 81 of file doCalculus.h.

◆ NameList

template<GUM_Numeric GUM_SCALAR>
using gum::DoCalculus< GUM_SCALAR >::NameList = std::vector< std::string >

Convenience container for name-based overloads.

Definition at line 84 of file doCalculus.h.

Constructor & Destructor Documentation

◆ DoCalculus()

template<GUM_Numeric GUM_SCALAR>
gum::DoCalculus< GUM_SCALAR >::DoCalculus ( const CausalModel< GUM_SCALAR > & cm)
inlineexplicitnoexcept

Definition at line 88 of file doCalculus.h.

88: _cm{cm} {}
Instance-based do-calculus utilities bound to a single CausalModel.
Definition doCalculus.h:78
const CausalModel< GUM_SCALAR > & _cm
Definition doCalculus.h:127

References _cm.

Member Function Documentation

◆ _ancestorsIn_()

template<GUM_Numeric GUM_SCALAR>
NodeSet gum::DoCalculus< GUM_SCALAR >::_ancestorsIn_ ( const DAG & g,
const NodeSet & T )
staticprivate

Ancestors (in DAG g) of a set T (including T).

Definition at line 378 of file doCalculus_tpl.h.

378 {
379 NodeSet res = T * g.nodes().asNodeSet(); // keep only nodes that actually exist in g
380
382 for (auto t: res)
383 q.push(t);
384
385 while (!q.empty()) {
386 NodeId u = q.front();
387 q.pop();
388 for (auto p: g.parents(u)) {
389 if (!res.contains(p)) {
390 res.insert(p);
391 q.push(p);
392 }
393 }
394 }
395 return res;
396 }

References gum::NodeGraphPart::asNodeSet(), gum::Set< Key >::contains(), gum::Set< Key >::insert(), gum::NodeGraphPart::nodes(), and gum::ArcGraphPart::parents().

Referenced by _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _cDecomposition_()

template<GUM_Numeric GUM_SCALAR>
std::vector< NodeSet > gum::DoCalculus< GUM_SCALAR >::_cDecomposition_ ( const CausalModel< GUM_SCALAR > & cm) const
private

c-decomposition (confounding components) among observed nodes.

Definition at line 286 of file doCalculus_tpl.h.

286 {
287 // c-components: undirected connectivity among observed children of the same latent.
288 const NodeSet lat = cm.latentVariablesIds();
289 const NodeSet V = cm.observationalBN().internalDag().nodes().asNodeSet();
290
291 // adjacency among observed nodes, via latent co-children
293 for (auto v: V)
294 adj[v]; // ensure key exists
295
296 for (auto h: lat) {
297 const auto& ch = cm.children(h);
298 // connect all observed pairs of children with an undirected edge
299 for (auto it1 = ch.begin(); it1 != ch.end(); ++it1) {
300 if (lat.contains(*it1)) continue;
301 auto it2 = it1;
302 ++it2;
303 for (; it2 != ch.end(); ++it2) {
304 if (lat.contains(*it2)) continue;
305 adj[*it1].push_back(*it2);
306 adj[*it2].push_back(*it1);
307 }
308 }
309 }
310
311 // BFS/DFS over observed nodes
314 for (auto v: V) {
315 if (seen.count(v)) continue;
318 q.push(v);
319 seen.insert(v);
320 comp.insert(v);
321 while (!q.empty()) {
322 auto u = q.front();
323 q.pop();
324 for (auto w: adj[u]) {
325 if (!seen.count(w)) {
326 seen.insert(w);
327 comp.insert(w);
328 q.push(w);
329 }
330 }
331 }
332 comps.push_back(std::move(comp));
333 }
334 return comps;
335 }

References gum::CausalModel< GUM_SCALAR >::children(), gum::Set< Key >::contains(), gum::Set< Key >::insert(), gum::CausalModel< GUM_SCALAR >::latentVariablesIds(), and gum::CausalModel< GUM_SCALAR >::observationalBN().

Referenced by _cDecompositionOn_(), and _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _cDecompositionOn_()

template<GUM_Numeric GUM_SCALAR>
std::vector< NodeSet > gum::DoCalculus< GUM_SCALAR >::_cDecompositionOn_ ( const CausalModel< GUM_SCALAR > & sub) const
private

c-decomposition on an induced submodel (observed nodes only).

Definition at line 339 of file doCalculus_tpl.h.

339 {
340 return _cDecomposition_(sub);
341 }
std::vector< NodeSet > _cDecomposition_(const CausalModel< GUM_SCALAR > &cm) const
c-decomposition (confounding components) among observed nodes.

References _cDecomposition_().

Referenced by _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _hedgeExceptionMsg()

template<GUM_Numeric GUM_SCALAR>
std::string gum::DoCalculus< GUM_SCALAR >::_hedgeExceptionMsg ( const CausalModel< GUM_SCALAR > & cm,
const NodeSet & Y,
const NodeSet & X,
const NodeSet & V,
const NodeSet & S )
staticprivate

Format a hedge exception message.

Definition at line 252 of file doCalculus_tpl.h.

256 {
257 // turn NodeSets into sorted name lists
258 auto namesOf = [&](const NodeSet& Ns) {
260 out.reserve(Ns.size());
261 for (auto id: Ns)
262 out.push_back(cm.nameFromId(id));
263 std::sort(out.begin(), out.end());
264 return out;
265 };
266
267 const auto yN = namesOf(Y);
268 const auto xN = namesOf(X);
269 const auto vN = namesOf(V);
270 const auto sN = namesOf(S);
271
272 return std::format("Not identifiable: hedge detected. "
273 "Target P{} | do{}. Witness: V = {} is a single c-component and S = {} "
274 "is a c-component in both G and G\\X (forms a hedge). "
275 "Interpretation: unblocked confounding remains under intervention; "
276 "the effect cannot be expressed from the observational distribution "
277 "using current graph structure.",
282 }

References gum::CausalModel< GUM_SCALAR >::nameFromId().

Referenced by _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _ID_()

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::_ID_ ( const CausalModel< GUM_SCALAR > & cm,
const NodeSet & Y,
const NodeSet & X,
std::unique_ptr< ASTtree< GUM_SCALAR > > P ) const
private

ID core (recursive), with optional accumulated distribution P (AST) to carry in decompositions.

Definition at line 528 of file doCalculus_tpl.h.

531 {
532 const NodeSet lat = cm.latentVariablesIds();
533 const NodeSet V = cm.observationalBN().internalDag().nodes().asNodeSet();
534
535 // --- 1) if X is empty ---
536 if (X.empty()) {
537 if (!P) {
538 // return P(Y)
540 for (auto y: Y)
541 Yn.insert(cm.nameFromId(y));
543 }
544 // return sum_{V\Y} P
545 NodeSet Vy = V - Y;
546 if (Vy.empty()) return std::move(P);
548 tosum.reserve(Vy.size());
549 for (auto v: Vy)
550 tosum.push_back(cm.nameFromId(v));
552 }
553
554 // --- 2) restrict to ancestors of Y in G ---
555 {
556 DAG g = cm.causalDAG();
558 A += Y;
559 // Only observed ancestors (V is observed-only)
560 NodeSet Aobs = A - lat;
561 if (Aobs.size() != V.size()) {
562 // sum out V \ Aobs from P if present, recurse on induced submodel G[Aobs]
563 if (P) {
564 NodeSet VminusA = V - Aobs;
565 if (!VminusA.empty()) {
567 tosum.reserve(VminusA.size());
568 for (auto v: VminusA)
569 tosum.push_back(cm.nameFromId(v));
571 }
572 }
573 auto sub = cm.inducedCausalSubModel(cm, Aobs);
574 NodeSet XA = X * Aobs;
575 return _ID_(sub, Y, XA, std::move(P));
576 }
577 }
578
579
580 // --- 3) W := (V\X) \ An_{G_{\overline X}}(Y); if non-empty, move W to X and recurse ---
581 {
582 DAG gXbar = _removeIncomingInto_(cm.causalDAG(), X);
584 AnY += Y;
585 NodeSet W = (V - X) - AnY;
586 if (!W.empty()) { return _ID_(cm, Y, X + W, std::move(P)); }
587 }
588
589 // --- 4) c-decompose G[V\X]; if >1 component, multiply and sum ---
590 {
591 auto gvx = cm.inducedCausalSubModel(cm, V - X);
593
594 if (icomp.size() > 1) {
595 // t = Π_i ID(S_i, V \ S_i)
597 for (size_t i = 0; i < icomp.size(); ++i) {
598 NodeSet Si = icomp[i];
599 NameList Sn;
600 for (auto s: Si)
601 Sn.push_back(cm.nameFromId(s));
602
605 auto term = _ID_(cm, Si, V - Si, std::move(Pcopy));
606 if (!t) t = std::move(term);
608 }
609 NodeSet Vyx = V - (X + Y);
610 if (Vyx.empty()) return t;
612 tosum.reserve(Vyx.size());
613 for (auto v: Vyx)
614 tosum.push_back(cm.nameFromId(v));
616 }
617
618 // Only one component:
619 NodeSet S = icomp.front();
620 auto cdg = _cDecomposition_(cm);
621
622 // --- 5) Hedge check: if full graph is one c-component, not identifiable
623 if (cdg.size() == 1) {
624 NodeSet all = cdg.front();
625 if (all.size() == V.size()) {
626 // build names for message
628 for (auto v: V)
629 Vn.insert(cm.nameFromId(v));
630 for (auto s: S)
631 Sn.insert(cm.nameFromId(s));
632 auto msg = _hedgeExceptionMsg(cm, Y, X, V, S);
634 }
635 }
636
637 // --- 6) If S is also a c-component in G: product of local conditionals over S
638 {
639 // test membership: S ∈ cdg ?
640 bool sInCdg = false;
641 for (const auto& comp: cdg) {
642 if (comp == S) {
643 sInCdg = true;
644 break;
645 }
646 }
647
648 if (sInCdg) {
649 // P_new := Π_{v∈S in topo order} P(v | v_π) under current P (if provided),
650 // otherwise P from the BN (chain rule)
651 const auto& bn = cm.observationalBN();
652 auto order = _topoObserved_(cm);
653
655
656 // Make a name lookup for quick topo prefix
658 for (Size i = 0; i < order.size(); ++i)
659 pos[order[i]] = i;
660
661 // build in topo order filtered to S
663 ordering.reserve(S.size());
664 for (auto v: order)
665 if (S.contains(v)) ordering.push_back(v);
666
667 for (Size j = 0; j < ordering.size(); ++j) {
668 auto v = ordering[j];
670 if (!P) {
671 // from BN: P(v | predecessors) using chain rule prefix
672 Size k = pos[v];
674 for (Size i = 0; i < k; ++i)
675 condNames.insert(cm.nameFromId(order[i]));
677 lhs.insert(cm.nameFromId(v));
680 } else {
681 // from accumulated P: factorize Q[S] by full-order prefix marginals
682 Size k = pos[v];
684 prefixNames.reserve(k + 1);
685 for (Size i = 0; i <= k; ++i)
686 prefixNames.push_back(cm.nameFromId(order[i]));
687
688 // numerator = marginal of P on current prefix
691 for (auto u: V) {
692 const auto& un = cm.nameFromId(u);
693 if (std::find(prefixNames.begin(), prefixNames.end(), un) == prefixNames.end()) {
694 sumNum.push_back(un);
695 }
696 }
697 if (!sumNum.empty()) {
699 }
700
701 if (k == 0) {
703 lhs.insert(cm.nameFromId(v));
705
706 } else {
707 // denominator = marginal of P on previous prefix
710 prevPrefix.reserve(k);
711 for (Size i = 0; i < k; ++i)
712 prevPrefix.push_back(cm.nameFromId(order[i]));
714 for (auto u: V) {
715 const auto& un = cm.nameFromId(u);
716 if (std::find(prevPrefix.begin(), prevPrefix.end(), un) == prevPrefix.end()) {
717 sumDen.push_back(un);
718 }
719 }
720 if (!sumDen.empty()) {
722 }
724 }
725 }
726
727 if (!prod) prod = std::move(term);
729 }
730
731 NodeSet Sy = S - Y;
732 if (Sy.empty()) return prod;
734 tosum.reserve(Sy.size());
735 for (auto v: Sy)
736 tosum.push_back(cm.nameFromId(v));
738 }
739 }
740
741 // --- 7) Else: pick a c-component S' of G, build Q[S'] then recurse on G[S']
742 {
743 // choose the c-component of G that contains S (SP Step 7: S ⊆ S')
744 NodeSet Spr;
745 bool found = false;
746 for (const auto& comp: cdg) {
747 if ((S - comp).empty()) {
748 Spr = comp;
749 found = true;
750 break;
751 }
752 }
753 if (!found) {
754 // Fallback should not happen; be conservative
755 Spr = S;
756 }
757
758 const auto& bn = cm.observationalBN();
759 auto order = _topoObserved_(cm);
760
762
764 for (Size i = 0; i < order.size(); ++i)
765 pos[order[i]] = i;
766
768 ordering.reserve(Spr.size());
769 for (auto v: order)
770 if (Spr.contains(v)) ordering.push_back(v);
771
772 for (Size j = 0; j < ordering.size(); ++j) {
773 auto v = ordering[j];
775 if (!P) {
776 Size k = pos[v];
778 for (Size i = 0; i < k; ++i)
779 condNames.insert(cm.nameFromId(order[i]));
781 lhs.insert(cm.nameFromId(v));
784 } else {
785 // from accumulated P: factorize Q[Spr] by full-order prefix marginals
786 Size k = pos[v];
788 prefixNames.reserve(k + 1);
789 for (Size i = 0; i <= k; ++i)
790 prefixNames.push_back(cm.nameFromId(order[i]));
791
792 // numerator = marginal of P on current prefix
795 for (auto u: V) {
796 const auto& un = cm.nameFromId(u);
797 if (std::find(prefixNames.begin(), prefixNames.end(), un) == prefixNames.end()) {
798 sumNum.push_back(un);
799 }
800 }
801 if (!sumNum.empty()) {
803 }
804
805 if (k == 0) {
807 lhs.insert(cm.nameFromId(v));
809
810 } else {
811 // denominator = marginal of P on previous prefix
814 prevPrefix.reserve(k);
815 for (Size i = 0; i < k; ++i)
816 prevPrefix.push_back(cm.nameFromId(order[i]));
818 for (auto u: V) {
819 const auto& un = cm.nameFromId(u);
820 if (std::find(prevPrefix.begin(), prevPrefix.end(), un) == prevPrefix.end()) {
821 sumDen.push_back(un);
822 }
823 }
824 if (!sumDen.empty()) {
826 }
828 }
829 }
830
831 if (!prod) prod = std::move(term);
833 }
834
835 auto sub = cm.inducedCausalSubModel(cm, Spr);
836 NodeSet Yspr = Y * Spr; // <— important
837 NodeSet Xspr = X * Spr;
838
839 if (Yspr.empty()) {
841 "Internal error in ID step 7: chosen C-component is disjoint from Y.");
842 }
843
844 return _ID_(sub, Yspr, Xspr, std::move(prod));
845 }
846 }
847 }
FormulaPtr _ID_(const CausalModel< GUM_SCALAR > &cm, const NodeSet &Y, const NodeSet &X, std::unique_ptr< ASTtree< GUM_SCALAR > > P) const
ID core (recursive), with optional accumulated distribution P (AST) to carry in decompositions.
static DAG _removeIncomingInto_(const DAG &dag, const NodeSet &X)
DAG copy with all incoming arcs into X removed (G_{\overline X}).
std::vector< NodeId > _topoObserved_(const CausalModel< GUM_SCALAR > &cm) const
Topological order over observed nodes (ignoring latent parents).
std::vector< std::string > NameList
Convenience container for name-based overloads.
Definition doCalculus.h:84
std::vector< NodeSet > _cDecompositionOn_(const CausalModel< GUM_SCALAR > &sub) const
c-decomposition on an induced submodel (observed nodes only).
static NodeSet _ancestorsIn_(const DAG &g, const NodeSet &T)
Ancestors (in DAG g) of a set T (including T).
static std::string _hedgeExceptionMsg(const CausalModel< GUM_SCALAR > &cm, const NodeSet &Y, const NodeSet &X, const NodeSet &V, const NodeSet &S)
Format a hedge exception message.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76

References _ancestorsIn_(), _cDecomposition_(), _cDecompositionOn_(), _hedgeExceptionMsg(), _ID_(), _removeIncomingInto_(), _topoObserved_(), gum::CausalModel< GUM_SCALAR >::causalDAG(), gum::Set< Key >::contains(), gum::Set< Key >::empty(), GUM_ERROR, gum::CausalModel< GUM_SCALAR >::inducedCausalSubModel(), gum::Set< Key >::insert(), gum::CausalModel< GUM_SCALAR >::latentVariablesIds(), gum::CausalModel< GUM_SCALAR >::nameFromId(), gum::CausalModel< GUM_SCALAR >::observationalBN(), and gum::Set< Key >::size().

Referenced by _ID_(), and identifyingIntervention().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _removeIncomingInto_()

template<GUM_Numeric GUM_SCALAR>
DAG gum::DoCalculus< GUM_SCALAR >::_removeIncomingInto_ ( const DAG & dag,
const NodeSet & X )
staticprivate

DAG copy with all incoming arcs into X removed (G_{\overline X}).

Definition at line 399 of file doCalculus_tpl.h.

399 {
400 DAG g = dag; // copy
401 for (auto x: X) {
402 auto ps = g.parents(x); // snapshot
403 for (auto p: ps)
404 g.eraseArc(Arc(p, x));
405 }
406 return g;
407 }

References gum::ArcGraphPart::eraseArc(), and gum::ArcGraphPart::parents().

Referenced by _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _removeInIntoDoing_outOfKnowing_()

template<GUM_Numeric GUM_SCALAR>
DAG gum::DoCalculus< GUM_SCALAR >::_removeInIntoDoing_outOfKnowing_ ( const DAG & dag,
const NodeSet & doing,
const NodeSet & knowing )
staticprivate

IDC helper: DAG copy with incoming into doing removed AND outgoing from knowing removed.

Definition at line 410 of file doCalculus_tpl.h.

412 {
413 DAG g = dag; // copy
414 for (auto x: doing) {
415 auto ps = g.parents(x);
416 for (auto p: ps)
417 g.eraseArc(Arc(p, x));
418 }
419 for (auto z: knowing) {
420 auto ch = g.children(z);
421 for (auto c: ch)
422 g.eraseArc(Arc(z, c));
423 }
424 return g;
425 }

References gum::ArcGraphPart::children(), gum::ArcGraphPart::eraseArc(), and gum::ArcGraphPart::parents().

Referenced by doCalculusWithObservation().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _topoObserved_()

template<GUM_Numeric GUM_SCALAR>
std::vector< NodeId > gum::DoCalculus< GUM_SCALAR >::_topoObserved_ ( const CausalModel< GUM_SCALAR > & cm) const
private

Topological order over observed nodes (ignoring latent parents).

Definition at line 345 of file doCalculus_tpl.h.

345 {
346 const auto& dag = cm.observationalBN().internalDag();
347 const NodeSet V = dag.nodes().asNodeSet();
348
349 // indegree in the observed DAG
351 for (auto v: V)
352 indeg[v] = dag.parents(v).size();
353
355 order.reserve(V.size());
356
357 // Kahn on observed DAG only
359 for (const auto& kv: indeg)
360 if (kv.second == 0) q.push(kv.first);
361
362 while (!q.empty()) {
363 const NodeId u = q.front();
364 q.pop();
365 order.push_back(u);
366
367 for (auto c: dag.children(u)) {
368 if (indeg[c] > 0) {
369 --indeg[c];
370 if (indeg[c] == 0) q.push(c);
371 }
372 }
373 }
374 return order;
375 }

References gum::CausalModel< GUM_SCALAR >::observationalBN(), and gum::Set< Key >::size().

Referenced by _ID_().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ doCalculus() [1/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::doCalculus ( const NameList & on,
const NameList & doing ) const

Definition at line 457 of file doCalculus_tpl.h.

457 {
459 }
FormulaPtr identifyingIntervention(const NodeSet &Y, const NodeSet &X) const
ID: identify P(Y | do(X)) (NodeId-based).

References identifyingIntervention().

Here is the call graph for this function:

◆ doCalculus() [2/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::doCalculus ( const NodeSet & on,
const NodeSet & doing ) const

Thin wrapper around ID: doCalculus(on, doing).

Definition at line 451 of file doCalculus_tpl.h.

451 {
453 }

References identifyingIntervention().

Here is the call graph for this function:

◆ doCalculusWithObservation() [1/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::doCalculusWithObservation ( const NameList & on,
const NameList & doing,
const NameList & knowing ) const

Definition at line 509 of file doCalculus_tpl.h.

511 {
512 NodeSet ON, DO, KN;
513 for (const auto& s: on)
514 ON.insert(_cm.idFromName(s));
515 for (const auto& s: doing)
516 DO.insert(_cm.idFromName(s));
517 for (const auto& s: knowing)
518 KN.insert(_cm.idFromName(s));
520 }
FormulaPtr doCalculusWithObservation(const NodeSet &on, const NodeSet &doing, const NodeSet &knowing) const
IDC: identify P(on | do(doing), knowing).

References _cm, doCalculusWithObservation(), and gum::Set< Key >::insert().

Here is the call graph for this function:

◆ doCalculusWithObservation() [2/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::doCalculusWithObservation ( const NodeSet & on,
const NodeSet & doing,
const NodeSet & knowing ) const

IDC: identify P(on | do(doing), knowing).

Definition at line 463 of file doCalculus_tpl.h.

465 {
466 // IDC reduction: try to promote any Z∈knowing to doing if Z ⟂ on | doing ∪ (knowing\{Z})
467 // in the graph where incoming into doing and outgoing from knowing are removed.
468 if (!knowing.empty()) {
470
471 for (auto z: knowing) {
472 NodeSet Zi;
473 Zi.insert(z);
476 // recurse with z moved to doing
480 }
481 }
482 }
483
484 // Fallback: ratio P(on ∪ knowing | do(doing)) / P(knowing | do(doing))
485 NameList Yw, W;
486 for (auto y: on)
487 Yw.push_back(_cm.nameFromId(y));
488 for (auto w: knowing)
489 W.push_back(_cm.nameFromId(w));
490
491 NameList Xn;
492 for (auto x: doing)
493 Xn.push_back(_cm.nameFromId(x));
494
495 // numerator: ID on Y ∪ W ; denominator: ID on W (if W empty, denominator is 1)
497
499 if (knowing.empty()) {
500 // P(Y|do(X)) when knowing is empty: just return numerator
501 return num;
502 }
505 }
static DAG _removeInIntoDoing_outOfKnowing_(const DAG &dag, const NodeSet &doing, const NodeSet &knowing)
IDC helper: DAG copy with incoming into doing removed AND outgoing from knowing removed.

References _cm, _removeInIntoDoing_outOfKnowing_(), doCalculusWithObservation(), gum::Set< Key >::empty(), identifyingIntervention(), gum::Set< Key >::insert(), and gum::Separation::isDSeparated().

Referenced by doCalculusWithObservation(), and doCalculusWithObservation().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getBackDoorTree() [1/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::getBackDoorTree ( NodeId cause,
NodeId effect,
const NodeSet & zset ) const

Definition at line 92 of file doCalculus_tpl.h.

94 {
95 // Preconditions: cause/effect must be observed (non-latent), Z must not contain latents.
96 const NodeSet lat = _cm.latentVariablesIds();
97 if (lat.contains(cause) || lat.contains(effect)) {
99 "getBackDoorTree: 'cause' and 'effect' must be observed (non-latent).");
100 }
101 for (auto z: zset) {
102 if (lat.contains(z)) {
103 GUM_ERROR(InvalidArgument, "getBackDoorTree: zset contains a latent variable.");
104 }
105 }
106
107 // ids -> names
108 const auto& dag = _cm.causalDAG();
109 auto mapO = _cm.id2name(/*includeLatentVariables=*/false);
110 const std::string xName = _cm.nameFromId(cause);
111 const std::string yName = _cm.nameFromId(effect);
112
115 zOrder.reserve(zset.size());
116 for (auto z: zset) {
117 const std::string zn = _cm.nameFromId(z);
118 zNames.insert(zn);
119 zOrder.push_back(zn);
120 }
121 std::sort(zOrder.begin(), zOrder.end());
122
123 // P(y | x, z)
125 lhsY.insert(yName);
127 knw.insert(xName);
128 for (const auto& zn: zNames)
129 knw.insert(zn);
131
132 if (zNames.empty()) {
133 // returns P(y | x)
134 return Pyxz;
135 }
136
137 // P(z)
139
140 // P(y|x,z) * P(z), then sum over Z
142 terms.emplace_back(std::move(Pyxz));
143 terms.emplace_back(std::move(Pz));
145
147 }
std::unique_ptr< ASTtree< GUM_SCALAR > > productOfTrees(std::vector< std::unique_ptr< ASTtree< GUM_SCALAR > > > &&lterms)
Build a product AST from a list of terms.
Definition doAST_tpl.h:633

References _cm, gum::Set< Key >::contains(), gum::Set< Key >::empty(), GUM_ERROR, gum::Set< Key >::insert(), gum::productOfTrees(), and gum::Set< Key >::size().

Referenced by gum::CausalImpact< GUM_ELEMENT >::_disjoint_(), and getBackDoorTree().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getBackDoorTree() [2/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::getBackDoorTree ( std::string_view causeName,
std::string_view effectName,
const NameList & zNames ) const

Definition at line 151 of file doCalculus_tpl.h.

153 {
154 const NodeId x = _cm.idFromName(causeName);
155 const NodeId y = _cm.idFromName(effectName);
156
157 NodeSet Z;
158 for (const auto& zn: zNames)
159 Z.insert(_cm.idFromName(zn));
160
161 return getBackDoorTree(x, y, Z);
162 }
FormulaPtr getBackDoorTree(NodeId cause, NodeId effect, const NodeSet &zset) const

References _cm, getBackDoorTree(), and gum::Set< Key >::insert().

Here is the call graph for this function:

◆ getFrontDoorTree() [1/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::getFrontDoorTree ( NodeId cause,
NodeId effect,
const NodeSet & zset ) const

Definition at line 166 of file doCalculus_tpl.h.

168 {
169 // --- Preconditions ---
170 const NodeSet lat = _cm.latentVariablesIds();
171 if (lat.contains(cause) || lat.contains(effect)) {
173 "getFrontDoorTree: 'cause' and 'effect' must be observed (non-latent).");
174 }
175 if (zset.empty()) {
176 GUM_ERROR(InvalidArgument, "getFrontDoorTree: zset must be non-empty for frontdoor.");
177 }
178 if (zset.contains(cause) || zset.contains(effect)) {
179 GUM_ERROR(InvalidArgument, "getFrontDoorTree: zset must be disjoint from {cause,effect}.");
180 }
181 for (auto z: zset) {
182 if (lat.contains(z)) {
183 GUM_ERROR(InvalidArgument, "getFrontDoorTree: zset contains a latent variable.");
184 }
185 }
186
187 // --- ids → names ---
188 const std::string xName = _cm.nameFromId(cause);
189 const std::string yName = _cm.nameFromId(effect);
190
193 zOrder.reserve(zset.size());
194 for (auto z: zset) {
195 const auto zn = _cm.nameFromId(z);
196 zNames.insert(zn);
197 zOrder.push_back(zn);
198 }
199 std::sort(zOrder.begin(), zOrder.end()); // deterministic
200
201 // --- Build P(Z | X) using DAG ctor (observed-only mapping inferred from model) ---
202 const auto& dag = _cm.causalDAG();
203 auto mapO = _cm.id2name(/*includeLatentVariables=*/false);
204
206 condX.insert(xName);
208
209 // --- Build sum_x P(Y | X, Z) * P(X) ---
211 ySet.insert(yName);
212
214 condXZ.insert(xName);
216
218 xSet.insert(xName);
220
221 auto inner_prod
225
226 // --- P(Z|X) * (sum_x ...) then sum over Z ---
227 auto outer_prod
230 }

References _cm, gum::Set< Key >::contains(), gum::Set< Key >::empty(), GUM_ERROR, gum::Set< Key >::insert(), and gum::Set< Key >::size().

Referenced by getFrontDoorTree().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getFrontDoorTree() [2/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::getFrontDoorTree ( std::string_view causeName,
std::string_view effectName,
const NameList & zNames ) const

Definition at line 234 of file doCalculus_tpl.h.

236 {
237 const NodeId x = _cm.idFromName(causeName);
238 const NodeId y = _cm.idFromName(effectName);
239
240 NodeSet Z;
241 for (const auto& zn: zNames)
242 Z.insert(_cm.idFromName(zn));
243
244 return getFrontDoorTree(x, y, Z);
245 }
FormulaPtr getFrontDoorTree(NodeId cause, NodeId effect, const NodeSet &zset) const

References _cm, getFrontDoorTree(), and gum::Set< Key >::insert().

Here is the call graph for this function:

◆ identifyingIntervention() [1/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::identifyingIntervention ( const NameList & Y,
const NameList & X ) const

ID: identify P(Y | do(X)) (name-based).

Definition at line 439 of file doCalculus_tpl.h.

440 {
441 NodeSet YY, XX;
442 for (const auto& s: Y)
443 YY.insert(_cm.idFromName(s));
444 for (const auto& s: X)
445 XX.insert(_cm.idFromName(s));
447 }

References _cm, identifyingIntervention(), and gum::Set< Key >::insert().

Here is the call graph for this function:

◆ identifyingIntervention() [2/2]

template<GUM_Numeric GUM_SCALAR>
DoCalculus< GUM_SCALAR >::FormulaPtr gum::DoCalculus< GUM_SCALAR >::identifyingIntervention ( const NodeSet & Y,
const NodeSet & X ) const

ID: identify P(Y | do(X)) (NodeId-based).

Definition at line 433 of file doCalculus_tpl.h.

433 {
434 return _ID_(_cm, Y, X, nullptr);
435 }

References _cm, and _ID_().

Referenced by doCalculus(), doCalculus(), doCalculusWithObservation(), and identifyingIntervention().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ model()

template<GUM_Numeric GUM_SCALAR>
const CausalModel< GUM_SCALAR > & gum::DoCalculus< GUM_SCALAR >::model ( ) const
noexcept

Definition at line 82 of file doCalculus_tpl.h.

82 {
83 return _cm;
84 }

References _cm.

Member Data Documentation

◆ _cm


The documentation for this class was generated from the following files: