Source code for pywhy_graphs.classes.admg
from typing import Iterator, Mapping, Set
import networkx as nx
import pywhy_graphs.networkx as pywhy_nx
from .base import AncestralMixin
[docs]
class ADMG(pywhy_nx.MixedEdgeGraph, AncestralMixin):
    """Acyclic directed mixed graph (ADMG).
    A causal graph with two different edge types: bidirected and traditional
    directed edges. Directed edges constitute causal relations as a
    causal DAG did, while bidirected edges constitute the presence of a
    latent confounder.
    Parameters
    ----------
    incoming_directed_edges : input directed edges (optional, default: None)
        Data to initialize directed edges. All arguments that are accepted
        by `networkx.DiGraph` are accepted.
    incoming_bidirected_edges : input bidirected edges (optional, default: None)
        Data to initialize bidirected edges. All arguments that are accepted
        by `networkx.Graph` are accepted.
    incoming_undirected_edges : input undirected edges (optional, default: None)
        Data to initialize undirected edges. All arguments that are accepted
        by `networkx.Graph` are accepted.
    directed_edge_name : str
        The name for the directed edges. By default 'directed'.
    bidirected_edge_name : str
        The name for the bidirected edges. By default 'bidirected'.
    undirected_edge_name : str
        The name for the directed edges. By default 'undirected'.
    attr : keyword arguments, optional (default= no attributes)
        Attributes to add to graph as key=value pairs.
    See Also
    --------
    networkx.DiGraph
    networkx.Graph
    pywhy_graphs.networkx.MixedEdgeGraph
    Notes
    -----
    **Edge Type Subgraphs**
    The data structure underneath the hood is stored in two networkx graphs:
    ``networkx.Graph`` and ``networkx.DiGraph`` to represent the non-directed
    edges and directed edges. Non-directed edges in an ADMG can be present as
    bidirected edges standing for latent confounders, or undirected edges
    standing for selection bias.
    - Directed edges (<-, ->, indicating causal relationship) = `networkx.DiGraph`
        The subgraph of directed edges may be accessed by the
        `ADMG.sub_directed_graph`. Their edges in networkx format can be
        accessed by `ADMG.directed_edges` and the corresponding name of the
        edge type by `ADMG.directed_edge_name`.
    - Bidirected edges (<->, indicating latent confounder) = `networkx.Graph`
        The subgraph of bidirected edges may be accessed by the
        `ADMG.sub_bidirected_graph`. Their edges in networkx format can be
        accessed by `ADMG.bidirected_edges` and the corresponding name of the
        edge type by `ADMG.bidirected_edge_name`.
    - Undirected edges (--, indicating selection bias) = `networkx.Graph`
        The subgraph of undirected edges may be accessed by the
        `ADMG.sub_undirected_graph`. Their edges in networkx format can be
        accessed by `ADMG.undirected_edges` and the corresponding name of the
        edge type by `ADMG.undirected_edge_name`.
    By definition, no cycles may exist due to the directed edges. However, beyond
    that multiple types of edges between the same pairs of nodes are possible.
    """
    def __init__(
        self,
        incoming_directed_edges=None,
        incoming_bidirected_edges=None,
        incoming_undirected_edges=None,
        directed_edge_name: str = "directed",
        bidirected_edge_name: str = "bidirected",
        undirected_edge_name: str = "undirected",
        **attr,
    ):
        super().__init__(**attr)
        self.add_edge_type(nx.DiGraph(incoming_directed_edges), directed_edge_name)
        self.add_edge_type(nx.Graph(incoming_bidirected_edges), bidirected_edge_name)
        self.add_edge_type(nx.Graph(incoming_undirected_edges), undirected_edge_name)
        self._directed_name = directed_edge_name
        self._bidirected_name = bidirected_edge_name
        self._undirected_name = undirected_edge_name
        if not nx.is_directed_acyclic_graph(self.sub_directed_graph()):
            raise RuntimeError(f"{self} is not a DAG, which it should be.")
    @property
    def undirected_edge_name(self) -> str:
        """Name of the undirected edge internal graph."""
        return self._undirected_name
    @property
    def directed_edge_name(self) -> str:
        """Name of the directed edge internal graph."""
        return self._directed_name
    @property
    def bidirected_edge_name(self) -> str:
        """Name of the bidirected edge internal graph."""
        return self._bidirected_name
[docs]
    def c_components(self) -> Iterator[Set]:
        """Generate confounded components of the graph.
        Note the trivial c-component of a node without bidirected
        edges is the node themself.
        Returns
        -------
        comp : iterator of sets
            The c-components.
        """
        return nx.connected_components(self.sub_bidirected_graph()) 
    @property
    def bidirected_edges(self) -> Mapping:
        """``EdgeView`` of the bidirected edges."""
        return self.get_graphs(self._bidirected_name).edges
    @property
    def undirected_edges(self) -> Mapping:
        """``EdgeView`` of the undirected edges."""
        return self.get_graphs(self._undirected_name).edges
    @property
    def directed_edges(self) -> Mapping:
        """``EdgeView`` of the directed edges."""
        return self.get_graphs(self._directed_name).edges
[docs]
    def sub_directed_graph(self) -> nx.DiGraph:
        """Sub-graph of just the directed edges."""
        return self._get_internal_graph(self._directed_name) 
[docs]
    def sub_bidirected_graph(self) -> nx.Graph:
        """Sub-graph of just the bidirected edges."""
        return self._get_internal_graph(self._bidirected_name) 
[docs]
    def sub_undirected_graph(self) -> nx.Graph:
        """Sub-graph of just the undirected edges."""
        return self._get_internal_graph(self._undirected_name)